From 88671184e20b3ad3791125cf96c83ca048cb2861 Mon Sep 17 00:00:00 2001 From: jeff Date: Sat, 28 Feb 2015 23:36:47 -0500 Subject: refactor converter a bit for upcoming convert gui --- src/cuchaz/enigma/ConvertMain.java | 70 +++++ src/cuchaz/enigma/convert/ClassForest.java | 4 - src/cuchaz/enigma/convert/ClassMatcher.java | 356 ----------------------- src/cuchaz/enigma/convert/ClassMatching.java | 8 - src/cuchaz/enigma/convert/MappingsConverter.java | 180 ++++++++++++ src/cuchaz/enigma/convert/Matches.java | 89 ++++++ src/cuchaz/enigma/convert/MatchesReader.java | 45 +++ src/cuchaz/enigma/convert/MatchesWriter.java | 41 +++ src/cuchaz/enigma/gui/MatchingGui.java | 140 +++++++++ 9 files changed, 565 insertions(+), 368 deletions(-) create mode 100644 src/cuchaz/enigma/ConvertMain.java delete mode 100644 src/cuchaz/enigma/convert/ClassMatcher.java create mode 100644 src/cuchaz/enigma/convert/MappingsConverter.java create mode 100644 src/cuchaz/enigma/convert/Matches.java create mode 100644 src/cuchaz/enigma/convert/MatchesReader.java create mode 100644 src/cuchaz/enigma/convert/MatchesWriter.java create mode 100644 src/cuchaz/enigma/gui/MatchingGui.java (limited to 'src') diff --git a/src/cuchaz/enigma/ConvertMain.java b/src/cuchaz/enigma/ConvertMain.java new file mode 100644 index 00000000..7ba47617 --- /dev/null +++ b/src/cuchaz/enigma/ConvertMain.java @@ -0,0 +1,70 @@ +package cuchaz.enigma; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.jar.JarFile; + +import cuchaz.enigma.convert.MappingsConverter; +import cuchaz.enigma.convert.Matches; +import cuchaz.enigma.convert.MatchesReader; +import cuchaz.enigma.convert.MatchesWriter; +import cuchaz.enigma.gui.MatchingGui; +import cuchaz.enigma.mapping.MappingParseException; +import cuchaz.enigma.mapping.Mappings; +import cuchaz.enigma.mapping.MappingsReader; + + +public class ConvertMain { + + public static void main(String[] args) + throws IOException, MappingParseException { + + // init 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"); + Mappings mappings = new MappingsReader().read(new FileReader(inMappingsFile)); + File matchingFile = new File(inMappingsFile.getName() + ".matching"); + + //computeMatches(matchingFile, sourceJar, destJar, mappings); + editMatches(matchingFile, sourceJar, destJar, mappings); + //convertMappings(outMappingsFile, mappings, matchingFile); + + /* TODO + // write out the converted 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 computeMatches(File matchingFile, JarFile sourceJar, JarFile destJar, Mappings mappings) + throws IOException { + Matches matches = MappingsConverter.computeMatches(sourceJar, destJar, mappings); + MatchesWriter.write(matches, matchingFile); + System.out.println("Wrote:\n\t" + matchingFile.getAbsolutePath()); + } + + private static void editMatches(File matchingFile, JarFile sourceJar, JarFile destJar, Mappings mappings) + throws IOException { + System.out.println("Reading matches..."); + Matches matches = MatchesReader.read(matchingFile); + System.out.println("Indexing source jar..."); + Deobfuscator sourceDeobfuscator = new Deobfuscator(sourceJar); + sourceDeobfuscator.setMappings(mappings); + System.out.println("Indexing dest jar..."); + Deobfuscator destDeobfuscator = new Deobfuscator(destJar); + System.out.println("Starting GUI..."); + new MatchingGui(matches, sourceDeobfuscator, destDeobfuscator); + } + + private static void convertMappings(File outMappingsFile, Mappings mappings, File matchingFile) + throws IOException { + Matches matches = MatchesReader.read(matchingFile); + MappingsConverter.convertMappings(mappings, matches.getUniqueMatches()); + } +} diff --git a/src/cuchaz/enigma/convert/ClassForest.java b/src/cuchaz/enigma/convert/ClassForest.java index e113eeb9..36731393 100644 --- a/src/cuchaz/enigma/convert/ClassForest.java +++ b/src/cuchaz/enigma/convert/ClassForest.java @@ -18,10 +18,6 @@ public class ClassForest { m_forest = HashMultimap.create(); } - public ClassIdentifier getIdentifier() { - return m_identifier; - } - public void addAll(Iterable entries) { for (ClassEntry entry : entries) { add(entry); diff --git a/src/cuchaz/enigma/convert/ClassMatcher.java b/src/cuchaz/enigma/convert/ClassMatcher.java deleted file mode 100644 index f43f5b20..00000000 --- a/src/cuchaz/enigma/convert/ClassMatcher.java +++ /dev/null @@ -1,356 +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.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.jar.JarFile; - -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.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 { - - // 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)); - convertMappings(sourceJar, destJar, mappings, fallbackMatching); - - // write out the converted 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, Map fallbackMatching) { - - // 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); - - // compute the matching - ClassMatching matching = computeMatching(sourceJar, sourceIndex, destJar, destIndex); - - // get all the obf class names used in the mappings - Set usedClasses = Sets.newHashSet(); - for (String className : mappings.getAllObfClassNames()) { - usedClasses.add(new ClassEntry(className)); - } - System.out.println("Mappings reference " + usedClasses.size() + " classes"); - - // 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; - } - - // 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()); - - // 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(scoredDestClasses.keySet()); - Collections.sort(scores, Collections.reverseOrder()); - 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); - if (bestScore > 0 && bestMatches.size() == 1) { - ClassIdentity bestMatch = bestMatches.iterator().next(); - if (bestMatch.getClassEntry().equals(sourceClass.getClassEntry())) { - // use it - System.out.println("\tAutomatically choosing likely match: " + bestMatch.getClassEntry().getName()); - destClasses.clear(); - destClasses.add(bestMatch); - } - } - */ - } - - // 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 - 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 change = iter.next(); - if (unsortedChanges.containsKey(change.getValue())) { - sortedChanges.put(change.getKey(), change.getValue()); - iter.remove(); - } - } - - // did we remove any changes? - if (numChangesLeft - unsortedChanges.size() > 0) { - // keep going - numChangesLeft = unsortedChanges.size(); - } else { - // can't sort anymore. There must be a loop - break; - } - } - 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 : sortedChanges.entrySet()) { - mappings.renameObfClass(entry.getKey().getName(), entry.getValue().getName()); - } - - // check the method matches - System.out.println("Checking methods..."); - for (ClassMapping classMapping : mappings.classes()) { - ClassEntry classEntry = new ClassEntry(classMapping.getObfFullName()); - for (MethodMapping methodMapping : classMapping.methods()) { - - // skip constructors - if (methodMapping.getObfName().equals("")) { - continue; - } - - MethodEntry methodEntry = new MethodEntry( - classEntry, - methodMapping.getObfName(), - methodMapping.getObfSignature() - ); - 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()); - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - System.err.println("\t\t" + EntryFactory.getBehaviorEntry(behavior)); - } - - System.err.println("\tAvailable source methods:"); - c = sourceLoader.loadClass(matchedClassNames.inverse().get(classMapping.getObfFullName())); - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - System.err.println("\t\t" + EntryFactory.getBehaviorEntry(behavior)); - } - */ - } - } - } - - System.out.println("Done!"); - } - - public static ClassMatching computeMatching(JarFile sourceJar, JarIndex sourceIndex, JarFile destJar, JarIndex destIndex) { - - System.out.println("Iteratively matching classes..."); - - ClassMatching lastMatching = null; - int round = 0; - SidedClassNamer sourceNamer = null; - SidedClassNamer destNamer = null; - for (boolean useReferences : Arrays.asList(false, true)) { - - 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) - ); - - if (lastMatching == null) { - // search all classes - matching.match(sourceIndex.getObfClassEntries(), destIndex.getObfClassEntries()); - } else { - // 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(); - - // did we match anything new this time? - if (uniqueMatches.size() > numUniqueMatchesLastTime) { - numUniqueMatchesLastTime = uniqueMatches.size(); - lastMatching = matching; - } else { - break; - } - - // update the namers - ClassNamer namer = new ClassNamer(uniqueMatches); - sourceNamer = namer.getSourceNamer(); - destNamer = namer.getDestNamer(); - } - } - - return lastMatching; - } - - private static void printScoredMatches(int maxScore, List scores, Multimap scoredMatches) { - int numScoredMatchesShown = 0; - for (int score : scores) { - 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; - } - } - } - } - - private static List getClassNames(Collection classes) { - List out = Lists.newArrayList(); - for (ClassEntry c : classes) { - out.add(c.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 b94fd8bd..9f931300 100644 --- a/src/cuchaz/enigma/convert/ClassMatching.java +++ b/src/cuchaz/enigma/convert/ClassMatching.java @@ -125,14 +125,6 @@ public class ClassMatching { return classes; } - public ClassIdentifier getSourceIdentifier() { - return m_sourceClasses.getIdentifier(); - } - - public ClassIdentifier getDestIdentifier() { - return m_destClasses.getIdentifier(); - } - @Override public String toString() { diff --git a/src/cuchaz/enigma/convert/MappingsConverter.java b/src/cuchaz/enigma/convert/MappingsConverter.java new file mode 100644 index 00000000..d0f9382a --- /dev/null +++ b/src/cuchaz/enigma/convert/MappingsConverter.java @@ -0,0 +1,180 @@ +/******************************************************************************* + * 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.util.Arrays; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.jar.JarFile; + +import com.google.common.collect.BiMap; +import com.google.common.collect.Maps; + +import cuchaz.enigma.analysis.JarIndex; +import cuchaz.enigma.convert.ClassNamer.SidedClassNamer; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.Mappings; + +public class MappingsConverter { + + public static Matches computeMatches(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); + + // compute the matching + ClassMatching matching = computeMatching(sourceJar, sourceIndex, destJar, destIndex); + return new Matches(matching.matches()); + } + + public static ClassMatching computeMatching(JarFile sourceJar, JarIndex sourceIndex, JarFile destJar, JarIndex destIndex) { + + System.out.println("Iteratively matching classes"); + + ClassMatching lastMatching = null; + int round = 0; + SidedClassNamer sourceNamer = null; + SidedClassNamer destNamer = null; + for (boolean useReferences : Arrays.asList(false, true)) { + + 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) + ); + + if (lastMatching == null) { + // search all classes + matching.match(sourceIndex.getObfClassEntries(), destIndex.getObfClassEntries()); + } else { + // 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(); + + // did we match anything new this time? + if (uniqueMatches.size() > numUniqueMatchesLastTime) { + numUniqueMatchesLastTime = uniqueMatches.size(); + lastMatching = matching; + } else { + break; + } + + // update the namers + ClassNamer namer = new ClassNamer(uniqueMatches); + sourceNamer = namer.getSourceNamer(); + destNamer = namer.getDestNamer(); + } + } + + return lastMatching; + } + + public static void convertMappings(Mappings mappings, BiMap changes) { + + // 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 sortedChanges = Maps.newLinkedHashMap(); + int numChangesLeft = changes.size(); + while (!changes.isEmpty()) { + Iterator> iter = changes.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry change = iter.next(); + if (changes.containsKey(change.getValue())) { + sortedChanges.put(change.getKey(), change.getValue()); + iter.remove(); + } + } + + // did we remove any changes? + if (numChangesLeft - changes.size() > 0) { + // keep going + numChangesLeft = changes.size(); + } else { + // can't sort anymore. There must be a loop + break; + } + } + if (!changes.isEmpty()) { + throw new Error("Unable to sort class changes! There must be a cycle."); + } + + // convert the mappings in the correct class order + for (Map.Entry entry : sortedChanges.entrySet()) { + mappings.renameObfClass(entry.getKey().getName(), entry.getValue().getName()); + } + } + + /* TODO: after we get a mapping, check to see that the other entries match + public static void checkMethods() { + + // check the method matches + System.out.println("Checking methods..."); + for (ClassMapping classMapping : mappings.classes()) { + ClassEntry classEntry = new ClassEntry(classMapping.getObfFullName()); + for (MethodMapping methodMapping : classMapping.methods()) { + + // skip constructors + if (methodMapping.getObfName().equals("")) { + continue; + } + + MethodEntry methodEntry = new MethodEntry( + classEntry, + methodMapping.getObfName(), + methodMapping.getObfSignature() + ); + 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()); + for (CtBehavior behavior : c.getDeclaredBehaviors()) { + System.err.println("\t\t" + EntryFactory.getBehaviorEntry(behavior)); + } + + System.err.println("\tAvailable source methods:"); + c = sourceLoader.loadClass(matchedClassNames.inverse().get(classMapping.getObfFullName())); + for (CtBehavior behavior : c.getDeclaredBehaviors()) { + System.err.println("\t\t" + EntryFactory.getBehaviorEntry(behavior)); + } + } + } + } + + System.out.println("Done!"); + } + */ +} diff --git a/src/cuchaz/enigma/convert/Matches.java b/src/cuchaz/enigma/convert/Matches.java new file mode 100644 index 00000000..75ecc2a8 --- /dev/null +++ b/src/cuchaz/enigma/convert/Matches.java @@ -0,0 +1,89 @@ +package cuchaz.enigma.convert; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +import cuchaz.enigma.mapping.ClassEntry; + + +public class Matches implements Iterable { + + Collection m_matches; + BiMap m_uniqueMatches; + Map m_ambiguousMatchesBySource; + Map m_ambiguousMatchesByDest; + Set m_unmatchedSourceClasses; + Set m_unmatchedDestClasses; + + public Matches() { + this(new ArrayList()); + } + + public Matches(Collection matches) { + m_matches = matches; + m_uniqueMatches = HashBiMap.create(); + m_ambiguousMatchesBySource = Maps.newHashMap(); + m_ambiguousMatchesByDest = Maps.newHashMap(); + m_unmatchedSourceClasses = Sets.newHashSet(); + m_unmatchedDestClasses = Sets.newHashSet(); + + for (ClassMatch match : matches) { + indexMatch(match); + } + } + + public void add(ClassMatch match) { + m_matches.add(match); + indexMatch(match); + } + + public int size() { + return m_matches.size(); + } + + @Override + public Iterator iterator() { + return m_matches.iterator(); + } + + private void indexMatch(ClassMatch match) { + if (!match.isMatched()) { + // unmatched + m_unmatchedSourceClasses.addAll(match.sourceClasses); + m_unmatchedDestClasses.addAll(match.destClasses); + } else { + if (match.isAmbiguous()) { + // ambiguously matched + for (ClassEntry entry : match.sourceClasses) { + m_ambiguousMatchesBySource.put(entry, match); + } + for (ClassEntry entry : match.destClasses) { + m_ambiguousMatchesByDest.put(entry, match); + } + } else { + // uniquely matched + m_uniqueMatches.put(match.getUniqueSource(), match.getUniqueDest()); + } + } + } + + public BiMap getUniqueMatches() { + return m_uniqueMatches; + } + + public Set getUnmatchedSourceClasses() { + return m_unmatchedSourceClasses; + } + + public Set getUnmatchedDestClasses() { + return m_unmatchedDestClasses; + } +} diff --git a/src/cuchaz/enigma/convert/MatchesReader.java b/src/cuchaz/enigma/convert/MatchesReader.java new file mode 100644 index 00000000..808f8d0a --- /dev/null +++ b/src/cuchaz/enigma/convert/MatchesReader.java @@ -0,0 +1,45 @@ +package cuchaz.enigma.convert; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.Collection; +import java.util.List; + +import com.beust.jcommander.internal.Lists; + +import cuchaz.enigma.mapping.ClassEntry; + + +public class MatchesReader { + + public static Matches read(File file) + throws IOException { + try (BufferedReader in = new BufferedReader(new FileReader(file))) { + Matches matches = new Matches(); + String line = null; + while ((line = in.readLine()) != null) { + matches.add(readMatch(line)); + } + return matches; + } + } + + private static ClassMatch readMatch(String line) + throws IOException { + String[] sides = line.split(":", 2); + return new ClassMatch(readClasses(sides[0]), readClasses(sides[1])); + } + + private static Collection readClasses(String in) { + List entries = Lists.newArrayList(); + for (String className : in.split(",")) { + className = className.trim(); + if (className.length() > 0) { + entries.add(new ClassEntry(className)); + } + } + return entries; + } +} diff --git a/src/cuchaz/enigma/convert/MatchesWriter.java b/src/cuchaz/enigma/convert/MatchesWriter.java new file mode 100644 index 00000000..49ffb6d7 --- /dev/null +++ b/src/cuchaz/enigma/convert/MatchesWriter.java @@ -0,0 +1,41 @@ +package cuchaz.enigma.convert; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; + +import cuchaz.enigma.mapping.ClassEntry; + + +public class MatchesWriter { + + public static void write(Matches matches, File file) + throws IOException { + try (FileWriter out = new FileWriter(file)) { + for (ClassMatch match : matches) { + writeMatch(out, match); + } + } + } + + private static void writeMatch(FileWriter out, ClassMatch match) + throws IOException { + writeClasses(out, match.sourceClasses); + out.write(":"); + writeClasses(out, match.destClasses); + out.write("\n"); + } + + private static void writeClasses(FileWriter out, Iterable classes) + throws IOException { + boolean isFirst = true; + for (ClassEntry entry : classes) { + if (isFirst) { + isFirst = false; + } else { + out.write(","); + } + out.write(entry.toString()); + } + } +} diff --git a/src/cuchaz/enigma/gui/MatchingGui.java b/src/cuchaz/enigma/gui/MatchingGui.java new file mode 100644 index 00000000..53c767ac --- /dev/null +++ b/src/cuchaz/enigma/gui/MatchingGui.java @@ -0,0 +1,140 @@ +package cuchaz.enigma.gui; + +import cuchaz.enigma.Deobfuscator; +import cuchaz.enigma.convert.Matches; + + +public class MatchingGui { + + public MatchingGui(Matches matches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { + // TODO Auto-generated constructor stub + } + + + /* TODO: see if we can use any of this here + public static doTheThings() { + + // get all the obf class names used in the mappings + Set usedClasses = Sets.newHashSet(); + for (String className : mappings.getAllObfClassNames()) { + usedClasses.add(new ClassEntry(className)); + } + System.out.println(String.format("Mappings reference %d/%d classes", + usedClasses.size(), sourceIndex.getObfClassEntries().size() + )); + + // get the used matches + Collection matches = matching.matches(); + Matches usedMatches = new Matches(); + for (ClassMatch match : matching.matches()) { + if (!match.intersectSourceClasses(usedClasses).isEmpty()) { + usedMatches.add(match); + } + } + System.out.println(String.format("Mappings reference %d/%d match groups", + usedMatches.size(), matches.size() + )); + + // 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; + } + + usedMatches.add(match); + + // 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()); + + // 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(scoredDestClasses.keySet()); + Collections.sort(scores, Collections.reverseOrder()); + printScoredMatches(sourceIdentity.getMaxMatchScore(), scores, scoredDestClasses); + } + + // bail if there were unmatched classes + if (!unmatchedUsedClasses.isEmpty()) { + throw new Error("There were " + unmatchedUsedClasses.size() + " unmatched classes!"); + } + } + + private static void printScoredMatches(int maxScore, List scores, Multimap scoredMatches) { + int numScoredMatchesShown = 0; + for (int score : scores) { + 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; + } + } + } + } + + private static List getClassNames(Collection classes) { + List out = Lists.newArrayList(); + for (ClassEntry c : classes) { + out.add(c.getName()); + } + Collections.sort(out); + return out; + } + */ +} -- cgit v1.2.3