From 430df87ba5d855ca29bc53a5765a2862d2209098 Mon Sep 17 00:00:00 2001 From: jeff Date: Tue, 10 Mar 2015 00:55:03 -0400 Subject: tweaks and improvements to field matching gui --- src/cuchaz/enigma/ConvertMain.java | 73 +---- src/cuchaz/enigma/Deobfuscator.java | 12 +- src/cuchaz/enigma/analysis/SourceIndex.java | 8 +- .../analysis/SourceIndexBehaviorVisitor.java | 10 +- src/cuchaz/enigma/convert/FieldMatches.java | 81 +++++- src/cuchaz/enigma/convert/MappingsConverter.java | 130 ++++++--- src/cuchaz/enigma/convert/MatchesReader.java | 22 +- src/cuchaz/enigma/convert/MatchesWriter.java | 10 + src/cuchaz/enigma/gui/ClassMatchingGui.java | 24 +- src/cuchaz/enigma/gui/CodeReader.java | 8 +- src/cuchaz/enigma/gui/FieldMatchingGui.java | 317 ++++++++++++++++----- src/cuchaz/enigma/gui/GuiTricks.java | 20 ++ 12 files changed, 495 insertions(+), 220 deletions(-) diff --git a/src/cuchaz/enigma/ConvertMain.java b/src/cuchaz/enigma/ConvertMain.java index a5a00e8b..a45fb35c 100644 --- a/src/cuchaz/enigma/ConvertMain.java +++ b/src/cuchaz/enigma/ConvertMain.java @@ -4,12 +4,8 @@ import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; -import java.util.Set; import java.util.jar.JarFile; -import com.google.common.collect.BiMap; -import com.google.common.collect.Sets; - import cuchaz.enigma.convert.ClassMatches; import cuchaz.enigma.convert.FieldMatches; import cuchaz.enigma.convert.MappingsConverter; @@ -17,18 +13,11 @@ import cuchaz.enigma.convert.MatchesReader; import cuchaz.enigma.convert.MatchesWriter; import cuchaz.enigma.gui.ClassMatchingGui; import cuchaz.enigma.gui.FieldMatchingGui; -import cuchaz.enigma.mapping.ClassEntry; -import cuchaz.enigma.mapping.ClassMapping; -import cuchaz.enigma.mapping.ClassNameReplacer; -import cuchaz.enigma.mapping.EntryFactory; -import cuchaz.enigma.mapping.FieldEntry; -import cuchaz.enigma.mapping.FieldMapping; import cuchaz.enigma.mapping.MappingParseException; import cuchaz.enigma.mapping.Mappings; import cuchaz.enigma.mapping.MappingsChecker; import cuchaz.enigma.mapping.MappingsReader; import cuchaz.enigma.mapping.MappingsWriter; -import cuchaz.enigma.mapping.Type; public class ConvertMain { @@ -63,7 +52,7 @@ public class ConvertMain { private static void computeClassMatches(File classMatchesFile, JarFile sourceJar, JarFile destJar, Mappings mappings) throws IOException { - ClassMatches classMatches = MappingsConverter.computeMatches(sourceJar, destJar, mappings); + ClassMatches classMatches = MappingsConverter.computeClassMatches(sourceJar, destJar, mappings); MatchesWriter.writeClasses(classMatches, classMatchesFile); System.out.println("Wrote:\n\t" + classMatchesFile.getAbsolutePath()); } @@ -115,70 +104,12 @@ public class ConvertMain { System.out.println("Writing field matches..."); // get the matched and unmatched field mappings - FieldMatches fieldMatches = new FieldMatches(); - - // 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); - } - - // get matched fields (anything that's left after the checks/drops is matched( - for (ClassMapping classMapping : destMappings.classes()) { - collectMatchedFields(fieldMatches, classMapping, classMatches); - } - - // get unmatched dest fields - Set unmatchedDestFields = Sets.newHashSet(); - for (FieldEntry destFieldEntry : destDeobfuscator.getJarIndex().getObfFieldEntries()) { - if (!fieldMatches.isDestMatched(destFieldEntry)) { - unmatchedDestFields.add(destFieldEntry); - } - } - fieldMatches.addUnmatchedDestFields(unmatchedDestFields); + FieldMatches fieldMatches = MappingsConverter.computeFieldMatches(destDeobfuscator, destMappings, classMatches); MatchesWriter.writeFields(fieldMatches, fieldMatchesFile); System.out.println("Wrote:\n\t" + fieldMatchesFile.getAbsolutePath()); } - private static void collectMatchedFields(FieldMatches fieldMatches, ClassMapping destClassMapping, ClassMatches classMatches) { - - // 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); - } - - // recurse - for (ClassMapping destInnerClassMapping : destClassMapping.innerClasses()) { - collectMatchedFields(fieldMatches, destInnerClassMapping, classMatches); - } - } - - private static FieldEntry translate(FieldEntry in, BiMap map) { - return new FieldEntry( - map.get(in.getClassEntry()), - in.getName(), - translate(in.getType(), map) - ); - } - - private static Type translate(Type type, final BiMap map) { - return new Type(type, new ClassNameReplacer() { - @Override - public String replace(String inClassName) { - ClassEntry outClassEntry = map.get(new ClassEntry(inClassName)); - if (outClassEntry == null) { - return null; - } - return outClassEntry.getName(); - } - }); - } - private static void editFieldMatches(JarFile sourceJar, JarFile destJar, File destMappingsFile, Mappings sourceMappings, File classMatchesFile, final File fieldMatchesFile) throws IOException, MappingParseException { diff --git a/src/cuchaz/enigma/Deobfuscator.java b/src/cuchaz/enigma/Deobfuscator.java index 35cfd0b6..f5012bde 100644 --- a/src/cuchaz/enigma/Deobfuscator.java +++ b/src/cuchaz/enigma/Deobfuscator.java @@ -225,8 +225,18 @@ public class Deobfuscator { } public SourceIndex getSourceIndex(CompilationUnit sourceTree, String source) { + return getSourceIndex(sourceTree, source, null); + } + + public SourceIndex getSourceIndex(CompilationUnit sourceTree, String source, Boolean ignoreBadTokens) { + // build the source index - SourceIndex index = new SourceIndex(source); + SourceIndex index; + if (ignoreBadTokens != null) { + index = new SourceIndex(source, ignoreBadTokens); + } else { + index = new SourceIndex(source); + } sourceTree.acceptVisitor(new SourceIndexVisitor(), index); // DEBUG diff --git a/src/cuchaz/enigma/analysis/SourceIndex.java b/src/cuchaz/enigma/analysis/SourceIndex.java index b3fb751a..8f751ef5 100644 --- a/src/cuchaz/enigma/analysis/SourceIndex.java +++ b/src/cuchaz/enigma/analysis/SourceIndex.java @@ -32,9 +32,15 @@ public class SourceIndex { private Multimap,Token> m_referenceToTokens; private Map m_declarationToToken; private List m_lineOffsets; + private boolean m_ignoreBadTokens; public SourceIndex(String source) { + this(source, true); + } + + public SourceIndex(String source, boolean ignoreBadTokens) { m_source = source; + m_ignoreBadTokens = ignoreBadTokens; m_tokenToReference = Maps.newTreeMap(); m_referenceToTokens = HashMultimap.create(); m_declarationToToken = Maps.newHashMap(); @@ -83,7 +89,7 @@ public class SourceIndex { // System.out.println( String.format( "%s \"%s\" region: %s", node.getNodeType(), name, region ) ); // if the token has a $ in it, something's wrong. Ignore this token - if (name.lastIndexOf('$') >= 0) { + if (name.lastIndexOf('$') >= 0 && m_ignoreBadTokens) { // DEBUG System.err.println(String.format("WARNING: %s \"%s\" is probably a bad token. It was ignored", node.getNodeType(), name)); return null; diff --git a/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java b/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java index a9a055be..eb120b66 100644 --- a/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java +++ b/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java @@ -111,10 +111,12 @@ public class SourceIndexBehaviorVisitor extends SourceIndexVisitor { @Override public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) { ParameterDefinition def = node.getUserData(Keys.PARAMETER_DEFINITION); - MethodDefinition methodDef = (MethodDefinition)def.getMethod(); - BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(methodDef); - ArgumentEntry argumentEntry = new ArgumentEntry(behaviorEntry, def.getPosition(), node.getName()); - index.addDeclaration(node.getNameToken(), argumentEntry); + if (def.getMethod() instanceof MethodDefinition) { + MethodDefinition methodDef = (MethodDefinition)def.getMethod(); + BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(methodDef); + ArgumentEntry argumentEntry = new ArgumentEntry(behaviorEntry, def.getPosition(), node.getName()); + index.addDeclaration(node.getNameToken(), argumentEntry); + } return recurse(node, index); } diff --git a/src/cuchaz/enigma/convert/FieldMatches.java b/src/cuchaz/enigma/convert/FieldMatches.java index 6335974b..2973356b 100644 --- a/src/cuchaz/enigma/convert/FieldMatches.java +++ b/src/cuchaz/enigma/convert/FieldMatches.java @@ -7,6 +7,7 @@ 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.FieldEntry; @@ -15,21 +16,29 @@ import cuchaz.enigma.mapping.FieldEntry; public class FieldMatches { private BiMap m_matches; + private Multimap m_matchedSourceFields; private Multimap m_unmatchedSourceFields; private Multimap m_unmatchedDestFields; + private Multimap m_unmatchableSourceFields; public FieldMatches() { m_matches = HashBiMap.create(); + m_matchedSourceFields = HashMultimap.create(); m_unmatchedSourceFields = HashMultimap.create(); m_unmatchedDestFields = HashMultimap.create(); + m_unmatchableSourceFields = HashMultimap.create(); } public void addMatch(FieldEntry srcField, FieldEntry destField) { - m_matches.put(srcField, destField); + boolean wasAdded = m_matches.put(srcField, destField) == null; + assert (wasAdded); + wasAdded = m_matchedSourceFields.put(srcField.getClassEntry(), srcField); + assert (wasAdded); } public void addUnmatchedSourceField(FieldEntry fieldEntry) { - m_unmatchedSourceFields.put(fieldEntry.getClassEntry(), fieldEntry); + boolean wasAdded = m_unmatchedSourceFields.put(fieldEntry.getClassEntry(), fieldEntry); + assert (wasAdded); } public void addUnmatchedSourceFields(Iterable fieldEntries) { @@ -39,7 +48,8 @@ public class FieldMatches { } public void addUnmatchedDestField(FieldEntry fieldEntry) { - m_unmatchedDestFields.put(fieldEntry.getClassEntry(), fieldEntry); + boolean wasAdded = m_unmatchedDestFields.put(fieldEntry.getClassEntry(), fieldEntry); + assert (wasAdded); } public void addUnmatchedDestFields(Iterable fieldEntries) { @@ -48,9 +58,21 @@ public class FieldMatches { } } + public void addUnmatchableSourceField(FieldEntry sourceField) { + boolean wasAdded = m_unmatchableSourceFields.put(sourceField.getClassEntry(), sourceField); + assert (wasAdded); + } + public Set getSourceClassesWithUnmatchedFields() { return m_unmatchedSourceFields.keySet(); } + + public Collection getSourceClassesWithoutUnmatchedFields() { + Set out = Sets.newHashSet(); + out.addAll(m_matchedSourceFields.keySet()); + out.removeAll(m_unmatchedSourceFields.keySet()); + return out; + } public Collection getUnmatchedSourceFields() { return m_unmatchedSourceFields.values(); @@ -64,21 +86,60 @@ public class FieldMatches { return m_unmatchedDestFields.values(); } - public Collection getUnmatchedDestFields(ClassEntry sourceClass) { - return m_unmatchedDestFields.get(sourceClass); + public Collection getUnmatchedDestFields(ClassEntry destClass) { + return m_unmatchedDestFields.get(destClass); + } + + public Collection getUnmatchableSourceFields() { + return m_unmatchableSourceFields.values(); + } + + public boolean hasSource(FieldEntry fieldEntry) { + return m_matches.containsKey(fieldEntry) || m_unmatchedSourceFields.containsValue(fieldEntry); + } + + public boolean hasDest(FieldEntry fieldEntry) { + return m_matches.containsValue(fieldEntry) || m_unmatchedDestFields.containsValue(fieldEntry); } public BiMap matches() { return m_matches; } + + public boolean isMatchedSourceField(FieldEntry sourceField) { + return m_matches.containsKey(sourceField); + } - public boolean isDestMatched(FieldEntry destFieldEntry) { - return m_matches.containsValue(destFieldEntry); + public boolean isMatchedDestField(FieldEntry destField) { + return m_matches.containsValue(destField); } public void makeMatch(FieldEntry sourceField, FieldEntry destField) { - m_unmatchedSourceFields.remove(sourceField.getClassEntry(), sourceField); - m_unmatchedDestFields.remove(destField.getClassEntry(), destField); - m_matches.put(sourceField, destField); + boolean wasRemoved = m_unmatchedSourceFields.remove(sourceField.getClassEntry(), sourceField); + assert (wasRemoved); + wasRemoved = m_unmatchedDestFields.remove(destField.getClassEntry(), destField); + assert (wasRemoved); + addMatch(sourceField, destField); + } + + public boolean isMatched(FieldEntry sourceField, FieldEntry destField) { + FieldEntry match = m_matches.get(sourceField); + return match != null && match.equals(destField); + } + + public void unmakeMatch(FieldEntry sourceField, FieldEntry destField) { + boolean wasRemoved = m_matches.remove(sourceField) != null; + assert (wasRemoved); + wasRemoved = m_matchedSourceFields.remove(sourceField.getClassEntry(), sourceField); + assert (wasRemoved); + addUnmatchedSourceField(sourceField); + addUnmatchedDestField(destField); + } + + public void makeSourceUnmatchable(FieldEntry sourceField) { + assert(!isMatchedSourceField(sourceField)); + boolean wasRemoved = m_unmatchedSourceFields.remove(sourceField.getClassEntry(), sourceField); + assert (wasRemoved); + addUnmatchableSourceField(sourceField); } } diff --git a/src/cuchaz/enigma/convert/MappingsConverter.java b/src/cuchaz/enigma/convert/MappingsConverter.java index 667ee9de..9ab1baa3 100644 --- a/src/cuchaz/enigma/convert/MappingsConverter.java +++ b/src/cuchaz/enigma/convert/MappingsConverter.java @@ -17,6 +17,7 @@ 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; import com.beust.jcommander.internal.Lists; @@ -24,6 +25,7 @@ import com.google.common.collect.BiMap; import com.google.common.collect.HashMultimap; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; import cuchaz.enigma.Deobfuscator; import cuchaz.enigma.analysis.JarIndex; @@ -31,13 +33,17 @@ import cuchaz.enigma.convert.ClassNamer.SidedClassNamer; import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.ClassMapping; import cuchaz.enigma.mapping.ClassNameReplacer; +import cuchaz.enigma.mapping.EntryFactory; +import cuchaz.enigma.mapping.FieldEntry; import cuchaz.enigma.mapping.FieldMapping; import cuchaz.enigma.mapping.Mappings; +import cuchaz.enigma.mapping.MappingsChecker; import cuchaz.enigma.mapping.MethodMapping; +import cuchaz.enigma.mapping.Type; public class MappingsConverter { - public static ClassMatches computeMatches(JarFile sourceJar, JarFile destJar, Mappings mappings) { + public static ClassMatches computeClassMatches(JarFile sourceJar, JarFile destJar, Mappings mappings) { // index jars System.out.println("Indexing source jar..."); @@ -245,47 +251,101 @@ public class MappingsConverter { 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() { + + public static FieldMatches computeFieldMatches(Deobfuscator destDeobfuscator, Mappings destMappings, ClassMatches classMatches) { + + FieldMatches fieldMatches = new FieldMatches(); + + // 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); + } - // 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()) { + // get matched fields (anything that's left after the checks/drops is matched( + for (ClassMapping classMapping : destMappings.classes()) { + collectMatchedFields(fieldMatches, classMapping, classMatches); + } + + // get unmatched dest fields + for (FieldEntry destFieldEntry : destDeobfuscator.getJarIndex().getObfFieldEntries()) { + if (!fieldMatches.isMatchedDestField(destFieldEntry)) { + fieldMatches.addUnmatchedDestField(destFieldEntry); + } + } + + System.out.println("Automatching " + fieldMatches.getUnmatchedSourceFields().size() + " unmatched source fields..."); + + // 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))) { - // skip constructors - if (methodMapping.getObfName().equals("")) { - continue; - } + // get the possible dest matches + ClassEntry obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass); - 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)); + // 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); } } + + if (obfDestFields.size() == 1) { + // make the easy match + FieldEntry obfDestField = obfDestFields.iterator().next(); + fieldMatches.makeMatch(obfSourceField, obfDestField); + } else if (obfDestFields.isEmpty()) { + // no match is possible =( + fieldMatches.makeSourceUnmatchable(obfSourceField); + } } } - System.out.println("Done!"); + System.out.println(String.format("Ended up with %d ambiguous and %d unmatchable source fields", + fieldMatches.getUnmatchedSourceFields().size(), + fieldMatches.getUnmatchableSourceFields().size() + )); + + return fieldMatches; + } + + private static void collectMatchedFields(FieldMatches fieldMatches, ClassMapping destClassMapping, ClassMatches classMatches) { + + // 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); + } + + // recurse + for (ClassMapping destInnerClassMapping : destClassMapping.innerClasses()) { + collectMatchedFields(fieldMatches, destInnerClassMapping, classMatches); + } + } + + private static FieldEntry translate(FieldEntry in, BiMap map) { + return new FieldEntry( + map.get(in.getClassEntry()), + in.getName(), + translate(in.getType(), map) + ); + } + + private static Type translate(Type type, final BiMap map) { + return new Type(type, 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 1dd042da..921ab1d0 100644 --- a/src/cuchaz/enigma/convert/MatchesReader.java +++ b/src/cuchaz/enigma/convert/MatchesReader.java @@ -58,15 +58,19 @@ public class MatchesReader { } private static void readFieldMatch(FieldMatches matches, String line) { - String[] parts = line.split(":", 2); - FieldEntry source = readField(parts[0]); - FieldEntry dest = readField(parts[1]); - if (source != null && dest != null) { - matches.addMatch(source, dest); - } else if (source != null) { - matches.addUnmatchedSourceField(source); - } else if (dest != null) { - matches.addUnmatchedDestField(dest); + if (line.startsWith("!")) { + matches.addUnmatchableSourceField(readField(line.substring(1))); + } else { + String[] parts = line.split(":", 2); + FieldEntry source = readField(parts[0]); + FieldEntry dest = readField(parts[1]); + if (source != null && dest != null) { + matches.addMatch(source, dest); + } else if (source != null) { + matches.addUnmatchedSourceField(source); + } else if (dest != null) { + matches.addUnmatchedDestField(dest); + } } } diff --git a/src/cuchaz/enigma/convert/MatchesWriter.java b/src/cuchaz/enigma/convert/MatchesWriter.java index 6e371bcc..2118dd08 100644 --- a/src/cuchaz/enigma/convert/MatchesWriter.java +++ b/src/cuchaz/enigma/convert/MatchesWriter.java @@ -53,6 +53,9 @@ public class MatchesWriter { for (FieldEntry fieldEntry : fieldMatches.getUnmatchedDestFields()) { writeFieldMatch(out, null, fieldEntry); } + for (FieldEntry fieldEntry : fieldMatches.getUnmatchableSourceFields()) { + writeUnmatchableField(out, fieldEntry); + } } } @@ -68,6 +71,13 @@ public class MatchesWriter { out.write("\n"); } + private static void writeUnmatchableField(FileWriter out, FieldEntry fieldEntry) + throws IOException { + out.write("!"); + writeField(out, fieldEntry); + out.write("\n"); + } + private static void writeField(FileWriter out, FieldEntry fieldEntry) throws IOException { out.write(fieldEntry.getClassName()); diff --git a/src/cuchaz/enigma/gui/ClassMatchingGui.java b/src/cuchaz/enigma/gui/ClassMatchingGui.java index 6943c3ee..9e210ec4 100644 --- a/src/cuchaz/enigma/gui/ClassMatchingGui.java +++ b/src/cuchaz/enigma/gui/ClassMatchingGui.java @@ -6,7 +6,6 @@ import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; @@ -421,17 +420,17 @@ public class ClassMatchingGui { boolean isMatched = uniqueMatches.containsKey(obfSource) && uniqueMatches.containsValue(obfDest); boolean canMatch = !uniqueMatches.containsKey(obfSource) && ! uniqueMatches.containsValue(obfDest); - deactivateButton(m_matchButton); + GuiTricks.deactivateButton(m_matchButton); if (twoSelected) { if (isMatched) { - activateButton(m_matchButton, "Unmatch", new ActionListener() { + GuiTricks.activateButton(m_matchButton, "Unmatch", new ActionListener() { @Override public void actionPerformed(ActionEvent event) { onUnmatchClick(); } }); } else if (canMatch) { - activateButton(m_matchButton, "Match", new ActionListener() { + GuiTricks.activateButton(m_matchButton, "Match", new ActionListener() { @Override public void actionPerformed(ActionEvent event) { onMatchClick(); @@ -440,23 +439,6 @@ public class ClassMatchingGui { } } } - - private void deactivateButton(JButton button) { - button.setEnabled(false); - button.setText(""); - for (ActionListener listener : Arrays.asList(button.getActionListeners())) { - button.removeActionListener(listener); - } - } - - private void activateButton(JButton button, String text, ActionListener newListener) { - button.setText(text); - button.setEnabled(true); - for (ActionListener listener : Arrays.asList(button.getActionListeners())) { - button.removeActionListener(listener); - } - button.addActionListener(newListener); - } private void onMatchClick() { // precondition: source and dest classes are set correctly diff --git a/src/cuchaz/enigma/gui/CodeReader.java b/src/cuchaz/enigma/gui/CodeReader.java index aa7e2db2..743ef2e4 100644 --- a/src/cuchaz/enigma/gui/CodeReader.java +++ b/src/cuchaz/enigma/gui/CodeReader.java @@ -85,7 +85,11 @@ public class CodeReader extends JEditorPane { decompileClass(classEntry, deobfuscator, null); } - public void decompileClass(final ClassEntry classEntry, final Deobfuscator deobfuscator, final Runnable callback) { + public void decompileClass(ClassEntry classEntry, Deobfuscator deobfuscator, Runnable callback) { + decompileClass(classEntry, deobfuscator, null, callback); + } + + public void decompileClass(final ClassEntry classEntry, final Deobfuscator deobfuscator, final Boolean ignoreBadTokens, final Runnable callback) { if (classEntry == null) { setCode(null); @@ -109,7 +113,7 @@ public class CodeReader extends JEditorPane { CompilationUnit sourceTree = deobfuscator.getSourceTree(outermostClassEntry.getName()); String source = deobfuscator.getSource(sourceTree); setCode(source); - m_sourceIndex = deobfuscator.getSourceIndex(sourceTree, source); + m_sourceIndex = deobfuscator.getSourceIndex(sourceTree, source, ignoreBadTokens); if (callback != null) { callback.run(); diff --git a/src/cuchaz/enigma/gui/FieldMatchingGui.java b/src/cuchaz/enigma/gui/FieldMatchingGui.java index ef374c87..3f4a378c 100644 --- a/src/cuchaz/enigma/gui/FieldMatchingGui.java +++ b/src/cuchaz/enigma/gui/FieldMatchingGui.java @@ -6,20 +6,26 @@ 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.Set; +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.google.common.collect.Sets; +import com.beust.jcommander.internal.Lists; +import com.beust.jcommander.internal.Maps; import cuchaz.enigma.Constants; import cuchaz.enigma.Deobfuscator; @@ -37,16 +43,49 @@ 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; @@ -57,12 +96,11 @@ public class FieldMatchingGui { 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; - private Set m_obfUnmatchedSourceFields; - private Set m_obfUnmatchedDestFields; public FieldMatchingGui(ClassMatches classMatches, FieldMatches fieldMatches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { @@ -83,6 +121,24 @@ public class FieldMatchingGui { 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 @@ -117,6 +173,20 @@ public class FieldMatchingGui { } } }); + + // 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)); @@ -131,17 +201,13 @@ public class FieldMatchingGui { bottomPanel.setLayout(new FlowLayout()); pane.add(bottomPanel, BorderLayout.SOUTH); - m_matchButton = new JButton("Match"); - m_matchButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent event) { - match(); - } - }); + 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); @@ -161,10 +227,13 @@ public class FieldMatchingGui { m_obfDestClass = null; m_obfSourceField = null; m_obfDestField = null; - m_obfUnmatchedSourceFields = null; - m_obfUnmatchedDestFields = null; + setSourceType(SourceType.getDefault()); + updateButtons(); + } + + protected void setSourceType(SourceType val) { + m_sourceType = val; updateSourceClasses(); - updateMatchButton(); } public void setSaveListener(SaveListener val) { @@ -172,26 +241,41 @@ public class FieldMatchingGui { } private void updateSourceClasses() { - m_sourceClasses.setClasses(m_fieldMatches.getSourceClassesWithUnmatchedFields()); - m_sourceClasses.expandAll(); + + 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 obfSourceClass) { + protected void setSourceClass(ClassEntry sourceClass) { - m_obfSourceClass = obfSourceClass; - m_obfDestClass = m_classMatches.getUniqueMatches().get(obfSourceClass); + 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); } - updateUnmatchedFields(); - m_sourceReader.decompileClass(m_obfSourceClass, m_sourceDeobfuscator, new Runnable() { + m_sourceReader.decompileClass(m_obfSourceClass, m_sourceDeobfuscator, false, new Runnable() { @Override public void run() { updateSourceHighlights(); } }); - m_destReader.decompileClass(m_obfDestClass, m_destDeobfuscator, new Runnable() { + m_destReader.decompileClass(m_obfDestClass, m_destDeobfuscator, false, new Runnable() { @Override public void run() { updateDestHighlights(); @@ -199,71 +283,145 @@ public class FieldMatchingGui { }); } - private void updateUnmatchedFields() { - m_obfUnmatchedSourceFields = Sets.newHashSet(m_fieldMatches.getUnmatchedSourceFields(m_obfSourceClass)); - m_obfUnmatchedDestFields = Sets.newHashSet(); - for (FieldEntry destFieldEntry : m_destDeobfuscator.getJarIndex().getObfFieldEntries(m_obfDestClass)) { - if (!m_fieldMatches.isDestMatched(destFieldEntry)) { - m_obfUnmatchedDestFields.add(destFieldEntry); - } - } - } - protected void updateSourceHighlights() { - highlightFields(m_sourceReader, m_sourceDeobfuscator, m_obfUnmatchedSourceFields, m_fieldMatches.matches().keySet()); + highlightFields(m_sourceReader, m_sourceDeobfuscator, m_fieldMatches.matches().keySet(), m_fieldMatches.getUnmatchedSourceFields()); } protected void updateDestHighlights() { - highlightFields(m_destReader, m_destDeobfuscator, m_obfUnmatchedDestFields, m_fieldMatches.matches().values()); + highlightFields(m_destReader, m_destDeobfuscator, m_fieldMatches.matches().values(), m_fieldMatches.getUnmatchedDestFields()); } - private void highlightFields(CodeReader reader, Deobfuscator deobfuscator, Collection obfFieldEntries, Collection obfMatchedFieldEntries) { + private void highlightFields(CodeReader reader, Deobfuscator deobfuscator, Collection obfMatchedFields, Collection obfUnmatchedFields) { reader.clearHighlights(); SourceIndex index = reader.getSourceIndex(); - for (FieldEntry obfFieldEntry : obfFieldEntries) { + + // matched fields + for (FieldEntry obfFieldEntry : obfMatchedFields) { FieldEntry deobfFieldEntry = deobfuscator.deobfuscateEntry(obfFieldEntry); Token token = index.getDeclarationToken(deobfFieldEntry); - if (token == null) { - System.err.println("WARNING: Can't find declaration token for " + deobfFieldEntry); - } else { - reader.setHighlightedToken( - token, - obfMatchedFieldEntries.contains(obfFieldEntry) ? m_matchedHighlightPainter : m_unmatchedHighlightPainter - ); + 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); } } } - protected void onSelectSource(Entry entry) { - m_sourceLabel.setText(""); - m_obfSourceField = null; - if (entry != null && entry instanceof FieldEntry) { - FieldEntry fieldEntry = (FieldEntry)entry; - FieldEntry obfFieldEntry = m_sourceDeobfuscator.obfuscateEntry(fieldEntry); - if (m_obfUnmatchedSourceFields.contains(obfFieldEntry)) { - m_obfSourceField = obfFieldEntry; - m_sourceLabel.setText(fieldEntry.getName() + " " + fieldEntry.getType().toString()); + 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); + } } } - updateMatchButton(); + + updateButtons(); } - protected void onSelectDest(Entry entry) { - m_destLabel.setText(""); - m_obfDestField = null; - if (entry != null && entry instanceof FieldEntry) { - FieldEntry fieldEntry = (FieldEntry)entry; - FieldEntry obfFieldEntry = m_destDeobfuscator.obfuscateEntry(fieldEntry); - if (m_obfUnmatchedDestFields.contains(obfFieldEntry)) { - m_obfDestField = obfFieldEntry; - m_destLabel.setText(fieldEntry.getName() + " " + fieldEntry.getType().toString()); + 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); + } } } - updateMatchButton(); + + 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() { - private void updateMatchButton() { - m_matchButton.setEnabled(m_obfSourceField != null && m_obfDestField != null); + 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() { @@ -275,7 +433,34 @@ public class FieldMatchingGui { // update the ui onSelectSource(null); onSelectDest(null); - updateUnmatchedFields(); + 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(); diff --git a/src/cuchaz/enigma/gui/GuiTricks.java b/src/cuchaz/enigma/gui/GuiTricks.java index df9e2215..7e539a12 100644 --- a/src/cuchaz/enigma/gui/GuiTricks.java +++ b/src/cuchaz/enigma/gui/GuiTricks.java @@ -11,8 +11,11 @@ package cuchaz.enigma.gui; import java.awt.Font; +import java.awt.event.ActionListener; import java.awt.event.MouseEvent; +import java.util.Arrays; +import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.ToolTipManager; @@ -33,4 +36,21 @@ public class GuiTricks { manager.mouseMoved(new MouseEvent(component, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, 0, 0, 0, false)); manager.setInitialDelay(oldDelay); } + + public static void deactivateButton(JButton button) { + button.setEnabled(false); + button.setText(""); + for (ActionListener listener : Arrays.asList(button.getActionListeners())) { + button.removeActionListener(listener); + } + } + + public static void activateButton(JButton button, String text, ActionListener newListener) { + button.setText(text); + button.setEnabled(true); + for (ActionListener listener : Arrays.asList(button.getActionListeners())) { + button.removeActionListener(listener); + } + button.addActionListener(newListener); + } } -- cgit v1.2.3