From 59c592673635e989fd0785d41d51d7c3dd17cc0b Mon Sep 17 00:00:00 2001 From: jeff Date: Sat, 30 Aug 2014 16:31:31 -0400 Subject: debugging class matcher... almost got it! --- src/cuchaz/enigma/Deobfuscator.java | 2 +- src/cuchaz/enigma/analysis/JarIndex.java | 57 ++-- src/cuchaz/enigma/convert/ClassIdentity.java | 40 +-- src/cuchaz/enigma/convert/ClassMapper.java | 297 -------------------- src/cuchaz/enigma/convert/ClassMatcher.java | 398 +++++++++++++++++++++++++++ src/cuchaz/enigma/convert/ClassMatching.java | 2 +- src/cuchaz/enigma/mapping/Mappings.java | 7 + 7 files changed, 461 insertions(+), 342 deletions(-) delete mode 100644 src/cuchaz/enigma/convert/ClassMapper.java create mode 100644 src/cuchaz/enigma/convert/ClassMatcher.java (limited to 'src') diff --git a/src/cuchaz/enigma/Deobfuscator.java b/src/cuchaz/enigma/Deobfuscator.java index cc1465a..9a78f38 100644 --- a/src/cuchaz/enigma/Deobfuscator.java +++ b/src/cuchaz/enigma/Deobfuscator.java @@ -70,7 +70,7 @@ public class Deobfuscator // build the jar index m_jarIndex = new JarIndex(); - m_jarIndex.indexJar( m_jar ); + m_jarIndex.indexJar( m_jar, true ); // config the decompiler m_settings = DecompilerSettings.javaDefaults(); diff --git a/src/cuchaz/enigma/analysis/JarIndex.java b/src/cuchaz/enigma/analysis/JarIndex.java index b479b69..4279ded 100644 --- a/src/cuchaz/enigma/analysis/JarIndex.java +++ b/src/cuchaz/enigma/analysis/JarIndex.java @@ -79,7 +79,7 @@ public class JarIndex m_bridgeMethods = Maps.newHashMap(); } - public void indexJar( JarFile jar ) + public void indexJar( JarFile jar, boolean buildInnerClasses ) { // step 1: read the class names for( ClassEntry classEntry : JarClassIterator.getClassEntries( jar ) ) @@ -125,40 +125,43 @@ public class JarIndex } } - // step 4: index inner classes and anonymous classes - for( CtClass c : JarClassIterator.classes( jar ) ) + if( buildInnerClasses ) { - ClassRenamer.moveAllClassesOutOfDefaultPackage( c, Constants.NonePackage ); - String outerClassName = findOuterClass( c ); - if( outerClassName != null ) + // step 4: index inner classes and anonymous classes + for( CtClass c : JarClassIterator.classes( jar ) ) { - String innerClassName = Descriptor.toJvmName( c.getName() ); - m_innerClasses.put( outerClassName, innerClassName ); - m_outerClasses.put( innerClassName, outerClassName ); - - if( isAnonymousClass( c, outerClassName ) ) + ClassRenamer.moveAllClassesOutOfDefaultPackage( c, Constants.NonePackage ); + String outerClassName = findOuterClass( c ); + if( outerClassName != null ) { - m_anonymousClasses.add( innerClassName ); + String innerClassName = Descriptor.toJvmName( c.getName() ); + m_innerClasses.put( outerClassName, innerClassName ); + m_outerClasses.put( innerClassName, outerClassName ); - // DEBUG - //System.out.println( "ANONYMOUS: " + outerClassName + "$" + innerClassName ); - } - else - { - // DEBUG - //System.out.println( "INNER: " + outerClassName + "$" + innerClassName ); + if( isAnonymousClass( c, outerClassName ) ) + { + m_anonymousClasses.add( innerClassName ); + + // DEBUG + //System.out.println( "ANONYMOUS: " + outerClassName + "$" + innerClassName ); + } + else + { + // DEBUG + //System.out.println( "INNER: " + outerClassName + "$" + innerClassName ); + } } } + + // step 5: update other indices with inner class info + Map renames = Maps.newHashMap(); + for( Map.Entry entry : m_outerClasses.entrySet() ) + { + renames.put( entry.getKey(), entry.getValue() + "$" + new ClassEntry( entry.getKey() ).getSimpleName() ); + } + renameClasses( renames ); } - // step 5: update other indices with inner class info - Map renames = Maps.newHashMap(); - for( Map.Entry entry : m_outerClasses.entrySet() ) - { - renames.put( entry.getKey(), entry.getValue() + "$" + new ClassEntry( entry.getKey() ).getSimpleName() ); - } - renameClasses( renames ); - // step 5: update other indices with bridge method info renameMethods( m_bridgeMethods ); } diff --git a/src/cuchaz/enigma/convert/ClassIdentity.java b/src/cuchaz/enigma/convert/ClassIdentity.java index 980f31f..8de7128 100644 --- a/src/cuchaz/enigma/convert/ClassIdentity.java +++ b/src/cuchaz/enigma/convert/ClassIdentity.java @@ -16,7 +16,6 @@ import java.security.NoSuchAlgorithmException; import java.util.Enumeration; import java.util.List; import java.util.Map; -import java.util.Set; import javassist.CannotCompileException; import javassist.CtBehavior; @@ -36,8 +35,9 @@ import javassist.expr.MethodCall; import javassist.expr.NewExpr; import com.beust.jcommander.internal.Maps; -import com.beust.jcommander.internal.Sets; +import com.google.common.collect.HashMultiset; import com.google.common.collect.Lists; +import com.google.common.collect.Multiset; import cuchaz.enigma.Constants; import cuchaz.enigma.Util; @@ -62,14 +62,14 @@ public class ClassIdentity private ClassEntry m_classEntry; private String m_rawName; private SidedClassNamer m_namer; - private Set m_fields; - private Set m_methods; - private Set m_constructors; + private Multiset m_fields; + private Multiset m_methods; + private Multiset m_constructors; private String m_staticInitializer; private String m_extends; - private Set m_implements; - private Set m_implementations; - private Set m_references; + private Multiset m_implements; + private Multiset m_implementations; + private Multiset m_references; public ClassIdentity( CtClass c, SidedClassNamer namer, JarIndex index, boolean useReferences, boolean useRawNames ) { @@ -83,17 +83,17 @@ public class ClassIdentity { m_rawName = m_classEntry.getName(); } - m_fields = Sets.newHashSet(); + m_fields = HashMultiset.create(); for( CtField field : c.getDeclaredFields() ) { m_fields.add( scrubSignature( field.getSignature() ) ); } - m_methods = Sets.newHashSet(); + m_methods = HashMultiset.create(); for( CtMethod method : c.getDeclaredMethods() ) { m_methods.add( scrubSignature( method.getSignature() ) + "0x" + getBehaviorSignature( method ) ); } - m_constructors = Sets.newHashSet(); + m_constructors = HashMultiset.create(); for( CtConstructor constructor : c.getDeclaredConstructors() ) { m_constructors.add( scrubSignature( constructor.getSignature() ) + "0x" + getBehaviorSignature( constructor ) ); @@ -108,7 +108,7 @@ public class ClassIdentity { m_extends = scrubClassName( c.getClassFile().getSuperclass() ); } - m_implements = Sets.newHashSet(); + m_implements = HashMultiset.create(); for( String interfaceName : c.getClassFile().getInterfaces() ) { m_implements.add( scrubClassName( interfaceName ) ); @@ -116,7 +116,7 @@ public class ClassIdentity // stuff from the jar index - m_implementations = Sets.newHashSet(); + m_implementations = HashMultiset.create(); @SuppressWarnings( "unchecked" ) Enumeration implementations = index.getClassImplementations( null, m_classEntry ).children(); while( implementations.hasMoreElements() ) @@ -125,7 +125,7 @@ public class ClassIdentity m_implementations.add( scrubClassName( node.getClassEntry().getName() ) ); } - m_references = Sets.newHashSet(); + m_references = HashMultiset.create(); if( useReferences ) { for( CtField field : c.getDeclaredFields() ) @@ -154,7 +154,7 @@ public class ClassIdentity } } } - + private void addReference( EntryReference reference ) { if( reference.context.getSignature() != null ) @@ -473,7 +473,15 @@ public class ClassIdentity + getNumMatches( m_constructors, other.m_constructors ); } - private int getNumMatches( Set a, Set b ) + public boolean matches( CtClass c ) + { + // just compare declaration counts + return m_fields.size() == c.getDeclaredFields().length + && m_methods.size() == c.getDeclaredMethods().length + && m_constructors.size() == c.getDeclaredConstructors().length; + } + + private int getNumMatches( Multiset a, Multiset b ) { int numMatches = 0; for( String val : a ) diff --git a/src/cuchaz/enigma/convert/ClassMapper.java b/src/cuchaz/enigma/convert/ClassMapper.java deleted file mode 100644 index fd6ab92..0000000 --- a/src/cuchaz/enigma/convert/ClassMapper.java +++ /dev/null @@ -1,297 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Public License v3.0 - * which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/gpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ -package cuchaz.enigma.convert; - -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.util.Arrays; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; -import java.util.jar.JarFile; - -import javassist.CtClass; - -import com.beust.jcommander.internal.Sets; -import com.google.common.collect.Maps; - -import cuchaz.enigma.Constants; -import cuchaz.enigma.TranslatingTypeLoader; -import cuchaz.enigma.analysis.JarIndex; -import cuchaz.enigma.convert.ClassNamer.SidedClassNamer; -import cuchaz.enigma.mapping.ClassEntry; -import cuchaz.enigma.mapping.ClassMapping; -import cuchaz.enigma.mapping.MappingParseException; -import cuchaz.enigma.mapping.Mappings; -import cuchaz.enigma.mapping.MappingsReader; -import cuchaz.enigma.mapping.MappingsWriter; - -public class ClassMapper -{ - public static void main( String[] args ) - throws IOException, MappingParseException - { - // TEMP - JarFile fromJar = new JarFile( new File( "input/1.8-pre1.jar" ) ); - JarFile toJar = new JarFile( new File( "input/1.8-pre3.jar" ) ); - File inMappingsFile = new File( "../minecraft-mappings/1.8-pre.mappings" ); - File outMappingsFile = new File( "../minecraft-mappings/1.8-pre3.mappings" ); - - // compute the matching - ClassMatching matching = ClassMapper.computeMatching( fromJar, toJar ); - - // use the matching to convert the mappings - Mappings mappings = new MappingsReader().read( new FileReader( inMappingsFile ) ); - Map>> conversionMap = matching.getConversionMap(); - Map finalConversion = Maps.newHashMap(); - Set unmatchedSourceClasses = Sets.newHashSet(); - for( ClassMapping classMapping : mappings.classes() ) - { - // is there a match for this class? - Map.Entry> entry = conversionMap.get( classMapping.getObfName() ); - ClassIdentity sourceClass = entry.getKey(); - List matches = entry.getValue(); - - if( matches.isEmpty() ) - { - // no match! =( - System.out.println( "No exact match for source class " + classMapping.getObfName() ); - - // find the closest classes - TreeMap scoredMatches = Maps.newTreeMap( Collections.reverseOrder() ); - for( ClassIdentity c : matching.getUnmatchedDestClasses() ) - { - scoredMatches.put( sourceClass.getMatchScore( c ), c ); - } - Iterator> iter = scoredMatches.entrySet().iterator(); - for( int i=0; i<10 && iter.hasNext(); i++ ) - { - Map.Entry score = iter.next(); - System.out.println( String.format( "\tScore: %3d %s", score.getKey(), score.getValue().getClassEntry().getName() ) ); - } - - // does the best match have a non-zero score and the same name? - Map.Entry bestMatch = scoredMatches.firstEntry(); - if( bestMatch.getKey() > 0 && bestMatch.getValue().getClassEntry().equals( sourceClass.getClassEntry() ) ) - { - // use it - System.out.println( "\tAutomatically choosing likely match: " + bestMatch.getValue().getClassEntry().getName() ); - addFinalConversion( finalConversion, sourceClass, bestMatch.getValue() ); - } - else - { - unmatchedSourceClasses.add( classMapping.getObfName() ); - } - } - if( matches.size() == 1 ) - { - // unique match! We're good to go! - addFinalConversion( finalConversion, sourceClass, matches.get( 0 ) ); - } - else if( matches.size() > 1 ) - { - // too many matches! =( - unmatchedSourceClasses.add( classMapping.getObfName() ); - } - } - - // remove (and warn about) unmatched classes - if( !unmatchedSourceClasses.isEmpty() ) - { - System.err.println( "WARNING: there were unmatched classes!" ); - for( String className : unmatchedSourceClasses ) - { - System.err.println( "\t" + className ); - mappings.removeClassByObfName( className ); - } - System.err.println( "Mappings for these classes have been removed." ); - } - - // show the class name changes - for( Map.Entry entry : finalConversion.entrySet() ) - { - if( !entry.getKey().equals( entry.getValue() ) ) - { - System.out.println( String.format( "Class change: %s -> %s", entry.getKey(), entry.getValue() ) ); - } - } - - // do the final conversion - mappings.renameObfClasses( finalConversion ); - FileWriter writer = new FileWriter( outMappingsFile ); - new MappingsWriter().write( writer, mappings ); - writer.close(); - System.out.println( "Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath() ); - } - - public static ClassMatching computeMatching( JarFile sourceJar, JarFile destJar ) - { - // index jars - System.out.println( "Indexing source jar..." ); - JarIndex sourceIndex = new JarIndex(); - sourceIndex.indexJar( sourceJar ); - System.out.println( "Indexing dest jar..." ); - JarIndex destIndex = new JarIndex(); - destIndex.indexJar( destJar ); - - System.out.println( "Computing matching..." ); - - TranslatingTypeLoader sourceLoader = new TranslatingTypeLoader( sourceJar, sourceIndex ); - TranslatingTypeLoader destLoader = new TranslatingTypeLoader( destJar, destIndex ); - - ClassMatching matching = null; - for( boolean useRawNames : Arrays.asList( false, true ) ) - { - for( boolean useReferences : Arrays.asList( false, true ) ) - { - int numMatches = 0; - do - { - SidedClassNamer sourceNamer = null; - SidedClassNamer destNamer = null; - if( matching != null ) - { - // build a class namer - ClassNamer namer = new ClassNamer( matching.getUniqueMatches() ); - sourceNamer = namer.getSourceNamer(); - destNamer = namer.getDestNamer(); - - // note the number of matches - numMatches = matching.getUniqueMatches().size(); - } - - // get the entries left to match - Set sourceClassEntries = sourceIndex.getObfClassEntries(); - Set destClassEntries = destIndex.getObfClassEntries(); - if( matching != null ) - { - sourceClassEntries.clear(); - destClassEntries.clear(); - for( Map.Entry,List> entry : matching.getAmbiguousMatches().entrySet() ) - { - for( ClassIdentity c : entry.getKey() ) - { - sourceClassEntries.add( c.getClassEntry() ); - matching.removeSource( c ); - } - for( ClassIdentity c : entry.getValue() ) - { - destClassEntries.add( c.getClassEntry() ); - matching.removeDest( c ); - } - } - for( ClassIdentity c : matching.getUnmatchedSourceClasses() ) - { - sourceClassEntries.add( c.getClassEntry() ); - matching.removeSource( c ); - } - for( ClassIdentity c : matching.getUnmatchedDestClasses() ) - { - destClassEntries.add( c.getClassEntry() ); - matching.removeDest( c ); - } - } - else - { - matching = new ClassMatching(); - } - - // compute a matching for the classes - for( ClassEntry classEntry : sourceClassEntries ) - { - CtClass c = sourceLoader.loadClass( classEntry.getName() ); - ClassIdentity sourceClass = new ClassIdentity( c, sourceNamer, sourceIndex, useReferences, useRawNames ); - matching.addSource( sourceClass ); - } - for( ClassEntry classEntry : destClassEntries ) - { - CtClass c = destLoader.loadClass( classEntry.getName() ); - ClassIdentity destClass = new ClassIdentity( c, destNamer, destIndex, useReferences, useRawNames ); - matching.matchDestClass( destClass ); - } - - // TEMP - System.out.println( matching ); - } - while( matching.getUniqueMatches().size() - numMatches > 0 ); - } - } - - /* DEBUG: show some ambiguous matches - List,List>> ambiguousMatches = new ArrayList,List>>( matching.getAmbiguousMatches().entrySet() ); - Collections.sort( ambiguousMatches, new Comparator,List>>( ) - { - @Override - public int compare( Map.Entry,List> a, Map.Entry,List> b ) - { - String aName = a.getKey().get( 0 ).getClassEntry().getName(); - String bName = b.getKey().get( 0 ).getClassEntry().getName(); - return aName.compareTo( bName ); - } - } ); - for( Map.Entry,List> entry : ambiguousMatches ) - { - for( ClassIdentity c : entry.getKey() ) - { - System.out.print( c.getClassEntry().getName() + " " ); - } - System.out.println(); - } - Map.Entry,List> entry = ambiguousMatches.get( 7 ); - for( ClassIdentity c : entry.getKey() ) - { - System.out.println( c ); - } - for( ClassIdentity c : entry.getKey() ) - { - System.out.println( decompile( sourceLoader, c.getClassEntry() ) ); - } - */ - - return matching; - } - - private static void addFinalConversion( Map finalConversion, ClassIdentity sourceClass, ClassIdentity destClass ) - { - // flatten inner classes since these are all obf classes in the none package - String sourceClassName = sourceClass.getClassEntry().getName(); - if( sourceClass.getClassEntry().isInnerClass() ) - { - sourceClassName = Constants.NonePackage + "/" + sourceClass.getClassEntry().getInnerClassName(); - } - - String destClassName = destClass.getClassEntry().getName(); - if( destClass.getClassEntry().isInnerClass() ) - { - destClassName = Constants.NonePackage + "/" + destClass.getClassEntry().getInnerClassName(); - } - - finalConversion.put( sourceClassName, destClassName ); - } - - /* DEBUG - private static String decompile( TranslatingTypeLoader loader, ClassEntry classEntry ) - { - PlainTextOutput output = new PlainTextOutput(); - DecompilerSettings settings = DecompilerSettings.javaDefaults(); - settings.setForceExplicitImports( true ); - settings.setShowSyntheticMembers( true ); - settings.setTypeLoader( loader ); - Decompiler.decompile( classEntry.getName(), output, settings ); - return output.toString(); - } - */ -} diff --git a/src/cuchaz/enigma/convert/ClassMatcher.java b/src/cuchaz/enigma/convert/ClassMatcher.java new file mode 100644 index 0000000..ac07a5b --- /dev/null +++ b/src/cuchaz/enigma/convert/ClassMatcher.java @@ -0,0 +1,398 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.convert; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.jar.JarFile; + +import javassist.CtBehavior; +import javassist.CtClass; + +import com.beust.jcommander.internal.Lists; +import com.beust.jcommander.internal.Sets; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.Maps; + +import cuchaz.enigma.TranslatingTypeLoader; +import cuchaz.enigma.analysis.JarIndex; +import cuchaz.enigma.convert.ClassNamer.SidedClassNamer; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.ClassMapping; +import cuchaz.enigma.mapping.MappingParseException; +import cuchaz.enigma.mapping.Mappings; +import cuchaz.enigma.mapping.MappingsReader; +import cuchaz.enigma.mapping.MappingsWriter; +import cuchaz.enigma.mapping.MethodEntry; +import cuchaz.enigma.mapping.MethodMapping; + +public class ClassMatcher +{ + public static void main( String[] args ) + throws IOException, MappingParseException + { + // TEMP + JarFile sourceJar = new JarFile( new File( "input/1.8-pre1.jar" ) ); + JarFile destJar = new JarFile( new File( "input/1.8-pre2.jar" ) ); + File inMappingsFile = new File( "../minecraft-mappings/1.8-pre.mappings" ); + File outMappingsFile = new File( "../minecraft-mappings/1.8-pre2.mappings" ); + + // do the conversion + Mappings mappings = new MappingsReader().read( new FileReader( inMappingsFile ) ); + convertMappings( sourceJar, destJar, mappings ); + + // write out the convert mappings + FileWriter writer = new FileWriter( outMappingsFile ); + new MappingsWriter().write( writer, mappings ); + writer.close(); + System.out.println( "Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath() ); + } + + private static void convertMappings( JarFile sourceJar, JarFile destJar, Mappings mappings ) + { + // index jars + System.out.println( "Indexing source jar..." ); + JarIndex sourceIndex = new JarIndex(); + sourceIndex.indexJar( sourceJar, false ); + System.out.println( "Indexing dest jar..." ); + JarIndex destIndex = new JarIndex(); + destIndex.indexJar( destJar, false ); + TranslatingTypeLoader sourceLoader = new TranslatingTypeLoader( sourceJar, sourceIndex ); + TranslatingTypeLoader destLoader = new TranslatingTypeLoader( destJar, destIndex ); + + // compute the matching + ClassMatching matching = ClassMatcher.computeMatching( sourceIndex, sourceLoader, destIndex, destLoader ); + + // start the class conversion map with the unique and ambiguous matchings + Map>> conversionMap = matching.getConversionMap(); + + // probabilistically match the unmatched source classes + for( ClassIdentity sourceClass : new ArrayList( matching.getUnmatchedSourceClasses() ) ) + { + System.out.println( "No exact match for source class " + sourceClass.getClassEntry() ); + + // find the closest classes + TreeMap scoredMatches = Maps.newTreeMap( Collections.reverseOrder() ); + for( ClassIdentity c : matching.getUnmatchedDestClasses() ) + { + scoredMatches.put( sourceClass.getMatchScore( c ), c ); + } + Iterator> iter = scoredMatches.entrySet().iterator(); + for( int i=0; i<10 && iter.hasNext(); i++ ) + { + Map.Entry score = iter.next(); + System.out.println( String.format( "\tScore: %3d %s", score.getKey(), score.getValue().getClassEntry().getName() ) ); + } + + // does the best match have a non-zero score and the same name? + Map.Entry bestMatch = scoredMatches.firstEntry(); + if( bestMatch.getKey() > 0 && bestMatch.getValue().getClassEntry().equals( sourceClass.getClassEntry() ) ) + { + // use it + System.out.println( "\tAutomatically choosing likely match: " + bestMatch.getValue().getClassEntry().getName() ); + conversionMap.put( + sourceClass.getClassEntry().getName(), + new AbstractMap.SimpleEntry>( sourceClass, Arrays.asList( bestMatch.getValue() ) ) + ); + } + } + + // use the matching to convert the mappings + BiMap classConversion = HashBiMap.create(); + Set unmatchedSourceClasses = Sets.newHashSet(); + for( String className : mappings.getAllObfClassNames() ) + { + // is there a match for this class? + Map.Entry> entry = conversionMap.get( className ); + ClassIdentity sourceClass = entry.getKey(); + List matches = entry.getValue(); + + if( matches.isEmpty() ) + { + // no match! =( + unmatchedSourceClasses.add( className ); + } + else if( matches.size() == 1 ) + { + // unique match! We're good to go! + classConversion.put( + sourceClass.getClassEntry().getName(), + matches.get( 0 ).getClassEntry().getName() + ); + } + else if( matches.size() > 1 ) + { + // too many matches! =( + unmatchedSourceClasses.add( className ); + } + } + + // remove (and warn about) unmatched classes + if( !unmatchedSourceClasses.isEmpty() ) + { + System.err.println( "WARNING: there were unmatched classes!" ); + for( String className : unmatchedSourceClasses ) + { + System.err.println( "\t" + className ); + mappings.removeClassByObfName( className ); + } + System.err.println( "Mappings for these classes have been removed." ); + } + + // show the class name changes + for( Map.Entry entry : classConversion.entrySet() ) + { + if( !entry.getKey().equals( entry.getValue() ) ) + { + System.out.println( String.format( "Class change: %s -> %s", entry.getKey(), entry.getValue() ) ); + /* DEBUG + System.out.println( String.format( "\n%s\n%s", + new ClassIdentity( sourceLoader.loadClass( entry.getKey() ), null, sourceIndex, false, false ), + new ClassIdentity( destLoader.loadClass( entry.getValue() ), null, destIndex, false, false ) + ) ); + */ + } + } + + // TEMP: show some classes + for( String className : Arrays.asList( "none/em", "none/ej", "none/en" ) ) + { + System.out.println( String.format( "check: %s -> %s", className, classConversion.get( className ) ) ); + } + + // convert the classes + mappings.renameObfClasses( classConversion ); + + // look for method matches + System.out.println( "Matching methods..." ); + for( ClassMapping classMapping : mappings.classes() ) + { + ClassEntry classEntry = new ClassEntry( classMapping.getObfName() ); + for( MethodMapping methodMapping : classMapping.methods() ) + { + // skip constructors + if( methodMapping.getObfName().equals( "" ) ) + { + continue; + } + + MethodEntry methodEntry = new MethodEntry( + classEntry, + methodMapping.getObfName(), + methodMapping.getObfSignature() + ); + if( !destIndex.isMethodImplemented( methodEntry ) ) + { + System.err.println( "WARNING: method doesn't match: " + methodEntry ); + + // show the available methods + System.err.println( "\tAvailable dest methods:" ); + CtClass c = destLoader.loadClass( classMapping.getObfName() ); + for( CtBehavior behavior : c.getDeclaredBehaviors() ) + { + MethodEntry declaredMethodEntry = new MethodEntry( + new ClassEntry( classMapping.getObfName() ), + behavior.getName(), + behavior.getSignature() + ); + System.err.println( "\t\t" + declaredMethodEntry ); + } + + System.err.println( "\tAvailable source methods:" ); + c = sourceLoader.loadClass( classConversion.inverse().get( classMapping.getObfName() ) ); + for( CtBehavior behavior : c.getDeclaredBehaviors() ) + { + MethodEntry declaredMethodEntry = new MethodEntry( + new ClassEntry( classMapping.getObfName() ), + behavior.getName(), + behavior.getSignature() + ); + System.err.println( "\t\t" + declaredMethodEntry ); + } + } + } + } + } + + public static ClassMatching computeMatching( JarIndex sourceIndex, TranslatingTypeLoader sourceLoader, JarIndex destIndex, TranslatingTypeLoader destLoader ) + { + System.out.println( "Matching classes..." ); + ClassMatching matching = null; + for( boolean useRawNames : Arrays.asList( false/*, true*/ ) ) + { + for( boolean useReferences : Arrays.asList( false, true ) ) + { + int numMatches = 0; + do + { + SidedClassNamer sourceNamer = null; + SidedClassNamer destNamer = null; + if( matching != null ) + { + // build a class namer + ClassNamer namer = new ClassNamer( matching.getUniqueMatches() ); + sourceNamer = namer.getSourceNamer(); + destNamer = namer.getDestNamer(); + + // note the number of matches + numMatches = matching.getUniqueMatches().size(); + } + + // get the entries left to match + Set sourceClassEntries = sourceIndex.getObfClassEntries(); + Set destClassEntries = destIndex.getObfClassEntries(); + if( matching != null ) + { + sourceClassEntries.clear(); + destClassEntries.clear(); + for( Map.Entry,List> entry : matching.getAmbiguousMatches().entrySet() ) + { + for( ClassIdentity c : entry.getKey() ) + { + sourceClassEntries.add( c.getClassEntry() ); + matching.removeSource( c ); + } + for( ClassIdentity c : entry.getValue() ) + { + destClassEntries.add( c.getClassEntry() ); + matching.removeDest( c ); + } + } + for( ClassIdentity c : matching.getUnmatchedSourceClasses() ) + { + sourceClassEntries.add( c.getClassEntry() ); + matching.removeSource( c ); + } + for( ClassIdentity c : matching.getUnmatchedDestClasses() ) + { + destClassEntries.add( c.getClassEntry() ); + matching.removeDest( c ); + } + } + else + { + matching = new ClassMatching(); + } + + // compute a matching for the classes + for( ClassEntry classEntry : sourceClassEntries ) + { + CtClass c = sourceLoader.loadClass( classEntry.getName() ); + ClassIdentity sourceClass = new ClassIdentity( c, sourceNamer, sourceIndex, useReferences, useRawNames ); + matching.addSource( sourceClass ); + } + for( ClassEntry classEntry : destClassEntries ) + { + CtClass c = destLoader.loadClass( classEntry.getName() ); + ClassIdentity destClass = new ClassIdentity( c, destNamer, destIndex, useReferences, useRawNames ); + matching.matchDestClass( destClass ); + } + + // TEMP + System.out.println( matching ); + } + while( matching.getUniqueMatches().size() - numMatches > 0 ); + } + } + + // DEBUG: check the class matches + System.out.println( "Checking class matches..." ); + for( Map.Entry entry : matching.getUniqueMatches().entrySet() ) + { + // check source + ClassIdentity sourceClass = entry.getKey(); + CtClass sourceC = sourceLoader.loadClass( sourceClass.getClassEntry().getName() ); + assert( sourceC != null ) + : "Unable to load source class " + sourceClass.getClassEntry(); + assert( sourceClass.matches( sourceC ) ) + : "Source " + sourceClass + " doesn't match " + new ClassIdentity( sourceC, null, sourceIndex, false, false ); + + // check dest + ClassIdentity destClass = entry.getValue(); + CtClass destC = destLoader.loadClass( destClass.getClassEntry().getName() ); + assert( destC != null ) + : "Unable to load dest class " + destClass.getClassEntry(); + assert( destClass.matches( destC ) ) + : "Dest " + destClass + " doesn't match " + new ClassIdentity( destC, null, destIndex, false, false ); + } + + // warn about the ambiguous matchings + List,List>> ambiguousMatches = new ArrayList,List>>( matching.getAmbiguousMatches().entrySet() ); + Collections.sort( ambiguousMatches, new Comparator,List>>( ) + { + @Override + public int compare( Map.Entry,List> a, Map.Entry,List> b ) + { + String aName = a.getKey().get( 0 ).getClassEntry().getName(); + String bName = b.getKey().get( 0 ).getClassEntry().getName(); + return aName.compareTo( bName ); + } + } ); + for( Map.Entry,List> entry : ambiguousMatches ) + { + System.out.println( "Ambiguous matching:" ); + System.out.println( "\tSource: " + getClassNames( entry.getKey() ) ); + System.out.println( "\tDest: " + getClassNames( entry.getValue() ) ); + } + + /* DEBUG + Map.Entry,List> entry = ambiguousMatches.get( 7 ); + for( ClassIdentity c : entry.getKey() ) + { + System.out.println( c ); + } + for( ClassIdentity c : entry.getKey() ) + { + System.out.println( decompile( sourceLoader, c.getClassEntry() ) ); + } + */ + + return matching; + } + + private static List getClassNames( Collection classes ) + { + List out = Lists.newArrayList(); + for( ClassIdentity c : classes ) + { + out.add( c.getClassEntry().getName() ); + } + Collections.sort( out ); + return out; + } + + /* DEBUG + private static String decompile( TranslatingTypeLoader loader, ClassEntry classEntry ) + { + PlainTextOutput output = new PlainTextOutput(); + DecompilerSettings settings = DecompilerSettings.javaDefaults(); + settings.setForceExplicitImports( true ); + settings.setShowSyntheticMembers( true ); + settings.setTypeLoader( loader ); + Decompiler.decompile( classEntry.getName(), output, settings ); + return output.toString(); + } + */ +} diff --git a/src/cuchaz/enigma/convert/ClassMatching.java b/src/cuchaz/enigma/convert/ClassMatching.java index 4e9fe39..ef5a7d8 100644 --- a/src/cuchaz/enigma/convert/ClassMatching.java +++ b/src/cuchaz/enigma/convert/ClassMatching.java @@ -96,7 +96,7 @@ public class ClassMatching if( matchedSourceClasses.size() == 1 && matchedDestClasses.size() == 1 ) { ClassIdentity matchedSourceClass = matchedSourceClasses.iterator().next(); - ClassIdentity matchedDestClass = matchedSourceClasses.iterator().next(); + ClassIdentity matchedDestClass = matchedDestClasses.iterator().next(); uniqueMatches.put( matchedSourceClass, matchedDestClass ); } } diff --git a/src/cuchaz/enigma/mapping/Mappings.java b/src/cuchaz/enigma/mapping/Mappings.java index 70bea25..c92f8de 100644 --- a/src/cuchaz/enigma/mapping/Mappings.java +++ b/src/cuchaz/enigma/mapping/Mappings.java @@ -15,6 +15,7 @@ import java.io.InputStream; import java.io.ObjectInputStream; import java.io.Serializable; import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.zip.GZIPInputStream; @@ -161,4 +162,10 @@ public class Mappings implements Serializable m_classesByDeobf.remove( classMapping.getDeobfName() ); } } + + public List getAllObfClassNames( ) + { + // TODO: implement this + return null; + } } -- cgit v1.2.3