diff options
| author | 2014-08-30 11:41:17 -0400 | |
|---|---|---|
| committer | 2014-08-30 11:41:17 -0400 | |
| commit | e43fac9f55cfeebacd869352bfb090b7d8d063c1 (patch) | |
| tree | dd4b01daa04dbdcecc765c7270e18bdf1b63d97f | |
| parent | working on version conversion (diff) | |
| download | enigma-fork-e43fac9f55cfeebacd869352bfb090b7d8d063c1.tar.gz enigma-fork-e43fac9f55cfeebacd869352bfb090b7d8d063c1.tar.xz enigma-fork-e43fac9f55cfeebacd869352bfb090b7d8d063c1.zip | |
got a decent class matcher working
| -rw-r--r-- | src/cuchaz/enigma/TranslatingTypeLoader.java | 27 | ||||
| -rw-r--r-- | src/cuchaz/enigma/analysis/JarClassIterator.java | 72 | ||||
| -rw-r--r-- | src/cuchaz/enigma/analysis/JarIndex.java | 34 | ||||
| -rw-r--r-- | src/cuchaz/enigma/bytecode/ClassRenamer.java | 35 | ||||
| -rw-r--r-- | src/cuchaz/enigma/convert/ClassIdentity.java | 255 | ||||
| -rw-r--r-- | src/cuchaz/enigma/convert/ClassMapper.java | 202 | ||||
| -rw-r--r-- | src/cuchaz/enigma/convert/ClassMatching.java | 184 | ||||
| -rw-r--r-- | src/cuchaz/enigma/convert/ClassNamer.java | 75 | ||||
| -rw-r--r-- | src/cuchaz/enigma/mapping/Translator.java | 9 |
9 files changed, 748 insertions, 145 deletions
diff --git a/src/cuchaz/enigma/TranslatingTypeLoader.java b/src/cuchaz/enigma/TranslatingTypeLoader.java index 1e0e95a..e70093e 100644 --- a/src/cuchaz/enigma/TranslatingTypeLoader.java +++ b/src/cuchaz/enigma/TranslatingTypeLoader.java | |||
| @@ -47,6 +47,11 @@ public class TranslatingTypeLoader implements ITypeLoader | |||
| 47 | private Map<String,byte[]> m_cache; | 47 | private Map<String,byte[]> m_cache; |
| 48 | private ClasspathTypeLoader m_defaultTypeLoader; | 48 | private ClasspathTypeLoader m_defaultTypeLoader; |
| 49 | 49 | ||
| 50 | public TranslatingTypeLoader( JarFile jar, JarIndex jarIndex ) | ||
| 51 | { | ||
| 52 | this( jar, jarIndex, new Translator(), new Translator() ); | ||
| 53 | } | ||
| 54 | |||
| 50 | public TranslatingTypeLoader( JarFile jar, JarIndex jarIndex, Translator obfuscatingTranslator, Translator deobfuscatingTranslator ) | 55 | public TranslatingTypeLoader( JarFile jar, JarIndex jarIndex, Translator obfuscatingTranslator, Translator deobfuscatingTranslator ) |
| 51 | { | 56 | { |
| 52 | m_jar = jar; | 57 | m_jar = jar; |
| @@ -90,6 +95,28 @@ public class TranslatingTypeLoader implements ITypeLoader | |||
| 90 | return true; | 95 | return true; |
| 91 | } | 96 | } |
| 92 | 97 | ||
| 98 | public CtClass loadClass( String deobfClassName ) | ||
| 99 | { | ||
| 100 | byte[] data = loadType( deobfClassName ); | ||
| 101 | if( data == null ) | ||
| 102 | { | ||
| 103 | return null; | ||
| 104 | } | ||
| 105 | |||
| 106 | // return a javassist handle for the class | ||
| 107 | String javaClassFileName = Descriptor.toJavaName( deobfClassName ); | ||
| 108 | ClassPool classPool = new ClassPool(); | ||
| 109 | classPool.insertClassPath( new ByteArrayClassPath( javaClassFileName, data ) ); | ||
| 110 | try | ||
| 111 | { | ||
| 112 | return classPool.get( javaClassFileName ); | ||
| 113 | } | ||
| 114 | catch( NotFoundException ex ) | ||
| 115 | { | ||
| 116 | throw new Error( ex ); | ||
| 117 | } | ||
| 118 | } | ||
| 119 | |||
| 93 | private byte[] loadType( String deobfClassName ) | 120 | private byte[] loadType( String deobfClassName ) |
| 94 | { | 121 | { |
| 95 | // what class file should we actually load? | 122 | // what class file should we actually load? |
diff --git a/src/cuchaz/enigma/analysis/JarClassIterator.java b/src/cuchaz/enigma/analysis/JarClassIterator.java index 6c9f124..10ae805 100644 --- a/src/cuchaz/enigma/analysis/JarClassIterator.java +++ b/src/cuchaz/enigma/analysis/JarClassIterator.java | |||
| @@ -67,33 +67,7 @@ public class JarClassIterator implements Iterator<CtClass> | |||
| 67 | JarEntry entry = m_iter.next(); | 67 | JarEntry entry = m_iter.next(); |
| 68 | try | 68 | try |
| 69 | { | 69 | { |
| 70 | // read the class into a buffer | 70 | return getClass( m_jar, entry ); |
| 71 | ByteArrayOutputStream bos = new ByteArrayOutputStream(); | ||
| 72 | byte[] buf = new byte[Constants.KiB]; | ||
| 73 | int totalNumBytesRead = 0; | ||
| 74 | InputStream in = m_jar.getInputStream( entry ); | ||
| 75 | while( in.available() > 0 ) | ||
| 76 | { | ||
| 77 | int numBytesRead = in.read( buf ); | ||
| 78 | if( numBytesRead < 0 ) | ||
| 79 | { | ||
| 80 | break; | ||
| 81 | } | ||
| 82 | bos.write( buf, 0, numBytesRead ); | ||
| 83 | |||
| 84 | // sanity checking | ||
| 85 | totalNumBytesRead += numBytesRead; | ||
| 86 | if( totalNumBytesRead > Constants.MiB ) | ||
| 87 | { | ||
| 88 | throw new Error( "Class file " + entry.getName() + " larger than 1 MiB! Something is wrong!" ); | ||
| 89 | } | ||
| 90 | } | ||
| 91 | |||
| 92 | // get a javassist handle for the class | ||
| 93 | String className = Descriptor.toJavaName( getClassEntry( entry ).getName() ); | ||
| 94 | ClassPool classPool = new ClassPool(); | ||
| 95 | classPool.insertClassPath( new ByteArrayClassPath( className, bos.toByteArray() ) ); | ||
| 96 | return classPool.get( className ); | ||
| 97 | } | 71 | } |
| 98 | catch( IOException | NotFoundException ex ) | 72 | catch( IOException | NotFoundException ex ) |
| 99 | { | 73 | { |
| @@ -136,6 +110,50 @@ public class JarClassIterator implements Iterator<CtClass> | |||
| 136 | }; | 110 | }; |
| 137 | } | 111 | } |
| 138 | 112 | ||
| 113 | public static CtClass getClass( JarFile jar, ClassEntry classEntry ) | ||
| 114 | { | ||
| 115 | try | ||
| 116 | { | ||
| 117 | return getClass( jar, new JarEntry( classEntry.getName() + ".class" ) ); | ||
| 118 | } | ||
| 119 | catch( IOException | NotFoundException ex ) | ||
| 120 | { | ||
| 121 | throw new Error( "Unable to load class: " + classEntry.getName() ); | ||
| 122 | } | ||
| 123 | } | ||
| 124 | |||
| 125 | private static CtClass getClass( JarFile jar, JarEntry entry ) | ||
| 126 | throws IOException, NotFoundException | ||
| 127 | { | ||
| 128 | // read the class into a buffer | ||
| 129 | ByteArrayOutputStream bos = new ByteArrayOutputStream(); | ||
| 130 | byte[] buf = new byte[Constants.KiB]; | ||
| 131 | int totalNumBytesRead = 0; | ||
| 132 | InputStream in = jar.getInputStream( entry ); | ||
| 133 | while( in.available() > 0 ) | ||
| 134 | { | ||
| 135 | int numBytesRead = in.read( buf ); | ||
| 136 | if( numBytesRead < 0 ) | ||
| 137 | { | ||
| 138 | break; | ||
| 139 | } | ||
| 140 | bos.write( buf, 0, numBytesRead ); | ||
| 141 | |||
| 142 | // sanity checking | ||
| 143 | totalNumBytesRead += numBytesRead; | ||
| 144 | if( totalNumBytesRead > Constants.MiB ) | ||
| 145 | { | ||
| 146 | throw new Error( "Class file " + entry.getName() + " larger than 1 MiB! Something is wrong!" ); | ||
| 147 | } | ||
| 148 | } | ||
| 149 | |||
| 150 | // get a javassist handle for the class | ||
| 151 | String className = Descriptor.toJavaName( getClassEntry( entry ).getName() ); | ||
| 152 | ClassPool classPool = new ClassPool(); | ||
| 153 | classPool.insertClassPath( new ByteArrayClassPath( className, bos.toByteArray() ) ); | ||
| 154 | return classPool.get( className ); | ||
| 155 | } | ||
| 156 | |||
| 139 | private static ClassEntry getClassEntry( JarEntry entry ) | 157 | private static ClassEntry getClassEntry( JarEntry entry ) |
| 140 | { | 158 | { |
| 141 | return new ClassEntry( entry.getName().substring( 0, entry.getName().length() - ".class".length() ) ); | 159 | return new ClassEntry( entry.getName().substring( 0, entry.getName().length() - ".class".length() ) ); |
diff --git a/src/cuchaz/enigma/analysis/JarIndex.java b/src/cuchaz/enigma/analysis/JarIndex.java index deacf16..b479b69 100644 --- a/src/cuchaz/enigma/analysis/JarIndex.java +++ b/src/cuchaz/enigma/analysis/JarIndex.java | |||
| @@ -95,7 +95,7 @@ public class JarIndex | |||
| 95 | // step 2: index method/field access | 95 | // step 2: index method/field access |
| 96 | for( CtClass c : JarClassIterator.classes( jar ) ) | 96 | for( CtClass c : JarClassIterator.classes( jar ) ) |
| 97 | { | 97 | { |
| 98 | fixClass( c ); | 98 | ClassRenamer.moveAllClassesOutOfDefaultPackage( c, Constants.NonePackage ); |
| 99 | ClassEntry classEntry = new ClassEntry( Descriptor.toJvmName( c.getName() ) ); | 99 | ClassEntry classEntry = new ClassEntry( Descriptor.toJvmName( c.getName() ) ); |
| 100 | for( CtField field : c.getDeclaredFields() ) | 100 | for( CtField field : c.getDeclaredFields() ) |
| 101 | { | 101 | { |
| @@ -112,7 +112,7 @@ public class JarIndex | |||
| 112 | // step 3: index the types, methods | 112 | // step 3: index the types, methods |
| 113 | for( CtClass c : JarClassIterator.classes( jar ) ) | 113 | for( CtClass c : JarClassIterator.classes( jar ) ) |
| 114 | { | 114 | { |
| 115 | fixClass( c ); | 115 | ClassRenamer.moveAllClassesOutOfDefaultPackage( c, Constants.NonePackage ); |
| 116 | String className = Descriptor.toJvmName( c.getName() ); | 116 | String className = Descriptor.toJvmName( c.getName() ); |
| 117 | m_ancestries.addSuperclass( className, Descriptor.toJvmName( c.getClassFile().getSuperclass() ) ); | 117 | m_ancestries.addSuperclass( className, Descriptor.toJvmName( c.getClassFile().getSuperclass() ) ); |
| 118 | for( String interfaceName : c.getClassFile().getInterfaces() ) | 118 | for( String interfaceName : c.getClassFile().getInterfaces() ) |
| @@ -128,8 +128,7 @@ public class JarIndex | |||
| 128 | // step 4: index inner classes and anonymous classes | 128 | // step 4: index inner classes and anonymous classes |
| 129 | for( CtClass c : JarClassIterator.classes( jar ) ) | 129 | for( CtClass c : JarClassIterator.classes( jar ) ) |
| 130 | { | 130 | { |
| 131 | fixClass( c ); | 131 | ClassRenamer.moveAllClassesOutOfDefaultPackage( c, Constants.NonePackage ); |
| 132 | |||
| 133 | String outerClassName = findOuterClass( c ); | 132 | String outerClassName = findOuterClass( c ); |
| 134 | if( outerClassName != null ) | 133 | if( outerClassName != null ) |
| 135 | { | 134 | { |
| @@ -164,17 +163,6 @@ public class JarIndex | |||
| 164 | renameMethods( m_bridgeMethods ); | 163 | renameMethods( m_bridgeMethods ); |
| 165 | } | 164 | } |
| 166 | 165 | ||
| 167 | private void fixClass( CtClass c ) | ||
| 168 | { | ||
| 169 | ClassEntry classEntry = new ClassEntry( Descriptor.toJvmName( c.getName() ) ); | ||
| 170 | if( classEntry.isInDefaultPackage() ) | ||
| 171 | { | ||
| 172 | // move class out of default package | ||
| 173 | classEntry = new ClassEntry( Constants.NonePackage + "/" + classEntry.getName() ); | ||
| 174 | ClassRenamer.moveAllClassesOutOfDefaultPackage( c, Constants.NonePackage ); | ||
| 175 | } | ||
| 176 | } | ||
| 177 | |||
| 178 | private void indexBehavior( CtBehavior behavior ) | 166 | private void indexBehavior( CtBehavior behavior ) |
| 179 | { | 167 | { |
| 180 | // get the method entry | 168 | // get the method entry |
| @@ -729,6 +717,22 @@ public class JarIndex | |||
| 729 | 717 | ||
| 730 | private void renameClasses( Map<String,String> renames ) | 718 | private void renameClasses( Map<String,String> renames ) |
| 731 | { | 719 | { |
| 720 | // rename class entries | ||
| 721 | Set<ClassEntry> obfClassEntries = Sets.newHashSet(); | ||
| 722 | for( ClassEntry classEntry : m_obfClassEntries ) | ||
| 723 | { | ||
| 724 | if( renames.containsKey( classEntry.getName() ) ) | ||
| 725 | { | ||
| 726 | obfClassEntries.add( new ClassEntry( renames.get( classEntry.getName() ) ) ); | ||
| 727 | } | ||
| 728 | else | ||
| 729 | { | ||
| 730 | obfClassEntries.add( classEntry ); | ||
| 731 | } | ||
| 732 | } | ||
| 733 | m_obfClassEntries = obfClassEntries; | ||
| 734 | |||
| 735 | // rename others | ||
| 732 | m_ancestries.renameClasses( renames ); | 736 | m_ancestries.renameClasses( renames ); |
| 733 | renameClassesInMultimap( renames, m_methodImplementations ); | 737 | renameClassesInMultimap( renames, m_methodImplementations ); |
| 734 | renameClassesInMultimap( renames, m_behaviorReferences ); | 738 | renameClassesInMultimap( renames, m_behaviorReferences ); |
diff --git a/src/cuchaz/enigma/bytecode/ClassRenamer.java b/src/cuchaz/enigma/bytecode/ClassRenamer.java index f3a8c0e..efe22a1 100644 --- a/src/cuchaz/enigma/bytecode/ClassRenamer.java +++ b/src/cuchaz/enigma/bytecode/ClassRenamer.java | |||
| @@ -14,6 +14,7 @@ import java.util.Map; | |||
| 14 | import java.util.Set; | 14 | import java.util.Set; |
| 15 | 15 | ||
| 16 | import javassist.ClassMap; | 16 | import javassist.ClassMap; |
| 17 | import javassist.CtBehavior; | ||
| 17 | import javassist.CtClass; | 18 | import javassist.CtClass; |
| 18 | import javassist.bytecode.ConstPool; | 19 | import javassist.bytecode.ConstPool; |
| 19 | import javassist.bytecode.Descriptor; | 20 | import javassist.bytecode.Descriptor; |
| @@ -23,6 +24,8 @@ import com.beust.jcommander.internal.Sets; | |||
| 23 | import com.google.common.collect.Maps; | 24 | import com.google.common.collect.Maps; |
| 24 | 25 | ||
| 25 | import cuchaz.enigma.mapping.ClassEntry; | 26 | import cuchaz.enigma.mapping.ClassEntry; |
| 27 | import cuchaz.enigma.mapping.SignatureUpdater; | ||
| 28 | import cuchaz.enigma.mapping.SignatureUpdater.ClassNameUpdater; | ||
| 26 | 29 | ||
| 27 | public class ClassRenamer | 30 | public class ClassRenamer |
| 28 | { | 31 | { |
| @@ -115,5 +118,37 @@ public class ClassRenamer | |||
| 115 | } | 118 | } |
| 116 | } | 119 | } |
| 117 | ClassRenamer.renameClasses( c, map ); | 120 | ClassRenamer.renameClasses( c, map ); |
| 121 | |||
| 122 | // TEMP | ||
| 123 | for( ClassEntry classEntry : ClassRenamer.getAllClassEntries( c ) ) | ||
| 124 | { | ||
| 125 | if( classEntry.isInDefaultPackage() ) | ||
| 126 | { | ||
| 127 | throw new Error( "!!! " + classEntry ); | ||
| 128 | } | ||
| 129 | } | ||
| 130 | |||
| 131 | // TEMP | ||
| 132 | for( CtBehavior behavior : c.getDeclaredBehaviors() ) | ||
| 133 | { | ||
| 134 | if( behavior.getSignature() == null ) | ||
| 135 | { | ||
| 136 | continue; | ||
| 137 | } | ||
| 138 | |||
| 139 | SignatureUpdater.update( behavior.getSignature(), new ClassNameUpdater( ) | ||
| 140 | { | ||
| 141 | @Override | ||
| 142 | public String update( String className ) | ||
| 143 | { | ||
| 144 | ClassEntry classEntry = new ClassEntry( className ); | ||
| 145 | if( classEntry.isInDefaultPackage() ) | ||
| 146 | { | ||
| 147 | throw new Error( "!!! " + className ); | ||
| 148 | } | ||
| 149 | return className; | ||
| 150 | } | ||
| 151 | } ); | ||
| 152 | } | ||
| 118 | } | 153 | } |
| 119 | } | 154 | } |
diff --git a/src/cuchaz/enigma/convert/ClassIdentity.java b/src/cuchaz/enigma/convert/ClassIdentity.java index aecf7fc..0a3a449 100644 --- a/src/cuchaz/enigma/convert/ClassIdentity.java +++ b/src/cuchaz/enigma/convert/ClassIdentity.java | |||
| @@ -13,10 +13,12 @@ package cuchaz.enigma.convert; | |||
| 13 | import java.io.UnsupportedEncodingException; | 13 | import java.io.UnsupportedEncodingException; |
| 14 | import java.security.MessageDigest; | 14 | import java.security.MessageDigest; |
| 15 | import java.security.NoSuchAlgorithmException; | 15 | import java.security.NoSuchAlgorithmException; |
| 16 | import java.util.Enumeration; | ||
| 16 | import java.util.List; | 17 | import java.util.List; |
| 17 | import java.util.Map; | 18 | import java.util.Map; |
| 18 | import java.util.Set; | 19 | import java.util.Set; |
| 19 | 20 | ||
| 21 | import javassist.CannotCompileException; | ||
| 20 | import javassist.CtBehavior; | 22 | import javassist.CtBehavior; |
| 21 | import javassist.CtClass; | 23 | import javassist.CtClass; |
| 22 | import javassist.CtConstructor; | 24 | import javassist.CtConstructor; |
| @@ -27,34 +29,58 @@ import javassist.bytecode.CodeIterator; | |||
| 27 | import javassist.bytecode.ConstPool; | 29 | import javassist.bytecode.ConstPool; |
| 28 | import javassist.bytecode.Descriptor; | 30 | import javassist.bytecode.Descriptor; |
| 29 | import javassist.bytecode.Opcode; | 31 | import javassist.bytecode.Opcode; |
| 32 | import javassist.expr.ConstructorCall; | ||
| 33 | import javassist.expr.ExprEditor; | ||
| 34 | import javassist.expr.FieldAccess; | ||
| 35 | import javassist.expr.MethodCall; | ||
| 36 | import javassist.expr.NewExpr; | ||
| 30 | 37 | ||
| 31 | import com.beust.jcommander.internal.Maps; | 38 | import com.beust.jcommander.internal.Maps; |
| 32 | import com.beust.jcommander.internal.Sets; | 39 | import com.beust.jcommander.internal.Sets; |
| 33 | import com.google.common.collect.Lists; | 40 | import com.google.common.collect.Lists; |
| 34 | 41 | ||
| 42 | import cuchaz.enigma.Constants; | ||
| 35 | import cuchaz.enigma.Util; | 43 | import cuchaz.enigma.Util; |
| 44 | import cuchaz.enigma.analysis.ClassImplementationsTreeNode; | ||
| 45 | import cuchaz.enigma.analysis.EntryReference; | ||
| 46 | import cuchaz.enigma.analysis.JarIndex; | ||
| 36 | import cuchaz.enigma.bytecode.ConstPoolEditor; | 47 | import cuchaz.enigma.bytecode.ConstPoolEditor; |
| 37 | import cuchaz.enigma.bytecode.InfoType; | 48 | import cuchaz.enigma.bytecode.InfoType; |
| 38 | import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor; | 49 | import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor; |
| 50 | import cuchaz.enigma.convert.ClassNamer.SidedClassNamer; | ||
| 51 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 39 | import cuchaz.enigma.mapping.ClassEntry; | 52 | import cuchaz.enigma.mapping.ClassEntry; |
| 53 | import cuchaz.enigma.mapping.ConstructorEntry; | ||
| 54 | import cuchaz.enigma.mapping.Entry; | ||
| 55 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 56 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 40 | import cuchaz.enigma.mapping.SignatureUpdater; | 57 | import cuchaz.enigma.mapping.SignatureUpdater; |
| 41 | import cuchaz.enigma.mapping.SignatureUpdater.ClassNameUpdater; | 58 | import cuchaz.enigma.mapping.SignatureUpdater.ClassNameUpdater; |
| 42 | 59 | ||
| 43 | public class ClassIdentity | 60 | public class ClassIdentity |
| 44 | { | 61 | { |
| 45 | private ClassEntry m_classEntry; | 62 | private ClassEntry m_classEntry; |
| 63 | private SidedClassNamer m_namer; | ||
| 46 | private Set<String> m_fields; | 64 | private Set<String> m_fields; |
| 47 | private Set<String> m_methods; | 65 | private Set<String> m_methods; |
| 48 | private Set<String> m_constructors; | 66 | private Set<String> m_constructors; |
| 49 | private String m_staticInitializer; | 67 | private String m_staticInitializer; |
| 68 | private String m_extends; | ||
| 69 | private Set<String> m_implements; | ||
| 70 | private Set<String> m_implementations; | ||
| 71 | private Set<String> m_references; | ||
| 50 | 72 | ||
| 51 | public ClassIdentity( CtClass c ) | 73 | public ClassIdentity( CtClass c, SidedClassNamer namer, JarIndex index, boolean useReferences ) |
| 52 | { | 74 | { |
| 75 | m_namer = namer; | ||
| 76 | |||
| 77 | // stuff from the bytecode | ||
| 78 | |||
| 53 | m_classEntry = new ClassEntry( Descriptor.toJvmName( c.getName() ) ); | 79 | m_classEntry = new ClassEntry( Descriptor.toJvmName( c.getName() ) ); |
| 54 | m_fields = Sets.newHashSet(); | 80 | m_fields = Sets.newHashSet(); |
| 55 | for( CtField field : c.getDeclaredFields() ) | 81 | for( CtField field : c.getDeclaredFields() ) |
| 56 | { | 82 | { |
| 57 | m_fields.add( scrubSignature( scrubSignature( field.getSignature() ) ) ); | 83 | m_fields.add( scrubSignature( field.getSignature() ) ); |
| 58 | } | 84 | } |
| 59 | m_methods = Sets.newHashSet(); | 85 | m_methods = Sets.newHashSet(); |
| 60 | for( CtMethod method : c.getDeclaredMethods() ) | 86 | for( CtMethod method : c.getDeclaredMethods() ) |
| @@ -71,6 +97,73 @@ public class ClassIdentity | |||
| 71 | { | 97 | { |
| 72 | m_staticInitializer = getBehaviorSignature( c.getClassInitializer() ); | 98 | m_staticInitializer = getBehaviorSignature( c.getClassInitializer() ); |
| 73 | } | 99 | } |
| 100 | m_extends = ""; | ||
| 101 | if( c.getClassFile().getSuperclass() != null ) | ||
| 102 | { | ||
| 103 | m_extends = scrubClassName( c.getClassFile().getSuperclass() ); | ||
| 104 | } | ||
| 105 | m_implements = Sets.newHashSet(); | ||
| 106 | for( String interfaceName : c.getClassFile().getInterfaces() ) | ||
| 107 | { | ||
| 108 | m_implements.add( scrubClassName( interfaceName ) ); | ||
| 109 | } | ||
| 110 | |||
| 111 | // stuff from the jar index | ||
| 112 | |||
| 113 | m_implementations = Sets.newHashSet(); | ||
| 114 | @SuppressWarnings( "unchecked" ) | ||
| 115 | Enumeration<ClassImplementationsTreeNode> implementations = index.getClassImplementations( null, m_classEntry ).children(); | ||
| 116 | while( implementations.hasMoreElements() ) | ||
| 117 | { | ||
| 118 | ClassImplementationsTreeNode node = implementations.nextElement(); | ||
| 119 | m_implementations.add( scrubClassName( node.getClassEntry().getName() ) ); | ||
| 120 | } | ||
| 121 | |||
| 122 | m_references = Sets.newHashSet(); | ||
| 123 | if( useReferences ) | ||
| 124 | { | ||
| 125 | for( CtField field : c.getDeclaredFields() ) | ||
| 126 | { | ||
| 127 | FieldEntry fieldEntry = new FieldEntry( m_classEntry, field.getName() ); | ||
| 128 | for( EntryReference<FieldEntry,BehaviorEntry> reference : index.getFieldReferences( fieldEntry ) ) | ||
| 129 | { | ||
| 130 | addReference( reference ); | ||
| 131 | } | ||
| 132 | } | ||
| 133 | for( CtMethod method : c.getDeclaredMethods() ) | ||
| 134 | { | ||
| 135 | MethodEntry methodEntry = new MethodEntry( m_classEntry, method.getName(), method.getSignature() ); | ||
| 136 | for( EntryReference<BehaviorEntry,BehaviorEntry> reference : index.getBehaviorReferences( methodEntry ) ) | ||
| 137 | { | ||
| 138 | addReference( reference ); | ||
| 139 | } | ||
| 140 | } | ||
| 141 | for( CtConstructor constructor : c.getDeclaredConstructors() ) | ||
| 142 | { | ||
| 143 | ConstructorEntry constructorEntry = new ConstructorEntry( m_classEntry, constructor.getSignature() ); | ||
| 144 | for( EntryReference<BehaviorEntry,BehaviorEntry> reference : index.getBehaviorReferences( constructorEntry ) ) | ||
| 145 | { | ||
| 146 | addReference( reference ); | ||
| 147 | } | ||
| 148 | } | ||
| 149 | } | ||
| 150 | } | ||
| 151 | |||
| 152 | private void addReference( EntryReference<? extends Entry,BehaviorEntry> reference ) | ||
| 153 | { | ||
| 154 | if( reference.context.getSignature() != null ) | ||
| 155 | { | ||
| 156 | m_references.add( String.format( "%s_%s", | ||
| 157 | scrubClassName( reference.context.getClassName() ), | ||
| 158 | scrubSignature( reference.context.getSignature() ) | ||
| 159 | ) ); | ||
| 160 | } | ||
| 161 | else | ||
| 162 | { | ||
| 163 | m_references.add( String.format( "%s_<clinit>", | ||
| 164 | scrubClassName( reference.context.getClassName() ) | ||
| 165 | ) ); | ||
| 166 | } | ||
| 74 | } | 167 | } |
| 75 | 168 | ||
| 76 | public ClassEntry getClassEntry( ) | 169 | public ClassEntry getClassEntry( ) |
| @@ -109,9 +202,38 @@ public class ClassIdentity | |||
| 109 | buf.append( m_staticInitializer ); | 202 | buf.append( m_staticInitializer ); |
| 110 | buf.append( "\n" ); | 203 | buf.append( "\n" ); |
| 111 | } | 204 | } |
| 205 | if( m_extends.length() > 0 ) | ||
| 206 | { | ||
| 207 | buf.append( "\textends " ); | ||
| 208 | buf.append( m_extends ); | ||
| 209 | buf.append( "\n" ); | ||
| 210 | } | ||
| 211 | for( String interfaceName : m_implements ) | ||
| 212 | { | ||
| 213 | buf.append( "\timplements " ); | ||
| 214 | buf.append( interfaceName ); | ||
| 215 | buf.append( "\n" ); | ||
| 216 | } | ||
| 217 | for( String implementation : m_implementations ) | ||
| 218 | { | ||
| 219 | buf.append( "\timplemented by " ); | ||
| 220 | buf.append( implementation ); | ||
| 221 | buf.append( "\n" ); | ||
| 222 | } | ||
| 223 | for( String reference : m_references ) | ||
| 224 | { | ||
| 225 | buf.append( "\treference " ); | ||
| 226 | buf.append( reference ); | ||
| 227 | buf.append( "\n" ); | ||
| 228 | } | ||
| 112 | return buf.toString(); | 229 | return buf.toString(); |
| 113 | } | 230 | } |
| 114 | 231 | ||
| 232 | private String scrubClassName( String className ) | ||
| 233 | { | ||
| 234 | return scrubSignature( "L" + Descriptor.toJvmName( className ) + ";" ); | ||
| 235 | } | ||
| 236 | |||
| 115 | private String scrubSignature( String signature ) | 237 | private String scrubSignature( String signature ) |
| 116 | { | 238 | { |
| 117 | return SignatureUpdater.update( signature, new ClassNameUpdater( ) | 239 | return SignatureUpdater.update( signature, new ClassNameUpdater( ) |
| @@ -121,12 +243,30 @@ public class ClassIdentity | |||
| 121 | @Override | 243 | @Override |
| 122 | public String update( String className ) | 244 | public String update( String className ) |
| 123 | { | 245 | { |
| 124 | // does the class have a package? | 246 | // classes not in the none package can be passed through |
| 125 | if( className.indexOf( '/' ) >= 0 ) | 247 | ClassEntry classEntry = new ClassEntry( className ); |
| 248 | if( !classEntry.getPackageName().equals( Constants.NonePackage ) ) | ||
| 126 | { | 249 | { |
| 127 | return className; | 250 | return className; |
| 128 | } | 251 | } |
| 129 | 252 | ||
| 253 | // is this class ourself? | ||
| 254 | if( className.equals( m_classEntry.getName() ) ) | ||
| 255 | { | ||
| 256 | return "CSelf"; | ||
| 257 | } | ||
| 258 | |||
| 259 | // try the namer | ||
| 260 | if( m_namer != null ) | ||
| 261 | { | ||
| 262 | String newName = m_namer.getName( className ); | ||
| 263 | if( newName != null ) | ||
| 264 | { | ||
| 265 | return newName; | ||
| 266 | } | ||
| 267 | } | ||
| 268 | |||
| 269 | // otherwise, use local naming | ||
| 130 | if( !m_classNames.containsKey( className ) ) | 270 | if( !m_classNames.containsKey( className ) ) |
| 131 | { | 271 | { |
| 132 | m_classNames.put( className, getNewClassName() ); | 272 | m_classNames.put( className, getNewClassName() ); |
| @@ -141,6 +281,11 @@ public class ClassIdentity | |||
| 141 | } ); | 281 | } ); |
| 142 | } | 282 | } |
| 143 | 283 | ||
| 284 | private boolean isClassMatchedUniquely( String className ) | ||
| 285 | { | ||
| 286 | return m_namer != null && m_namer.getName( Descriptor.toJvmName( className ) ) != null; | ||
| 287 | } | ||
| 288 | |||
| 144 | private String getBehaviorSignature( CtBehavior behavior ) | 289 | private String getBehaviorSignature( CtBehavior behavior ) |
| 145 | { | 290 | { |
| 146 | try | 291 | try |
| @@ -153,8 +298,7 @@ public class ClassIdentity | |||
| 153 | 298 | ||
| 154 | // compute the hash from the opcodes | 299 | // compute the hash from the opcodes |
| 155 | ConstPool constants = behavior.getMethodInfo().getConstPool(); | 300 | ConstPool constants = behavior.getMethodInfo().getConstPool(); |
| 156 | ConstPoolEditor editor = new ConstPoolEditor( constants ); | 301 | final MessageDigest digest = MessageDigest.getInstance( "MD5" ); |
| 157 | MessageDigest digest = MessageDigest.getInstance( "MD5" ); | ||
| 158 | CodeIterator iter = behavior.getMethodInfo().getCodeAttribute().iterator(); | 302 | CodeIterator iter = behavior.getMethodInfo().getCodeAttribute().iterator(); |
| 159 | while( iter.hasNext() ) | 303 | while( iter.hasNext() ) |
| 160 | { | 304 | { |
| @@ -164,46 +308,91 @@ public class ClassIdentity | |||
| 164 | int opcode = iter.byteAt( pos ); | 308 | int opcode = iter.byteAt( pos ); |
| 165 | digest.update( (byte)opcode ); | 309 | digest.update( (byte)opcode ); |
| 166 | 310 | ||
| 167 | // is there a constant value here? | ||
| 168 | int constIndex = -1; | ||
| 169 | switch( opcode ) | 311 | switch( opcode ) |
| 170 | { | 312 | { |
| 171 | case Opcode.LDC: | 313 | case Opcode.LDC: |
| 172 | constIndex = iter.byteAt( pos + 1 ); | 314 | { |
| 315 | int constIndex = iter.byteAt( pos + 1 ); | ||
| 316 | updateHashWithConstant( digest, constants, constIndex ); | ||
| 317 | } | ||
| 173 | break; | 318 | break; |
| 174 | 319 | ||
| 175 | case Opcode.LDC_W: | 320 | case Opcode.LDC_W: |
| 176 | constIndex = ( iter.byteAt( pos + 1 ) << 8 ) | iter.byteAt( pos + 2 ); | ||
| 177 | break; | ||
| 178 | |||
| 179 | case Opcode.LDC2_W: | 321 | case Opcode.LDC2_W: |
| 180 | constIndex = ( iter.byteAt( pos + 1 ) << 8 ) | iter.byteAt( pos + 2 ); | 322 | { |
| 323 | int constIndex = ( iter.byteAt( pos + 1 ) << 8 ) | iter.byteAt( pos + 2 ); | ||
| 324 | updateHashWithConstant( digest, constants, constIndex ); | ||
| 325 | } | ||
| 181 | break; | 326 | break; |
| 182 | } | 327 | } |
| 328 | } | ||
| 329 | |||
| 330 | // update hash with method and field accesses | ||
| 331 | behavior.instrument( new ExprEditor( ) | ||
| 332 | { | ||
| 333 | @Override | ||
| 334 | public void edit( MethodCall call ) | ||
| 335 | { | ||
| 336 | updateHashWithString( digest, scrubClassName( call.getClassName() ) ); | ||
| 337 | updateHashWithString( digest, scrubSignature( call.getSignature() ) ); | ||
| 338 | if( isClassMatchedUniquely( call.getClassName() ) ) | ||
| 339 | { | ||
| 340 | updateHashWithString( digest, call.getMethodName() ); | ||
| 341 | } | ||
| 342 | } | ||
| 183 | 343 | ||
| 184 | if( constIndex >= 0 ) | 344 | @Override |
| 345 | public void edit( FieldAccess access ) | ||
| 185 | { | 346 | { |
| 186 | // update the hash with the constant value | 347 | updateHashWithString( digest, scrubClassName( access.getClassName() ) ); |
| 187 | ConstInfoAccessor item = editor.getItem( constIndex ); | 348 | updateHashWithString( digest, scrubSignature( access.getSignature() ) ); |
| 188 | if( item.getType() == InfoType.StringInfo ) | 349 | if( isClassMatchedUniquely( access.getClassName() ) ) |
| 189 | { | 350 | { |
| 190 | String val = constants.getStringInfo( constIndex ); | 351 | updateHashWithString( digest, access.getFieldName() ); |
| 191 | try | ||
| 192 | { | ||
| 193 | digest.update( val.getBytes( "UTF8" ) ); | ||
| 194 | } | ||
| 195 | catch( UnsupportedEncodingException ex ) | ||
| 196 | { | ||
| 197 | throw new Error( ex ); | ||
| 198 | } | ||
| 199 | } | 352 | } |
| 200 | } | 353 | } |
| 201 | } | 354 | |
| 355 | @Override | ||
| 356 | public void edit( ConstructorCall call ) | ||
| 357 | { | ||
| 358 | updateHashWithString( digest, scrubClassName( call.getClassName() ) ); | ||
| 359 | updateHashWithString( digest, scrubSignature( call.getSignature() ) ); | ||
| 360 | } | ||
| 361 | |||
| 362 | @Override | ||
| 363 | public void edit( NewExpr expr ) | ||
| 364 | { | ||
| 365 | updateHashWithString( digest, scrubClassName( expr.getClassName() ) ); | ||
| 366 | } | ||
| 367 | } ); | ||
| 202 | 368 | ||
| 203 | // convert the hash to a hex string | 369 | // convert the hash to a hex string |
| 204 | return toHex( digest.digest() ); | 370 | return toHex( digest.digest() ); |
| 205 | } | 371 | } |
| 206 | catch( BadBytecode | NoSuchAlgorithmException ex ) | 372 | catch( BadBytecode | NoSuchAlgorithmException | CannotCompileException ex ) |
| 373 | { | ||
| 374 | throw new Error( ex ); | ||
| 375 | } | ||
| 376 | } | ||
| 377 | |||
| 378 | private void updateHashWithConstant( MessageDigest digest, ConstPool constants, int index ) | ||
| 379 | { | ||
| 380 | ConstPoolEditor editor = new ConstPoolEditor( constants ); | ||
| 381 | ConstInfoAccessor item = editor.getItem( index ); | ||
| 382 | if( item.getType() == InfoType.StringInfo ) | ||
| 383 | { | ||
| 384 | updateHashWithString( digest, constants.getStringInfo( index ) ); | ||
| 385 | } | ||
| 386 | // TODO: other constants | ||
| 387 | } | ||
| 388 | |||
| 389 | private void updateHashWithString( MessageDigest digest, String val ) | ||
| 390 | { | ||
| 391 | try | ||
| 392 | { | ||
| 393 | digest.update( val.getBytes( "UTF8" ) ); | ||
| 394 | } | ||
| 395 | catch( UnsupportedEncodingException ex ) | ||
| 207 | { | 396 | { |
| 208 | throw new Error( ex ); | 397 | throw new Error( ex ); |
| 209 | } | 398 | } |
| @@ -239,7 +428,11 @@ public class ClassIdentity | |||
| 239 | return m_fields.equals( other.m_fields ) | 428 | return m_fields.equals( other.m_fields ) |
| 240 | && m_methods.equals( other.m_methods ) | 429 | && m_methods.equals( other.m_methods ) |
| 241 | && m_constructors.equals( other.m_constructors ) | 430 | && m_constructors.equals( other.m_constructors ) |
| 242 | && m_staticInitializer.equals( other.m_staticInitializer ); | 431 | && m_staticInitializer.equals( other.m_staticInitializer ) |
| 432 | && m_extends.equals( other.m_extends ) | ||
| 433 | && m_implements.equals( other.m_implements ) | ||
| 434 | && m_implementations.equals( other.m_implementations ) | ||
| 435 | && m_references.equals( other.m_references ); | ||
| 243 | } | 436 | } |
| 244 | 437 | ||
| 245 | @Override | 438 | @Override |
| @@ -250,6 +443,10 @@ public class ClassIdentity | |||
| 250 | objs.addAll( m_methods ); | 443 | objs.addAll( m_methods ); |
| 251 | objs.addAll( m_constructors ); | 444 | objs.addAll( m_constructors ); |
| 252 | objs.add( m_staticInitializer ); | 445 | objs.add( m_staticInitializer ); |
| 446 | objs.add( m_extends ); | ||
| 447 | objs.addAll( m_implements ); | ||
| 448 | objs.addAll( m_implementations ); | ||
| 449 | objs.addAll( m_references ); | ||
| 253 | return Util.combineHashesOrdered( objs ); | 450 | return Util.combineHashesOrdered( objs ); |
| 254 | } | 451 | } |
| 255 | } | 452 | } |
diff --git a/src/cuchaz/enigma/convert/ClassMapper.java b/src/cuchaz/enigma/convert/ClassMapper.java index 5a16a1c..fe48c50 100644 --- a/src/cuchaz/enigma/convert/ClassMapper.java +++ b/src/cuchaz/enigma/convert/ClassMapper.java | |||
| @@ -12,31 +12,20 @@ package cuchaz.enigma.convert; | |||
| 12 | 12 | ||
| 13 | import java.io.File; | 13 | import java.io.File; |
| 14 | import java.io.IOException; | 14 | import java.io.IOException; |
| 15 | import java.util.Collection; | 15 | import java.util.Arrays; |
| 16 | import java.util.List; | 16 | import java.util.List; |
| 17 | import java.util.Map; | 17 | import java.util.Map; |
| 18 | import java.util.Set; | ||
| 18 | import java.util.jar.JarFile; | 19 | import java.util.jar.JarFile; |
| 19 | 20 | ||
| 20 | import javassist.CtClass; | 21 | import javassist.CtClass; |
| 21 | 22 | import cuchaz.enigma.TranslatingTypeLoader; | |
| 22 | import com.beust.jcommander.internal.Lists; | 23 | import cuchaz.enigma.analysis.JarIndex; |
| 23 | import com.beust.jcommander.internal.Maps; | 24 | import cuchaz.enigma.convert.ClassNamer.SidedClassNamer; |
| 24 | import com.google.common.collect.ArrayListMultimap; | ||
| 25 | import com.google.common.collect.Multimap; | ||
| 26 | |||
| 27 | import cuchaz.enigma.analysis.JarClassIterator; | ||
| 28 | import cuchaz.enigma.mapping.ClassEntry; | 25 | import cuchaz.enigma.mapping.ClassEntry; |
| 29 | 26 | ||
| 30 | public class ClassMapper | 27 | public class ClassMapper |
| 31 | { | 28 | { |
| 32 | private int m_numSourceClasses; | ||
| 33 | private int m_numDestClasses; | ||
| 34 | private Multimap<ClassIdentity,ClassIdentity> m_sourceClasses; | ||
| 35 | private Multimap<ClassIdentity,ClassIdentity> m_destClasses; | ||
| 36 | private List<ClassIdentity> m_unmatchedSourceClasses; | ||
| 37 | private List<ClassIdentity> m_unmatchedDestClasses; | ||
| 38 | private Map<ClassEntry,ClassIdentity> m_sourceKeyIndex; | ||
| 39 | |||
| 40 | public static void main( String[] args ) | 29 | public static void main( String[] args ) |
| 41 | throws IOException | 30 | throws IOException |
| 42 | { | 31 | { |
| @@ -44,81 +33,146 @@ public class ClassMapper | |||
| 44 | JarFile fromJar = new JarFile( new File( "input/1.8-pre1.jar" ) ); | 33 | JarFile fromJar = new JarFile( new File( "input/1.8-pre1.jar" ) ); |
| 45 | JarFile toJar = new JarFile( new File( "input/1.8-pre2.jar" ) ); | 34 | JarFile toJar = new JarFile( new File( "input/1.8-pre2.jar" ) ); |
| 46 | 35 | ||
| 47 | ClassMapper mapper = new ClassMapper( fromJar, toJar ); | 36 | // compute the matching |
| 48 | System.out.println( String.format( "Mapped %d/%d source classes (%d unmatched) to %d/%d dest classes (%d unmatched)", | 37 | ClassMatching matching = ClassMapper.computeMatching( fromJar, toJar ); |
| 49 | mapper.m_sourceClasses.size(), mapper.m_numSourceClasses, mapper.m_unmatchedSourceClasses.size(), | 38 | |
| 50 | mapper.m_destClasses.size(), mapper.m_numDestClasses, mapper.m_unmatchedDestClasses.size() | 39 | // TODO: use the matching to convert the mappings |
| 51 | ) ); | ||
| 52 | } | 40 | } |
| 53 | 41 | ||
| 54 | public ClassMapper( JarFile sourceJar, JarFile destJar ) | 42 | public static ClassMatching computeMatching( JarFile sourceJar, JarFile destJar ) |
| 55 | { | 43 | { |
| 56 | m_numSourceClasses = JarClassIterator.getClassEntries( sourceJar ).size(); | 44 | // index jars |
| 57 | m_numDestClasses = JarClassIterator.getClassEntries( destJar ).size(); | 45 | System.out.println( "Indexing source jar..." ); |
| 46 | JarIndex sourceIndex = new JarIndex(); | ||
| 47 | sourceIndex.indexJar( sourceJar ); | ||
| 48 | System.out.println( "Indexing dest jar..." ); | ||
| 49 | JarIndex destIndex = new JarIndex(); | ||
| 50 | destIndex.indexJar( destJar ); | ||
| 58 | 51 | ||
| 59 | // compute identities for the source classes | 52 | System.out.println( "Computing matching..." ); |
| 60 | m_sourceClasses = ArrayListMultimap.create(); | ||
| 61 | m_sourceKeyIndex = Maps.newHashMap(); | ||
| 62 | for( CtClass c : JarClassIterator.classes( sourceJar ) ) | ||
| 63 | { | ||
| 64 | ClassIdentity sourceClass = new ClassIdentity( c ); | ||
| 65 | m_sourceClasses.put( sourceClass, sourceClass ); | ||
| 66 | m_sourceKeyIndex.put( sourceClass.getClassEntry(), sourceClass ); | ||
| 67 | } | ||
| 68 | 53 | ||
| 69 | // match the dest classes to the source classes | 54 | TranslatingTypeLoader sourceLoader = new TranslatingTypeLoader( sourceJar, sourceIndex ); |
| 70 | m_destClasses = ArrayListMultimap.create(); | 55 | TranslatingTypeLoader destLoader = new TranslatingTypeLoader( destJar, destIndex ); |
| 71 | m_unmatchedDestClasses = Lists.newArrayList(); | 56 | |
| 72 | for( CtClass c : JarClassIterator.classes( destJar ) ) | 57 | ClassMatching matching = null; |
| 58 | for( boolean useReferences : Arrays.asList( false, true ) ) | ||
| 73 | { | 59 | { |
| 74 | ClassIdentity destClass = new ClassIdentity( c ); | 60 | int numMatches = 0; |
| 75 | Collection<ClassIdentity> matchedSourceClasses = m_sourceClasses.get( destClass ); | 61 | do |
| 76 | if( matchedSourceClasses.isEmpty() ) | ||
| 77 | { | ||
| 78 | // unmatched dest class | ||
| 79 | m_unmatchedDestClasses.add( destClass ); | ||
| 80 | } | ||
| 81 | else | ||
| 82 | { | 62 | { |
| 83 | ClassIdentity sourceClass = matchedSourceClasses.iterator().next(); | 63 | SidedClassNamer sourceNamer = null; |
| 84 | m_destClasses.put( sourceClass, destClass ); | 64 | SidedClassNamer destNamer = null; |
| 65 | if( matching != null ) | ||
| 66 | { | ||
| 67 | // build a class namer | ||
| 68 | ClassNamer namer = new ClassNamer( matching.getUniqueMatches() ); | ||
| 69 | sourceNamer = namer.getSourceNamer(); | ||
| 70 | destNamer = namer.getDestNamer(); | ||
| 71 | |||
| 72 | // note the number of matches | ||
| 73 | numMatches = matching.getUniqueMatches().size(); | ||
| 74 | } | ||
| 75 | |||
| 76 | // get the entries left to match | ||
| 77 | Set<ClassEntry> sourceClassEntries = sourceIndex.getObfClassEntries(); | ||
| 78 | Set<ClassEntry> destClassEntries = destIndex.getObfClassEntries(); | ||
| 79 | if( matching != null ) | ||
| 80 | { | ||
| 81 | sourceClassEntries.clear(); | ||
| 82 | destClassEntries.clear(); | ||
| 83 | for( Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry : matching.getAmbiguousMatches().entrySet() ) | ||
| 84 | { | ||
| 85 | for( ClassIdentity c : entry.getKey() ) | ||
| 86 | { | ||
| 87 | sourceClassEntries.add( c.getClassEntry() ); | ||
| 88 | matching.removeSource( c ); | ||
| 89 | } | ||
| 90 | for( ClassIdentity c : entry.getValue() ) | ||
| 91 | { | ||
| 92 | destClassEntries.add( c.getClassEntry() ); | ||
| 93 | matching.removeDest( c ); | ||
| 94 | } | ||
| 95 | } | ||
| 96 | for( ClassIdentity c : matching.getUnmatchedSourceClasses() ) | ||
| 97 | { | ||
| 98 | sourceClassEntries.add( c.getClassEntry() ); | ||
| 99 | matching.removeSource( c ); | ||
| 100 | } | ||
| 101 | for( ClassIdentity c : matching.getUnmatchedDestClasses() ) | ||
| 102 | { | ||
| 103 | destClassEntries.add( c.getClassEntry() ); | ||
| 104 | matching.removeDest( c ); | ||
| 105 | } | ||
| 106 | } | ||
| 107 | else | ||
| 108 | { | ||
| 109 | matching = new ClassMatching(); | ||
| 110 | } | ||
| 111 | |||
| 112 | // compute a matching for the classes | ||
| 113 | for( ClassEntry classEntry : sourceClassEntries ) | ||
| 114 | { | ||
| 115 | CtClass c = sourceLoader.loadClass( classEntry.getName() ); | ||
| 116 | ClassIdentity sourceClass = new ClassIdentity( c, sourceNamer, sourceIndex, useReferences ); | ||
| 117 | matching.addSource( sourceClass ); | ||
| 118 | } | ||
| 119 | for( ClassEntry classEntry : destClassEntries ) | ||
| 120 | { | ||
| 121 | CtClass c = destLoader.loadClass( classEntry.getName() ); | ||
| 122 | ClassIdentity destClass = new ClassIdentity( c, destNamer, destIndex, useReferences ); | ||
| 123 | matching.matchDestClass( destClass ); | ||
| 124 | } | ||
| 125 | |||
| 126 | // TEMP | ||
| 127 | System.out.println( matching ); | ||
| 85 | } | 128 | } |
| 129 | while( matching.getUniqueMatches().size() - numMatches > 0 ); | ||
| 86 | } | 130 | } |
| 87 | 131 | ||
| 88 | // get unmatched source classes | 132 | /* DEBUG: show some ambiguous matches |
| 89 | m_unmatchedSourceClasses = Lists.newArrayList(); | 133 | List<Map.Entry<List<ClassIdentity>,List<ClassIdentity>>> ambiguousMatches = new ArrayList<Map.Entry<List<ClassIdentity>,List<ClassIdentity>>>( matching.getAmbiguousMatches().entrySet() ); |
| 90 | for( ClassIdentity sourceClass : m_sourceClasses.keySet() ) | 134 | Collections.sort( ambiguousMatches, new Comparator<Map.Entry<List<ClassIdentity>,List<ClassIdentity>>>( ) |
| 91 | { | 135 | { |
| 92 | Collection<ClassIdentity> matchedSourceClasses = m_sourceClasses.get( sourceClass ); | 136 | @Override |
| 93 | Collection<ClassIdentity> matchedDestClasses = m_destClasses.get( sourceClass ); | 137 | public int compare( Map.Entry<List<ClassIdentity>,List<ClassIdentity>> a, Map.Entry<List<ClassIdentity>,List<ClassIdentity>> b ) |
| 94 | if( matchedDestClasses.isEmpty() ) | ||
| 95 | { | 138 | { |
| 96 | m_unmatchedSourceClasses.add( sourceClass ); | 139 | String aName = a.getKey().get( 0 ).getClassEntry().getName(); |
| 140 | String bName = b.getKey().get( 0 ).getClassEntry().getName(); | ||
| 141 | return aName.compareTo( bName ); | ||
| 97 | } | 142 | } |
| 98 | else if( matchedDestClasses.size() > 1 ) | 143 | } ); |
| 144 | for( Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry : ambiguousMatches ) | ||
| 145 | { | ||
| 146 | for( ClassIdentity c : entry.getKey() ) | ||
| 99 | { | 147 | { |
| 100 | // warn about identity collisions | 148 | System.out.print( c.getClassEntry().getName() + " " ); |
| 101 | System.err.println( String.format( "WARNING: identity collision:\n\tSource: %s\n\t Dest: %s", | ||
| 102 | getClassEntries( matchedSourceClasses ), | ||
| 103 | getClassEntries( matchedDestClasses ) | ||
| 104 | ) ); | ||
| 105 | } | 149 | } |
| 150 | System.out.println(); | ||
| 151 | } | ||
| 152 | Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry = ambiguousMatches.get( 7 ); | ||
| 153 | for( ClassIdentity c : entry.getKey() ) | ||
| 154 | { | ||
| 155 | System.out.println( c ); | ||
| 106 | } | 156 | } |
| 157 | for( ClassIdentity c : entry.getKey() ) | ||
| 158 | { | ||
| 159 | System.out.println( decompile( sourceLoader, c.getClassEntry() ) ); | ||
| 160 | } | ||
| 161 | */ | ||
| 162 | |||
| 163 | return matching; | ||
| 107 | } | 164 | } |
| 108 | 165 | ||
| 109 | public Map.Entry<Collection<ClassEntry>,Collection<ClassEntry>> getMapping( ClassEntry sourceEntry ) | 166 | /* DEBUG |
| 167 | private static String decompile( TranslatingTypeLoader loader, ClassEntry classEntry ) | ||
| 110 | { | 168 | { |
| 111 | // TODO | 169 | PlainTextOutput output = new PlainTextOutput(); |
| 112 | return null; | 170 | DecompilerSettings settings = DecompilerSettings.javaDefaults(); |
| 113 | } | 171 | settings.setForceExplicitImports( true ); |
| 114 | 172 | settings.setShowSyntheticMembers( true ); | |
| 115 | private Collection<ClassEntry> getClassEntries( Collection<ClassIdentity> classes ) | 173 | settings.setTypeLoader( loader ); |
| 116 | { | 174 | Decompiler.decompile( classEntry.getName(), output, settings ); |
| 117 | List<ClassEntry> entries = Lists.newArrayList(); | 175 | return output.toString(); |
| 118 | for( ClassIdentity c : classes ) | ||
| 119 | { | ||
| 120 | entries.add( c.getClassEntry() ); | ||
| 121 | } | ||
| 122 | return entries; | ||
| 123 | } | 176 | } |
| 177 | */ | ||
| 124 | } | 178 | } |
diff --git a/src/cuchaz/enigma/convert/ClassMatching.java b/src/cuchaz/enigma/convert/ClassMatching.java new file mode 100644 index 0000000..fea8438 --- /dev/null +++ b/src/cuchaz/enigma/convert/ClassMatching.java | |||
| @@ -0,0 +1,184 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.convert; | ||
| 12 | |||
| 13 | import java.util.ArrayList; | ||
| 14 | import java.util.Collection; | ||
| 15 | import java.util.List; | ||
| 16 | import java.util.Map; | ||
| 17 | |||
| 18 | import com.beust.jcommander.internal.Lists; | ||
| 19 | import com.google.common.collect.ArrayListMultimap; | ||
| 20 | import com.google.common.collect.BiMap; | ||
| 21 | import com.google.common.collect.HashBiMap; | ||
| 22 | import com.google.common.collect.Multimap; | ||
| 23 | |||
| 24 | public class ClassMatching | ||
| 25 | { | ||
| 26 | private Multimap<ClassIdentity,ClassIdentity> m_sourceClasses; | ||
| 27 | private Multimap<ClassIdentity,ClassIdentity> m_matchedDestClasses; | ||
| 28 | private List<ClassIdentity> m_unmatchedDestClasses; | ||
| 29 | |||
| 30 | public ClassMatching( ) | ||
| 31 | { | ||
| 32 | m_sourceClasses = ArrayListMultimap.create(); | ||
| 33 | m_matchedDestClasses = ArrayListMultimap.create(); | ||
| 34 | m_unmatchedDestClasses = Lists.newArrayList(); | ||
| 35 | } | ||
| 36 | |||
| 37 | public void addSource( ClassIdentity c ) | ||
| 38 | { | ||
| 39 | m_sourceClasses.put( c, c ); | ||
| 40 | } | ||
| 41 | |||
| 42 | public void matchDestClass( ClassIdentity destClass ) | ||
| 43 | { | ||
| 44 | Collection<ClassIdentity> matchedSourceClasses = m_sourceClasses.get( destClass ); | ||
| 45 | if( matchedSourceClasses.isEmpty() ) | ||
| 46 | { | ||
| 47 | // no match | ||
| 48 | m_unmatchedDestClasses.add( destClass ); | ||
| 49 | } | ||
| 50 | else | ||
| 51 | { | ||
| 52 | // found a match | ||
| 53 | m_matchedDestClasses.put( destClass, destClass ); | ||
| 54 | |||
| 55 | // DEBUG | ||
| 56 | ClassIdentity sourceClass = matchedSourceClasses.iterator().next(); | ||
| 57 | assert( sourceClass.hashCode() == destClass.hashCode() ); | ||
| 58 | assert( sourceClass.equals( destClass ) ); | ||
| 59 | } | ||
| 60 | } | ||
| 61 | |||
| 62 | public void removeSource( ClassIdentity sourceClass ) | ||
| 63 | { | ||
| 64 | m_sourceClasses.remove( sourceClass, sourceClass ); | ||
| 65 | } | ||
| 66 | |||
| 67 | public void removeDest( ClassIdentity destClass ) | ||
| 68 | { | ||
| 69 | m_matchedDestClasses.remove( destClass, destClass ); | ||
| 70 | m_unmatchedDestClasses.remove( destClass ); | ||
| 71 | } | ||
| 72 | |||
| 73 | public List<ClassIdentity> getSourceClasses( ) | ||
| 74 | { | ||
| 75 | return new ArrayList<ClassIdentity>( m_sourceClasses.values() ); | ||
| 76 | } | ||
| 77 | |||
| 78 | public List<ClassIdentity> getDestClasses( ) | ||
| 79 | { | ||
| 80 | List<ClassIdentity> classes = Lists.newArrayList(); | ||
| 81 | classes.addAll( m_matchedDestClasses.values() ); | ||
| 82 | classes.addAll( m_unmatchedDestClasses ); | ||
| 83 | return classes; | ||
| 84 | } | ||
| 85 | |||
| 86 | public BiMap<ClassIdentity,ClassIdentity> getUniqueMatches( ) | ||
| 87 | { | ||
| 88 | BiMap<ClassIdentity,ClassIdentity> uniqueMatches = HashBiMap.create(); | ||
| 89 | for( ClassIdentity sourceClass : m_sourceClasses.keySet() ) | ||
| 90 | { | ||
| 91 | Collection<ClassIdentity> matchedSourceClasses = m_sourceClasses.get( sourceClass ); | ||
| 92 | Collection<ClassIdentity> matchedDestClasses = m_matchedDestClasses.get( sourceClass ); | ||
| 93 | if( matchedSourceClasses.size() == 1 && matchedDestClasses.size() == 1 ) | ||
| 94 | { | ||
| 95 | ClassIdentity matchedSourceClass = matchedSourceClasses.iterator().next(); | ||
| 96 | ClassIdentity matchedDestClass = matchedSourceClasses.iterator().next(); | ||
| 97 | uniqueMatches.put( matchedSourceClass, matchedDestClass ); | ||
| 98 | } | ||
| 99 | } | ||
| 100 | return uniqueMatches; | ||
| 101 | } | ||
| 102 | |||
| 103 | public BiMap<List<ClassIdentity>,List<ClassIdentity>> getAmbiguousMatches( ) | ||
| 104 | { | ||
| 105 | BiMap<List<ClassIdentity>,List<ClassIdentity>> ambiguousMatches = HashBiMap.create(); | ||
| 106 | for( ClassIdentity sourceClass : m_sourceClasses.keySet() ) | ||
| 107 | { | ||
| 108 | Collection<ClassIdentity> matchedSourceClasses = m_sourceClasses.get( sourceClass ); | ||
| 109 | Collection<ClassIdentity> matchedDestClasses = m_matchedDestClasses.get( sourceClass ); | ||
| 110 | if( matchedSourceClasses.size() > 1 && matchedDestClasses.size() > 1 ) | ||
| 111 | { | ||
| 112 | ambiguousMatches.put( | ||
| 113 | new ArrayList<ClassIdentity>( matchedSourceClasses ), | ||
| 114 | new ArrayList<ClassIdentity>( matchedDestClasses ) | ||
| 115 | ); | ||
| 116 | } | ||
| 117 | } | ||
| 118 | return ambiguousMatches; | ||
| 119 | } | ||
| 120 | |||
| 121 | public int getNumAmbiguousSourceMatches( ) | ||
| 122 | { | ||
| 123 | int num = 0; | ||
| 124 | for( Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry : getAmbiguousMatches().entrySet() ) | ||
| 125 | { | ||
| 126 | num += entry.getKey().size(); | ||
| 127 | } | ||
| 128 | return num; | ||
| 129 | } | ||
| 130 | |||
| 131 | public int getNumAmbiguousDestMatches( ) | ||
| 132 | { | ||
| 133 | int num = 0; | ||
| 134 | for( Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry : getAmbiguousMatches().entrySet() ) | ||
| 135 | { | ||
| 136 | num += entry.getValue().size(); | ||
| 137 | } | ||
| 138 | return num; | ||
| 139 | } | ||
| 140 | |||
| 141 | public List<ClassIdentity> getUnmatchedSourceClasses( ) | ||
| 142 | { | ||
| 143 | List<ClassIdentity> classes = Lists.newArrayList(); | ||
| 144 | for( ClassIdentity sourceClass : getSourceClasses() ) | ||
| 145 | { | ||
| 146 | if( m_matchedDestClasses.get( sourceClass ).isEmpty() ) | ||
| 147 | { | ||
| 148 | classes.add( sourceClass ); | ||
| 149 | } | ||
| 150 | } | ||
| 151 | return classes; | ||
| 152 | } | ||
| 153 | |||
| 154 | public List<ClassIdentity> getUnmatchedDestClasses( ) | ||
| 155 | { | ||
| 156 | return new ArrayList<ClassIdentity>( m_unmatchedDestClasses ); | ||
| 157 | } | ||
| 158 | |||
| 159 | @Override | ||
| 160 | public String toString( ) | ||
| 161 | { | ||
| 162 | StringBuilder buf = new StringBuilder(); | ||
| 163 | |||
| 164 | buf.append( "Source classes: " ); | ||
| 165 | buf.append( getSourceClasses().size() ); | ||
| 166 | buf.append( "\n\tUnique: " ); | ||
| 167 | buf.append( getUniqueMatches().size() ); | ||
| 168 | buf.append( "\n\tAmbiguous: " ); | ||
| 169 | buf.append( getNumAmbiguousSourceMatches() ); | ||
| 170 | buf.append( "\n\tUnmatched: " ); | ||
| 171 | buf.append( getUnmatchedSourceClasses().size() ); | ||
| 172 | |||
| 173 | buf.append( "\nDest classes: " ); | ||
| 174 | buf.append( getDestClasses().size() ); | ||
| 175 | buf.append( "\n\tUnique: " ); | ||
| 176 | buf.append( getUniqueMatches().size() ); | ||
| 177 | buf.append( "\n\tAmbiguous: " ); | ||
| 178 | buf.append( getNumAmbiguousDestMatches() ); | ||
| 179 | buf.append( "\n\tUnmatched: " ); | ||
| 180 | buf.append( getUnmatchedDestClasses().size() ); | ||
| 181 | |||
| 182 | return buf.toString(); | ||
| 183 | } | ||
| 184 | } | ||
diff --git a/src/cuchaz/enigma/convert/ClassNamer.java b/src/cuchaz/enigma/convert/ClassNamer.java new file mode 100644 index 0000000..1cd9665 --- /dev/null +++ b/src/cuchaz/enigma/convert/ClassNamer.java | |||
| @@ -0,0 +1,75 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.convert; | ||
| 12 | |||
| 13 | import java.util.Map; | ||
| 14 | |||
| 15 | import com.beust.jcommander.internal.Maps; | ||
| 16 | import com.google.common.collect.BiMap; | ||
| 17 | |||
| 18 | public class ClassNamer | ||
| 19 | { | ||
| 20 | public interface SidedClassNamer | ||
| 21 | { | ||
| 22 | String getName( String name ); | ||
| 23 | } | ||
| 24 | |||
| 25 | private Map<String,String> m_sourceNames; | ||
| 26 | private Map<String,String> m_destNames; | ||
| 27 | |||
| 28 | public ClassNamer( BiMap<ClassIdentity,ClassIdentity> mappings ) | ||
| 29 | { | ||
| 30 | // convert the identity mappings to name maps | ||
| 31 | m_sourceNames = Maps.newHashMap(); | ||
| 32 | m_destNames = Maps.newHashMap(); | ||
| 33 | int i = 0; | ||
| 34 | for( Map.Entry<ClassIdentity,ClassIdentity> entry : mappings.entrySet() ) | ||
| 35 | { | ||
| 36 | String name = String.format( "M%04d", i++ ); | ||
| 37 | m_sourceNames.put( entry.getKey().getClassEntry().getName(), name ); | ||
| 38 | m_destNames.put( entry.getValue().getClassEntry().getName(), name ); | ||
| 39 | } | ||
| 40 | } | ||
| 41 | |||
| 42 | public String getSourceName( String name ) | ||
| 43 | { | ||
| 44 | return m_sourceNames.get( name ); | ||
| 45 | } | ||
| 46 | |||
| 47 | public String getDestName( String name ) | ||
| 48 | { | ||
| 49 | return m_destNames.get( name ); | ||
| 50 | } | ||
| 51 | |||
| 52 | public SidedClassNamer getSourceNamer( ) | ||
| 53 | { | ||
| 54 | return new SidedClassNamer( ) | ||
| 55 | { | ||
| 56 | @Override | ||
| 57 | public String getName( String name ) | ||
| 58 | { | ||
| 59 | return getSourceName( name ); | ||
| 60 | } | ||
| 61 | }; | ||
| 62 | } | ||
| 63 | |||
| 64 | public SidedClassNamer getDestNamer( ) | ||
| 65 | { | ||
| 66 | return new SidedClassNamer( ) | ||
| 67 | { | ||
| 68 | @Override | ||
| 69 | public String getName( String name ) | ||
| 70 | { | ||
| 71 | return getDestName( name ); | ||
| 72 | } | ||
| 73 | }; | ||
| 74 | } | ||
| 75 | } | ||
diff --git a/src/cuchaz/enigma/mapping/Translator.java b/src/cuchaz/enigma/mapping/Translator.java index a671c27..23bf095 100644 --- a/src/cuchaz/enigma/mapping/Translator.java +++ b/src/cuchaz/enigma/mapping/Translator.java | |||
| @@ -14,6 +14,8 @@ import java.util.ArrayList; | |||
| 14 | import java.util.List; | 14 | import java.util.List; |
| 15 | import java.util.Map; | 15 | import java.util.Map; |
| 16 | 16 | ||
| 17 | import com.beust.jcommander.internal.Maps; | ||
| 18 | |||
| 17 | import cuchaz.enigma.analysis.Ancestries; | 19 | import cuchaz.enigma.analysis.Ancestries; |
| 18 | import cuchaz.enigma.mapping.SignatureUpdater.ClassNameUpdater; | 20 | import cuchaz.enigma.mapping.SignatureUpdater.ClassNameUpdater; |
| 19 | 21 | ||
| @@ -23,6 +25,13 @@ public class Translator | |||
| 23 | public Map<String,ClassMapping> m_classes; | 25 | public Map<String,ClassMapping> m_classes; |
| 24 | private Ancestries m_ancestries; | 26 | private Ancestries m_ancestries; |
| 25 | 27 | ||
| 28 | public Translator( ) | ||
| 29 | { | ||
| 30 | m_direction = null; | ||
| 31 | m_classes = Maps.newHashMap(); | ||
| 32 | m_ancestries = new Ancestries(); | ||
| 33 | } | ||
| 34 | |||
| 26 | protected Translator( TranslationDirection direction, Map<String,ClassMapping> classes, Ancestries ancestries ) | 35 | protected Translator( TranslationDirection direction, Map<String,ClassMapping> classes, Ancestries ancestries ) |
| 27 | { | 36 | { |
| 28 | m_direction = direction; | 37 | m_direction = direction; |