From e33b4003a5c423894e7aef575faff359dd1d33b1 Mon Sep 17 00:00:00 2001 From: jeff Date: Wed, 11 Mar 2015 11:03:16 -0400 Subject: generalized field matching added method matching --- src/cuchaz/enigma/ConvertMain.java | 82 +++- src/cuchaz/enigma/convert/MappingsConverter.java | 196 ++++++--- src/cuchaz/enigma/convert/MatchesReader.java | 46 ++- src/cuchaz/enigma/convert/MatchesWriter.java | 50 ++- src/cuchaz/enigma/convert/MemberMatches.java | 145 +++++++ src/cuchaz/enigma/gui/FieldMatchingGui.java | 474 ---------------------- src/cuchaz/enigma/gui/MemberMatchingGui.java | 489 +++++++++++++++++++++++ src/cuchaz/enigma/mapping/ClassMapping.java | 4 + src/cuchaz/enigma/mapping/EntryFactory.java | 12 + src/cuchaz/enigma/mapping/FieldMapping.java | 7 +- src/cuchaz/enigma/mapping/MemberMapping.java | 6 + src/cuchaz/enigma/mapping/MethodMapping.java | 11 +- 12 files changed, 957 insertions(+), 565 deletions(-) create mode 100644 src/cuchaz/enigma/convert/MemberMatches.java delete mode 100644 src/cuchaz/enigma/gui/FieldMatchingGui.java create mode 100644 src/cuchaz/enigma/gui/MemberMatchingGui.java create mode 100644 src/cuchaz/enigma/mapping/MemberMapping.java (limited to 'src') diff --git a/src/cuchaz/enigma/ConvertMain.java b/src/cuchaz/enigma/ConvertMain.java index e012f19..c3a2ad5 100644 --- a/src/cuchaz/enigma/ConvertMain.java +++ b/src/cuchaz/enigma/ConvertMain.java @@ -7,12 +7,14 @@ import java.io.IOException; import java.util.jar.JarFile; import cuchaz.enigma.convert.ClassMatches; -import cuchaz.enigma.convert.FieldMatches; import cuchaz.enigma.convert.MappingsConverter; import cuchaz.enigma.convert.MatchesReader; import cuchaz.enigma.convert.MatchesWriter; +import cuchaz.enigma.convert.MemberMatches; import cuchaz.enigma.gui.ClassMatchingGui; -import cuchaz.enigma.gui.FieldMatchingGui; +import cuchaz.enigma.gui.MemberMatchingGui; +import cuchaz.enigma.mapping.BehaviorEntry; +import cuchaz.enigma.mapping.FieldEntry; import cuchaz.enigma.mapping.MappingParseException; import cuchaz.enigma.mapping.Mappings; import cuchaz.enigma.mapping.MappingsChecker; @@ -34,13 +36,21 @@ public class ConvertMain { Mappings mappings = new MappingsReader().read(new FileReader(inMappingsFile)); File classMatchesFile = new File(inMappingsFile.getName() + ".class.matches"); File fieldMatchesFile = new File(inMappingsFile.getName() + ".field.matches"); + File methodMatchesFile = new File(inMappingsFile.getName() + ".method.matches"); + // match classes //computeClassMatches(classMatchesFile, sourceJar, destJar, mappings); - editClasssMatches(classMatchesFile, sourceJar, destJar, mappings); + //editClasssMatches(classMatchesFile, sourceJar, destJar, mappings); //convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile); + + // match fields //computeFieldMatches(fieldMatchesFile, destJar, outMappingsFile, classMatchesFile); //editFieldMatches(sourceJar, destJar, outMappingsFile, mappings, classMatchesFile, fieldMatchesFile); + // match methods/constructors + //computeMethodMatches(methodMatchesFile, destJar, outMappingsFile, classMatchesFile); + editMethodMatches(sourceJar, destJar, outMappingsFile, mappings, classMatchesFile, methodMatchesFile); + /* TODO // write out the converted mappings FileWriter writer = new FileWriter(outMappingsFile); @@ -91,7 +101,7 @@ public class ConvertMain { System.out.println("Write converted mappings to: " + outMappingsFile.getAbsolutePath()); } - private static void computeFieldMatches(File fieldMatchesFile, JarFile destJar, File destMappingsFile, File classMatchesFile) + private static void computeFieldMatches(File memberMatchesFile, JarFile destJar, File destMappingsFile, File classMatchesFile) throws IOException, MappingParseException { System.out.println("Reading class matches..."); @@ -101,13 +111,13 @@ public class ConvertMain { System.out.println("Indexing dest jar..."); Deobfuscator destDeobfuscator = new Deobfuscator(destJar); - System.out.println("Writing field matches..."); + System.out.println("Writing matches..."); - // get the matched and unmatched field mappings - FieldMatches fieldMatches = MappingsConverter.computeFieldMatches(destDeobfuscator, destMappings, classMatches); + // get the matched and unmatched mappings + MemberMatches fieldMatches = MappingsConverter.computeFieldMatches(destDeobfuscator, destMappings, classMatches); - MatchesWriter.writeFields(fieldMatches, fieldMatchesFile); - System.out.println("Wrote:\n\t" + fieldMatchesFile.getAbsolutePath()); + MatchesWriter.writeMembers(fieldMatches, memberMatchesFile); + System.out.println("Wrote:\n\t" + memberMatchesFile.getAbsolutePath()); } private static void editFieldMatches(JarFile sourceJar, JarFile destJar, File destMappingsFile, Mappings sourceMappings, File classMatchesFile, final File fieldMatchesFile) @@ -115,7 +125,7 @@ public class ConvertMain { System.out.println("Reading matches..."); ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile); - FieldMatches fieldMatches = MatchesReader.readFields(fieldMatchesFile); + MemberMatches fieldMatches = MatchesReader.readMembers(fieldMatchesFile); // prep deobfuscators Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar); @@ -125,18 +135,64 @@ public class ConvertMain { checker.dropBrokenMappings(destMappings); deobfuscators.dest.setMappings(destMappings); - new FieldMatchingGui(classMatches, fieldMatches, deobfuscators.source, deobfuscators.dest).setSaveListener(new FieldMatchingGui.SaveListener() { + new MemberMatchingGui(classMatches, fieldMatches, deobfuscators.source, deobfuscators.dest).setSaveListener(new MemberMatchingGui.SaveListener() { @Override - public void save(FieldMatches matches) { + public void save(MemberMatches matches) { try { - MatchesWriter.writeFields(matches, fieldMatchesFile); + MatchesWriter.writeMembers(matches, fieldMatchesFile); } catch (IOException ex) { throw new Error(ex); } } }); } + + private static void computeMethodMatches(File methodMatchesFile, JarFile destJar, File destMappingsFile, File classMatchesFile) + throws IOException, MappingParseException { + + System.out.println("Reading class matches..."); + ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile); + System.out.println("Reading mappings..."); + Mappings destMappings = new MappingsReader().read(new FileReader(destMappingsFile)); + System.out.println("Indexing dest jar..."); + Deobfuscator destDeobfuscator = new Deobfuscator(destJar); + + System.out.println("Writing method matches..."); + + // get the matched and unmatched mappings + MemberMatches methodMatches = MappingsConverter.computeBehaviorMatches(destDeobfuscator, destMappings, classMatches); + + MatchesWriter.writeMembers(methodMatches, methodMatchesFile); + System.out.println("Wrote:\n\t" + methodMatchesFile.getAbsolutePath()); + } + private static void editMethodMatches(JarFile sourceJar, JarFile destJar, File destMappingsFile, Mappings sourceMappings, File classMatchesFile, final File methodMatchesFile) + throws IOException, MappingParseException { + + System.out.println("Reading matches..."); + ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile); + MemberMatches methodMatches = MatchesReader.readMembers(methodMatchesFile); + + // prep deobfuscators + Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar); + deobfuscators.source.setMappings(sourceMappings); + Mappings destMappings = new MappingsReader().read(new FileReader(destMappingsFile)); + MappingsChecker checker = new MappingsChecker(deobfuscators.dest.getJarIndex()); + checker.dropBrokenMappings(destMappings); + deobfuscators.dest.setMappings(destMappings); + + new MemberMatchingGui(classMatches, methodMatches, deobfuscators.source, deobfuscators.dest).setSaveListener(new MemberMatchingGui.SaveListener() { + @Override + public void save(MemberMatches matches) { + try { + MatchesWriter.writeMembers(matches, methodMatchesFile); + } catch (IOException ex) { + throw new Error(ex); + } + } + }); + } + private static class Deobfuscators { public Deobfuscator source; diff --git a/src/cuchaz/enigma/convert/MappingsConverter.java b/src/cuchaz/enigma/convert/MappingsConverter.java index 9ab1baa..2987ea0 100644 --- a/src/cuchaz/enigma/convert/MappingsConverter.java +++ b/src/cuchaz/enigma/convert/MappingsConverter.java @@ -11,12 +11,12 @@ package cuchaz.enigma.convert; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; import java.util.jar.JarFile; @@ -30,15 +30,20 @@ import com.google.common.collect.Sets; import cuchaz.enigma.Deobfuscator; import cuchaz.enigma.analysis.JarIndex; import cuchaz.enigma.convert.ClassNamer.SidedClassNamer; +import cuchaz.enigma.mapping.BehaviorEntry; import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.ClassMapping; import cuchaz.enigma.mapping.ClassNameReplacer; -import cuchaz.enigma.mapping.EntryFactory; +import cuchaz.enigma.mapping.ConstructorEntry; +import cuchaz.enigma.mapping.Entry; import cuchaz.enigma.mapping.FieldEntry; import cuchaz.enigma.mapping.FieldMapping; import cuchaz.enigma.mapping.Mappings; import cuchaz.enigma.mapping.MappingsChecker; +import cuchaz.enigma.mapping.MemberMapping; +import cuchaz.enigma.mapping.MethodEntry; import cuchaz.enigma.mapping.MethodMapping; +import cuchaz.enigma.mapping.Signature; import cuchaz.enigma.mapping.Type; public class MappingsConverter { @@ -124,8 +129,8 @@ public class MappingsConverter { public static Mappings newMappings(ClassMatches matches, Mappings oldMappings, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { // sort the unique matches by size of inner class chain - Multimap> matchesByDestChainSize = HashMultimap.create(); - for (Entry match : matches.getUniqueMatches().entrySet()) { + Multimap> matchesByDestChainSize = HashMultimap.create(); + for (java.util.Map.Entry match : matches.getUniqueMatches().entrySet()) { int chainSize = destDeobfuscator.getJarIndex().getObfClassChain(match.getValue()).size(); matchesByDestChainSize.put(chainSize, match); } @@ -135,7 +140,7 @@ public class MappingsConverter { List chainSizes = Lists.newArrayList(matchesByDestChainSize.keySet()); Collections.sort(chainSizes); for (int chainSize : chainSizes) { - for (Entry match : matchesByDestChainSize.get(chainSize)) { + for (java.util.Map.Entry match : matchesByDestChainSize.get(chainSize)) { // get class info ClassEntry obfSourceClassEntry = match.getKey(); @@ -251,89 +256,172 @@ public class MappingsConverter { mappings.renameObfClass(entry.getKey().getName(), entry.getValue().getName()); } } + + public static interface Doer { + Collection getDroppedEntries(MappingsChecker checker); + Collection getObfEntries(JarIndex jarIndex); + Collection> getMappings(ClassMapping destClassMapping); + Set filterEntries(Collection obfEntries, T obfSourceEntry, ClassMatches classMatches); + } + + public static MemberMatches computeFieldMatches(Deobfuscator destDeobfuscator, Mappings destMappings, ClassMatches classMatches) { + return computeMemberMatches(destDeobfuscator, destMappings, classMatches, new Doer() { + + @Override + public Collection getDroppedEntries(MappingsChecker checker) { + return checker.getDroppedFieldMappings().keySet(); + } + + @Override + public Collection getObfEntries(JarIndex jarIndex) { + return jarIndex.getObfFieldEntries(); + } + + @Override + public Collection> getMappings(ClassMapping destClassMapping) { + return (Collection>)destClassMapping.fields(); + } + + @Override + public Set filterEntries(Collection obfDestFields, FieldEntry obfSourceField, ClassMatches classMatches) { + Set out = Sets.newHashSet(); + for (FieldEntry obfDestField : obfDestFields) { + Type translatedDestType = translate(obfDestField.getType(), classMatches.getUniqueMatches().inverse()); + if (translatedDestType.equals(obfSourceField.getType())) { + out.add(obfDestField); + } + } + return out; + } + }); + } + + public static MemberMatches computeBehaviorMatches(Deobfuscator destDeobfuscator, Mappings destMappings, ClassMatches classMatches) { + return computeMemberMatches(destDeobfuscator, destMappings, classMatches, new Doer() { + + @Override + public Collection getDroppedEntries(MappingsChecker checker) { + return checker.getDroppedMethodMappings().keySet(); + } + + @Override + public Collection getObfEntries(JarIndex jarIndex) { + return jarIndex.getObfBehaviorEntries(); + } - public static FieldMatches computeFieldMatches(Deobfuscator destDeobfuscator, Mappings destMappings, ClassMatches classMatches) { + @Override + public Collection> getMappings(ClassMapping destClassMapping) { + return (Collection>)destClassMapping.methods(); + } + + @Override + public Set filterEntries(Collection obfDestFields, BehaviorEntry obfSourceField, ClassMatches classMatches) { + Set out = Sets.newHashSet(); + for (BehaviorEntry obfDestField : obfDestFields) { + Signature translatedDestSignature = translate(obfDestField.getSignature(), classMatches.getUniqueMatches().inverse()); + if (translatedDestSignature == null && obfSourceField.getSignature() == null) { + out.add(obfDestField); + } else if (translatedDestSignature == null || obfSourceField.getSignature() == null) { + // skip it + } else if (translatedDestSignature.equals(obfSourceField.getSignature())) { + out.add(obfDestField); + } + } + return out; + } + }); + } + + public static MemberMatches computeMemberMatches(Deobfuscator destDeobfuscator, Mappings destMappings, ClassMatches classMatches, Doer doer) { - FieldMatches fieldMatches = new FieldMatches(); + MemberMatches memberMatches = new MemberMatches(); // unmatched source fields are easy MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex()); checker.dropBrokenMappings(destMappings); - for (FieldEntry destObfField : checker.getDroppedFieldMappings().keySet()) { - FieldEntry srcObfField = translate(destObfField, classMatches.getUniqueMatches().inverse()); - fieldMatches.addUnmatchedSourceField(srcObfField); + for (T destObfEntry : doer.getDroppedEntries(checker)) { + T srcObfEntry = translate(destObfEntry, classMatches.getUniqueMatches().inverse()); + memberMatches.addUnmatchedSourceEntry(srcObfEntry); } // get matched fields (anything that's left after the checks/drops is matched( for (ClassMapping classMapping : destMappings.classes()) { - collectMatchedFields(fieldMatches, classMapping, classMatches); + collectMatchedFields(memberMatches, classMapping, classMatches, doer); } // get unmatched dest fields - for (FieldEntry destFieldEntry : destDeobfuscator.getJarIndex().getObfFieldEntries()) { - if (!fieldMatches.isMatchedDestField(destFieldEntry)) { - fieldMatches.addUnmatchedDestField(destFieldEntry); + for (T destEntry : doer.getObfEntries(destDeobfuscator.getJarIndex())) { + if (!memberMatches.isMatchedDestEntry(destEntry)) { + memberMatches.addUnmatchedDestEntry(destEntry); } } - System.out.println("Automatching " + fieldMatches.getUnmatchedSourceFields().size() + " unmatched source fields..."); + System.out.println("Automatching " + memberMatches.getUnmatchedSourceEntries().size() + " unmatched source entries..."); // go through the unmatched source fields and try to pick out the easy matches - for (ClassEntry obfSourceClass : Lists.newArrayList(fieldMatches.getSourceClassesWithUnmatchedFields())) { - for (FieldEntry obfSourceField : Lists.newArrayList(fieldMatches.getUnmatchedSourceFields(obfSourceClass))) { + for (ClassEntry obfSourceClass : Lists.newArrayList(memberMatches.getSourceClassesWithUnmatchedEntries())) { + for (T obfSourceEntry : Lists.newArrayList(memberMatches.getUnmatchedSourceEntries(obfSourceClass))) { // get the possible dest matches ClassEntry obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass); - // filter by type - Set obfDestFields = Sets.newHashSet(); - for (FieldEntry obfDestField : fieldMatches.getUnmatchedDestFields(obfDestClass)) { - Type translatedDestType = translate(obfDestField.getType(), classMatches.getUniqueMatches().inverse()); - if (translatedDestType.equals(obfSourceField.getType())) { - obfDestFields.add(obfDestField); - } - } + // filter by type/signature + Set obfDestEntries = doer.filterEntries(memberMatches.getUnmatchedDestEntries(obfDestClass), obfSourceEntry, classMatches); - if (obfDestFields.size() == 1) { + if (obfDestEntries.size() == 1) { // make the easy match - FieldEntry obfDestField = obfDestFields.iterator().next(); - fieldMatches.makeMatch(obfSourceField, obfDestField); - } else if (obfDestFields.isEmpty()) { + memberMatches.makeMatch(obfSourceEntry, obfDestEntries.iterator().next()); + } else if (obfDestEntries.isEmpty()) { // no match is possible =( - fieldMatches.makeSourceUnmatchable(obfSourceField); + memberMatches.makeSourceUnmatchable(obfSourceEntry); } } } - System.out.println(String.format("Ended up with %d ambiguous and %d unmatchable source fields", - fieldMatches.getUnmatchedSourceFields().size(), - fieldMatches.getUnmatchableSourceFields().size() + System.out.println(String.format("Ended up with %d ambiguous and %d unmatchable source entries", + memberMatches.getUnmatchedSourceEntries().size(), + memberMatches.getUnmatchableSourceEntries().size() )); - return fieldMatches; + return memberMatches; } - private static void collectMatchedFields(FieldMatches fieldMatches, ClassMapping destClassMapping, ClassMatches classMatches) { + private static void collectMatchedFields(MemberMatches memberMatches, ClassMapping destClassMapping, ClassMatches classMatches, Doer doer) { // get the fields for this class - for (FieldMapping destFieldMapping : destClassMapping.fields()) { - FieldEntry destObfField = EntryFactory.getObfFieldEntry(destClassMapping, destFieldMapping); - FieldEntry srcObfField = translate(destObfField, classMatches.getUniqueMatches().inverse()); - fieldMatches.addMatch(srcObfField, destObfField); + for (MemberMapping destEntryMapping : doer.getMappings(destClassMapping)) { + T destObfField = destEntryMapping.getObfEntry(destClassMapping.getObfEntry()); + T srcObfField = translate(destObfField, classMatches.getUniqueMatches().inverse()); + memberMatches.addMatch(srcObfField, destObfField); } // recurse for (ClassMapping destInnerClassMapping : destClassMapping.innerClasses()) { - collectMatchedFields(fieldMatches, destInnerClassMapping, classMatches); + collectMatchedFields(memberMatches, destInnerClassMapping, classMatches, doer); } } - private static FieldEntry translate(FieldEntry in, BiMap map) { - return new FieldEntry( - map.get(in.getClassEntry()), - in.getName(), - translate(in.getType(), map) - ); + @SuppressWarnings("unchecked") + private static T translate(T in, BiMap map) { + if (in instanceof FieldEntry) { + return (T)new FieldEntry( + map.get(in.getClassEntry()), + in.getName(), + translate(((FieldEntry)in).getType(), map) + ); + } else if (in instanceof MethodEntry) { + return (T)new MethodEntry( + map.get(in.getClassEntry()), + in.getName(), + translate(((MethodEntry)in).getSignature(), map) + ); + } else if (in instanceof ConstructorEntry) { + return (T)new ConstructorEntry( + map.get(in.getClassEntry()), + translate(((ConstructorEntry)in).getSignature(), map) + ); + } + throw new Error("Unhandled entry type: " + in.getClass()); } private static Type translate(Type type, final BiMap map) { @@ -348,4 +436,20 @@ public class MappingsConverter { } }); } + + private static Signature translate(Signature signature, final BiMap map) { + if (signature == null) { + return null; + } + return new Signature(signature, new ClassNameReplacer() { + @Override + public String replace(String inClassName) { + ClassEntry outClassEntry = map.get(new ClassEntry(inClassName)); + if (outClassEntry == null) { + return null; + } + return outClassEntry.getName(); + } + }); + } } diff --git a/src/cuchaz/enigma/convert/MatchesReader.java b/src/cuchaz/enigma/convert/MatchesReader.java index 921ab1d..dac2f05 100644 --- a/src/cuchaz/enigma/convert/MatchesReader.java +++ b/src/cuchaz/enigma/convert/MatchesReader.java @@ -10,6 +10,8 @@ import java.util.List; import com.beust.jcommander.internal.Lists; import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.Entry; +import cuchaz.enigma.mapping.EntryFactory; import cuchaz.enigma.mapping.FieldEntry; import cuchaz.enigma.mapping.Type; @@ -45,45 +47,57 @@ public class MatchesReader { return entries; } - public static FieldMatches readFields(File file) + public static MemberMatches readMembers(File file) throws IOException { try (BufferedReader in = new BufferedReader(new FileReader(file))) { - FieldMatches matches = new FieldMatches(); + MemberMatches matches = new MemberMatches(); String line = null; while ((line = in.readLine()) != null) { - readFieldMatch(matches, line); + readMemberMatch(matches, line); } return matches; } } - private static void readFieldMatch(FieldMatches matches, String line) { + private static void readMemberMatch(MemberMatches matches, String line) { if (line.startsWith("!")) { - matches.addUnmatchableSourceField(readField(line.substring(1))); + T source = readEntry(line.substring(1)); + matches.addUnmatchableSourceEntry(source); } else { String[] parts = line.split(":", 2); - FieldEntry source = readField(parts[0]); - FieldEntry dest = readField(parts[1]); + T source = readEntry(parts[0]); + T dest = readEntry(parts[1]); if (source != null && dest != null) { matches.addMatch(source, dest); } else if (source != null) { - matches.addUnmatchedSourceField(source); + matches.addUnmatchedSourceEntry(source); } else if (dest != null) { - matches.addUnmatchedDestField(dest); + matches.addUnmatchedDestEntry(dest); } } } - private static FieldEntry readField(String in) { + @SuppressWarnings("unchecked") + private static T readEntry(String in) { if (in.length() <= 0) { return null; } String[] parts = in.split(" "); - assert(parts.length == 3); - return new FieldEntry( - new ClassEntry(parts[0]), - parts[1], - new Type(parts[2]) - ); + if (parts.length == 3 && parts[2].indexOf('(') < 0) { + return (T)new FieldEntry( + new ClassEntry(parts[0]), + parts[1], + new Type(parts[2]) + ); + } else { + assert(parts.length == 2 || parts.length == 3); + if (parts.length == 2) { + return (T)EntryFactory.getBehaviorEntry(parts[0], parts[1]); + } else if (parts.length == 3) { + return (T)EntryFactory.getBehaviorEntry(parts[0], parts[1], parts[2]); + } else { + throw new Error("Malformed behavior entry: " + in); + } + } } } diff --git a/src/cuchaz/enigma/convert/MatchesWriter.java b/src/cuchaz/enigma/convert/MatchesWriter.java index 2118dd0..9e9ead0 100644 --- a/src/cuchaz/enigma/convert/MatchesWriter.java +++ b/src/cuchaz/enigma/convert/MatchesWriter.java @@ -5,7 +5,9 @@ import java.io.FileWriter; import java.io.IOException; import java.util.Map; +import cuchaz.enigma.mapping.BehaviorEntry; import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.Entry; import cuchaz.enigma.mapping.FieldEntry; @@ -41,43 +43,52 @@ public class MatchesWriter { } } - public static void writeFields(FieldMatches fieldMatches, File file) + public static void writeMembers(MemberMatches matches, File file) throws IOException { try (FileWriter out = new FileWriter(file)) { - for (Map.Entry match : fieldMatches.matches().entrySet()) { - writeFieldMatch(out, match.getKey(), match.getValue()); + for (Map.Entry match : matches.matches().entrySet()) { + writeMemberMatch(out, match.getKey(), match.getValue()); } - for (FieldEntry fieldEntry : fieldMatches.getUnmatchedSourceFields()) { - writeFieldMatch(out, fieldEntry, null); + for (T entry : matches.getUnmatchedSourceEntries()) { + writeMemberMatch(out, entry, null); } - for (FieldEntry fieldEntry : fieldMatches.getUnmatchedDestFields()) { - writeFieldMatch(out, null, fieldEntry); + for (T entry : matches.getUnmatchedDestEntries()) { + writeMemberMatch(out, null, entry); } - for (FieldEntry fieldEntry : fieldMatches.getUnmatchableSourceFields()) { - writeUnmatchableField(out, fieldEntry); + for (T entry : matches.getUnmatchableSourceEntries()) { + writeUnmatchableEntry(out, entry); } } } - private static void writeFieldMatch(FileWriter out, FieldEntry source, FieldEntry dest) + private static void writeMemberMatch(FileWriter out, T source, T dest) throws IOException { if (source != null) { - writeField(out, source); + writeEntry(out, source); } out.write(":"); if (dest != null) { - writeField(out, dest); + writeEntry(out, dest); } out.write("\n"); } - private static void writeUnmatchableField(FileWriter out, FieldEntry fieldEntry) + private static void writeUnmatchableEntry(FileWriter out, T entry) throws IOException { out.write("!"); - writeField(out, fieldEntry); + writeEntry(out, entry); out.write("\n"); } + private static void writeEntry(FileWriter out, T entry) + throws IOException { + if (entry instanceof FieldEntry) { + writeField(out, (FieldEntry)entry); + } else if (entry instanceof BehaviorEntry) { + writeBehavior(out, (BehaviorEntry)entry); + } + } + private static void writeField(FileWriter out, FieldEntry fieldEntry) throws IOException { out.write(fieldEntry.getClassName()); @@ -86,4 +97,15 @@ public class MatchesWriter { out.write(" "); out.write(fieldEntry.getType().toString()); } + + private static void writeBehavior(FileWriter out, BehaviorEntry behaviorEntry) + throws IOException { + out.write(behaviorEntry.getClassName()); + out.write(" "); + out.write(behaviorEntry.getName()); + out.write(" "); + if (behaviorEntry.getSignature() != null) { + out.write(behaviorEntry.getSignature().toString()); + } + } } diff --git a/src/cuchaz/enigma/convert/MemberMatches.java b/src/cuchaz/enigma/convert/MemberMatches.java new file mode 100644 index 0000000..1078ab7 --- /dev/null +++ b/src/cuchaz/enigma/convert/MemberMatches.java @@ -0,0 +1,145 @@ +package cuchaz.enigma.convert; + +import java.util.Collection; +import java.util.Set; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; + +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.Entry; + + +public class MemberMatches { + + private BiMap m_matches; + private Multimap m_matchedSourceEntries; + private Multimap m_unmatchedSourceEntries; + private Multimap m_unmatchedDestEntries; + private Multimap m_unmatchableSourceEntries; + + public MemberMatches() { + m_matches = HashBiMap.create(); + m_matchedSourceEntries = HashMultimap.create(); + m_unmatchedSourceEntries = HashMultimap.create(); + m_unmatchedDestEntries = HashMultimap.create(); + m_unmatchableSourceEntries = HashMultimap.create(); + } + + public void addMatch(T srcEntry, T destEntry) { + boolean wasAdded = m_matches.put(srcEntry, destEntry) == null; + assert (wasAdded); + wasAdded = m_matchedSourceEntries.put(srcEntry.getClassEntry(), srcEntry); + assert (wasAdded); + } + + public void addUnmatchedSourceEntry(T sourceEntry) { + boolean wasAdded = m_unmatchedSourceEntries.put(sourceEntry.getClassEntry(), sourceEntry); + assert (wasAdded); + } + + public void addUnmatchedSourceEntries(Iterable sourceEntries) { + for (T sourceEntry : sourceEntries) { + addUnmatchedSourceEntry(sourceEntry); + } + } + + public void addUnmatchedDestEntry(T destEntry) { + boolean wasAdded = m_unmatchedDestEntries.put(destEntry.getClassEntry(), destEntry); + assert (wasAdded); + } + + public void addUnmatchedDestEntries(Iterable destEntriesntries) { + for (T entry : destEntriesntries) { + addUnmatchedDestEntry(entry); + } + } + + public void addUnmatchableSourceEntry(T sourceEntry) { + boolean wasAdded = m_unmatchableSourceEntries.put(sourceEntry.getClassEntry(), sourceEntry); + assert (wasAdded); + } + + public Set getSourceClassesWithUnmatchedEntries() { + return m_unmatchedSourceEntries.keySet(); + } + + public Collection getSourceClassesWithoutUnmatchedEntries() { + Set out = Sets.newHashSet(); + out.addAll(m_matchedSourceEntries.keySet()); + out.removeAll(m_unmatchedSourceEntries.keySet()); + return out; + } + + public Collection getUnmatchedSourceEntries() { + return m_unmatchedSourceEntries.values(); + } + + public Collection getUnmatchedSourceEntries(ClassEntry sourceClass) { + return m_unmatchedSourceEntries.get(sourceClass); + } + + public Collection getUnmatchedDestEntries() { + return m_unmatchedDestEntries.values(); + } + + public Collection getUnmatchedDestEntries(ClassEntry destClass) { + return m_unmatchedDestEntries.get(destClass); + } + + public Collection getUnmatchableSourceEntries() { + return m_unmatchableSourceEntries.values(); + } + + public boolean hasSource(T sourceEntry) { + return m_matches.containsKey(sourceEntry) || m_unmatchedSourceEntries.containsValue(sourceEntry); + } + + public boolean hasDest(T destEntry) { + return m_matches.containsValue(destEntry) || m_unmatchedDestEntries.containsValue(destEntry); + } + + public BiMap matches() { + return m_matches; + } + + public boolean isMatchedSourceEntry(T sourceEntry) { + return m_matches.containsKey(sourceEntry); + } + + public boolean isMatchedDestEntry(T destEntry) { + return m_matches.containsValue(destEntry); + } + + public void makeMatch(T sourceEntry, T destEntry) { + boolean wasRemoved = m_unmatchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry); + assert (wasRemoved); + wasRemoved = m_unmatchedDestEntries.remove(destEntry.getClassEntry(), destEntry); + assert (wasRemoved); + addMatch(sourceEntry, destEntry); + } + + public boolean isMatched(T sourceEntry, T destEntry) { + T match = m_matches.get(sourceEntry); + return match != null && match.equals(destEntry); + } + + public void unmakeMatch(T sourceEntry, T destEntry) { + boolean wasRemoved = m_matches.remove(sourceEntry) != null; + assert (wasRemoved); + wasRemoved = m_matchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry); + assert (wasRemoved); + addUnmatchedSourceEntry(sourceEntry); + addUnmatchedDestEntry(destEntry); + } + + public void makeSourceUnmatchable(T sourceEntry) { + assert(!isMatchedSourceEntry(sourceEntry)); + boolean wasRemoved = m_unmatchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry); + assert (wasRemoved); + addUnmatchableSourceEntry(sourceEntry); + } +} diff --git a/src/cuchaz/enigma/gui/FieldMatchingGui.java b/src/cuchaz/enigma/gui/FieldMatchingGui.java deleted file mode 100644 index 3f4a378..0000000 --- a/src/cuchaz/enigma/gui/FieldMatchingGui.java +++ /dev/null @@ -1,474 +0,0 @@ -package cuchaz.enigma.gui; - -import java.awt.BorderLayout; -import java.awt.Container; -import java.awt.Dimension; -import java.awt.FlowLayout; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -import javax.swing.BoxLayout; -import javax.swing.ButtonGroup; -import javax.swing.JButton; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JRadioButton; -import javax.swing.JScrollPane; -import javax.swing.JSplitPane; -import javax.swing.WindowConstants; -import javax.swing.text.Highlighter.HighlightPainter; - -import com.beust.jcommander.internal.Lists; -import com.beust.jcommander.internal.Maps; - -import cuchaz.enigma.Constants; -import cuchaz.enigma.Deobfuscator; -import cuchaz.enigma.analysis.EntryReference; -import cuchaz.enigma.analysis.SourceIndex; -import cuchaz.enigma.analysis.Token; -import cuchaz.enigma.convert.ClassMatches; -import cuchaz.enigma.convert.FieldMatches; -import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener; -import cuchaz.enigma.mapping.ClassEntry; -import cuchaz.enigma.mapping.Entry; -import cuchaz.enigma.mapping.FieldEntry; -import de.sciss.syntaxpane.DefaultSyntaxKit; - - -public class FieldMatchingGui { - - private static enum SourceType { - Matched { - - @Override - public Collection getObfSourceClasses(FieldMatches matches) { - return matches.getSourceClassesWithoutUnmatchedFields(); - } - }, - Unmatched { - - @Override - public Collection getObfSourceClasses(FieldMatches matches) { - return matches.getSourceClassesWithUnmatchedFields(); - } - }; - - public JRadioButton newRadio(ActionListener listener, ButtonGroup group) { - JRadioButton button = new JRadioButton(name(), this == getDefault()); - button.setActionCommand(name()); - button.addActionListener(listener); - group.add(button); - return button; - } - - public abstract Collection getObfSourceClasses(FieldMatches matches); - - public static SourceType getDefault() { - return values()[0]; - } - } - - public static interface SaveListener { - public void save(FieldMatches matches); - } - - // controls - private JFrame m_frame; - private Map m_sourceTypeButtons; - private ClassSelector m_sourceClasses; - private CodeReader m_sourceReader; - private CodeReader m_destReader; - private JButton m_matchButton; - private JButton m_unmatchableButton; - private JLabel m_sourceLabel; - private JLabel m_destLabel; - private HighlightPainter m_unmatchedHighlightPainter; - private HighlightPainter m_matchedHighlightPainter; - - private ClassMatches m_classMatches; - private FieldMatches m_fieldMatches; - private Deobfuscator m_sourceDeobfuscator; - private Deobfuscator m_destDeobfuscator; - private SaveListener m_saveListener; - private SourceType m_sourceType; - private ClassEntry m_obfSourceClass; - private ClassEntry m_obfDestClass; - private FieldEntry m_obfSourceField; - private FieldEntry m_obfDestField; - - public FieldMatchingGui(ClassMatches classMatches, FieldMatches fieldMatches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { - - m_classMatches = classMatches; - m_fieldMatches = fieldMatches; - m_sourceDeobfuscator = sourceDeobfuscator; - m_destDeobfuscator = destDeobfuscator; - - // init frame - m_frame = new JFrame(Constants.Name + " - Field Matcher"); - final Container pane = m_frame.getContentPane(); - pane.setLayout(new BorderLayout()); - - // init classes side - JPanel classesPanel = new JPanel(); - classesPanel.setLayout(new BoxLayout(classesPanel, BoxLayout.PAGE_AXIS)); - classesPanel.setPreferredSize(new Dimension(200, 0)); - pane.add(classesPanel, BorderLayout.WEST); - classesPanel.add(new JLabel("Classes")); - - // init source type radios - JPanel sourceTypePanel = new JPanel(); - classesPanel.add(sourceTypePanel); - sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS)); - ActionListener sourceTypeListener = new ActionListener() { - @Override - public void actionPerformed(ActionEvent event) { - setSourceType(SourceType.valueOf(event.getActionCommand())); - } - }; - ButtonGroup sourceTypeButtons = new ButtonGroup(); - m_sourceTypeButtons = Maps.newHashMap(); - for (SourceType sourceType : SourceType.values()) { - JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons); - m_sourceTypeButtons.put(sourceType, button); - sourceTypePanel.add(button); - } - - m_sourceClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator); - m_sourceClasses.setListener(new ClassSelectionListener() { - @Override - public void onSelectClass(ClassEntry classEntry) { - setSourceClass(classEntry); - } - }); - JScrollPane sourceScroller = new JScrollPane(m_sourceClasses); - classesPanel.add(sourceScroller); - - // init readers - DefaultSyntaxKit.initKit(); - m_sourceReader = new CodeReader(); - m_sourceReader.setSelectionListener(new CodeReader.SelectionListener() { - @Override - public void onSelect(EntryReference reference) { - if (reference != null) { - onSelectSource(reference.entry); - } else { - onSelectSource(null); - } - } - }); - m_destReader = new CodeReader(); - m_destReader.setSelectionListener(new CodeReader.SelectionListener() { - @Override - public void onSelect(EntryReference reference) { - if (reference != null) { - onSelectDest(reference.entry); - } else { - onSelectDest(null); - } - } - }); - - // add key bindings - KeyAdapter keyListener = new KeyAdapter() { - @Override - public void keyPressed(KeyEvent event) { - switch (event.getKeyCode()) { - case KeyEvent.VK_M: - m_matchButton.doClick(); - break; - } - } - }; - m_sourceReader.addKeyListener(keyListener); - m_destReader.addKeyListener(keyListener); - - // init all the splits - JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(m_sourceReader), new JScrollPane(m_destReader)); - splitRight.setResizeWeight(0.5); // resize 50:50 - JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, classesPanel, splitRight); - splitLeft.setResizeWeight(0); // let the right side take all the slack - pane.add(splitLeft, BorderLayout.CENTER); - splitLeft.resetToPreferredSizes(); - - // init bottom panel - JPanel bottomPanel = new JPanel(); - bottomPanel.setLayout(new FlowLayout()); - pane.add(bottomPanel, BorderLayout.SOUTH); - - m_matchButton = new JButton(); - m_unmatchableButton = new JButton(); - - m_sourceLabel = new JLabel(); - bottomPanel.add(m_sourceLabel); - bottomPanel.add(m_matchButton); - bottomPanel.add(m_unmatchableButton); - m_destLabel = new JLabel(); - bottomPanel.add(m_destLabel); - - // show the frame - pane.doLayout(); - m_frame.setSize(1024, 576); - m_frame.setMinimumSize(new Dimension(640, 480)); - m_frame.setVisible(true); - m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - - m_unmatchedHighlightPainter = new ObfuscatedHighlightPainter(); - m_matchedHighlightPainter = new DeobfuscatedHighlightPainter(); - - // init state - m_saveListener = null; - m_obfSourceClass = null; - m_obfDestClass = null; - m_obfSourceField = null; - m_obfDestField = null; - setSourceType(SourceType.getDefault()); - updateButtons(); - } - - protected void setSourceType(SourceType val) { - m_sourceType = val; - updateSourceClasses(); - } - - public void setSaveListener(SaveListener val) { - m_saveListener = val; - } - - private void updateSourceClasses() { - - String selectedPackage = m_sourceClasses.getSelectedPackage(); - - List deobfClassEntries = Lists.newArrayList(); - for (ClassEntry entry : m_sourceType.getObfSourceClasses(m_fieldMatches)) { - deobfClassEntries.add(m_sourceDeobfuscator.deobfuscateEntry(entry)); - } - m_sourceClasses.setClasses(deobfClassEntries); - - if (selectedPackage != null) { - m_sourceClasses.expandPackage(selectedPackage); - } - - for (SourceType sourceType : SourceType.values()) { - m_sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)", - sourceType.name(), sourceType.getObfSourceClasses(m_fieldMatches).size() - )); - } - } - - protected void setSourceClass(ClassEntry sourceClass) { - - m_obfSourceClass = m_sourceDeobfuscator.obfuscateEntry(sourceClass); - m_obfDestClass = m_classMatches.getUniqueMatches().get(m_obfSourceClass); - if (m_obfDestClass == null) { - throw new Error("No matching dest class for source class: " + m_obfSourceClass); - } - - m_sourceReader.decompileClass(m_obfSourceClass, m_sourceDeobfuscator, false, new Runnable() { - @Override - public void run() { - updateSourceHighlights(); - } - }); - m_destReader.decompileClass(m_obfDestClass, m_destDeobfuscator, false, new Runnable() { - @Override - public void run() { - updateDestHighlights(); - } - }); - } - - protected void updateSourceHighlights() { - highlightFields(m_sourceReader, m_sourceDeobfuscator, m_fieldMatches.matches().keySet(), m_fieldMatches.getUnmatchedSourceFields()); - } - - protected void updateDestHighlights() { - highlightFields(m_destReader, m_destDeobfuscator, m_fieldMatches.matches().values(), m_fieldMatches.getUnmatchedDestFields()); - } - - private void highlightFields(CodeReader reader, Deobfuscator deobfuscator, Collection obfMatchedFields, Collection obfUnmatchedFields) { - reader.clearHighlights(); - SourceIndex index = reader.getSourceIndex(); - - // matched fields - for (FieldEntry obfFieldEntry : obfMatchedFields) { - FieldEntry deobfFieldEntry = deobfuscator.deobfuscateEntry(obfFieldEntry); - Token token = index.getDeclarationToken(deobfFieldEntry); - if (token != null) { - reader.setHighlightedToken(token, m_matchedHighlightPainter); - } - } - - // unmatched fields - for (FieldEntry obfFieldEntry : obfUnmatchedFields) { - FieldEntry deobfFieldEntry = deobfuscator.deobfuscateEntry(obfFieldEntry); - Token token = index.getDeclarationToken(deobfFieldEntry); - if (token != null) { - reader.setHighlightedToken(token, m_unmatchedHighlightPainter); - } - } - } - - private boolean isSelectionMatched() { - return m_obfSourceField != null && m_obfDestField != null - && m_fieldMatches.isMatched(m_obfSourceField, m_obfDestField); - } - - protected void onSelectSource(Entry source) { - - // start with no selection - if (isSelectionMatched()) { - setDest(null); - } - setSource(null); - - // then look for a valid source selection - if (source != null && source instanceof FieldEntry) { - FieldEntry sourceField = (FieldEntry)source; - FieldEntry obfSourceField = m_sourceDeobfuscator.obfuscateEntry(sourceField); - if (m_fieldMatches.hasSource(obfSourceField)) { - setSource(obfSourceField); - - // look for a matched dest too - FieldEntry obfDestField = m_fieldMatches.matches().get(obfSourceField); - if (obfDestField != null) { - setDest(obfDestField); - } - } - } - - updateButtons(); - } - - protected void onSelectDest(Entry dest) { - - // start with no selection - if (isSelectionMatched()) { - setSource(null); - } - setDest(null); - - // then look for a valid dest selection - if (dest != null && dest instanceof FieldEntry) { - FieldEntry destField = (FieldEntry)dest; - FieldEntry obfDestField = m_destDeobfuscator.obfuscateEntry(destField); - if (m_fieldMatches.hasDest(obfDestField)) { - setDest(obfDestField); - - // look for a matched source too - FieldEntry obfSourceField = m_fieldMatches.matches().inverse().get(obfDestField); - if (obfSourceField != null) { - setSource(obfSourceField); - } - } - } - - updateButtons(); - } - - private void setSource(FieldEntry obfField) { - if (obfField == null) { - m_obfSourceField = obfField; - m_sourceLabel.setText(""); - } else { - m_obfSourceField = obfField; - FieldEntry deobfField = m_sourceDeobfuscator.deobfuscateEntry(obfField); - m_sourceLabel.setText(deobfField.getName() + " " + deobfField.getType().toString()); - } - } - - private void setDest(FieldEntry obfField) { - if (obfField == null) { - m_obfDestField = obfField; - m_destLabel.setText(""); - } else { - m_obfDestField = obfField; - FieldEntry deobfField = m_destDeobfuscator.deobfuscateEntry(obfField); - m_destLabel.setText(deobfField.getName() + " " + deobfField.getType().toString()); - } - } - - private void updateButtons() { - - GuiTricks.deactivateButton(m_matchButton); - GuiTricks.deactivateButton(m_unmatchableButton); - - if (m_obfSourceField != null && m_obfDestField != null) { - if (m_fieldMatches.isMatched(m_obfSourceField, m_obfDestField)) { - GuiTricks.activateButton(m_matchButton, "Unmatch", new ActionListener() { - @Override - public void actionPerformed(ActionEvent event) { - unmatch(); - } - }); - } else if (!m_fieldMatches.isMatchedSourceField(m_obfSourceField) && !m_fieldMatches.isMatchedDestField(m_obfDestField)) { - GuiTricks.activateButton(m_matchButton, "Match", new ActionListener() { - @Override - public void actionPerformed(ActionEvent event) { - match(); - } - }); - } - } else if (m_obfSourceField != null) { - GuiTricks.activateButton(m_unmatchableButton, "Set Unmatchable", new ActionListener() { - @Override - public void actionPerformed(ActionEvent event) { - unmatchable(); - } - }); - } - } - - protected void match() { - - // update the field matches - m_fieldMatches.makeMatch(m_obfSourceField, m_obfDestField); - save(); - - // update the ui - onSelectSource(null); - onSelectDest(null); - updateSourceHighlights(); - updateDestHighlights(); - updateSourceClasses(); - } - - protected void unmatch() { - - // update the field matches - m_fieldMatches.unmakeMatch(m_obfSourceField, m_obfDestField); - save(); - - // update the ui - onSelectSource(null); - onSelectDest(null); - updateSourceHighlights(); - updateDestHighlights(); - updateSourceClasses(); - } - - protected void unmatchable() { - - // update the field matches - m_fieldMatches.makeSourceUnmatchable(m_obfSourceField); - save(); - - // update the ui - onSelectSource(null); - onSelectDest(null); - updateSourceHighlights(); - updateDestHighlights(); - updateSourceClasses(); - } - - private void save() { - if (m_saveListener != null) { - m_saveListener.save(m_fieldMatches); - } - } -} diff --git a/src/cuchaz/enigma/gui/MemberMatchingGui.java b/src/cuchaz/enigma/gui/MemberMatchingGui.java new file mode 100644 index 0000000..52545b3 --- /dev/null +++ b/src/cuchaz/enigma/gui/MemberMatchingGui.java @@ -0,0 +1,489 @@ +package cuchaz.enigma.gui; + +import java.awt.BorderLayout; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import javax.swing.BoxLayout; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.WindowConstants; +import javax.swing.text.Highlighter.HighlightPainter; + +import com.beust.jcommander.internal.Lists; +import com.beust.jcommander.internal.Maps; + +import cuchaz.enigma.Constants; +import cuchaz.enigma.Deobfuscator; +import cuchaz.enigma.analysis.EntryReference; +import cuchaz.enigma.analysis.SourceIndex; +import cuchaz.enigma.analysis.Token; +import cuchaz.enigma.convert.ClassMatches; +import cuchaz.enigma.convert.MemberMatches; +import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.Entry; +import de.sciss.syntaxpane.DefaultSyntaxKit; + + +public class MemberMatchingGui { + + private static enum SourceType { + Matched { + + @Override + public Collection getObfSourceClasses(MemberMatches matches) { + return matches.getSourceClassesWithoutUnmatchedEntries(); + } + }, + Unmatched { + + @Override + public Collection getObfSourceClasses(MemberMatches matches) { + return matches.getSourceClassesWithUnmatchedEntries(); + } + }; + + public JRadioButton newRadio(ActionListener listener, ButtonGroup group) { + JRadioButton button = new JRadioButton(name(), this == getDefault()); + button.setActionCommand(name()); + button.addActionListener(listener); + group.add(button); + return button; + } + + public abstract Collection getObfSourceClasses(MemberMatches matches); + + public static SourceType getDefault() { + return values()[0]; + } + } + + public static interface SaveListener { + public void save(MemberMatches matches); + } + + // controls + private JFrame m_frame; + private Map m_sourceTypeButtons; + private ClassSelector m_sourceClasses; + private CodeReader m_sourceReader; + private CodeReader m_destReader; + private JButton m_matchButton; + private JButton m_unmatchableButton; + private JLabel m_sourceLabel; + private JLabel m_destLabel; + private HighlightPainter m_unmatchedHighlightPainter; + private HighlightPainter m_matchedHighlightPainter; + + private ClassMatches m_classMatches; + private MemberMatches m_memberMatches; + private Deobfuscator m_sourceDeobfuscator; + private Deobfuscator m_destDeobfuscator; + private SaveListener m_saveListener; + private SourceType m_sourceType; + private ClassEntry m_obfSourceClass; + private ClassEntry m_obfDestClass; + private T m_obfSourceEntry; + private T m_obfDestEntry; + + public MemberMatchingGui(ClassMatches classMatches, MemberMatches fieldMatches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { + + m_classMatches = classMatches; + m_memberMatches = fieldMatches; + m_sourceDeobfuscator = sourceDeobfuscator; + m_destDeobfuscator = destDeobfuscator; + + // init frame + m_frame = new JFrame(Constants.Name + " - Member Matcher"); + final Container pane = m_frame.getContentPane(); + pane.setLayout(new BorderLayout()); + + // init classes side + JPanel classesPanel = new JPanel(); + classesPanel.setLayout(new BoxLayout(classesPanel, BoxLayout.PAGE_AXIS)); + classesPanel.setPreferredSize(new Dimension(200, 0)); + pane.add(classesPanel, BorderLayout.WEST); + classesPanel.add(new JLabel("Classes")); + + // init source type radios + JPanel sourceTypePanel = new JPanel(); + classesPanel.add(sourceTypePanel); + sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS)); + ActionListener sourceTypeListener = new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + setSourceType(SourceType.valueOf(event.getActionCommand())); + } + }; + ButtonGroup sourceTypeButtons = new ButtonGroup(); + m_sourceTypeButtons = Maps.newHashMap(); + for (SourceType sourceType : SourceType.values()) { + JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons); + m_sourceTypeButtons.put(sourceType, button); + sourceTypePanel.add(button); + } + + m_sourceClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator); + m_sourceClasses.setListener(new ClassSelectionListener() { + @Override + public void onSelectClass(ClassEntry classEntry) { + setSourceClass(classEntry); + } + }); + JScrollPane sourceScroller = new JScrollPane(m_sourceClasses); + classesPanel.add(sourceScroller); + + // init readers + DefaultSyntaxKit.initKit(); + m_sourceReader = new CodeReader(); + m_sourceReader.setSelectionListener(new CodeReader.SelectionListener() { + @Override + public void onSelect(EntryReference reference) { + if (reference != null) { + onSelectSource(reference.entry); + } else { + onSelectSource(null); + } + } + }); + m_destReader = new CodeReader(); + m_destReader.setSelectionListener(new CodeReader.SelectionListener() { + @Override + public void onSelect(EntryReference reference) { + if (reference != null) { + onSelectDest(reference.entry); + } else { + onSelectDest(null); + } + } + }); + + // add key bindings + KeyAdapter keyListener = new KeyAdapter() { + @Override + public void keyPressed(KeyEvent event) { + switch (event.getKeyCode()) { + case KeyEvent.VK_M: + m_matchButton.doClick(); + break; + } + } + }; + m_sourceReader.addKeyListener(keyListener); + m_destReader.addKeyListener(keyListener); + + // init all the splits + JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(m_sourceReader), new JScrollPane(m_destReader)); + splitRight.setResizeWeight(0.5); // resize 50:50 + JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, classesPanel, splitRight); + splitLeft.setResizeWeight(0); // let the right side take all the slack + pane.add(splitLeft, BorderLayout.CENTER); + splitLeft.resetToPreferredSizes(); + + // init bottom panel + JPanel bottomPanel = new JPanel(); + bottomPanel.setLayout(new FlowLayout()); + pane.add(bottomPanel, BorderLayout.SOUTH); + + m_matchButton = new JButton(); + m_unmatchableButton = new JButton(); + + m_sourceLabel = new JLabel(); + bottomPanel.add(m_sourceLabel); + bottomPanel.add(m_matchButton); + bottomPanel.add(m_unmatchableButton); + m_destLabel = new JLabel(); + bottomPanel.add(m_destLabel); + + // show the frame + pane.doLayout(); + m_frame.setSize(1024, 576); + m_frame.setMinimumSize(new Dimension(640, 480)); + m_frame.setVisible(true); + m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + + m_unmatchedHighlightPainter = new ObfuscatedHighlightPainter(); + m_matchedHighlightPainter = new DeobfuscatedHighlightPainter(); + + // init state + m_saveListener = null; + m_obfSourceClass = null; + m_obfDestClass = null; + m_obfSourceEntry = null; + m_obfDestEntry = null; + setSourceType(SourceType.getDefault()); + updateButtons(); + } + + protected void setSourceType(SourceType val) { + m_sourceType = val; + updateSourceClasses(); + } + + public void setSaveListener(SaveListener val) { + m_saveListener = val; + } + + private void updateSourceClasses() { + + String selectedPackage = m_sourceClasses.getSelectedPackage(); + + List deobfClassEntries = Lists.newArrayList(); + for (ClassEntry entry : m_sourceType.getObfSourceClasses(m_memberMatches)) { + deobfClassEntries.add(m_sourceDeobfuscator.deobfuscateEntry(entry)); + } + m_sourceClasses.setClasses(deobfClassEntries); + + if (selectedPackage != null) { + m_sourceClasses.expandPackage(selectedPackage); + } + + for (SourceType sourceType : SourceType.values()) { + m_sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)", + sourceType.name(), sourceType.getObfSourceClasses(m_memberMatches).size() + )); + } + } + + protected void setSourceClass(ClassEntry sourceClass) { + + m_obfSourceClass = m_sourceDeobfuscator.obfuscateEntry(sourceClass); + m_obfDestClass = m_classMatches.getUniqueMatches().get(m_obfSourceClass); + if (m_obfDestClass == null) { + throw new Error("No matching dest class for source class: " + m_obfSourceClass); + } + + m_sourceReader.decompileClass(m_obfSourceClass, m_sourceDeobfuscator, false, new Runnable() { + @Override + public void run() { + updateSourceHighlights(); + } + }); + m_destReader.decompileClass(m_obfDestClass, m_destDeobfuscator, false, new Runnable() { + @Override + public void run() { + updateDestHighlights(); + } + }); + } + + protected void updateSourceHighlights() { + highlightEntries(m_sourceReader, m_sourceDeobfuscator, m_memberMatches.matches().keySet(), m_memberMatches.getUnmatchedSourceEntries()); + } + + protected void updateDestHighlights() { + highlightEntries(m_destReader, m_destDeobfuscator, m_memberMatches.matches().values(), m_memberMatches.getUnmatchedDestEntries()); + } + + private void highlightEntries(CodeReader reader, Deobfuscator deobfuscator, Collection obfMatchedEntries, Collection obfUnmatchedEntries) { + reader.clearHighlights(); + SourceIndex index = reader.getSourceIndex(); + + // matched fields + for (T obfT : obfMatchedEntries) { + T deobfT = deobfuscator.deobfuscateEntry(obfT); + Token token = index.getDeclarationToken(deobfT); + if (token != null) { + reader.setHighlightedToken(token, m_matchedHighlightPainter); + } + } + + // unmatched fields + for (T obfT : obfUnmatchedEntries) { + T deobfT = deobfuscator.deobfuscateEntry(obfT); + Token token = index.getDeclarationToken(deobfT); + if (token != null) { + reader.setHighlightedToken(token, m_unmatchedHighlightPainter); + } + } + } + + private boolean isSelectionMatched() { + return m_obfSourceEntry != null && m_obfDestEntry != null + && m_memberMatches.isMatched(m_obfSourceEntry, m_obfDestEntry); + } + + protected void onSelectSource(Entry source) { + + // start with no selection + if (isSelectionMatched()) { + setDest(null); + } + setSource(null); + + // then look for a valid source selection + if (source != null) { + + // this looks really scary, but it's actually ok + // Deobfuscator.obfuscateEntry can handle all implementations of Entry + // and MemberMatches.hasSource() will only pass entries that actually match T + @SuppressWarnings("unchecked") + T sourceEntry = (T)source; + + T obfSourceEntry = m_sourceDeobfuscator.obfuscateEntry(sourceEntry); + if (m_memberMatches.hasSource(obfSourceEntry)) { + setSource(obfSourceEntry); + + // look for a matched dest too + T obfDestEntry = m_memberMatches.matches().get(obfSourceEntry); + if (obfDestEntry != null) { + setDest(obfDestEntry); + } + } + } + + updateButtons(); + } + + protected void onSelectDest(Entry dest) { + + // start with no selection + if (isSelectionMatched()) { + setSource(null); + } + setDest(null); + + // then look for a valid dest selection + if (dest != null) { + + // this looks really scary, but it's actually ok + // Deobfuscator.obfuscateEntry can handle all implementations of Entry + // and MemberMatches.hasSource() will only pass entries that actually match T + @SuppressWarnings("unchecked") + T destEntry = (T)dest; + + T obfDestEntry = m_destDeobfuscator.obfuscateEntry(destEntry); + if (m_memberMatches.hasDest(obfDestEntry)) { + setDest(obfDestEntry); + + // look for a matched source too + T obfSourceEntry = m_memberMatches.matches().inverse().get(obfDestEntry); + if (obfSourceEntry != null) { + setSource(obfSourceEntry); + } + } + } + + updateButtons(); + } + + private void setSource(T obfEntry) { + if (obfEntry == null) { + m_obfSourceEntry = obfEntry; + m_sourceLabel.setText(""); + } else { + m_obfSourceEntry = obfEntry; + m_sourceLabel.setText(getEntryLabel(obfEntry, m_sourceDeobfuscator)); + } + } + + private void setDest(T obfEntry) { + if (obfEntry == null) { + m_obfDestEntry = obfEntry; + m_destLabel.setText(""); + } else { + m_obfDestEntry = obfEntry; + m_destLabel.setText(getEntryLabel(obfEntry, m_destDeobfuscator)); + } + } + + private String getEntryLabel(T obfEntry, Deobfuscator deobfuscator) { + // deobfuscate, then take off the class name + T deobfEntry = deobfuscator.deobfuscateEntry(obfEntry); + return deobfEntry.toString().substring(deobfEntry.getClassName().length() + 1); + } + + private void updateButtons() { + + GuiTricks.deactivateButton(m_matchButton); + GuiTricks.deactivateButton(m_unmatchableButton); + + if (m_obfSourceEntry != null && m_obfDestEntry != null) { + if (m_memberMatches.isMatched(m_obfSourceEntry, m_obfDestEntry)) { + GuiTricks.activateButton(m_matchButton, "Unmatch", new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + unmatch(); + } + }); + } else if (!m_memberMatches.isMatchedSourceEntry(m_obfSourceEntry) && !m_memberMatches.isMatchedDestEntry(m_obfDestEntry)) { + GuiTricks.activateButton(m_matchButton, "Match", new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + match(); + } + }); + } + } else if (m_obfSourceEntry != null) { + GuiTricks.activateButton(m_unmatchableButton, "Set Unmatchable", new ActionListener() { + @Override + public void actionPerformed(ActionEvent event) { + unmatchable(); + } + }); + } + } + + protected void match() { + + // update the field matches + m_memberMatches.makeMatch(m_obfSourceEntry, m_obfDestEntry); + save(); + + // update the ui + onSelectSource(null); + onSelectDest(null); + updateSourceHighlights(); + updateDestHighlights(); + updateSourceClasses(); + } + + protected void unmatch() { + + // update the field matches + m_memberMatches.unmakeMatch(m_obfSourceEntry, m_obfDestEntry); + save(); + + // update the ui + onSelectSource(null); + onSelectDest(null); + updateSourceHighlights(); + updateDestHighlights(); + updateSourceClasses(); + } + + protected void unmatchable() { + + // update the field matches + m_memberMatches.makeSourceUnmatchable(m_obfSourceEntry); + save(); + + // update the ui + onSelectSource(null); + onSelectDest(null); + updateSourceHighlights(); + updateDestHighlights(); + updateSourceClasses(); + } + + private void save() { + if (m_saveListener != null) { + m_saveListener.save(m_memberMatches); + } + } +} diff --git a/src/cuchaz/enigma/mapping/ClassMapping.java b/src/cuchaz/enigma/mapping/ClassMapping.java index 43605e5..07fed32 100644 --- a/src/cuchaz/enigma/mapping/ClassMapping.java +++ b/src/cuchaz/enigma/mapping/ClassMapping.java @@ -434,4 +434,8 @@ public class ClassMapping implements Serializable, Comparable { public static boolean isSimpleClassName(String name) { return name.indexOf('/') < 0 && name.indexOf('$') < 0; } + + public ClassEntry getObfEntry() { + return new ClassEntry(m_obfFullName); + } } diff --git a/src/cuchaz/enigma/mapping/EntryFactory.java b/src/cuchaz/enigma/mapping/EntryFactory.java index 333bb09..7bc6183 100644 --- a/src/cuchaz/enigma/mapping/EntryFactory.java +++ b/src/cuchaz/enigma/mapping/EntryFactory.java @@ -138,6 +138,10 @@ public class EntryFactory { public static BehaviorEntry getBehaviorEntry(String className, String behaviorName, String behaviorSignature) { return getBehaviorEntry(new ClassEntry(className), behaviorName, new Signature(behaviorSignature)); } + + public static BehaviorEntry getBehaviorEntry(String className, String behaviorName) { + return getBehaviorEntry(new ClassEntry(className), behaviorName); + } public static BehaviorEntry getBehaviorEntry(ClassEntry classEntry, String behaviorName, Signature behaviorSignature) { if (behaviorName.equals("")) { @@ -149,6 +153,14 @@ public class EntryFactory { } } + public static BehaviorEntry getBehaviorEntry(ClassEntry classEntry, String behaviorName) { + if(behaviorName.equals("")) { + return new ConstructorEntry(classEntry); + } else { + throw new IllegalArgumentException("Only class initializers don't have signatures"); + } + } + public static BehaviorEntry getBehaviorEntry(MethodDefinition def) { if (def.isConstructor() || def.isTypeInitializer()) { return getConstructorEntry(def); diff --git a/src/cuchaz/enigma/mapping/FieldMapping.java b/src/cuchaz/enigma/mapping/FieldMapping.java index 55b0a19..1289351 100644 --- a/src/cuchaz/enigma/mapping/FieldMapping.java +++ b/src/cuchaz/enigma/mapping/FieldMapping.java @@ -12,7 +12,7 @@ package cuchaz.enigma.mapping; import java.io.Serializable; -public class FieldMapping implements Serializable, Comparable { +public class FieldMapping implements Serializable, Comparable, MemberMapping { private static final long serialVersionUID = 8610742471440861315L; @@ -72,4 +72,9 @@ public class FieldMapping implements Serializable, Comparable { } return false; } + + @Override + public FieldEntry getObfEntry(ClassEntry classEntry) { + return new FieldEntry(classEntry, m_obfName, new Type(m_obfType)); + } } diff --git a/src/cuchaz/enigma/mapping/MemberMapping.java b/src/cuchaz/enigma/mapping/MemberMapping.java new file mode 100644 index 0000000..d62a7c1 --- /dev/null +++ b/src/cuchaz/enigma/mapping/MemberMapping.java @@ -0,0 +1,6 @@ +package cuchaz.enigma.mapping; + + +public interface MemberMapping { + T getObfEntry(ClassEntry classEntry); +} diff --git a/src/cuchaz/enigma/mapping/MethodMapping.java b/src/cuchaz/enigma/mapping/MethodMapping.java index bf8a94f..bf6dacc 100644 --- a/src/cuchaz/enigma/mapping/MethodMapping.java +++ b/src/cuchaz/enigma/mapping/MethodMapping.java @@ -16,7 +16,7 @@ import java.util.Map.Entry; import com.google.common.collect.Maps; -public class MethodMapping implements Serializable, Comparable { +public class MethodMapping implements Serializable, Comparable, MemberMapping { private static final long serialVersionUID = -4409570216084263978L; @@ -170,4 +170,13 @@ public class MethodMapping implements Serializable, Comparable { } return false; } + + @Override + public BehaviorEntry getObfEntry(ClassEntry classEntry) { + if (isConstructor()) { + return new ConstructorEntry(classEntry, m_obfSignature); + } else { + return new MethodEntry(classEntry, m_obfName, m_obfSignature); + } + } } -- cgit v1.2.3