From 741e3472f76d959645ee0e025547d69a03e5b6f2 Mon Sep 17 00:00:00 2001 From: jeff Date: Sat, 28 Feb 2015 18:00:25 -0500 Subject: fix up conversion tool to handle Minecraft 1.8.3 --- src/cuchaz/enigma/convert/ClassForest.java | 50 ++++ src/cuchaz/enigma/convert/ClassIdentifier.java | 41 +++ src/cuchaz/enigma/convert/ClassIdentity.java | 125 +++++---- src/cuchaz/enigma/convert/ClassMatch.java | 72 +++++ src/cuchaz/enigma/convert/ClassMatcher.java | 356 +++++++++++-------------- src/cuchaz/enigma/convert/ClassMatching.java | 200 +++++++------- src/cuchaz/enigma/convert/ClassNamer.java | 10 +- src/cuchaz/enigma/mapping/Translator.java | 1 + 8 files changed, 486 insertions(+), 369 deletions(-) create mode 100644 src/cuchaz/enigma/convert/ClassForest.java create mode 100644 src/cuchaz/enigma/convert/ClassIdentifier.java create mode 100644 src/cuchaz/enigma/convert/ClassMatch.java diff --git a/src/cuchaz/enigma/convert/ClassForest.java b/src/cuchaz/enigma/convert/ClassForest.java new file mode 100644 index 00000000..e113eeb9 --- /dev/null +++ b/src/cuchaz/enigma/convert/ClassForest.java @@ -0,0 +1,50 @@ +package cuchaz.enigma.convert; + +import java.util.Collection; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; + +import cuchaz.enigma.mapping.ClassEntry; + + +public class ClassForest { + + private ClassIdentifier m_identifier; + private Multimap m_forest; + + public ClassForest(ClassIdentifier identifier) { + m_identifier = identifier; + m_forest = HashMultimap.create(); + } + + public ClassIdentifier getIdentifier() { + return m_identifier; + } + + public void addAll(Iterable entries) { + for (ClassEntry entry : entries) { + add(entry); + } + } + + private void add(ClassEntry entry) { + m_forest.put(m_identifier.identify(entry), entry); + } + + public Collection identities() { + return m_forest.keySet(); + } + + public Collection classes() { + return m_forest.values(); + } + + public Collection getClasses(ClassIdentity identity) { + return m_forest.get(identity); + } + + public boolean containsIdentity(ClassIdentity identity) { + return m_forest.containsKey(identity); + } +} diff --git a/src/cuchaz/enigma/convert/ClassIdentifier.java b/src/cuchaz/enigma/convert/ClassIdentifier.java new file mode 100644 index 00000000..bdbf11b2 --- /dev/null +++ b/src/cuchaz/enigma/convert/ClassIdentifier.java @@ -0,0 +1,41 @@ +package cuchaz.enigma.convert; + +import java.util.Map; +import java.util.jar.JarFile; + +import javassist.CtClass; + +import com.beust.jcommander.internal.Maps; + +import cuchaz.enigma.TranslatingTypeLoader; +import cuchaz.enigma.analysis.JarIndex; +import cuchaz.enigma.convert.ClassNamer.SidedClassNamer; +import cuchaz.enigma.mapping.ClassEntry; + + +public class ClassIdentifier { + + private JarIndex m_index; + private SidedClassNamer m_namer; + private boolean m_useReferences; + private TranslatingTypeLoader m_loader; + private Map m_cache; + + public ClassIdentifier(JarFile jar, JarIndex index, SidedClassNamer namer, boolean useReferences) { + m_index = index; + m_namer = namer; + m_useReferences = useReferences; + m_loader = new TranslatingTypeLoader(jar, index); + m_cache = Maps.newHashMap(); + } + + public ClassIdentity identify(ClassEntry classEntry) { + ClassIdentity identity = m_cache.get(classEntry); + if (identity == null) { + CtClass c = m_loader.loadClass(classEntry.getName()); + identity = new ClassIdentity(c, m_namer, m_index, m_useReferences); + m_cache.put(classEntry, identity); + } + return identity; + } +} diff --git a/src/cuchaz/enigma/convert/ClassIdentity.java b/src/cuchaz/enigma/convert/ClassIdentity.java index b5140124..3736a533 100644 --- a/src/cuchaz/enigma/convert/ClassIdentity.java +++ b/src/cuchaz/enigma/convert/ClassIdentity.java @@ -52,9 +52,10 @@ import cuchaz.enigma.mapping.BehaviorEntry; import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.ClassNameReplacer; import cuchaz.enigma.mapping.Entry; -import cuchaz.enigma.mapping.FieldEntry; import cuchaz.enigma.mapping.EntryFactory; +import cuchaz.enigma.mapping.FieldEntry; import cuchaz.enigma.mapping.Signature; +import cuchaz.enigma.mapping.Type; public class ClassIdentity { @@ -68,7 +69,45 @@ public class ClassIdentity { private Multiset m_implements; private Multiset m_implementations; private Multiset m_references; - + + private final ClassNameReplacer m_classNameReplacer = new ClassNameReplacer() { + + private Map m_classNames = Maps.newHashMap(); + + @Override + public String replace(String className) { + + // classes not in the none package can be passed through + ClassEntry classEntry = new ClassEntry(className); + if (!classEntry.getPackageName().equals(Constants.NonePackage)) { + return className; + } + + // is this class ourself? + if (className.equals(m_classEntry.getName())) { + return "CSelf"; + } + + // try the namer + if (m_namer != null) { + String newName = m_namer.getName(className); + if (newName != null) { + return newName; + } + } + + // otherwise, use local naming + if (!m_classNames.containsKey(className)) { + m_classNames.put(className, getNewClassName()); + } + return m_classNames.get(className); + } + + private String getNewClassName() { + return String.format("C%03d", m_classNames.size()); + } + }; + public ClassIdentity(CtClass c, SidedClassNamer namer, JarIndex index, boolean useReferences) { m_namer = namer; @@ -77,7 +116,7 @@ public class ClassIdentity { m_classEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); m_fields = HashMultiset.create(); for (CtField field : c.getDeclaredFields()) { - m_fields.add(scrubSignature(field.getSignature())); + m_fields.add(scrubType(field.getSignature())); } m_methods = HashMultiset.create(); for (CtMethod method : c.getDeclaredMethods()) { @@ -93,11 +132,11 @@ public class ClassIdentity { } m_extends = ""; if (c.getClassFile().getSuperclass() != null) { - m_extends = scrubClassName(c.getClassFile().getSuperclass()); + m_extends = scrubClassName(Descriptor.toJvmName(c.getClassFile().getSuperclass())); } m_implements = HashMultiset.create(); for (String interfaceName : c.getClassFile().getInterfaces()) { - m_implements.add(scrubClassName(interfaceName)); + m_implements.add(scrubClassName(Descriptor.toJvmName(interfaceName))); } // stuff from the jar index @@ -132,9 +171,14 @@ public class ClassIdentity { private void addReference(EntryReference reference) { if (reference.context.getSignature() != null) { - m_references.add(String.format("%s_%s", scrubClassName(reference.context.getClassName()), scrubSignature(reference.context.getSignature()))); + m_references.add(String.format("%s_%s", + scrubClassName(reference.context.getClassName()), + scrubSignature(reference.context.getSignature()) + )); } else { - m_references.add(String.format("%s_", scrubClassName(reference.context.getClassName()))); + m_references.add(String.format("%s_", + scrubClassName(reference.context.getClassName()) + )); } } @@ -194,52 +238,27 @@ public class ClassIdentity { } private String scrubClassName(String className) { - return scrubSignature("L" + Descriptor.toJvmName(className) + ";"); + return m_classNameReplacer.replace(className); + } + + private String scrubType(String typeName) { + return scrubType(new Type(typeName)).toString(); + } + + private Type scrubType(Type type) { + if (type.hasClass()) { + return new Type(type, m_classNameReplacer); + } else { + return type; + } } private String scrubSignature(String signature) { - return scrubSignature(new Signature(signature)); + return scrubSignature(new Signature(signature)).toString(); } - private String scrubSignature(Signature signature) { - - return new Signature(signature, new ClassNameReplacer() { - - private Map m_classNames = Maps.newHashMap(); - - @Override - public String replace(String className) { - - // classes not in the none package can be passed through - ClassEntry classEntry = new ClassEntry(className); - if (!classEntry.getPackageName().equals(Constants.NonePackage)) { - return className; - } - - // is this class ourself? - if (className.equals(m_classEntry.getName())) { - return "CSelf"; - } - - // try the namer - if (m_namer != null) { - String newName = m_namer.getName(className); - if (newName != null) { - return newName; - } - } - - // otherwise, use local naming - if (!m_classNames.containsKey(className)) { - m_classNames.put(className, getNewClassName()); - } - return m_classNames.get(className); - } - - private String getNewClassName() { - return String.format("C%03d", m_classNames.size()); - } - }).toString(); + private Signature scrubSignature(Signature signature) { + return new Signature(signature, m_classNameReplacer); } private boolean isClassMatchedUniquely(String className) { @@ -284,7 +303,7 @@ public class ClassIdentity { behavior.instrument(new ExprEditor() { @Override public void edit(MethodCall call) { - updateHashWithString(digest, scrubClassName(call.getClassName())); + updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName()))); updateHashWithString(digest, scrubSignature(call.getSignature())); if (isClassMatchedUniquely(call.getClassName())) { updateHashWithString(digest, call.getMethodName()); @@ -293,8 +312,8 @@ public class ClassIdentity { @Override public void edit(FieldAccess access) { - updateHashWithString(digest, scrubClassName(access.getClassName())); - updateHashWithString(digest, scrubSignature(access.getSignature())); + updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(access.getClassName()))); + updateHashWithString(digest, scrubType(access.getSignature())); if (isClassMatchedUniquely(access.getClassName())) { updateHashWithString(digest, access.getFieldName()); } @@ -302,13 +321,13 @@ public class ClassIdentity { @Override public void edit(ConstructorCall call) { - updateHashWithString(digest, scrubClassName(call.getClassName())); + updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName()))); updateHashWithString(digest, scrubSignature(call.getSignature())); } @Override public void edit(NewExpr expr) { - updateHashWithString(digest, scrubClassName(expr.getClassName())); + updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(expr.getClassName()))); } }); diff --git a/src/cuchaz/enigma/convert/ClassMatch.java b/src/cuchaz/enigma/convert/ClassMatch.java new file mode 100644 index 00000000..9cecf701 --- /dev/null +++ b/src/cuchaz/enigma/convert/ClassMatch.java @@ -0,0 +1,72 @@ +package cuchaz.enigma.convert; + +import java.util.Collection; +import java.util.Set; + +import com.google.common.collect.Sets; + +import cuchaz.enigma.Util; +import cuchaz.enigma.mapping.ClassEntry; + + +public class ClassMatch { + + public Set sourceClasses; + public Set destClasses; + + public ClassMatch(Collection sourceClasses, Collection destClasses) { + this.sourceClasses = Sets.newHashSet(sourceClasses); + this.destClasses = Sets.newHashSet(destClasses); + } + + public ClassMatch(ClassEntry sourceClass, ClassEntry destClass) { + this.sourceClasses = Sets.newHashSet(sourceClass); + this.destClasses = Sets.newHashSet(destClass); + } + + public boolean isMatched() { + return sourceClasses.size() > 0 && destClasses.size() > 0; + } + + public boolean isAmbiguous() { + return sourceClasses.size() > 1 || destClasses.size() > 1; + } + + public ClassEntry getUniqueSource() { + if (sourceClasses.size() != 1) { + throw new IllegalStateException("Match has ambiguous source!"); + } + return sourceClasses.iterator().next(); + } + + public ClassEntry getUniqueDest() { + if (destClasses.size() != 1) { + throw new IllegalStateException("Match has ambiguous source!"); + } + return destClasses.iterator().next(); + } + + public Set intersectSourceClasses(Set classes) { + Set intersection = Sets.newHashSet(sourceClasses); + intersection.retainAll(classes); + return intersection; + } + + @Override + public int hashCode() { + return Util.combineHashesOrdered(sourceClasses, destClasses); + } + + @Override + public boolean equals(Object other) { + if (other instanceof ClassMatch) { + return equals((ClassMatch)other); + } + return false; + } + + public boolean equals(ClassMatch other) { + return this.sourceClasses.equals(other.sourceClasses) + && this.destClasses.equals(other.destClasses); + } +} diff --git a/src/cuchaz/enigma/convert/ClassMatcher.java b/src/cuchaz/enigma/convert/ClassMatcher.java index 224d0048..f43f5b20 100644 --- a/src/cuchaz/enigma/convert/ClassMatcher.java +++ b/src/cuchaz/enigma/convert/ClassMatcher.java @@ -26,9 +26,6 @@ import java.util.Map; import java.util.Set; import java.util.jar.JarFile; -import javassist.CtBehavior; -import javassist.CtClass; - import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; @@ -37,12 +34,10 @@ import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; -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.EntryFactory; import cuchaz.enigma.mapping.MappingParseException; import cuchaz.enigma.mapping.Mappings; import cuchaz.enigma.mapping.MappingsReader; @@ -52,18 +47,23 @@ 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-pre3.jar")); - JarFile destJar = new JarFile(new File("input/1.8.jar")); - File inMappingsFile = new File("../Enigma Mappings/1.8-pre3.mappings"); - File outMappingsFile = new File("../Enigma Mappings/1.8.mappings"); + public static void main(String[] args) + throws IOException, MappingParseException { + + // setup files + File home = new File(System.getProperty("user.home")); + JarFile sourceJar = new JarFile(new File(home, ".minecraft/versions/1.8/1.8.jar")); + JarFile destJar = new JarFile(new File(home, ".minecraft/versions/1.8.3/1.8.3.jar")); + File inMappingsFile = new File("../Enigma Mappings/1.8.mappings"); + File outMappingsFile = new File("../Enigma Mappings/1.8.3.mappings"); // define a matching to use when the automated system cannot find a match Map fallbackMatching = Maps.newHashMap(); + /* fallbackMatching.put("none/ayb", "none/ayf"); fallbackMatching.put("none/ayd", "none/ayd"); fallbackMatching.put("none/bgk", "unknown/bgk"); + */ // do the conversion Mappings mappings = new MappingsReader().read(new FileReader(inMappingsFile)); @@ -77,6 +77,7 @@ public class ClassMatcher { } private static void convertMappings(JarFile sourceJar, JarFile destJar, Mappings mappings, Map fallbackMatching) { + // index jars System.out.println("Indexing source jar..."); JarIndex sourceIndex = new JarIndex(); @@ -84,48 +85,88 @@ public class ClassMatcher { 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 = computeMatching(sourceIndex, sourceLoader, destIndex, destLoader); - Map>> matchingIndex = matching.getIndex(); + ClassMatching matching = computeMatching(sourceJar, sourceIndex, destJar, destIndex); // get all the obf class names used in the mappings - Set usedClassNames = mappings.getAllObfClassNames(); - Set allClassNames = Sets.newHashSet(); - for (ClassEntry classEntry : sourceIndex.getObfClassEntries()) { - allClassNames.add(classEntry.getName()); + Set usedClasses = Sets.newHashSet(); + for (String className : mappings.getAllObfClassNames()) { + usedClasses.add(new ClassEntry(className)); } - usedClassNames.retainAll(allClassNames); - System.out.println("Used " + usedClassNames.size() + " classes in the mappings"); + System.out.println("Mappings reference " + usedClasses.size() + " classes"); - // probabilistically match the non-uniquely-matched source classes - for (Map.Entry> entry : matchingIndex.values()) { - ClassIdentity sourceClass = entry.getKey(); - List destClasses = entry.getValue(); - - // skip classes that are uniquely matched - if (destClasses.size() == 1) { + // see what the used classes map to + BiMap uniqueUsedMatches = HashBiMap.create(); + Map ambiguousUsedMatches = Maps.newHashMap(); + Set unmatchedUsedClasses = Sets.newHashSet(); + for (ClassMatch match : matching.matches()) { + Set matchUsedClasses = match.intersectSourceClasses(usedClasses); + if (matchUsedClasses.isEmpty()) { continue; } - - // skip classes that aren't used in the mappings - if (!usedClassNames.contains(sourceClass.getClassEntry().getName())) { - continue; + + // classify the match + if (!match.isMatched()) { + // unmatched + unmatchedUsedClasses.addAll(matchUsedClasses); + } else { + if (match.isAmbiguous()) { + // ambiguously matched + for (ClassEntry matchUsedClass : matchUsedClasses) { + ambiguousUsedMatches.put(matchUsedClass, match); + } + } else { + // uniquely matched + uniqueUsedMatches.put(match.getUniqueSource(), match.getUniqueDest()); + } } + } + + // get unmatched dest classes + Set unmatchedDestClasses = Sets.newHashSet(); + for (ClassMatch match : matching.matches()) { + if (!match.isMatched()) { + unmatchedDestClasses.addAll(match.destClasses); + } + } + + // warn about the ambiguous used matches + if (ambiguousUsedMatches.size() > 0) { + System.out.println(String.format("%d source classes have ambiguous mappings", ambiguousUsedMatches.size())); + List ambiguousMatchesList = Lists.newArrayList(Sets.newHashSet(ambiguousUsedMatches.values())); + Collections.sort(ambiguousMatchesList, new Comparator() { + @Override + public int compare(ClassMatch a, ClassMatch b) { + String aName = a.sourceClasses.iterator().next().getName(); + String bName = b.sourceClasses.iterator().next().getName(); + return aName.compareTo(bName); + } + }); + for (ClassMatch match : ambiguousMatchesList) { + System.out.println("Ambiguous matching:"); + System.out.println("\tSource: " + getClassNames(match.sourceClasses)); + System.out.println("\tDest: " + getClassNames(match.destClasses)); + } + } + + // warn about unmatched used classes + for (ClassEntry unmatchedUsedClass : unmatchedUsedClasses) { + System.out.println("No exact match for source class " + unmatchedUsedClass.getClassEntry()); - System.out.println("No exact match for source class " + sourceClass.getClassEntry()); - - // find the closest classes - Multimap scoredMatches = ArrayListMultimap.create(); - for (ClassIdentity c : destClasses) { - scoredMatches.put(sourceClass.getMatchScore(c), c); + // rank all the unmatched dest classes against the used class + ClassIdentity sourceIdentity = matching.getSourceIdentifier().identify(unmatchedUsedClass); + Multimap scoredDestClasses = ArrayListMultimap.create(); + for (ClassEntry unmatchedDestClass : unmatchedDestClasses) { + ClassIdentity destIdentity = matching.getDestIdentifier().identify(unmatchedDestClass); + scoredDestClasses.put(sourceIdentity.getMatchScore(destIdentity), unmatchedDestClass); } - List scores = new ArrayList(scoredMatches.keySet()); + + List scores = new ArrayList(scoredDestClasses.keySet()); Collections.sort(scores, Collections.reverseOrder()); - printScoredMatches(sourceClass.getMaxMatchScore(), scores, scoredMatches); + printScoredMatches(sourceIdentity.getMaxMatchScore(), scores, scoredDestClasses); + /* TODO: re-enable auto-pick logic // does the best match have a non-zero score and the same name? int bestScore = scores.get(0); Collection bestMatches = scoredMatches.get(bestScore); @@ -138,85 +179,45 @@ public class ClassMatcher { destClasses.add(bestMatch); } } + */ } - // group the matching into unique and non-unique matches - BiMap matchedClassNames = HashBiMap.create(); - Set unmatchedSourceClassNames = Sets.newHashSet(); - for (String className : usedClassNames) { - // is there a match for this class? - Map.Entry> entry = matchingIndex.get(className); - ClassIdentity sourceClass = entry.getKey(); - List matches = entry.getValue(); - - if (matches.size() == 1) { - // unique match! We're good to go! - matchedClassNames.put(sourceClass.getClassEntry().getName(), matches.get(0).getClassEntry().getName()); - } else { - // no match, check the fallback matching - String fallbackMatch = fallbackMatching.get(className); - if (fallbackMatch != null) { - matchedClassNames.put(sourceClass.getClassEntry().getName(), fallbackMatch); - } else { - unmatchedSourceClassNames.add(className); - } - } - } - - // report unmatched classes - if (!unmatchedSourceClassNames.isEmpty()) { - System.err.println("ERROR: there were unmatched classes!"); - for (String className : unmatchedSourceClassNames) { - System.err.println("\t" + className); - } - return; - } - - // get the class name changes from the matched class names - Map classChanges = Maps.newHashMap(); - for (Map.Entry entry : matchedClassNames.entrySet()) { - if (!entry.getKey().equals(entry.getValue())) { - classChanges.put(entry.getKey(), 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) - )); - */ - } + // bail if there were unmatched classes + if (!unmatchedUsedClasses.isEmpty()) { + throw new Error("There were " + unmatchedUsedClasses.size() + " unmatched classes!"); } // sort the changes so classes are renamed in the correct order // ie. if we have the mappings a->b, b->c, we have to apply b->c before a->b - LinkedHashMap orderedClassChanges = Maps.newLinkedHashMap(); - int numChangesLeft = classChanges.size(); - while (!classChanges.isEmpty()) { - Iterator> iter = classChanges.entrySet().iterator(); + BiMap unsortedChanges = HashBiMap.create(uniqueUsedMatches); + LinkedHashMap sortedChanges = Maps.newLinkedHashMap(); + int numChangesLeft = unsortedChanges.size(); + while (!unsortedChanges.isEmpty()) { + Iterator> iter = unsortedChanges.entrySet().iterator(); while (iter.hasNext()) { - Map.Entry entry = iter.next(); - if (classChanges.get(entry.getValue()) == null) { - orderedClassChanges.put(entry.getKey(), entry.getValue()); + Map.Entry change = iter.next(); + if (unsortedChanges.containsKey(change.getValue())) { + sortedChanges.put(change.getKey(), change.getValue()); iter.remove(); } } // did we remove any changes? - if (numChangesLeft - classChanges.size() > 0) { + if (numChangesLeft - unsortedChanges.size() > 0) { // keep going - numChangesLeft = classChanges.size(); + numChangesLeft = unsortedChanges.size(); } else { // can't sort anymore. There must be a loop break; } } - if (classChanges.size() > 0) { - throw new Error(String.format("Unable to sort %d/%d class changes!", classChanges.size(), matchedClassNames.size())); + if (!unsortedChanges.isEmpty()) { + throw new Error(String.format("Unable to sort %d/%d class changes!", unsortedChanges.size(), uniqueUsedMatches.size())); } // convert the mappings in the correct class order - for (Map.Entry entry : orderedClassChanges.entrySet()) { - mappings.renameObfClass(entry.getKey(), entry.getValue()); + for (Map.Entry entry : sortedChanges.entrySet()) { + mappings.renameObfClass(entry.getKey().getName(), entry.getValue().getName()); } // check the method matches @@ -238,6 +239,7 @@ public class ClassMatcher { if (!destIndex.containsObfBehavior(methodEntry)) { System.err.println("WARNING: method doesn't match: " + methodEntry); + /* TODO: show methods if needed // show the available methods System.err.println("\tAvailable dest methods:"); CtClass c = destLoader.loadClass(classMapping.getObfFullName()); @@ -250,6 +252,7 @@ public class ClassMatcher { for (CtBehavior behavior : c.getDeclaredBehaviors()) { System.err.println("\t\t" + EntryFactory.getBehaviorEntry(behavior)); } + */ } } } @@ -257,125 +260,72 @@ public class ClassMatcher { System.out.println("Done!"); } - public static ClassMatching computeMatching(JarIndex sourceIndex, TranslatingTypeLoader sourceLoader, JarIndex destIndex, TranslatingTypeLoader destLoader) { + public static ClassMatching computeMatching(JarFile sourceJar, JarIndex sourceIndex, JarFile destJar, JarIndex destIndex) { - System.out.println("Matching classes..."); + System.out.println("Iteratively matching classes..."); - ClassMatching matching = null; + ClassMatching lastMatching = null; + int round = 0; + SidedClassNamer sourceNamer = null; + SidedClassNamer destNamer = null; 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(); - } + + int numUniqueMatchesLastTime = 0; + if (lastMatching != null) { + numUniqueMatchesLastTime = lastMatching.uniqueMatches().size(); + } + + while (true) { + + System.out.println("Round " + (++round) + " ..."); + + // init the matching with identity settings + ClassMatching matching = new ClassMatching( + new ClassIdentifier(sourceJar, sourceIndex, sourceNamer, useReferences), + new ClassIdentifier(destJar, destIndex, destNamer, useReferences) + ); - // get the entries left to match - Set sourceClassEntries = Sets.newHashSet(); - Set destClassEntries = Sets.newHashSet(); - if (matching == null) { - sourceClassEntries.addAll(sourceIndex.getObfClassEntries()); - destClassEntries.addAll(destIndex.getObfClassEntries()); - matching = new ClassMatching(); + if (lastMatching == null) { + // search all classes + matching.match(sourceIndex.getObfClassEntries(), destIndex.getObfClassEntries()); } else { - 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); + // we already know about these matches + matching.addKnownMatches(lastMatching.uniqueMatches()); + + // search unmatched and ambiguously-matched classes + matching.match(lastMatching.unmatchedSourceClasses(), lastMatching.unmatchedDestClasses()); + for (ClassMatch match : lastMatching.ambiguousMatches()) { + matching.match(match.sourceClasses, match.destClasses); } } + System.out.println(matching); + BiMap uniqueMatches = matching.uniqueMatches(); - // compute a matching for the classes - for (ClassEntry classEntry : sourceClassEntries) { - CtClass c = sourceLoader.loadClass(classEntry.getName()); - ClassIdentity sourceClass = new ClassIdentity(c, sourceNamer, sourceIndex, useReferences); - matching.addSource(sourceClass); - } - for (ClassEntry classEntry : destClassEntries) { - CtClass c = destLoader.loadClass(classEntry.getName()); - ClassIdentity destClass = new ClassIdentity(c, destNamer, destIndex, useReferences); - matching.matchDestClass(destClass); + // did we match anything new this time? + if (uniqueMatches.size() > numUniqueMatchesLastTime) { + numUniqueMatchesLastTime = uniqueMatches.size(); + lastMatching = matching; + } else { + break; } - // TEMP - System.out.println(matching); - } while (matching.getUniqueMatches().size() - numMatches > 0); - } - - // check the class matches - System.out.println("Checking class matches..."); - ClassNamer namer = new ClassNamer(matching.getUniqueMatches()); - SidedClassNamer sourceNamer = namer.getSourceNamer(); - SidedClassNamer destNamer = namer.getDestNamer(); - 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, sourceNamer, sourceIndex, 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, destNamer, destIndex, 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); + // update the namers + ClassNamer namer = new ClassNamer(uniqueMatches); + sourceNamer = namer.getSourceNamer(); + destNamer = namer.getDestNamer(); } - }); - 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; + return lastMatching; } - private static void printScoredMatches(int maxScore, List scores, Multimap scoredMatches) { + private static void printScoredMatches(int maxScore, List scores, Multimap scoredMatches) { int numScoredMatchesShown = 0; for (int score : scores) { - for (ClassIdentity scoredMatch : scoredMatches.get(score)) { - System.out.println(String.format("\tScore: %3d %3.0f%% %s", score, 100.0 * score / maxScore, scoredMatch.getClassEntry().getName())); + for (ClassEntry classEntry : scoredMatches.get(score)) { + System.out.println(String.format("\tScore: %3d %3.0f%% %s", + score, 100.0 * score / maxScore, classEntry.getName() + )); if (numScoredMatchesShown++ > 10) { return; } @@ -383,10 +333,10 @@ public class ClassMatcher { } } - private static List getClassNames(Collection classes) { + private static List getClassNames(Collection classes) { List out = Lists.newArrayList(); - for (ClassIdentity c : classes) { - out.add(c.getClassEntry().getName()); + for (ClassEntry c : classes) { + out.add(c.getName()); } Collections.sort(out); return out; diff --git a/src/cuchaz/enigma/convert/ClassMatching.java b/src/cuchaz/enigma/convert/ClassMatching.java index 53b6f7f4..b94fd8bd 100644 --- a/src/cuchaz/enigma/convert/ClassMatching.java +++ b/src/cuchaz/enigma/convert/ClassMatching.java @@ -10,164 +10,146 @@ ******************************************************************************/ package cuchaz.enigma.convert; -import java.util.AbstractMap; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.List; -import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; -import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; + +import cuchaz.enigma.mapping.ClassEntry; public class ClassMatching { - private Multimap m_sourceClasses; - private Multimap m_matchedDestClasses; - private List m_unmatchedDestClasses; - - public ClassMatching() { - m_sourceClasses = ArrayListMultimap.create(); - m_matchedDestClasses = ArrayListMultimap.create(); - m_unmatchedDestClasses = Lists.newArrayList(); - } + private ClassForest m_sourceClasses; + private ClassForest m_destClasses; + private BiMap m_knownMatches; - public void addSource(ClassIdentity c) { - m_sourceClasses.put(c, c); + public ClassMatching(ClassIdentifier sourceIdentifier, ClassIdentifier destIdentifier) { + m_sourceClasses = new ClassForest(sourceIdentifier); + m_destClasses = new ClassForest(destIdentifier); + m_knownMatches = HashBiMap.create(); } - public void matchDestClass(ClassIdentity destClass) { - Collection matchedSourceClasses = m_sourceClasses.get(destClass); - if (matchedSourceClasses.isEmpty()) { - // no match - m_unmatchedDestClasses.add(destClass); - } else { - // found a match - m_matchedDestClasses.put(destClass, destClass); - - // DEBUG - ClassIdentity sourceClass = matchedSourceClasses.iterator().next(); - assert (sourceClass.hashCode() == destClass.hashCode()); - assert (sourceClass.equals(destClass)); - } + public void addKnownMatches(BiMap knownMatches) { + m_knownMatches.putAll(knownMatches); } - public void removeSource(ClassIdentity sourceClass) { - m_sourceClasses.remove(sourceClass, sourceClass); + public void match(Iterable sourceClasses, Iterable destClasses) { + m_sourceClasses.addAll(sourceClasses); + m_destClasses.addAll(destClasses); } - public void removeDest(ClassIdentity destClass) { - m_matchedDestClasses.remove(destClass, destClass); - m_unmatchedDestClasses.remove(destClass); + public Collection matches() { + List matches = Lists.newArrayList(); + for (Entry entry : m_knownMatches.entrySet()) { + matches.add(new ClassMatch( + entry.getKey(), + entry.getValue() + )); + } + for (ClassIdentity identity : m_sourceClasses.identities()) { + matches.add(new ClassMatch( + m_sourceClasses.getClasses(identity), + m_destClasses.getClasses(identity) + )); + } + for (ClassIdentity identity : m_destClasses.identities()) { + if (!m_sourceClasses.containsIdentity(identity)) { + matches.add(new ClassMatch( + new ArrayList(), + m_destClasses.getClasses(identity) + )); + } + } + return matches; } - public List getSourceClasses() { - return new ArrayList(m_sourceClasses.values()); + public Collection sourceClasses() { + Set classes = Sets.newHashSet(); + for (ClassMatch match : matches()) { + classes.addAll(match.sourceClasses); + } + return classes; } - public List getDestClasses() { - List classes = Lists.newArrayList(); - classes.addAll(m_matchedDestClasses.values()); - classes.addAll(m_unmatchedDestClasses); + public Collection destClasses() { + Set classes = Sets.newHashSet(); + for (ClassMatch match : matches()) { + classes.addAll(match.destClasses); + } return classes; } - public BiMap getUniqueMatches() { - BiMap uniqueMatches = HashBiMap.create(); - for (ClassIdentity sourceClass : m_sourceClasses.keySet()) { - Collection matchedSourceClasses = m_sourceClasses.get(sourceClass); - Collection matchedDestClasses = m_matchedDestClasses.get(sourceClass); - if (matchedSourceClasses.size() == 1 && matchedDestClasses.size() == 1) { - ClassIdentity matchedSourceClass = matchedSourceClasses.iterator().next(); - ClassIdentity matchedDestClass = matchedDestClasses.iterator().next(); - uniqueMatches.put(matchedSourceClass, matchedDestClass); + public BiMap uniqueMatches() { + BiMap uniqueMatches = HashBiMap.create(); + for (ClassMatch match : matches()) { + if (match.isMatched() && !match.isAmbiguous()) { + uniqueMatches.put(match.getUniqueSource(), match.getUniqueDest()); } } return uniqueMatches; } - public BiMap,List> getAmbiguousMatches() { - BiMap,List> ambiguousMatches = HashBiMap.create(); - for (ClassIdentity sourceClass : m_sourceClasses.keySet()) { - Collection matchedSourceClasses = m_sourceClasses.get(sourceClass); - Collection matchedDestClasses = m_matchedDestClasses.get(sourceClass); - if (matchedSourceClasses.size() > 1 && matchedDestClasses.size() > 1) { - ambiguousMatches.put( - new ArrayList(matchedSourceClasses), - new ArrayList(matchedDestClasses) - ); + public Collection ambiguousMatches() { + List ambiguousMatches = Lists.newArrayList(); + for (ClassMatch match : matches()) { + if (match.isMatched() && match.isAmbiguous()) { + ambiguousMatches.add(match); } } return ambiguousMatches; } - public int getNumAmbiguousSourceMatches() { - int num = 0; - for (Map.Entry,List> entry : getAmbiguousMatches().entrySet()) { - num += entry.getKey().size(); - } - return num; - } - - public int getNumAmbiguousDestMatches() { - int num = 0; - for (Map.Entry,List> entry : getAmbiguousMatches().entrySet()) { - num += entry.getValue().size(); + public Collection unmatchedSourceClasses() { + List classes = Lists.newArrayList(); + for (ClassMatch match : matches()) { + if (!match.isMatched() && !match.sourceClasses.isEmpty()) { + classes.addAll(match.sourceClasses); + } } - return num; + return classes; } - public List getUnmatchedSourceClasses() { - List classes = Lists.newArrayList(); - for (ClassIdentity sourceClass : getSourceClasses()) { - if (m_matchedDestClasses.get(sourceClass).isEmpty()) { - classes.add(sourceClass); + public Collection unmatchedDestClasses() { + List classes = Lists.newArrayList(); + for (ClassMatch match : matches()) { + if (!match.isMatched() && !match.destClasses.isEmpty()) { + classes.addAll(match.destClasses); } } return classes; } - public List getUnmatchedDestClasses() { - return new ArrayList(m_unmatchedDestClasses); + public ClassIdentifier getSourceIdentifier() { + return m_sourceClasses.getIdentifier(); } - public Map>> getIndex() { - Map>> conversion = Maps.newHashMap(); - for (Map.Entry entry : getUniqueMatches().entrySet()) { - conversion.put( - entry.getKey().getClassEntry().getName(), - new AbstractMap.SimpleEntry>(entry.getKey(), Arrays.asList(entry.getValue())) - ); - } - for (Map.Entry,List> entry : getAmbiguousMatches().entrySet()) { - for (ClassIdentity sourceClass : entry.getKey()) { - conversion.put( - sourceClass.getClassEntry().getName(), - new AbstractMap.SimpleEntry>(sourceClass, entry.getValue()) - ); - } - } - for (ClassIdentity sourceClass : getUnmatchedSourceClasses()) { - conversion.put( - sourceClass.getClassEntry().getName(), - new AbstractMap.SimpleEntry>(sourceClass, getUnmatchedDestClasses()) - ); - } - return conversion; + public ClassIdentifier getDestIdentifier() { + return m_destClasses.getIdentifier(); } @Override public String toString() { + + // count the ambiguous classes + int numAmbiguousSource = 0; + int numAmbiguousDest = 0; + for (ClassMatch match : ambiguousMatches()) { + numAmbiguousSource += match.sourceClasses.size(); + numAmbiguousDest += match.destClasses.size(); + } + StringBuilder buf = new StringBuilder(); - buf.append(String.format("%12s%8s%8s\n", "", "Source", "Dest")); - buf.append(String.format("%12s%8d%8d\n", "Classes", getSourceClasses().size(), getDestClasses().size())); - buf.append(String.format("%12s%8d%8d\n", "Unique", getUniqueMatches().size(), getUniqueMatches().size())); - buf.append(String.format("%12s%8d%8d\n", "Ambiguous", getNumAmbiguousSourceMatches(), getNumAmbiguousDestMatches())); - buf.append(String.format("%12s%8d%8d\n", "Unmatched", getUnmatchedSourceClasses().size(), getUnmatchedDestClasses().size())); + buf.append(String.format("%20s%8s%8s\n", "", "Source", "Dest")); + buf.append(String.format("%20s%8d%8d\n", "Classes", sourceClasses().size(), destClasses().size())); + buf.append(String.format("%20s%8d%8d\n", "Uniquely matched", uniqueMatches().size(), uniqueMatches().size())); + buf.append(String.format("%20s%8d%8d\n", "Ambiguously matched", numAmbiguousSource, numAmbiguousDest)); + buf.append(String.format("%20s%8d%8d\n", "Unmatched", unmatchedSourceClasses().size(), unmatchedDestClasses().size())); return buf.toString(); } } diff --git a/src/cuchaz/enigma/convert/ClassNamer.java b/src/cuchaz/enigma/convert/ClassNamer.java index 1b6e81c8..6423a189 100644 --- a/src/cuchaz/enigma/convert/ClassNamer.java +++ b/src/cuchaz/enigma/convert/ClassNamer.java @@ -15,6 +15,8 @@ import java.util.Map; import com.google.common.collect.BiMap; import com.google.common.collect.Maps; +import cuchaz.enigma.mapping.ClassEntry; + public class ClassNamer { public interface SidedClassNamer { @@ -24,15 +26,15 @@ public class ClassNamer { private Map m_sourceNames; private Map m_destNames; - public ClassNamer(BiMap mappings) { + public ClassNamer(BiMap mappings) { // convert the identity mappings to name maps m_sourceNames = Maps.newHashMap(); m_destNames = Maps.newHashMap(); int i = 0; - for (Map.Entry entry : mappings.entrySet()) { + for (Map.Entry entry : mappings.entrySet()) { String name = String.format("M%04d", i++); - m_sourceNames.put(entry.getKey().getClassEntry().getName(), name); - m_destNames.put(entry.getValue().getClassEntry().getName(), name); + m_sourceNames.put(entry.getKey().getName(), name); + m_destNames.put(entry.getValue().getName(), name); } } diff --git a/src/cuchaz/enigma/mapping/Translator.java b/src/cuchaz/enigma/mapping/Translator.java index d9850324..d3b6e771 100644 --- a/src/cuchaz/enigma/mapping/Translator.java +++ b/src/cuchaz/enigma/mapping/Translator.java @@ -27,6 +27,7 @@ public class Translator { public Translator() { m_direction = null; m_classes = Maps.newHashMap(); + m_index = new TranslationIndex(); } public Translator(TranslationDirection direction, Map classes, TranslationIndex index) { -- cgit v1.2.3