/* * [FileType.java] * * Summary: Track the various types of exe file. Also classify a given EXE file. * * Copyright: (c) 2012-2017 Roedy Green, Canadian Mind Products, http://mindprod.com * * Licence: This software may be copied and used freely for any purpose but military. * http://mindprod.com/contact/nonmil.html * * Requires: JDK 1.8+ * * Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/ * * Version History: * 1.0 2012-01-08 initial version. * 1.1 2012-05-07 update Jet version signature. add debugging code. * 1.2 2012-11-15 update Jet version signature for MP5 * 1.3 2016-06-20 update to Jet 11.0 mp3 */ package com.mindprod.which; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import static java.lang.System.*; /** * Track the various types of exe file. Also classify a given EXE file. * * @author Roedy Green, Canadian Mind Products * @version 1.3 2016-06-20 update to Jet 11.0 mp3 * @since 2012-01-08 */ public enum FileType { // If there is a new maintenance pack for jet, // set JET_CURRENT_MP. No embedded numbers will change. // todo: add detect for 64-bit Jet. Totally different layout, undocumented. // name, ret code. Do not adjust when new version of Jet released. // These numbers are return codes, not codes embedded in the exe file DOS16( "DOS 16-bit", 8 ), WIN16( "Windows 16-bit", 16 ), WIN32( "Windows 32-bit", 32 ), WIN32JET( "Windows 32-bit Jet version 11.0 mp3", 33 ), WIN32JETOBS( "Windows 32-bit Jet, executable out of date (or possibly obsolete Which.java)", 34 ), AMD64( "Windows AMD 64-bit", 64 ), AMD64JET( "Windows AMD 64-bit Jet", 65 ), AMD64JETOBS( "Windows AMD 64-bit Jet, executable out of date (or possibly obsolete Which.java)", 66 ), IA64( "Itanium 64-bit", 70 ), UNKNOWN( "an unknown type", 90 ), INDETERMINATE( "unable to find/read the file", 91 ); /** * true if want extra debugging */ private static final boolean DEBUGGING = false; /** * Current jet format version, hopefully will not change. * Run in debug mode to determine new value. */ private static final int JET_CURRENT_FORMAT_VERSION = 4; /** * Current jet major versiorn. Must be updated when Excelsior issues major Jet release. * Run in debug mode to determine new value. * If you change this, change wording for WIN32JET */ private static final int JET_CURRENT_MAJOR_VERSION = 11; /** * Current jet minor version */ private static final int JET_CURRENT_MINOR_VERSION = 0; /** * marker for current Jet patch. Must be updated when Jet issues a new maintenance patch. * Run in debug mode to determine new value. Don't include (pro, en) or will not handle other Jet variants */ private static final String JET_CURRENT_MP = "jet-1100-mp3"; /** * Description of this file type */ private final String desc; /** * return code to use when find this type of exe */ private final int retCode; /** * constructor * * @param desc description for this file type * @param retCode return code to use when find this type of exe */ private FileType( final String desc, final int retCode ) { this.desc = desc; this.retCode = retCode; } /** * Classify the given exe file * * @param f file to classify * * @return FileType of the file */ static FileType classify( File f ) { try { // O P E N final RandomAccessFile raf = new RandomAccessFile( f, "r" /* read only */ ); // R E A D raf.seek( 0 ); final int m = raf.readUnsignedByte(); final int z = raf.readUnsignedByte(); if ( !( m == 0x4d && z == 0x5a ) ) // MZ { return UNKNOWN; } raf.seek( 0x3c ); final int d = toLittleEndianUnsignedShort( raf.readUnsignedShort() ); if ( d == 0 ) { return DOS16; } raf.seek( d ); final int p1 = raf.readUnsignedByte(); final int p2 = raf.readUnsignedByte(); if ( p1 == 0x4e && p2 == 0x45 ) // NE { return WIN16; } final int p3 = raf.readUnsignedByte(); final int p4 = raf.readUnsignedByte(); if ( !( p1 == 0x50 && p2 == 0x45 && p3 == 0 && p4 == 0 ) ) // PE { return UNKNOWN; } final int b1 = raf.readUnsignedByte(); final int b2 = raf.readUnsignedByte(); if ( b1 == 0x4c && b2 == 0x01 ) { // some sort of WIN32 return classifyJet( raf ); } if ( b1 == 0x64 && b2 == 0x86 ) { // some sort of WIN64 switch ( classifyJet( raf ) ) { case WIN32: return AMD64; case WIN32JET: return AMD64JET; case WIN32JETOBS: return AMD64JETOBS; default: throw new IllegalArgumentException( "bug: classifyJet returned unexpected value" ); } } if ( b1 == 0 && b2 == 0x02 ) { return IA64; } // C L O S E raf.close(); return UNKNOWN; } catch ( IOException e ) { err.println( e.getMessage() ); return INDETERMINATE; } } /** * Decide if this exe is Jet and if it is up to date. * * @param raf Access to the exe/dll file. * * @return WIN32 WIN32JET or WIN32JETOBS (even for 64 bit) * @throws IOException if problem reading file. */ private static FileType classifyJet( final RandomAccessFile raf ) throws IOException { /* Section called .config Magic : 4 bytes, should be 0x00425043 (le) FormatVersion : 4 bytes, equals to 4 for JET 11.0 executables. (le) MajorJetVersion : 4 bytes, 11 for JET 11.0(le) MinorJetVersion : 4 bytes, 0 for JET 11.0(le) */ raf.seek( 0x2f4 ); final int offset = toLittleEndianUnsignedInt( raf.readInt() ); raf.seek( offset ); final int c = raf.readUnsignedByte(); final int p = raf.readUnsignedByte(); final int b = raf.readUnsignedByte(); final int z = raf.readUnsignedByte(); // signature , in decimal 67 80 66 0 if ( c == 0x43 && p == 0x50 && b == 0x42 && z == 0 ) { // is jet final int formatVersion = toLittleEndianUnsignedInt( raf.readInt() ); final int majorJetVersion = toLittleEndianUnsignedInt( raf.readInt() ); final int minorJetVersion = toLittleEndianUnsignedInt( raf.readInt() ); // how many bytes to scan for update marker final int buffSize = Math.min( ( int ) ( raf.length() - raf.getFilePointer() ), 256 ); final byte[] buffer = new byte[ buffSize ]; raf.readFully( buffer ); // convert byte buffer to String final String look = new String( buffer, "ASCII" ); if ( DEBUGGING ) { out.println( "\nformatVersion:" + formatVersion + "\nmajorJetVersion:" + majorJetVersion + "\nminorJetVersion:" + minorJetVersion ); out.println( "xray of executable expecting " + JET_CURRENT_MP + " maintenance pack." ); for ( int i = 0; i < look.length(); i++ ) { // don't try to print control chars final char possControl = look.charAt( i ); out.print( possControl >= 32 ? possControl : '.' ); } out.println(); } return ( formatVersion == JET_CURRENT_FORMAT_VERSION && majorJetVersion == JET_CURRENT_MAJOR_VERSION && minorJetVersion == JET_CURRENT_MINOR_VERSION && look.contains( JET_CURRENT_MP ) ) ? WIN32JET : WIN32JETOBS; } else { // not jet return WIN32; } } /** * adjust int that should have been read as little endian. * * @param i value to adjust * * @return adjusted value. */ private static int toLittleEndianUnsignedInt( int i ) { final int b3 = i >>> 24; final int b2 = i >>> 16 & 0xff; final int b1 = i >>> 8 & 0xff; final int b0 = i & 0xff; return b0 << 24 | b1 << 16 | b2 << 8 | b3; } /** * adjust short * that should have been read as little endian. * * @param i value to adjust * * @return adjusted value. */ private static int toLittleEndianUnsignedShort( int i ) { final int hi = i >>> 8 & 0xff; final int low = i & 0xff; return low << 8 | hi; } /** * get return code * * @return return code to use for this file type */ public int getRetCode() { return retCode; } /** * get description for this file type * * @return description */ public String toString() { return desc; } }