From 6e464ea251cab63c776ece0b2a356f1498ffa294 Mon Sep 17 00:00:00 2001 From: Thog Date: Wed, 8 Mar 2017 08:17:04 +0100 Subject: Follow Fabric guidelines --- src/main/java/cuchaz/enigma/CommandMain.java | 375 +++-- src/main/java/cuchaz/enigma/Constants.java | 11 +- src/main/java/cuchaz/enigma/ConvertMain.java | 669 ++++---- src/main/java/cuchaz/enigma/Deobfuscator.java | 1163 +++++++------- src/main/java/cuchaz/enigma/ExceptionIgnorer.java | 31 +- src/main/java/cuchaz/enigma/Main.java | 74 +- .../java/cuchaz/enigma/TranslatingTypeLoader.java | 402 ++--- src/main/java/cuchaz/enigma/analysis/Access.java | 59 +- .../enigma/analysis/BehaviorReferenceTreeNode.java | 131 +- .../java/cuchaz/enigma/analysis/BridgeMarker.java | 35 +- .../analysis/ClassImplementationsTreeNode.java | 95 +- .../enigma/analysis/ClassInheritanceTreeNode.java | 105 +- .../cuchaz/enigma/analysis/EntryReference.java | 207 +-- .../java/cuchaz/enigma/analysis/EntryRenamer.java | 232 +-- .../enigma/analysis/FieldReferenceTreeNode.java | 107 +- .../cuchaz/enigma/analysis/JarClassIterator.java | 194 +-- src/main/java/cuchaz/enigma/analysis/JarIndex.java | 1615 ++++++++++---------- .../analysis/MethodImplementationsTreeNode.java | 147 +- .../enigma/analysis/MethodInheritanceTreeNode.java | 167 +- .../cuchaz/enigma/analysis/ReferenceTreeNode.java | 5 +- .../java/cuchaz/enigma/analysis/SourceIndex.java | 305 ++-- .../analysis/SourceIndexBehaviorVisitor.java | 358 +++-- .../enigma/analysis/SourceIndexClassVisitor.java | 129 +- .../cuchaz/enigma/analysis/SourceIndexVisitor.java | 722 ++++----- src/main/java/cuchaz/enigma/analysis/Token.java | 80 +- .../cuchaz/enigma/analysis/TranslationIndex.java | 543 ++++--- .../cuchaz/enigma/analysis/TreeDumpVisitor.java | 833 +++++----- .../cuchaz/enigma/bytecode/ClassProtectifier.java | 60 +- .../cuchaz/enigma/bytecode/ClassPublifier.java | 60 +- .../java/cuchaz/enigma/bytecode/ClassRenamer.java | 1028 ++++++------- .../cuchaz/enigma/bytecode/ClassTranslator.java | 291 ++-- .../cuchaz/enigma/bytecode/ConstPoolEditor.java | 483 +++--- src/main/java/cuchaz/enigma/bytecode/InfoType.java | 492 +++--- .../cuchaz/enigma/bytecode/InnerClassWriter.java | 239 ++- .../enigma/bytecode/LocalVariableRenamer.java | 249 ++- .../enigma/bytecode/MethodParameterWriter.java | 75 +- .../enigma/bytecode/MethodParametersAttribute.java | 111 +- .../bytecode/accessors/ClassInfoAccessor.java | 77 +- .../bytecode/accessors/ConstInfoAccessor.java | 217 ++- .../accessors/InvokeDynamicInfoAccessor.java | 100 +- .../bytecode/accessors/MemberRefInfoAccessor.java | 99 +- .../accessors/MethodHandleInfoAccessor.java | 99 +- .../bytecode/accessors/MethodTypeInfoAccessor.java | 77 +- .../accessors/NameAndTypeInfoAccessor.java | 99 +- .../bytecode/accessors/StringInfoAccessor.java | 77 +- .../bytecode/accessors/Utf8InfoAccessor.java | 23 +- .../java/cuchaz/enigma/convert/ClassForest.java | 79 +- .../cuchaz/enigma/convert/ClassIdentifier.java | 61 +- .../java/cuchaz/enigma/convert/ClassIdentity.java | 821 +++++----- .../java/cuchaz/enigma/convert/ClassMatch.java | 106 +- .../java/cuchaz/enigma/convert/ClassMatches.java | 273 ++-- .../java/cuchaz/enigma/convert/ClassMatching.java | 256 ++-- .../java/cuchaz/enigma/convert/ClassNamer.java | 71 +- .../java/cuchaz/enigma/convert/FieldMatches.java | 261 ++-- .../cuchaz/enigma/convert/MappingsConverter.java | 1363 ++++++++--------- .../java/cuchaz/enigma/convert/MatchesReader.java | 157 +- .../java/cuchaz/enigma/convert/MatchesWriter.java | 185 +-- .../java/cuchaz/enigma/convert/MemberMatches.java | 315 ++-- src/main/java/cuchaz/enigma/gui/BrowserCaret.java | 17 +- .../java/cuchaz/enigma/gui/ClassMatchingGui.java | 990 ++++++------ src/main/java/cuchaz/enigma/gui/ClassSelector.java | 968 ++++++------ src/main/java/cuchaz/enigma/gui/CodeReader.java | 362 +++-- src/main/java/cuchaz/enigma/gui/Gui.java | 1594 ++++++++++--------- src/main/java/cuchaz/enigma/gui/GuiController.java | 640 ++++---- src/main/java/cuchaz/enigma/gui/GuiTricks.java | 41 +- .../java/cuchaz/enigma/gui/MemberMatchingGui.java | 815 +++++----- .../java/cuchaz/enigma/gui/ScoredClassEntry.java | 42 +- .../cuchaz/enigma/gui/TokenListCellRenderer.java | 35 +- .../java/cuchaz/enigma/gui/dialog/AboutDialog.java | 93 +- .../java/cuchaz/enigma/gui/dialog/CrashDialog.java | 118 +- .../cuchaz/enigma/gui/dialog/ProgressDialog.java | 157 +- .../java/cuchaz/enigma/gui/elements/MenuBar.java | 400 +++-- .../cuchaz/enigma/gui/elements/PopupMenuBar.java | 139 +- .../enigma/gui/filechooser/FileChooserAny.java | 11 +- .../enigma/gui/filechooser/FileChooserFile.java | 6 +- .../enigma/gui/filechooser/FileChooserFolder.java | 10 +- .../enigma/gui/highlight/BoxHighlightPainter.java | 87 +- .../highlight/DeobfuscatedHighlightPainter.java | 9 +- .../gui/highlight/ObfuscatedHighlightPainter.java | 9 +- .../gui/highlight/OtherHighlightPainter.java | 9 +- .../gui/highlight/SelectionHighlightPainter.java | 22 +- .../enigma/gui/node/ClassSelectorClassNode.java | 81 +- .../enigma/gui/node/ClassSelectorPackageNode.java | 81 +- .../java/cuchaz/enigma/gui/panels/PanelDeobf.java | 31 +- .../java/cuchaz/enigma/gui/panels/PanelEditor.java | 108 +- .../cuchaz/enigma/gui/panels/PanelIdentifier.java | 40 +- .../java/cuchaz/enigma/gui/panels/PanelObf.java | 57 +- .../java/cuchaz/enigma/mapping/ArgumentEntry.java | 185 +-- .../cuchaz/enigma/mapping/ArgumentMapping.java | 55 +- .../java/cuchaz/enigma/mapping/BehaviorEntry.java | 3 +- .../java/cuchaz/enigma/mapping/ClassEntry.java | 295 ++-- .../java/cuchaz/enigma/mapping/ClassMapping.java | 1042 +++++++------ .../cuchaz/enigma/mapping/ClassNameReplacer.java | 3 +- .../cuchaz/enigma/mapping/ConstructorEntry.java | 175 +-- src/main/java/cuchaz/enigma/mapping/Entry.java | 9 +- .../java/cuchaz/enigma/mapping/EntryFactory.java | 217 +-- .../java/cuchaz/enigma/mapping/FieldEntry.java | 139 +- .../java/cuchaz/enigma/mapping/FieldMapping.java | 180 ++- .../cuchaz/enigma/mapping/LocalVariableEntry.java | 186 ++- src/main/java/cuchaz/enigma/mapping/Mappings.java | 453 +++--- .../cuchaz/enigma/mapping/MappingsChecker.java | 155 +- .../enigma/mapping/MappingsEnigmaReader.java | 340 ++--- .../enigma/mapping/MappingsEnigmaWriter.java | 70 +- .../cuchaz/enigma/mapping/MappingsRenamer.java | 633 ++++---- .../cuchaz/enigma/mapping/MappingsSRGWriter.java | 112 +- .../java/cuchaz/enigma/mapping/MemberMapping.java | 6 +- .../java/cuchaz/enigma/mapping/MethodEntry.java | 145 +- .../java/cuchaz/enigma/mapping/MethodMapping.java | 393 +++-- .../java/cuchaz/enigma/mapping/NameValidator.java | 89 +- .../cuchaz/enigma/mapping/ProcyonEntryFactory.java | 91 +- src/main/java/cuchaz/enigma/mapping/Signature.java | 154 +- .../cuchaz/enigma/mapping/SignatureUpdater.java | 125 +- .../enigma/mapping/TranslationDirection.java | 27 +- .../java/cuchaz/enigma/mapping/Translator.java | 655 ++++---- src/main/java/cuchaz/enigma/mapping/Type.java | 431 +++--- .../enigma/throwables/IllegalNameException.java | 41 +- .../cuchaz/enigma/throwables/MappingConflict.java | 6 +- .../enigma/throwables/MappingParseException.java | 25 +- .../java/cuchaz/enigma/utils/ReadableToken.java | 25 +- src/main/java/cuchaz/enigma/utils/Utils.java | 114 +- src/test/java/cuchaz/enigma/TestDeobfed.java | 28 +- src/test/java/cuchaz/enigma/TestDeobfuscator.java | 29 +- src/test/java/cuchaz/enigma/TestEntryFactory.java | 49 +- src/test/java/cuchaz/enigma/TestInnerClasses.java | 53 +- .../enigma/TestJarIndexConstructorReferences.java | 63 +- .../cuchaz/enigma/TestJarIndexInheritanceTree.java | 117 +- .../java/cuchaz/enigma/TestJarIndexLoneClass.java | 67 +- src/test/java/cuchaz/enigma/TestSignature.java | 40 +- src/test/java/cuchaz/enigma/TestSourceIndex.java | 30 +- .../java/cuchaz/enigma/TestTokensConstructors.java | 45 +- src/test/java/cuchaz/enigma/TestTranslator.java | 19 +- src/test/java/cuchaz/enigma/TestType.java | 46 +- src/test/java/cuchaz/enigma/TokenChecker.java | 32 +- src/test/java/cuchaz/enigma/inputs/Keep.java | 3 +- .../enigma/inputs/constructors/BaseClass.java | 7 +- .../cuchaz/enigma/inputs/constructors/Caller.java | 17 +- .../inputs/constructors/DefaultConstructable.java | 3 +- .../enigma/inputs/constructors/SubClass.java | 11 +- .../enigma/inputs/constructors/SubSubClass.java | 5 +- .../enigma/inputs/inheritanceTree/BaseClass.java | 11 +- .../enigma/inputs/inheritanceTree/SubclassA.java | 5 +- .../enigma/inputs/inheritanceTree/SubclassB.java | 13 +- .../inputs/inheritanceTree/SubsubclassAA.java | 9 +- .../enigma/inputs/innerClasses/A_Anonymous.java | 5 +- .../innerClasses/B_AnonymousWithScopeArgs.java | 5 +- .../inputs/innerClasses/C_ConstructorArgs.java | 21 +- .../enigma/inputs/innerClasses/D_Simple.java | 5 +- .../innerClasses/E_AnonymousWithOuterAccess.java | 9 +- .../enigma/inputs/innerClasses/F_ClassTree.java | 16 +- .../cuchaz/enigma/inputs/loneClass/LoneClass.java | 9 +- .../cuchaz/enigma/inputs/translation/A_Basic.java | 13 +- .../enigma/inputs/translation/B_BaseClass.java | 9 +- .../enigma/inputs/translation/C_SubClass.java | 9 +- .../inputs/translation/D_AnonymousTesting.java | 5 +- .../enigma/inputs/translation/E_Bridges.java | 4 +- .../enigma/inputs/translation/F_ObjectMethods.java | 7 +- .../enigma/inputs/translation/G_OuterClass.java | 18 +- .../enigma/inputs/translation/H_NamelessClass.java | 20 +- .../enigma/inputs/translation/I_Generics.java | 22 +- 159 files changed, 15475 insertions(+), 15653 deletions(-) diff --git a/src/main/java/cuchaz/enigma/CommandMain.java b/src/main/java/cuchaz/enigma/CommandMain.java index b0a4107c..f546eb18 100644 --- a/src/main/java/cuchaz/enigma/CommandMain.java +++ b/src/main/java/cuchaz/enigma/CommandMain.java @@ -8,201 +8,198 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ -package cuchaz.enigma; -import java.io.File; -import java.util.jar.JarFile; +package cuchaz.enigma; import cuchaz.enigma.Deobfuscator.ProgressListener; import cuchaz.enigma.mapping.Mappings; import cuchaz.enigma.mapping.MappingsEnigmaReader; +import java.io.File; +import java.util.jar.JarFile; + public class CommandMain { - public static class ConsoleProgressListener implements ProgressListener { - - private static final int ReportTime = 5000; // 5s - - private int totalWork; - private long startTime; - private long lastReportTime; - - @Override - public void init(int totalWork, String title) { - this.totalWork = totalWork; - this.startTime = System.currentTimeMillis(); - this.lastReportTime = this.startTime; - System.out.println(title); - } - - @Override - public void onProgress(int numDone, String message) { - long now = System.currentTimeMillis(); - boolean isLastUpdate = numDone == this.totalWork; - boolean shouldReport = isLastUpdate || now - this.lastReportTime > ReportTime; - - if (shouldReport) { - int percent = numDone * 100 / this.totalWork; - System.out.println(String.format("\tProgress: %3d%%", percent)); - this.lastReportTime = now; - } - if (isLastUpdate) { - double elapsedSeconds = (now - this.startTime) / 1000.0; - System.out.println(String.format("Finished in %.1f seconds", elapsedSeconds)); - } - } - } - - public static void main(String[] args) throws Exception { - try { - // process the command - String command = getArg(args, 0, "command", true); - if (command.equalsIgnoreCase("deobfuscate")) { - deobfuscate(args); - } else if (command.equalsIgnoreCase("decompile")) { - decompile(args); - } else if (command.equalsIgnoreCase("protectify")) { - protectify(args); - } else if (command.equalsIgnoreCase("publify")) { - publify(args); - } else if (command.equalsIgnoreCase("convertmappings")) { - convertMappings(args); - } - else { - throw new IllegalArgumentException("Command not recognized: " + command); - } - } catch (IllegalArgumentException ex) { - System.out.println(ex.getMessage()); - printHelp(); - } - } - - private static void printHelp() { - System.out.println(String.format("%s - %s", Constants.NAME, Constants.VERSION)); - System.out.println("Usage:"); - System.out.println("\tjava -cp enigma.jar cuchaz.enigma.CommandMain "); - System.out.println("\twhere is one of:"); - System.out.println("\t\tdeobfuscate []"); - System.out.println("\t\tdecompile []"); - System.out.println("\t\tprotectify "); - System.out.println("\t\tpublify "); - System.out.println("\t\tconvertmappings "); - } - - private static void decompile(String[] args) throws Exception { - File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true)); - File fileJarOut = getWritableFolder(getArg(args, 2, "out folder", true)); - File fileMappings = getReadableFile(getArg(args, 3, "mappings file", false)); - Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn)); - deobfuscator.writeSources(fileJarOut, new ConsoleProgressListener()); - } - - private static void deobfuscate(String[] args) throws Exception { - File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true)); - File fileJarOut = getWritableFile(getArg(args, 2, "out jar", true)); - File fileMappings = getReadableFile(getArg(args, 3, "mappings file", false)); - Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn)); - deobfuscator.writeJar(fileJarOut, new ConsoleProgressListener()); - } - - private static void protectify(String[] args) throws Exception { - File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true)); - File fileJarOut = getWritableFile(getArg(args, 2, "out jar", true)); - Deobfuscator deobfuscator = getDeobfuscator(null, new JarFile(fileJarIn)); - deobfuscator.protectifyJar(fileJarOut, new ConsoleProgressListener()); - } - - private static void publify(String[] args) throws Exception { - File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true)); - File fileJarOut = getWritableFile(getArg(args, 2, "out jar", true)); - Deobfuscator deobfuscator = getDeobfuscator(null, new JarFile(fileJarIn)); - deobfuscator.publifyJar(fileJarOut, new ConsoleProgressListener()); - } - - private static Deobfuscator getDeobfuscator(File fileMappings, JarFile jar) throws Exception { - System.out.println("Reading jar..."); - Deobfuscator deobfuscator = new Deobfuscator(jar); - if (fileMappings != null) { - System.out.println("Reading mappings..."); - Mappings mappings = new MappingsEnigmaReader().read(fileMappings); - deobfuscator.setMappings(mappings); - } - return deobfuscator; - } - - private static void convertMappings(String[] args) throws Exception { - File fileMappings = getReadableFile(getArg(args, 1, "enigma mapping", true)); - File result = getWritableFile(getArg(args, 2, "enigma mapping", true)); - String name = getArg(args, 3, "format type", true); - Mappings.FormatType formatType; - try - { - formatType = Mappings.FormatType.valueOf(name.toUpperCase()); - } catch (IllegalArgumentException e) - { - throw new IllegalArgumentException(name + "is not a valid mapping format!"); - } - - System.out.println("Reading mappings..."); - Mappings mappings = new MappingsEnigmaReader().read(fileMappings); - System.out.println("Saving new mappings..."); - switch (formatType) - { - case SRG_FILE: - mappings.saveSRGMappings(result); - break; - default: - mappings.saveEnigmaMappings(result, Mappings.FormatType.ENIGMA_FILE != formatType); - break; - } - } - - private static String getArg(String[] args, int i, String name, boolean required) { - if (i >= args.length) { - if (required) { - throw new IllegalArgumentException(name + " is required"); - } else { - return null; - } - } - return args[i]; - } - - private static File getWritableFile(String path) { - if (path == null) { - return null; - } - File file = new File(path).getAbsoluteFile(); - File dir = file.getParentFile(); - if (dir == null) { - throw new IllegalArgumentException("Cannot write file: " + path); - } - // quick fix to avoid stupid stuff in Gradle code - if (!dir.isDirectory()) { - dir.mkdirs(); - } - return file; - } - - private static File getWritableFolder(String path) { - if (path == null) { - return null; - } - File dir = new File(path).getAbsoluteFile(); - if (!dir.exists()) { - throw new IllegalArgumentException("Cannot write to folder: " + dir); - } - return dir; - } - - private static File getReadableFile(String path) { - if (path == null) { - return null; - } - File file = new File(path).getAbsoluteFile(); - if (!file.exists()) { - throw new IllegalArgumentException("Cannot find file: " + file.getAbsolutePath()); - } - return file; - } + public static void main(String[] args) throws Exception { + try { + // process the command + String command = getArg(args, 0, "command", true); + if (command.equalsIgnoreCase("deobfuscate")) { + deobfuscate(args); + } else if (command.equalsIgnoreCase("decompile")) { + decompile(args); + } else if (command.equalsIgnoreCase("protectify")) { + protectify(args); + } else if (command.equalsIgnoreCase("publify")) { + publify(args); + } else if (command.equalsIgnoreCase("convertmappings")) { + convertMappings(args); + } else { + throw new IllegalArgumentException("Command not recognized: " + command); + } + } catch (IllegalArgumentException ex) { + System.out.println(ex.getMessage()); + printHelp(); + } + } + + private static void printHelp() { + System.out.println(String.format("%s - %s", Constants.NAME, Constants.VERSION)); + System.out.println("Usage:"); + System.out.println("\tjava -cp enigma.jar cuchaz.enigma.CommandMain "); + System.out.println("\twhere is one of:"); + System.out.println("\t\tdeobfuscate []"); + System.out.println("\t\tdecompile []"); + System.out.println("\t\tprotectify "); + System.out.println("\t\tpublify "); + System.out.println("\t\tconvertmappings "); + } + + private static void decompile(String[] args) throws Exception { + File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true)); + File fileJarOut = getWritableFolder(getArg(args, 2, "out folder", true)); + File fileMappings = getReadableFile(getArg(args, 3, "mappings file", false)); + Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn)); + deobfuscator.writeSources(fileJarOut, new ConsoleProgressListener()); + } + + private static void deobfuscate(String[] args) throws Exception { + File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true)); + File fileJarOut = getWritableFile(getArg(args, 2, "out jar", true)); + File fileMappings = getReadableFile(getArg(args, 3, "mappings file", false)); + Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn)); + deobfuscator.writeJar(fileJarOut, new ConsoleProgressListener()); + } + + private static void protectify(String[] args) throws Exception { + File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true)); + File fileJarOut = getWritableFile(getArg(args, 2, "out jar", true)); + Deobfuscator deobfuscator = getDeobfuscator(null, new JarFile(fileJarIn)); + deobfuscator.protectifyJar(fileJarOut, new ConsoleProgressListener()); + } + + private static void publify(String[] args) throws Exception { + File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true)); + File fileJarOut = getWritableFile(getArg(args, 2, "out jar", true)); + Deobfuscator deobfuscator = getDeobfuscator(null, new JarFile(fileJarIn)); + deobfuscator.publifyJar(fileJarOut, new ConsoleProgressListener()); + } + + private static Deobfuscator getDeobfuscator(File fileMappings, JarFile jar) throws Exception { + System.out.println("Reading jar..."); + Deobfuscator deobfuscator = new Deobfuscator(jar); + if (fileMappings != null) { + System.out.println("Reading mappings..."); + Mappings mappings = new MappingsEnigmaReader().read(fileMappings); + deobfuscator.setMappings(mappings); + } + return deobfuscator; + } + + private static void convertMappings(String[] args) throws Exception { + File fileMappings = getReadableFile(getArg(args, 1, "enigma mapping", true)); + File result = getWritableFile(getArg(args, 2, "enigma mapping", true)); + String name = getArg(args, 3, "format type", true); + Mappings.FormatType formatType; + try { + formatType = Mappings.FormatType.valueOf(name.toUpperCase()); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException(name + "is not a valid mapping format!"); + } + + System.out.println("Reading mappings..."); + Mappings mappings = new MappingsEnigmaReader().read(fileMappings); + System.out.println("Saving new mappings..."); + switch (formatType) { + case SRG_FILE: + mappings.saveSRGMappings(result); + break; + default: + mappings.saveEnigmaMappings(result, Mappings.FormatType.ENIGMA_FILE != formatType); + break; + } + } + + private static String getArg(String[] args, int i, String name, boolean required) { + if (i >= args.length) { + if (required) { + throw new IllegalArgumentException(name + " is required"); + } else { + return null; + } + } + return args[i]; + } + + private static File getWritableFile(String path) { + if (path == null) { + return null; + } + File file = new File(path).getAbsoluteFile(); + File dir = file.getParentFile(); + if (dir == null) { + throw new IllegalArgumentException("Cannot write file: " + path); + } + // quick fix to avoid stupid stuff in Gradle code + if (!dir.isDirectory()) { + dir.mkdirs(); + } + return file; + } + + private static File getWritableFolder(String path) { + if (path == null) { + return null; + } + File dir = new File(path).getAbsoluteFile(); + if (!dir.exists()) { + throw new IllegalArgumentException("Cannot write to folder: " + dir); + } + return dir; + } + + private static File getReadableFile(String path) { + if (path == null) { + return null; + } + File file = new File(path).getAbsoluteFile(); + if (!file.exists()) { + throw new IllegalArgumentException("Cannot find file: " + file.getAbsolutePath()); + } + return file; + } + + public static class ConsoleProgressListener implements ProgressListener { + + private static final int ReportTime = 5000; // 5s + + private int totalWork; + private long startTime; + private long lastReportTime; + + @Override + public void init(int totalWork, String title) { + this.totalWork = totalWork; + this.startTime = System.currentTimeMillis(); + this.lastReportTime = this.startTime; + System.out.println(title); + } + + @Override + public void onProgress(int numDone, String message) { + long now = System.currentTimeMillis(); + boolean isLastUpdate = numDone == this.totalWork; + boolean shouldReport = isLastUpdate || now - this.lastReportTime > ReportTime; + + if (shouldReport) { + int percent = numDone * 100 / this.totalWork; + System.out.println(String.format("\tProgress: %3d%%", percent)); + this.lastReportTime = now; + } + if (isLastUpdate) { + double elapsedSeconds = (now - this.startTime) / 1000.0; + System.out.println(String.format("Finished in %.1f seconds", elapsedSeconds)); + } + } + } } diff --git a/src/main/java/cuchaz/enigma/Constants.java b/src/main/java/cuchaz/enigma/Constants.java index 04730480..b08438a3 100644 --- a/src/main/java/cuchaz/enigma/Constants.java +++ b/src/main/java/cuchaz/enigma/Constants.java @@ -8,12 +8,13 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma; public class Constants { - public static final String NAME = "Enigma"; - public static final String VERSION = "0.11.0 (Fabric Fork)"; - public static final String URL = "http://www.cuchazinteractive.com/enigma"; - public static final int MiB = 1024 * 1024; // 1 mebibyte - public static final int KiB = 1024; // 1 kebibyte + public static final String NAME = "Enigma"; + public static final String VERSION = "0.11.0 (Fabric Fork)"; + public static final String URL = "http://www.cuchazinteractive.com/enigma"; + public static final int MiB = 1024 * 1024; // 1 mebibyte + public static final int KiB = 1024; // 1 kebibyte } diff --git a/src/main/java/cuchaz/enigma/ConvertMain.java b/src/main/java/cuchaz/enigma/ConvertMain.java index 48e7f27c..3d58f57c 100644 --- a/src/main/java/cuchaz/enigma/ConvertMain.java +++ b/src/main/java/cuchaz/enigma/ConvertMain.java @@ -8,11 +8,8 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ -package cuchaz.enigma; -import java.io.File; -import java.io.IOException; -import java.util.jar.JarFile; +package cuchaz.enigma; import cuchaz.enigma.convert.*; import cuchaz.enigma.gui.ClassMatchingGui; @@ -21,338 +18,340 @@ import cuchaz.enigma.mapping.*; import cuchaz.enigma.throwables.MappingConflict; import cuchaz.enigma.throwables.MappingParseException; +import java.io.File; +import java.io.IOException; +import java.util.jar.JarFile; public class ConvertMain { - public static void main(String[] args) - throws IOException, MappingParseException { - try { - //Get all are args - String JarOld = getArg(args, 1, "Path to Old Jar", true); - String JarNew = getArg(args, 2, "Path to New Jar", true); - String OldMappings = getArg(args, 3, "Path to old .mappings file", true); - String NewMappings = getArg(args, 4, "Path to new .mappings file", true); - String ClassMatches = getArg(args, 5, "Path to Class .matches file", true); - String FieldMatches = getArg(args, 6, "Path to Field .matches file", true); - String MethodMatches = getArg(args, 7, "Path to Method .matches file", true); - //OldJar - JarFile sourceJar = new JarFile(new File(JarOld)); - //NewJar - JarFile destJar = new JarFile(new File(JarNew)); - //Get the mapping files - File inMappingsFile = new File(OldMappings); - File outMappingsFile = new File(NewMappings); - Mappings mappings = new MappingsEnigmaReader().read(inMappingsFile); - //Make the Match Files.. - File classMatchesFile = new File(ClassMatches); - File fieldMatchesFile = new File(FieldMatches); - File methodMatchesFile = new File(MethodMatches); - - String command = getArg(args, 0, "command", true); - - if (command.equalsIgnoreCase("computeClassMatches")) { - computeClassMatches(classMatchesFile, sourceJar, destJar, mappings); - convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile); - } else if (command.equalsIgnoreCase("editClassMatches")) { - editClasssMatches(classMatchesFile, sourceJar, destJar, mappings); - convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile); - } else if (command.equalsIgnoreCase("computeFieldMatches")) { - computeFieldMatches(fieldMatchesFile, destJar, outMappingsFile, classMatchesFile); - convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile); - } else if (command.equalsIgnoreCase("editFieldMatches")) { - editFieldMatches(sourceJar, destJar, outMappingsFile, mappings, classMatchesFile, fieldMatchesFile); - convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile); - } else if (command.equalsIgnoreCase("computeMethodMatches")) { - computeMethodMatches(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile, methodMatchesFile); - convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile, methodMatchesFile); - } else if (command.equalsIgnoreCase("editMethodMatches")) { - editMethodMatches(sourceJar, destJar, outMappingsFile, mappings, classMatchesFile, methodMatchesFile); - convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile, methodMatchesFile); - } else if (command.equalsIgnoreCase("convertMappings")) { - convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile, methodMatchesFile); - } - } catch (MappingConflict ex) { - System.out.println(ex.getMessage()); - ex.printStackTrace(); - } catch (IllegalArgumentException ex) { - System.out.println(ex.getMessage()); - printHelp(); - } - } - - private static void printHelp() { - System.out.println(String.format("%s - %s", Constants.NAME, Constants.VERSION)); - System.out.println("Usage:"); - System.out.println("\tjava -cp enigma.jar cuchaz.enigma.ConvertMain "); - System.out.println("\tWhere is one of:"); - System.out.println("\t\tcomputeClassMatches"); - System.out.println("\t\teditClassMatches"); - System.out.println("\t\tcomputeFieldMatches"); - System.out.println("\t\teditFieldMatches"); - System.out.println("\t\teditMethodMatches"); - System.out.println("\t\tconvertMappings"); - System.out.println("\tWhere is the already mapped jar."); - System.out.println("\tWhere is the unmapped jar."); - System.out.println("\tWhere is the path to the mappings for the old jar."); - System.out.println("\tWhere is the new mappings. (Where you want to save them and there name)"); - System.out.println("\tWhere is the class matches file."); - System.out.println("\tWhere is the field matches file."); - System.out.println("\tWhere is the method matches file."); - } - - //Copy of getArg from CommandMain.... Should make a utils class. - private static String getArg(String[] args, int i, String name, boolean required) { - if (i >= args.length) { - if (required) { - throw new IllegalArgumentException(name + " is required"); - } else { - return null; - } - } - return args[i]; - } - - private static void computeClassMatches(File classMatchesFile, JarFile sourceJar, JarFile destJar, Mappings mappings) - throws IOException { - ClassMatches classMatches = MappingsConverter.computeClassMatches(sourceJar, destJar, mappings); - MatchesWriter.writeClasses(classMatches, classMatchesFile); - System.out.println("Wrote:\n\t" + classMatchesFile.getAbsolutePath()); - } - - private static void editClasssMatches(final File classMatchesFile, JarFile sourceJar, JarFile destJar, Mappings mappings) - throws IOException { - System.out.println("Reading class matches..."); - ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile); - Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar); - deobfuscators.source.setMappings(mappings); - System.out.println("Starting GUI..."); - new ClassMatchingGui(classMatches, deobfuscators.source, deobfuscators.dest).setSaveListener(matches -> - { - try { - MatchesWriter.writeClasses(matches, classMatchesFile); - } catch (IOException ex) { - throw new Error(ex); - } - }); - } - - @SuppressWarnings("unused") - private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchesFile) - throws IOException, MappingConflict { - System.out.println("Reading class matches..."); - ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile); - Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar); - deobfuscators.source.setMappings(mappings); - - Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.dest); - new MappingsEnigmaWriter().write(outMappingsFile, newMappings, true); - System.out.println("Write converted mappings to: " + outMappingsFile.getAbsolutePath()); - } - - private static void computeFieldMatches(File memberMatchesFile, 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 MappingsEnigmaReader().read(destMappingsFile); - System.out.println("Indexing dest jar..."); - Deobfuscator destDeobfuscator = new Deobfuscator(destJar); - - System.out.println("Writing matches..."); - - // get the matched and unmatched mappings - MemberMatches fieldMatches = MappingsConverter.computeMemberMatches( - destDeobfuscator, - destMappings, - classMatches, - MappingsConverter.getFieldDoer() - ); - - 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) - throws IOException, MappingParseException { - - System.out.println("Reading matches..."); - ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile); - MemberMatches fieldMatches = MatchesReader.readMembers(fieldMatchesFile); - - // prep deobfuscators - Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar); - deobfuscators.source.setMappings(sourceMappings); - Mappings destMappings = new MappingsEnigmaReader().read(destMappingsFile); - MappingsChecker checker = new MappingsChecker(deobfuscators.dest.getJarIndex()); - checker.dropBrokenMappings(destMappings); - deobfuscators.dest.setMappings(destMappings); - - new MemberMatchingGui<>(classMatches, fieldMatches, deobfuscators.source, deobfuscators.dest).setSaveListener( - matches -> - { - try { - MatchesWriter.writeMembers(matches, fieldMatchesFile); - } catch (IOException ex) { - throw new Error(ex); - } - }); - } - - @SuppressWarnings("unused") - private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchesFile, File fieldMatchesFile) - throws IOException, MappingConflict { - - System.out.println("Reading matches..."); - ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile); - MemberMatches fieldMatches = MatchesReader.readMembers(fieldMatchesFile); - - Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar); - deobfuscators.source.setMappings(mappings); - - // apply matches - Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.dest); - MappingsConverter.applyMemberMatches(newMappings, classMatches, fieldMatches, MappingsConverter.getFieldDoer()); - - // write out the converted mappings - - new MappingsEnigmaWriter().write(outMappingsFile, newMappings, true); - System.out.println("Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath()); - } - - - private static void computeMethodMatches(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings sourceMappings, File classMatchesFile, File fieldMatchesFile, File methodMatchesFile) - throws IOException, MappingParseException { - - System.out.println("Reading class matches..."); - ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile); - System.out.println("Reading dest mappings..."); - Mappings destMappings = new MappingsEnigmaReader().read(outMappingsFile); - System.out.println("Indexing dest jar..."); - Deobfuscator destDeobfuscator = new Deobfuscator(destJar); - System.out.println("Indexing source jar..."); - Deobfuscator sourceDeobfuscator = new Deobfuscator(sourceJar); - - System.out.println("Writing method matches..."); - - // get the matched and unmatched mappings - MemberMatches methodMatches = MappingsConverter.computeMethodsMatches( - destDeobfuscator, - destMappings, - sourceDeobfuscator, - sourceMappings, - classMatches, - MappingsConverter.getMethodDoer() - ); - - 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 MappingsEnigmaReader().read(destMappingsFile); - MappingsChecker checker = new MappingsChecker(deobfuscators.dest.getJarIndex()); - checker.dropBrokenMappings(destMappings); - deobfuscators.dest.setMappings(destMappings); - - new MemberMatchingGui<>(classMatches, methodMatches, deobfuscators.source, deobfuscators.dest).setSaveListener( - matches -> - { - try { - MatchesWriter.writeMembers(matches, methodMatchesFile); - } catch (IOException ex) { - throw new Error(ex); - } - }); - } - - private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchesFile, File fieldMatchesFile, File methodMatchesFile) - throws IOException, MappingConflict { - - System.out.println("Reading matches..."); - ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile); - MemberMatches fieldMatches = MatchesReader.readMembers(fieldMatchesFile); - MemberMatches methodMatches = MatchesReader.readMembers(methodMatchesFile); - - Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar); - deobfuscators.source.setMappings(mappings); - - // apply matches - Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.dest); - MappingsConverter.applyMemberMatches(newMappings, classMatches, fieldMatches, MappingsConverter.getFieldDoer()); - MappingsConverter.applyMemberMatches(newMappings, classMatches, methodMatches, MappingsConverter.getMethodDoer()); - - // check the final mappings - MappingsChecker checker = new MappingsChecker(deobfuscators.dest.getJarIndex()); - checker.dropBrokenMappings(newMappings); - - for (java.util.Map.Entry mapping : checker.getDroppedClassMappings().entrySet()) { - System.out.println("WARNING: Broken class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")"); - } - for (java.util.Map.Entry mapping : checker.getDroppedInnerClassMappings().entrySet()) { - System.out.println("WARNING: Broken inner class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")"); - } - for (java.util.Map.Entry mapping : checker.getDroppedFieldMappings().entrySet()) { - System.out.println("WARNING: Broken field entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")"); - } - for (java.util.Map.Entry mapping : checker.getDroppedMethodMappings().entrySet()) { - System.out.println("WARNING: Broken behavior entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")"); - } - - // write out the converted mappings - new MappingsEnigmaWriter().write(outMappingsFile, newMappings, true); - System.out.println("Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath()); - } - - private static class Deobfuscators { - - public Deobfuscator source; - public Deobfuscator dest; - - public Deobfuscators(JarFile sourceJar, JarFile destJar) { - System.out.println("Indexing source jar..."); - IndexerThread sourceIndexer = new IndexerThread(sourceJar); - sourceIndexer.start(); - System.out.println("Indexing dest jar..."); - IndexerThread destIndexer = new IndexerThread(destJar); - destIndexer.start(); - sourceIndexer.joinOrBail(); - destIndexer.joinOrBail(); - source = sourceIndexer.deobfuscator; - dest = destIndexer.deobfuscator; - } - } - - private static class IndexerThread extends Thread { - - private JarFile jarFile; - public Deobfuscator deobfuscator; - - public IndexerThread(JarFile jarFile) { - this.jarFile = jarFile; - deobfuscator = null; - } - - public void joinOrBail() { - try { - join(); - } catch (InterruptedException ex) { - throw new Error(ex); - } - } - - @Override - public void run() { - deobfuscator = new Deobfuscator(jarFile); - } - } + public static void main(String[] args) + throws IOException, MappingParseException { + try { + //Get all are args + String JarOld = getArg(args, 1, "Path to Old Jar", true); + String JarNew = getArg(args, 2, "Path to New Jar", true); + String OldMappings = getArg(args, 3, "Path to old .mappings file", true); + String NewMappings = getArg(args, 4, "Path to new .mappings file", true); + String ClassMatches = getArg(args, 5, "Path to Class .matches file", true); + String FieldMatches = getArg(args, 6, "Path to Field .matches file", true); + String MethodMatches = getArg(args, 7, "Path to Method .matches file", true); + //OldJar + JarFile sourceJar = new JarFile(new File(JarOld)); + //NewJar + JarFile destJar = new JarFile(new File(JarNew)); + //Get the mapping files + File inMappingsFile = new File(OldMappings); + File outMappingsFile = new File(NewMappings); + Mappings mappings = new MappingsEnigmaReader().read(inMappingsFile); + //Make the Match Files.. + File classMatchesFile = new File(ClassMatches); + File fieldMatchesFile = new File(FieldMatches); + File methodMatchesFile = new File(MethodMatches); + + String command = getArg(args, 0, "command", true); + + if (command.equalsIgnoreCase("computeClassMatches")) { + computeClassMatches(classMatchesFile, sourceJar, destJar, mappings); + convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile); + } else if (command.equalsIgnoreCase("editClassMatches")) { + editClasssMatches(classMatchesFile, sourceJar, destJar, mappings); + convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile); + } else if (command.equalsIgnoreCase("computeFieldMatches")) { + computeFieldMatches(fieldMatchesFile, destJar, outMappingsFile, classMatchesFile); + convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile); + } else if (command.equalsIgnoreCase("editFieldMatches")) { + editFieldMatches(sourceJar, destJar, outMappingsFile, mappings, classMatchesFile, fieldMatchesFile); + convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile); + } else if (command.equalsIgnoreCase("computeMethodMatches")) { + computeMethodMatches(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile, methodMatchesFile); + convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile, methodMatchesFile); + } else if (command.equalsIgnoreCase("editMethodMatches")) { + editMethodMatches(sourceJar, destJar, outMappingsFile, mappings, classMatchesFile, methodMatchesFile); + convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile, methodMatchesFile); + } else if (command.equalsIgnoreCase("convertMappings")) { + convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile, methodMatchesFile); + } + } catch (MappingConflict ex) { + System.out.println(ex.getMessage()); + ex.printStackTrace(); + } catch (IllegalArgumentException ex) { + System.out.println(ex.getMessage()); + printHelp(); + } + } + + private static void printHelp() { + System.out.println(String.format("%s - %s", Constants.NAME, Constants.VERSION)); + System.out.println("Usage:"); + System.out.println("\tjava -cp enigma.jar cuchaz.enigma.ConvertMain "); + System.out.println("\tWhere is one of:"); + System.out.println("\t\tcomputeClassMatches"); + System.out.println("\t\teditClassMatches"); + System.out.println("\t\tcomputeFieldMatches"); + System.out.println("\t\teditFieldMatches"); + System.out.println("\t\teditMethodMatches"); + System.out.println("\t\tconvertMappings"); + System.out.println("\tWhere is the already mapped jar."); + System.out.println("\tWhere is the unmapped jar."); + System.out.println("\tWhere is the path to the mappings for the old jar."); + System.out.println("\tWhere is the new mappings. (Where you want to save them and there name)"); + System.out.println("\tWhere is the class matches file."); + System.out.println("\tWhere is the field matches file."); + System.out.println("\tWhere is the method matches file."); + } + + //Copy of getArg from CommandMain.... Should make a utils class. + private static String getArg(String[] args, int i, String name, boolean required) { + if (i >= args.length) { + if (required) { + throw new IllegalArgumentException(name + " is required"); + } else { + return null; + } + } + return args[i]; + } + + private static void computeClassMatches(File classMatchesFile, JarFile sourceJar, JarFile destJar, Mappings mappings) + throws IOException { + ClassMatches classMatches = MappingsConverter.computeClassMatches(sourceJar, destJar, mappings); + MatchesWriter.writeClasses(classMatches, classMatchesFile); + System.out.println("Wrote:\n\t" + classMatchesFile.getAbsolutePath()); + } + + private static void editClasssMatches(final File classMatchesFile, JarFile sourceJar, JarFile destJar, Mappings mappings) + throws IOException { + System.out.println("Reading class matches..."); + ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile); + Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar); + deobfuscators.source.setMappings(mappings); + System.out.println("Starting GUI..."); + new ClassMatchingGui(classMatches, deobfuscators.source, deobfuscators.dest).setSaveListener(matches -> + { + try { + MatchesWriter.writeClasses(matches, classMatchesFile); + } catch (IOException ex) { + throw new Error(ex); + } + }); + } + + @SuppressWarnings("unused") + private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchesFile) + throws IOException, MappingConflict { + System.out.println("Reading class matches..."); + ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile); + Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar); + deobfuscators.source.setMappings(mappings); + + Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.dest); + new MappingsEnigmaWriter().write(outMappingsFile, newMappings, true); + System.out.println("Write converted mappings to: " + outMappingsFile.getAbsolutePath()); + } + + private static void computeFieldMatches(File memberMatchesFile, 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 MappingsEnigmaReader().read(destMappingsFile); + System.out.println("Indexing dest jar..."); + Deobfuscator destDeobfuscator = new Deobfuscator(destJar); + + System.out.println("Writing matches..."); + + // get the matched and unmatched mappings + MemberMatches fieldMatches = MappingsConverter.computeMemberMatches( + destDeobfuscator, + destMappings, + classMatches, + MappingsConverter.getFieldDoer() + ); + + 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) + throws IOException, MappingParseException { + + System.out.println("Reading matches..."); + ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile); + MemberMatches fieldMatches = MatchesReader.readMembers(fieldMatchesFile); + + // prep deobfuscators + Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar); + deobfuscators.source.setMappings(sourceMappings); + Mappings destMappings = new MappingsEnigmaReader().read(destMappingsFile); + MappingsChecker checker = new MappingsChecker(deobfuscators.dest.getJarIndex()); + checker.dropBrokenMappings(destMappings); + deobfuscators.dest.setMappings(destMappings); + + new MemberMatchingGui<>(classMatches, fieldMatches, deobfuscators.source, deobfuscators.dest).setSaveListener( + matches -> + { + try { + MatchesWriter.writeMembers(matches, fieldMatchesFile); + } catch (IOException ex) { + throw new Error(ex); + } + }); + } + + @SuppressWarnings("unused") + private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchesFile, File fieldMatchesFile) + throws IOException, MappingConflict { + + System.out.println("Reading matches..."); + ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile); + MemberMatches fieldMatches = MatchesReader.readMembers(fieldMatchesFile); + + Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar); + deobfuscators.source.setMappings(mappings); + + // apply matches + Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.dest); + MappingsConverter.applyMemberMatches(newMappings, classMatches, fieldMatches, MappingsConverter.getFieldDoer()); + + // write out the converted mappings + + new MappingsEnigmaWriter().write(outMappingsFile, newMappings, true); + System.out.println("Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath()); + } + + private static void computeMethodMatches(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings sourceMappings, File classMatchesFile, File fieldMatchesFile, File methodMatchesFile) + throws IOException, MappingParseException { + + System.out.println("Reading class matches..."); + ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile); + System.out.println("Reading dest mappings..."); + Mappings destMappings = new MappingsEnigmaReader().read(outMappingsFile); + System.out.println("Indexing dest jar..."); + Deobfuscator destDeobfuscator = new Deobfuscator(destJar); + System.out.println("Indexing source jar..."); + Deobfuscator sourceDeobfuscator = new Deobfuscator(sourceJar); + + System.out.println("Writing method matches..."); + + // get the matched and unmatched mappings + MemberMatches methodMatches = MappingsConverter.computeMethodsMatches( + destDeobfuscator, + destMappings, + sourceDeobfuscator, + sourceMappings, + classMatches, + MappingsConverter.getMethodDoer() + ); + + 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 MappingsEnigmaReader().read(destMappingsFile); + MappingsChecker checker = new MappingsChecker(deobfuscators.dest.getJarIndex()); + checker.dropBrokenMappings(destMappings); + deobfuscators.dest.setMappings(destMappings); + + new MemberMatchingGui<>(classMatches, methodMatches, deobfuscators.source, deobfuscators.dest).setSaveListener( + matches -> + { + try { + MatchesWriter.writeMembers(matches, methodMatchesFile); + } catch (IOException ex) { + throw new Error(ex); + } + }); + } + + private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchesFile, File fieldMatchesFile, File methodMatchesFile) + throws IOException, MappingConflict { + + System.out.println("Reading matches..."); + ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile); + MemberMatches fieldMatches = MatchesReader.readMembers(fieldMatchesFile); + MemberMatches methodMatches = MatchesReader.readMembers(methodMatchesFile); + + Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar); + deobfuscators.source.setMappings(mappings); + + // apply matches + Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.dest); + MappingsConverter.applyMemberMatches(newMappings, classMatches, fieldMatches, MappingsConverter.getFieldDoer()); + MappingsConverter.applyMemberMatches(newMappings, classMatches, methodMatches, MappingsConverter.getMethodDoer()); + + // check the final mappings + MappingsChecker checker = new MappingsChecker(deobfuscators.dest.getJarIndex()); + checker.dropBrokenMappings(newMappings); + + for (java.util.Map.Entry mapping : checker.getDroppedClassMappings().entrySet()) { + System.out.println("WARNING: Broken class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")"); + } + for (java.util.Map.Entry mapping : checker.getDroppedInnerClassMappings().entrySet()) { + System.out.println("WARNING: Broken inner class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")"); + } + for (java.util.Map.Entry mapping : checker.getDroppedFieldMappings().entrySet()) { + System.out.println("WARNING: Broken field entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")"); + } + for (java.util.Map.Entry mapping : checker.getDroppedMethodMappings().entrySet()) { + System.out.println("WARNING: Broken behavior entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")"); + } + + // write out the converted mappings + new MappingsEnigmaWriter().write(outMappingsFile, newMappings, true); + System.out.println("Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath()); + } + + private static class Deobfuscators { + + public Deobfuscator source; + public Deobfuscator dest; + + public Deobfuscators(JarFile sourceJar, JarFile destJar) { + System.out.println("Indexing source jar..."); + IndexerThread sourceIndexer = new IndexerThread(sourceJar); + sourceIndexer.start(); + System.out.println("Indexing dest jar..."); + IndexerThread destIndexer = new IndexerThread(destJar); + destIndexer.start(); + sourceIndexer.joinOrBail(); + destIndexer.joinOrBail(); + source = sourceIndexer.deobfuscator; + dest = destIndexer.deobfuscator; + } + } + + private static class IndexerThread extends Thread { + + public Deobfuscator deobfuscator; + private JarFile jarFile; + + public IndexerThread(JarFile jarFile) { + this.jarFile = jarFile; + deobfuscator = null; + } + + public void joinOrBail() { + try { + join(); + } catch (InterruptedException ex) { + throw new Error(ex); + } + } + + @Override + public void run() { + deobfuscator = new Deobfuscator(jarFile); + } + } } \ No newline at end of file diff --git a/src/main/java/cuchaz/enigma/Deobfuscator.java b/src/main/java/cuchaz/enigma/Deobfuscator.java index 2602abcc..934d02a5 100644 --- a/src/main/java/cuchaz/enigma/Deobfuscator.java +++ b/src/main/java/cuchaz/enigma/Deobfuscator.java @@ -8,6 +8,7 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma; import com.google.common.base.Charsets; @@ -40,587 +41,583 @@ import java.util.jar.JarOutputStream; public class Deobfuscator { - public interface ProgressListener { - void init(int totalWork, String title); - - void onProgress(int numDone, String message); - } - - private final JarFile jar; - private final DecompilerSettings settings; - private final JarIndex jarIndex; - private final MappingsRenamer renamer; - private final Map translatorCache; - private Mappings mappings; - - public Deobfuscator(JarFile jar) { - this.jar = jar; - - // build the jar index - this.jarIndex = new JarIndex(); - this.jarIndex.indexJar(this.jar, true); - - // config the decompiler - this.settings = DecompilerSettings.javaDefaults(); - this.settings.setMergeVariables(Utils.getSystemPropertyAsBoolean("enigma.mergeVariables", true)); - this.settings.setForceExplicitImports(Utils.getSystemPropertyAsBoolean("enigma.forceExplicitImports", true)); - this.settings.setForceExplicitTypeArguments( - Utils.getSystemPropertyAsBoolean("enigma.forceExplicitTypeArguments", true)); - // DEBUG - this.settings.setShowDebugLineNumbers(Utils.getSystemPropertyAsBoolean("enigma.showDebugLineNumbers", false)); - this.settings.setShowSyntheticMembers(Utils.getSystemPropertyAsBoolean("enigma.showSyntheticMembers", false)); - - // init defaults - this.translatorCache = Maps.newTreeMap(); - this.renamer = new MappingsRenamer(this.jarIndex, null); - // init mappings - setMappings(new Mappings()); - } - - public JarFile getJar() { - return this.jar; - } - - public String getJarName() { - return this.jar.getName(); - } - - public JarIndex getJarIndex() { - return this.jarIndex; - } - - public Mappings getMappings() { - return this.mappings; - } - - public void setMappings(Mappings val) { - setMappings(val, true); - } - - public void setMappings(Mappings val, boolean warnAboutDrops) { - if (val == null) { - val = new Mappings(); - } - - // drop mappings that don't match the jar - MappingsChecker checker = new MappingsChecker(this.jarIndex); - checker.dropBrokenMappings(val); - if (warnAboutDrops) { - for (java.util.Map.Entry mapping : checker.getDroppedClassMappings().entrySet()) { - System.out.println("WARNING: Couldn't find class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped."); - } - for (java.util.Map.Entry mapping : checker.getDroppedInnerClassMappings().entrySet()) { - System.out.println("WARNING: Couldn't find inner class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped."); - } - for (java.util.Map.Entry mapping : checker.getDroppedFieldMappings().entrySet()) { - System.out.println("WARNING: Couldn't find field entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped."); - } - for (java.util.Map.Entry mapping : checker.getDroppedMethodMappings().entrySet()) { - System.out.println("WARNING: Couldn't find behavior entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped."); - } - } - - this.mappings = val; - this.renamer.setMappings(mappings); - this.translatorCache.clear(); - } - - public Translator getTranslator(TranslationDirection direction) { - return this.translatorCache.computeIfAbsent(direction, - k -> this.mappings.getTranslator(direction, this.jarIndex.getTranslationIndex())); - } - - public void getSeparatedClasses(List obfClasses, List deobfClasses) { - for (ClassEntry obfClassEntry : this.jarIndex.getObfClassEntries()) { - // skip inner classes - if (obfClassEntry.isInnerClass()) { - continue; - } - - // separate the classes - ClassEntry deobfClassEntry = deobfuscateEntry(obfClassEntry); - if (!deobfClassEntry.equals(obfClassEntry)) { - // if the class has a mapping, clearly it's deobfuscated - deobfClasses.add(deobfClassEntry); - } else if (obfClassEntry.getPackageName() != null) { - // also call it deobufscated if it's not in the none package - deobfClasses.add(obfClassEntry); - } else { - // otherwise, assume it's still obfuscated - obfClasses.add(obfClassEntry); - } - } - } - - public TranslatingTypeLoader createTypeLoader() - { - return new TranslatingTypeLoader( - this.jar, - this.jarIndex, - getTranslator(TranslationDirection.Obfuscating), - getTranslator(TranslationDirection.Deobfuscating) - ); - } - - public CompilationUnit getSourceTree(String className) { - - // we don't know if this class name is obfuscated or deobfuscated - // we need to tell the decompiler the deobfuscated name so it doesn't get freaked out - // the decompiler only sees classes after deobfuscation, so we need to load it by the deobfuscated name if there is one - - // first, assume class name is deobf - String deobfClassName = className; - - // if it wasn't actually deobf, then we can find a mapping for it and get the deobf name - ClassMapping classMapping = this.mappings.getClassByObf(className); - if (classMapping != null && classMapping.getDeobfName() != null) { - deobfClassName = classMapping.getDeobfName(); - } - - // set the type loader - TranslatingTypeLoader loader = createTypeLoader(); - this.settings.setTypeLoader(loader); - - // see if procyon can find the type - TypeReference type = new MetadataSystem(loader).lookupType(deobfClassName); - if (type == null) { - throw new Error(String.format("Unable to find type: %s (deobf: %s)\nTried class names: %s", - className, deobfClassName, loader.getClassNamesToTry(deobfClassName) - )); - } - TypeDefinition resolvedType = type.resolve(); - - // decompile it! - DecompilerContext context = new DecompilerContext(); - context.setCurrentType(resolvedType); - context.setSettings(this.settings); - AstBuilder builder = new AstBuilder(context); - builder.addType(resolvedType); - builder.runTransformations(null); - return builder.getCompilationUnit(); - } - - 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; - if (ignoreBadTokens != null) { - index = new SourceIndex(source, ignoreBadTokens); - } else { - index = new SourceIndex(source); - } - sourceTree.acceptVisitor(new SourceIndexVisitor(), index); - - // DEBUG - // sourceTree.acceptVisitor( new TreeDumpVisitor( new File( "tree.txt" ) ), null ); - - // resolve all the classes in the source references - for (Token token : index.referenceTokens()) { - EntryReference deobfReference = index.getDeobfReference(token); - - // get the obfuscated entry - Entry obfEntry = obfuscateEntry(deobfReference.entry); - - // try to resolve the class - ClassEntry resolvedObfClassEntry = this.jarIndex.getTranslationIndex().resolveEntryClass(obfEntry); - if (resolvedObfClassEntry != null && !resolvedObfClassEntry.equals(obfEntry.getClassEntry())) { - // change the class of the entry - obfEntry = obfEntry.cloneToNewClass(resolvedObfClassEntry); - - // save the new deobfuscated reference - deobfReference.entry = deobfuscateEntry(obfEntry); - index.replaceDeobfReference(token, deobfReference); - } - - // DEBUG - // System.out.println( token + " -> " + reference + " -> " + index.getReferenceToken( reference ) ); - } - - return index; - } - - public String getSource(CompilationUnit sourceTree) { - // render the AST into source - StringWriter buf = new StringWriter(); - sourceTree.acceptVisitor(new InsertParenthesesVisitor(), null); - sourceTree.acceptVisitor(new JavaOutputVisitor(new PlainTextOutput(buf), this.settings), null); - return buf.toString(); - } - - public void writeSources(File dirOut, ProgressListener progress) { - // get the classes to decompile - Set classEntries = Sets.newHashSet(); - for (ClassEntry obfClassEntry : this.jarIndex.getObfClassEntries()) { - // skip inner classes - if (obfClassEntry.isInnerClass()) { - continue; - } - - classEntries.add(obfClassEntry); - } - - if (progress != null) { - progress.init(classEntries.size(), "Decompiling classes..."); - } - - // DEOBFUSCATE ALL THE THINGS!! @_@ - int i = 0; - for (ClassEntry obfClassEntry : classEntries) { - ClassEntry deobfClassEntry = deobfuscateEntry(new ClassEntry(obfClassEntry)); - if (progress != null) { - progress.onProgress(i++, deobfClassEntry.toString()); - } - - try { - // get the source - String source = getSource(getSourceTree(obfClassEntry.getName())); - - // write the file - File file = new File(dirOut, deobfClassEntry.getName().replace('.', '/') + ".java"); - file.getParentFile().mkdirs(); - try (OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(file), Charsets.UTF_8)) { - out.write(source); - } - } catch (Throwable t) { - // don't crash the whole world here, just log the error and keep going - // TODO: set up logback via log4j - System.err.println("Unable to deobfuscate class " + deobfClassEntry.toString() + " (" + obfClassEntry.toString() + ")"); - t.printStackTrace(System.err); - } - } - if (progress != null) { - progress.onProgress(i, "Done!"); - } - } - - private void addAllPotentialAncestors(Set classEntries, ClassEntry classObfEntry) { - for (ClassEntry interfaceEntry : jarIndex.getTranslationIndex().getInterfaces(classObfEntry)) { - if (classEntries.add(interfaceEntry)) { - addAllPotentialAncestors(classEntries, interfaceEntry); - } - } - - ClassEntry superClassEntry = jarIndex.getTranslationIndex().getSuperclass(classObfEntry); - if (superClassEntry != null && classEntries.add(superClassEntry)) { - addAllPotentialAncestors(classEntries, superClassEntry); - } - } - - private boolean isBehaviorProvider(ClassEntry classObfEntry, BehaviorEntry behaviorEntry) { - if (behaviorEntry instanceof MethodEntry) { - MethodEntry methodEntry = (MethodEntry) behaviorEntry; - - Set classEntries = new HashSet<>(); - addAllPotentialAncestors(classEntries, classObfEntry); - - for (ClassEntry parentEntry : classEntries) { - MethodEntry ancestorMethodEntry = new MethodEntry(parentEntry, methodEntry.getName(), methodEntry.getSignature()); - if (jarIndex.containsObfBehavior(ancestorMethodEntry)) { - return false; - } - } - } - - return true; - } - - public void rebuildMethodNames(ProgressListener progress) { - int i = 0; - Map> renameClassMap = new HashMap<>(); - - progress.init(getMappings().classes().size() * 3, "Rebuilding method names"); - - for (ClassMapping classMapping : Lists.newArrayList(getMappings().classes())) { - Map renameEntries = new HashMap<>(); - - progress.onProgress(i++, classMapping.getDeobfName()); - - for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) { - ClassEntry classObfEntry = classMapping.getObfEntry(); - BehaviorEntry obfEntry = methodMapping.getObfEntry(classObfEntry); - - if (isBehaviorProvider(classObfEntry, obfEntry)) { - if (hasDeobfuscatedName(obfEntry) && !(obfEntry instanceof ConstructorEntry) - && !(methodMapping.getDeobfName().equals(methodMapping.getObfName()))) { - renameEntries.put(obfEntry, methodMapping.getDeobfName()); - } - - for (ArgumentMapping argumentMapping : Lists.newArrayList(methodMapping.arguments())) { - Entry argObfEntry = argumentMapping.getObfEntry(obfEntry); - if (hasDeobfuscatedName(argObfEntry)) { - renameEntries.put(argObfEntry, deobfuscateEntry(argObfEntry).getName()); - } - } - } - } - - renameClassMap.put(classMapping, renameEntries); - } - - for (Map.Entry> renameClassMapEntry : renameClassMap.entrySet()) { - progress.onProgress(i++, renameClassMapEntry.getKey().getDeobfName()); - - for (Map.Entry entry : renameClassMapEntry.getValue().entrySet()) { - Entry obfEntry = entry.getKey(); - - removeMapping(obfEntry); - } - } - - for (Map.Entry> renameClassMapEntry : renameClassMap.entrySet()) { - progress.onProgress(i++, renameClassMapEntry.getKey().getDeobfName()); - - for (Map.Entry entry : renameClassMapEntry.getValue().entrySet()) { - Entry obfEntry = entry.getKey(); - String name = entry.getValue(); - - rename(obfEntry, name); - } - } - } - - public void writeJar(File out, ProgressListener progress) { - transformJar(out, progress, createTypeLoader()::transformClass); - } - - public void protectifyJar(File out, ProgressListener progress) { - transformJar(out, progress, ClassProtectifier::protectify); - } - - public void publifyJar(File out, ProgressListener progress) { - transformJar(out, progress, ClassPublifier::publify); - } - - public interface ClassTransformer { - CtClass transform(CtClass c) throws Exception; - } - - public void transformJar(File out, ProgressListener progress, ClassTransformer transformer) { - try (JarOutputStream outJar = new JarOutputStream(new FileOutputStream(out))) { - if (progress != null) { - progress.init(JarClassIterator.getClassEntries(this.jar).size(), "Transforming classes..."); - } - - int i = 0; - for (CtClass c : JarClassIterator.classes(this.jar)) { - if (progress != null) { - progress.onProgress(i++, c.getName()); - } - - try { - c = transformer.transform(c); - outJar.putNextEntry(new JarEntry(c.getName().replace('.', '/') + ".class")); - outJar.write(c.toBytecode()); - outJar.closeEntry(); - } catch (Throwable t) { - throw new Error("Unable to transform class " + c.getName(), t); - } - } - if (progress != null) { - progress.onProgress(i, "Done!"); - } - - outJar.close(); - } catch (IOException ex) { - throw new Error("Unable to write to Jar file!"); - } - } - - public T obfuscateEntry(T deobfEntry) { - if (deobfEntry == null) { - return null; - } - return getTranslator(TranslationDirection.Obfuscating).translateEntry(deobfEntry); - } - - public T deobfuscateEntry(T obfEntry) { - if (obfEntry == null) { - return null; - } - return getTranslator(TranslationDirection.Deobfuscating).translateEntry(obfEntry); - } - - public EntryReference obfuscateReference(EntryReference deobfReference) { - if (deobfReference == null) { - return null; - } - return new EntryReference<>(obfuscateEntry(deobfReference.entry), obfuscateEntry(deobfReference.context), deobfReference); - } - - public EntryReference deobfuscateReference(EntryReference obfReference) { - if (obfReference == null) { - return null; - } - return new EntryReference<>(deobfuscateEntry(obfReference.entry), deobfuscateEntry(obfReference.context), obfReference); - } - - public boolean isObfuscatedIdentifier(Entry obfEntry) { - return isObfuscatedIdentifier(obfEntry, false); - } - - public boolean isObfuscatedIdentifier(Entry obfEntry, boolean hack) { - - if (obfEntry instanceof MethodEntry) { - - // HACKHACK: Object methods are not obfuscated identifiers - MethodEntry obfMethodEntry = (MethodEntry) obfEntry; - String name = obfMethodEntry.getName(); - String sig = obfMethodEntry.getSignature().toString(); - if (name.equals("clone") && sig.equals("()Ljava/lang/Object;")) { - return false; - } else if (name.equals("equals") && sig.equals("(Ljava/lang/Object;)Z")) { - return false; - } else if (name.equals("finalize") && sig.equals("()V")) { - return false; - } else if (name.equals("getClass") && sig.equals("()Ljava/lang/Class;")) { - return false; - } else if (name.equals("hashCode") && sig.equals("()I")) { - return false; - } else if (name.equals("notify") && sig.equals("()V")) { - return false; - } else if (name.equals("notifyAll") && sig.equals("()V")) { - return false; - } else if (name.equals("toString") && sig.equals("()Ljava/lang/String;")) { - return false; - } else if (name.equals("wait") && sig.equals("()V")) { - return false; - } else if (name.equals("wait") && sig.equals("(J)V")) { - return false; - } else if (name.equals("wait") && sig.equals("(JI)V")) { - return false; - } - - // FIXME: HACK EVEN MORE HACK! - if (hack && this.jarIndex.containsObfEntry(obfEntry.getClassEntry())) - return true; - } - - return this.jarIndex.containsObfEntry(obfEntry); - } - - public boolean isRenameable(EntryReference obfReference, boolean activeHack) { - return obfReference.isNamed() && isObfuscatedIdentifier(obfReference.getNameableEntry(), activeHack); - } - - public boolean isRenameable(EntryReference obfReference) { - return isRenameable(obfReference, false); - } - - // NOTE: these methods are a bit messy... oh well - - public boolean hasDeobfuscatedName(Entry obfEntry) { - Translator translator = getTranslator(TranslationDirection.Deobfuscating); - if (obfEntry instanceof ClassEntry) { - ClassEntry obfClass = (ClassEntry) obfEntry; - List mappingChain = this.mappings.getClassMappingChain(obfClass); - ClassMapping classMapping = mappingChain.get(mappingChain.size() - 1); - return classMapping != null && classMapping.getDeobfName() != null; - } else if (obfEntry instanceof FieldEntry) { - return translator.translate((FieldEntry) obfEntry) != null; - } else if (obfEntry instanceof MethodEntry) { - return translator.translate((MethodEntry) obfEntry) != null; - } else if (obfEntry instanceof ConstructorEntry) { - // constructors have no names - return false; - } else if (obfEntry instanceof ArgumentEntry) { - return translator.translate((ArgumentEntry) obfEntry) != null; - } else if (obfEntry instanceof LocalVariableEntry) { - // TODO: Implement it - //return translator.translate((LocalVariableEntry)obfEntry) != null; - return false; - } else { - throw new Error("Unknown entry type: " + obfEntry.getClass().getName()); - } - } - - public void rename(Entry obfEntry, String newName) { - rename(obfEntry, newName, true); - } - - public void rename(Entry obfEntry, String newName, boolean clearCache) { - if (obfEntry instanceof ClassEntry) { - this.renamer.setClassName((ClassEntry) obfEntry, Descriptor.toJvmName(newName)); - } else if (obfEntry instanceof FieldEntry) { - this.renamer.setFieldName((FieldEntry) obfEntry, newName); - } else if (obfEntry instanceof MethodEntry) { - this.renamer.setMethodTreeName((MethodEntry) obfEntry, newName); - } else if (obfEntry instanceof ConstructorEntry) { - throw new IllegalArgumentException("Cannot rename constructors"); - } else if (obfEntry instanceof ArgumentEntry) { - this.renamer.setArgumentTreeName((ArgumentEntry) obfEntry, newName); - } else if (obfEntry instanceof LocalVariableEntry) { - // TODO: Implement it - } else { - throw new Error("Unknown entry type: " + obfEntry.getClass().getName()); - } - - // clear caches - if (clearCache) - this.translatorCache.clear(); - } - - public void removeMapping(Entry obfEntry) { - if (obfEntry instanceof ClassEntry) { - this.renamer.removeClassMapping((ClassEntry) obfEntry); - } else if (obfEntry instanceof FieldEntry) { - this.renamer.removeFieldMapping((FieldEntry) obfEntry); - } else if (obfEntry instanceof MethodEntry) { - this.renamer.removeMethodTreeMapping((MethodEntry) obfEntry); - } else if (obfEntry instanceof ConstructorEntry) { - throw new IllegalArgumentException("Cannot rename constructors"); - } else if (obfEntry instanceof ArgumentEntry) { - this.renamer.removeArgumentMapping((ArgumentEntry) obfEntry); - } else { - throw new Error("Unknown entry type: " + obfEntry); - } - - // clear caches - this.translatorCache.clear(); - } - - public void markAsDeobfuscated(Entry obfEntry) { - if (obfEntry instanceof ClassEntry) { - this.renamer.markClassAsDeobfuscated((ClassEntry) obfEntry); - } else if (obfEntry instanceof FieldEntry) { - this.renamer.markFieldAsDeobfuscated((FieldEntry) obfEntry); - } else if (obfEntry instanceof MethodEntry) { - this.renamer.markMethodTreeAsDeobfuscated((MethodEntry) obfEntry); - } else if (obfEntry instanceof ConstructorEntry) { - throw new IllegalArgumentException("Cannot rename constructors"); - } else if (obfEntry instanceof ArgumentEntry) { - this.renamer.markArgumentAsDeobfuscated((ArgumentEntry) obfEntry); - } else if (obfEntry instanceof LocalVariableEntry) { - // TODO: Implement it - } else { - throw new Error("Unknown entry type: " + obfEntry); - } - - // clear caches - this.translatorCache.clear(); - } - - public void changeModifier(Entry entry, Mappings.EntryModifier modifierEntry) - { - Entry obfEntry = obfuscateEntry(entry); - if (obfEntry instanceof ClassEntry) - this.renamer.setClassModifier((ClassEntry) obfEntry, modifierEntry); - else if (obfEntry instanceof FieldEntry) - this.renamer.setFieldModifier((FieldEntry) obfEntry, modifierEntry); - else if (obfEntry instanceof BehaviorEntry) - this.renamer.setMethodModifier((BehaviorEntry) obfEntry, modifierEntry); - else - throw new Error("Unknown entry type: " + obfEntry); - } - - public Mappings.EntryModifier getModifier(Entry obEntry) - { - Entry entry = obfuscateEntry(obEntry); - if (entry != null) - obEntry = entry; - return getTranslator(TranslationDirection.Deobfuscating).getModifier(obEntry); - } + private final JarFile jar; + private final DecompilerSettings settings; + private final JarIndex jarIndex; + private final MappingsRenamer renamer; + private final Map translatorCache; + private Mappings mappings; + public Deobfuscator(JarFile jar) { + this.jar = jar; + + // build the jar index + this.jarIndex = new JarIndex(); + this.jarIndex.indexJar(this.jar, true); + + // config the decompiler + this.settings = DecompilerSettings.javaDefaults(); + this.settings.setMergeVariables(Utils.getSystemPropertyAsBoolean("enigma.mergeVariables", true)); + this.settings.setForceExplicitImports(Utils.getSystemPropertyAsBoolean("enigma.forceExplicitImports", true)); + this.settings.setForceExplicitTypeArguments( + Utils.getSystemPropertyAsBoolean("enigma.forceExplicitTypeArguments", true)); + // DEBUG + this.settings.setShowDebugLineNumbers(Utils.getSystemPropertyAsBoolean("enigma.showDebugLineNumbers", false)); + this.settings.setShowSyntheticMembers(Utils.getSystemPropertyAsBoolean("enigma.showSyntheticMembers", false)); + + // init defaults + this.translatorCache = Maps.newTreeMap(); + this.renamer = new MappingsRenamer(this.jarIndex, null); + // init mappings + setMappings(new Mappings()); + } + + public JarFile getJar() { + return this.jar; + } + + public String getJarName() { + return this.jar.getName(); + } + + public JarIndex getJarIndex() { + return this.jarIndex; + } + + public Mappings getMappings() { + return this.mappings; + } + + public void setMappings(Mappings val) { + setMappings(val, true); + } + + public void setMappings(Mappings val, boolean warnAboutDrops) { + if (val == null) { + val = new Mappings(); + } + + // drop mappings that don't match the jar + MappingsChecker checker = new MappingsChecker(this.jarIndex); + checker.dropBrokenMappings(val); + if (warnAboutDrops) { + for (java.util.Map.Entry mapping : checker.getDroppedClassMappings().entrySet()) { + System.out.println("WARNING: Couldn't find class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped."); + } + for (java.util.Map.Entry mapping : checker.getDroppedInnerClassMappings().entrySet()) { + System.out.println("WARNING: Couldn't find inner class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped."); + } + for (java.util.Map.Entry mapping : checker.getDroppedFieldMappings().entrySet()) { + System.out.println("WARNING: Couldn't find field entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped."); + } + for (java.util.Map.Entry mapping : checker.getDroppedMethodMappings().entrySet()) { + System.out.println("WARNING: Couldn't find behavior entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped."); + } + } + + this.mappings = val; + this.renamer.setMappings(mappings); + this.translatorCache.clear(); + } + + public Translator getTranslator(TranslationDirection direction) { + return this.translatorCache.computeIfAbsent(direction, + k -> this.mappings.getTranslator(direction, this.jarIndex.getTranslationIndex())); + } + + public void getSeparatedClasses(List obfClasses, List deobfClasses) { + for (ClassEntry obfClassEntry : this.jarIndex.getObfClassEntries()) { + // skip inner classes + if (obfClassEntry.isInnerClass()) { + continue; + } + + // separate the classes + ClassEntry deobfClassEntry = deobfuscateEntry(obfClassEntry); + if (!deobfClassEntry.equals(obfClassEntry)) { + // if the class has a mapping, clearly it's deobfuscated + deobfClasses.add(deobfClassEntry); + } else if (obfClassEntry.getPackageName() != null) { + // also call it deobufscated if it's not in the none package + deobfClasses.add(obfClassEntry); + } else { + // otherwise, assume it's still obfuscated + obfClasses.add(obfClassEntry); + } + } + } + + public TranslatingTypeLoader createTypeLoader() { + return new TranslatingTypeLoader( + this.jar, + this.jarIndex, + getTranslator(TranslationDirection.Obfuscating), + getTranslator(TranslationDirection.Deobfuscating) + ); + } + + public CompilationUnit getSourceTree(String className) { + + // we don't know if this class name is obfuscated or deobfuscated + // we need to tell the decompiler the deobfuscated name so it doesn't get freaked out + // the decompiler only sees classes after deobfuscation, so we need to load it by the deobfuscated name if there is one + + // first, assume class name is deobf + String deobfClassName = className; + + // if it wasn't actually deobf, then we can find a mapping for it and get the deobf name + ClassMapping classMapping = this.mappings.getClassByObf(className); + if (classMapping != null && classMapping.getDeobfName() != null) { + deobfClassName = classMapping.getDeobfName(); + } + + // set the type loader + TranslatingTypeLoader loader = createTypeLoader(); + this.settings.setTypeLoader(loader); + + // see if procyon can find the type + TypeReference type = new MetadataSystem(loader).lookupType(deobfClassName); + if (type == null) { + throw new Error(String.format("Unable to find type: %s (deobf: %s)\nTried class names: %s", + className, deobfClassName, loader.getClassNamesToTry(deobfClassName) + )); + } + TypeDefinition resolvedType = type.resolve(); + + // decompile it! + DecompilerContext context = new DecompilerContext(); + context.setCurrentType(resolvedType); + context.setSettings(this.settings); + AstBuilder builder = new AstBuilder(context); + builder.addType(resolvedType); + builder.runTransformations(null); + return builder.getCompilationUnit(); + } + + 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; + if (ignoreBadTokens != null) { + index = new SourceIndex(source, ignoreBadTokens); + } else { + index = new SourceIndex(source); + } + sourceTree.acceptVisitor(new SourceIndexVisitor(), index); + + // DEBUG + // sourceTree.acceptVisitor( new TreeDumpVisitor( new File( "tree.txt" ) ), null ); + + // resolve all the classes in the source references + for (Token token : index.referenceTokens()) { + EntryReference deobfReference = index.getDeobfReference(token); + + // get the obfuscated entry + Entry obfEntry = obfuscateEntry(deobfReference.entry); + + // try to resolve the class + ClassEntry resolvedObfClassEntry = this.jarIndex.getTranslationIndex().resolveEntryClass(obfEntry); + if (resolvedObfClassEntry != null && !resolvedObfClassEntry.equals(obfEntry.getClassEntry())) { + // change the class of the entry + obfEntry = obfEntry.cloneToNewClass(resolvedObfClassEntry); + + // save the new deobfuscated reference + deobfReference.entry = deobfuscateEntry(obfEntry); + index.replaceDeobfReference(token, deobfReference); + } + + // DEBUG + // System.out.println( token + " -> " + reference + " -> " + index.getReferenceToken( reference ) ); + } + + return index; + } + + public String getSource(CompilationUnit sourceTree) { + // render the AST into source + StringWriter buf = new StringWriter(); + sourceTree.acceptVisitor(new InsertParenthesesVisitor(), null); + sourceTree.acceptVisitor(new JavaOutputVisitor(new PlainTextOutput(buf), this.settings), null); + return buf.toString(); + } + + public void writeSources(File dirOut, ProgressListener progress) { + // get the classes to decompile + Set classEntries = Sets.newHashSet(); + for (ClassEntry obfClassEntry : this.jarIndex.getObfClassEntries()) { + // skip inner classes + if (obfClassEntry.isInnerClass()) { + continue; + } + + classEntries.add(obfClassEntry); + } + + if (progress != null) { + progress.init(classEntries.size(), "Decompiling classes..."); + } + + // DEOBFUSCATE ALL THE THINGS!! @_@ + int i = 0; + for (ClassEntry obfClassEntry : classEntries) { + ClassEntry deobfClassEntry = deobfuscateEntry(new ClassEntry(obfClassEntry)); + if (progress != null) { + progress.onProgress(i++, deobfClassEntry.toString()); + } + + try { + // get the source + String source = getSource(getSourceTree(obfClassEntry.getName())); + + // write the file + File file = new File(dirOut, deobfClassEntry.getName().replace('.', '/') + ".java"); + file.getParentFile().mkdirs(); + try (OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(file), Charsets.UTF_8)) { + out.write(source); + } + } catch (Throwable t) { + // don't crash the whole world here, just log the error and keep going + // TODO: set up logback via log4j + System.err.println("Unable to deobfuscate class " + deobfClassEntry + " (" + obfClassEntry + ")"); + t.printStackTrace(System.err); + } + } + if (progress != null) { + progress.onProgress(i, "Done!"); + } + } + + private void addAllPotentialAncestors(Set classEntries, ClassEntry classObfEntry) { + for (ClassEntry interfaceEntry : jarIndex.getTranslationIndex().getInterfaces(classObfEntry)) { + if (classEntries.add(interfaceEntry)) { + addAllPotentialAncestors(classEntries, interfaceEntry); + } + } + + ClassEntry superClassEntry = jarIndex.getTranslationIndex().getSuperclass(classObfEntry); + if (superClassEntry != null && classEntries.add(superClassEntry)) { + addAllPotentialAncestors(classEntries, superClassEntry); + } + } + + private boolean isBehaviorProvider(ClassEntry classObfEntry, BehaviorEntry behaviorEntry) { + if (behaviorEntry instanceof MethodEntry) { + MethodEntry methodEntry = (MethodEntry) behaviorEntry; + + Set classEntries = new HashSet<>(); + addAllPotentialAncestors(classEntries, classObfEntry); + + for (ClassEntry parentEntry : classEntries) { + MethodEntry ancestorMethodEntry = new MethodEntry(parentEntry, methodEntry.getName(), methodEntry.getSignature()); + if (jarIndex.containsObfBehavior(ancestorMethodEntry)) { + return false; + } + } + } + + return true; + } + + public void rebuildMethodNames(ProgressListener progress) { + int i = 0; + Map> renameClassMap = new HashMap<>(); + + progress.init(getMappings().classes().size() * 3, "Rebuilding method names"); + + for (ClassMapping classMapping : Lists.newArrayList(getMappings().classes())) { + Map renameEntries = new HashMap<>(); + + progress.onProgress(i++, classMapping.getDeobfName()); + + for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) { + ClassEntry classObfEntry = classMapping.getObfEntry(); + BehaviorEntry obfEntry = methodMapping.getObfEntry(classObfEntry); + + if (isBehaviorProvider(classObfEntry, obfEntry)) { + if (hasDeobfuscatedName(obfEntry) && !(obfEntry instanceof ConstructorEntry) + && !(methodMapping.getDeobfName().equals(methodMapping.getObfName()))) { + renameEntries.put(obfEntry, methodMapping.getDeobfName()); + } + + for (ArgumentMapping argumentMapping : Lists.newArrayList(methodMapping.arguments())) { + Entry argObfEntry = argumentMapping.getObfEntry(obfEntry); + if (hasDeobfuscatedName(argObfEntry)) { + renameEntries.put(argObfEntry, deobfuscateEntry(argObfEntry).getName()); + } + } + } + } + + renameClassMap.put(classMapping, renameEntries); + } + + for (Map.Entry> renameClassMapEntry : renameClassMap.entrySet()) { + progress.onProgress(i++, renameClassMapEntry.getKey().getDeobfName()); + + for (Map.Entry entry : renameClassMapEntry.getValue().entrySet()) { + Entry obfEntry = entry.getKey(); + + removeMapping(obfEntry); + } + } + + for (Map.Entry> renameClassMapEntry : renameClassMap.entrySet()) { + progress.onProgress(i++, renameClassMapEntry.getKey().getDeobfName()); + + for (Map.Entry entry : renameClassMapEntry.getValue().entrySet()) { + Entry obfEntry = entry.getKey(); + String name = entry.getValue(); + + rename(obfEntry, name); + } + } + } + + public void writeJar(File out, ProgressListener progress) { + transformJar(out, progress, createTypeLoader()::transformClass); + } + + public void protectifyJar(File out, ProgressListener progress) { + transformJar(out, progress, ClassProtectifier::protectify); + } + + public void publifyJar(File out, ProgressListener progress) { + transformJar(out, progress, ClassPublifier::publify); + } + + public void transformJar(File out, ProgressListener progress, ClassTransformer transformer) { + try (JarOutputStream outJar = new JarOutputStream(new FileOutputStream(out))) { + if (progress != null) { + progress.init(JarClassIterator.getClassEntries(this.jar).size(), "Transforming classes..."); + } + + int i = 0; + for (CtClass c : JarClassIterator.classes(this.jar)) { + if (progress != null) { + progress.onProgress(i++, c.getName()); + } + + try { + c = transformer.transform(c); + outJar.putNextEntry(new JarEntry(c.getName().replace('.', '/') + ".class")); + outJar.write(c.toBytecode()); + outJar.closeEntry(); + } catch (Throwable t) { + throw new Error("Unable to transform class " + c.getName(), t); + } + } + if (progress != null) { + progress.onProgress(i, "Done!"); + } + + outJar.close(); + } catch (IOException ex) { + throw new Error("Unable to write to Jar file!"); + } + } + + public T obfuscateEntry(T deobfEntry) { + if (deobfEntry == null) { + return null; + } + return getTranslator(TranslationDirection.Obfuscating).translateEntry(deobfEntry); + } + + public T deobfuscateEntry(T obfEntry) { + if (obfEntry == null) { + return null; + } + return getTranslator(TranslationDirection.Deobfuscating).translateEntry(obfEntry); + } + + public EntryReference obfuscateReference(EntryReference deobfReference) { + if (deobfReference == null) { + return null; + } + return new EntryReference<>(obfuscateEntry(deobfReference.entry), obfuscateEntry(deobfReference.context), deobfReference); + } + + public EntryReference deobfuscateReference(EntryReference obfReference) { + if (obfReference == null) { + return null; + } + return new EntryReference<>(deobfuscateEntry(obfReference.entry), deobfuscateEntry(obfReference.context), obfReference); + } + + public boolean isObfuscatedIdentifier(Entry obfEntry) { + return isObfuscatedIdentifier(obfEntry, false); + } + + public boolean isObfuscatedIdentifier(Entry obfEntry, boolean hack) { + + if (obfEntry instanceof MethodEntry) { + + // HACKHACK: Object methods are not obfuscated identifiers + MethodEntry obfMethodEntry = (MethodEntry) obfEntry; + String name = obfMethodEntry.getName(); + String sig = obfMethodEntry.getSignature().toString(); + if (name.equals("clone") && sig.equals("()Ljava/lang/Object;")) { + return false; + } else if (name.equals("equals") && sig.equals("(Ljava/lang/Object;)Z")) { + return false; + } else if (name.equals("finalize") && sig.equals("()V")) { + return false; + } else if (name.equals("getClass") && sig.equals("()Ljava/lang/Class;")) { + return false; + } else if (name.equals("hashCode") && sig.equals("()I")) { + return false; + } else if (name.equals("notify") && sig.equals("()V")) { + return false; + } else if (name.equals("notifyAll") && sig.equals("()V")) { + return false; + } else if (name.equals("toString") && sig.equals("()Ljava/lang/String;")) { + return false; + } else if (name.equals("wait") && sig.equals("()V")) { + return false; + } else if (name.equals("wait") && sig.equals("(J)V")) { + return false; + } else if (name.equals("wait") && sig.equals("(JI)V")) { + return false; + } + + // FIXME: HACK EVEN MORE HACK! + if (hack && this.jarIndex.containsObfEntry(obfEntry.getClassEntry())) + return true; + } + + return this.jarIndex.containsObfEntry(obfEntry); + } + + public boolean isRenameable(EntryReference obfReference, boolean activeHack) { + return obfReference.isNamed() && isObfuscatedIdentifier(obfReference.getNameableEntry(), activeHack); + } + + public boolean isRenameable(EntryReference obfReference) { + return isRenameable(obfReference, false); + } + + public boolean hasDeobfuscatedName(Entry obfEntry) { + Translator translator = getTranslator(TranslationDirection.Deobfuscating); + if (obfEntry instanceof ClassEntry) { + ClassEntry obfClass = (ClassEntry) obfEntry; + List mappingChain = this.mappings.getClassMappingChain(obfClass); + ClassMapping classMapping = mappingChain.get(mappingChain.size() - 1); + return classMapping != null && classMapping.getDeobfName() != null; + } else if (obfEntry instanceof FieldEntry) { + return translator.translate((FieldEntry) obfEntry) != null; + } else if (obfEntry instanceof MethodEntry) { + return translator.translate((MethodEntry) obfEntry) != null; + } else if (obfEntry instanceof ConstructorEntry) { + // constructors have no names + return false; + } else if (obfEntry instanceof ArgumentEntry) { + return translator.translate((ArgumentEntry) obfEntry) != null; + } else if (obfEntry instanceof LocalVariableEntry) { + // TODO: Implement it + //return translator.translate((LocalVariableEntry)obfEntry) != null; + return false; + } else { + throw new Error("Unknown entry type: " + obfEntry.getClass().getName()); + } + } + + public void rename(Entry obfEntry, String newName) { + rename(obfEntry, newName, true); + } + + // NOTE: these methods are a bit messy... oh well + + public void rename(Entry obfEntry, String newName, boolean clearCache) { + if (obfEntry instanceof ClassEntry) { + this.renamer.setClassName((ClassEntry) obfEntry, Descriptor.toJvmName(newName)); + } else if (obfEntry instanceof FieldEntry) { + this.renamer.setFieldName((FieldEntry) obfEntry, newName); + } else if (obfEntry instanceof MethodEntry) { + this.renamer.setMethodTreeName((MethodEntry) obfEntry, newName); + } else if (obfEntry instanceof ConstructorEntry) { + throw new IllegalArgumentException("Cannot rename constructors"); + } else if (obfEntry instanceof ArgumentEntry) { + this.renamer.setArgumentTreeName((ArgumentEntry) obfEntry, newName); + } else if (obfEntry instanceof LocalVariableEntry) { + // TODO: Implement it + } else { + throw new Error("Unknown entry type: " + obfEntry.getClass().getName()); + } + + // clear caches + if (clearCache) + this.translatorCache.clear(); + } + + public void removeMapping(Entry obfEntry) { + if (obfEntry instanceof ClassEntry) { + this.renamer.removeClassMapping((ClassEntry) obfEntry); + } else if (obfEntry instanceof FieldEntry) { + this.renamer.removeFieldMapping((FieldEntry) obfEntry); + } else if (obfEntry instanceof MethodEntry) { + this.renamer.removeMethodTreeMapping((MethodEntry) obfEntry); + } else if (obfEntry instanceof ConstructorEntry) { + throw new IllegalArgumentException("Cannot rename constructors"); + } else if (obfEntry instanceof ArgumentEntry) { + this.renamer.removeArgumentMapping((ArgumentEntry) obfEntry); + } else { + throw new Error("Unknown entry type: " + obfEntry); + } + + // clear caches + this.translatorCache.clear(); + } + + public void markAsDeobfuscated(Entry obfEntry) { + if (obfEntry instanceof ClassEntry) { + this.renamer.markClassAsDeobfuscated((ClassEntry) obfEntry); + } else if (obfEntry instanceof FieldEntry) { + this.renamer.markFieldAsDeobfuscated((FieldEntry) obfEntry); + } else if (obfEntry instanceof MethodEntry) { + this.renamer.markMethodTreeAsDeobfuscated((MethodEntry) obfEntry); + } else if (obfEntry instanceof ConstructorEntry) { + throw new IllegalArgumentException("Cannot rename constructors"); + } else if (obfEntry instanceof ArgumentEntry) { + this.renamer.markArgumentAsDeobfuscated((ArgumentEntry) obfEntry); + } else if (obfEntry instanceof LocalVariableEntry) { + // TODO: Implement it + } else { + throw new Error("Unknown entry type: " + obfEntry); + } + + // clear caches + this.translatorCache.clear(); + } + + public void changeModifier(Entry entry, Mappings.EntryModifier modifierEntry) { + Entry obfEntry = obfuscateEntry(entry); + if (obfEntry instanceof ClassEntry) + this.renamer.setClassModifier((ClassEntry) obfEntry, modifierEntry); + else if (obfEntry instanceof FieldEntry) + this.renamer.setFieldModifier((FieldEntry) obfEntry, modifierEntry); + else if (obfEntry instanceof BehaviorEntry) + this.renamer.setMethodModifier((BehaviorEntry) obfEntry, modifierEntry); + else + throw new Error("Unknown entry type: " + obfEntry); + } + + public Mappings.EntryModifier getModifier(Entry obEntry) { + Entry entry = obfuscateEntry(obEntry); + if (entry != null) + obEntry = entry; + return getTranslator(TranslationDirection.Deobfuscating).getModifier(obEntry); + } + + public interface ProgressListener { + void init(int totalWork, String title); + + void onProgress(int numDone, String message); + } + + public interface ClassTransformer { + CtClass transform(CtClass c) throws Exception; + } } diff --git a/src/main/java/cuchaz/enigma/ExceptionIgnorer.java b/src/main/java/cuchaz/enigma/ExceptionIgnorer.java index fc89fa6d..84331cc1 100644 --- a/src/main/java/cuchaz/enigma/ExceptionIgnorer.java +++ b/src/main/java/cuchaz/enigma/ExceptionIgnorer.java @@ -8,27 +8,28 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma; public class ExceptionIgnorer { - public static boolean shouldIgnore(Throwable t) { + public static boolean shouldIgnore(Throwable t) { - // is this that pesky concurrent access bug in the highlight painter system? - // (ancient ui code is ancient) - if (t instanceof ArrayIndexOutOfBoundsException) { - StackTraceElement[] stackTrace = t.getStackTrace(); - if (stackTrace.length > 1) { + // is this that pesky concurrent access bug in the highlight painter system? + // (ancient ui code is ancient) + if (t instanceof ArrayIndexOutOfBoundsException) { + StackTraceElement[] stackTrace = t.getStackTrace(); + if (stackTrace.length > 1) { - // does this stack frame match javax.swing.text.DefaultHighlighter.paint*() ? - StackTraceElement frame = stackTrace[1]; - if (frame.getClassName().equals("javax.swing.text.DefaultHighlighter") && frame.getMethodName().startsWith("paint")) { - return true; - } - } - } + // does this stack frame match javax.swing.text.DefaultHighlighter.paint*() ? + StackTraceElement frame = stackTrace[1]; + if (frame.getClassName().equals("javax.swing.text.DefaultHighlighter") && frame.getMethodName().startsWith("paint")) { + return true; + } + } + } - return false; - } + return false; + } } diff --git a/src/main/java/cuchaz/enigma/Main.java b/src/main/java/cuchaz/enigma/Main.java index 73709868..fa8d0622 100644 --- a/src/main/java/cuchaz/enigma/Main.java +++ b/src/main/java/cuchaz/enigma/Main.java @@ -8,48 +8,48 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma; +import cuchaz.enigma.gui.Gui; + +import javax.swing.*; import java.io.File; import java.util.jar.JarFile; -import javax.swing.UIManager; - -import cuchaz.enigma.gui.Gui; - public class Main { - public static void main(String[] args) throws Exception { - if (System.getProperty("enigma.useSystemLookAndFeel", "true").equals("true")) - UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - Gui gui = new Gui(); - - // parse command-line args - if (args.length >= 1) { - gui.getController().openJar(new JarFile(getFile(args[0]))); - } - if (args.length >= 2) { - gui.getController().openEnigmaMappings(getFile(args[1])); - } - - // DEBUG - //gui.getController().openDeclaration(new ClassEntry("none/byp")); - } - - private static File getFile(String path) { - // expand ~ to the home dir - if (path.startsWith("~")) { - // get the home dir - File dirHome = new File(System.getProperty("user.home")); - - // is the path just ~/ or is it ~user/ ? - if (path.startsWith("~/")) { - return new File(dirHome, path.substring(2)); - } else { - return new File(dirHome.getParentFile(), path.substring(1)); - } - } - - return new File(path); - } + public static void main(String[] args) throws Exception { + if (System.getProperty("enigma.useSystemLookAndFeel", "true").equals("true")) + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + Gui gui = new Gui(); + + // parse command-line args + if (args.length >= 1) { + gui.getController().openJar(new JarFile(getFile(args[0]))); + } + if (args.length >= 2) { + gui.getController().openEnigmaMappings(getFile(args[1])); + } + + // DEBUG + //gui.getController().openDeclaration(new ClassEntry("none/byp")); + } + + private static File getFile(String path) { + // expand ~ to the home dir + if (path.startsWith("~")) { + // get the home dir + File dirHome = new File(System.getProperty("user.home")); + + // is the path just ~/ or is it ~user/ ? + if (path.startsWith("~/")) { + return new File(dirHome, path.substring(2)); + } else { + return new File(dirHome.getParentFile(), path.substring(1)); + } + } + + return new File(path); + } } diff --git a/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java b/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java index 73405662..7304f722 100644 --- a/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java +++ b/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java @@ -8,14 +8,24 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma; import com.google.common.collect.Lists; import com.google.common.collect.Maps; - import com.strobel.assembler.metadata.Buffer; import com.strobel.assembler.metadata.ClasspathTypeLoader; import com.strobel.assembler.metadata.ITypeLoader; +import cuchaz.enigma.analysis.BridgeMarker; +import cuchaz.enigma.analysis.JarIndex; +import cuchaz.enigma.bytecode.ClassTranslator; +import cuchaz.enigma.bytecode.InnerClassWriter; +import cuchaz.enigma.bytecode.LocalVariableRenamer; +import cuchaz.enigma.bytecode.MethodParameterWriter; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.Translator; +import javassist.*; +import javassist.bytecode.Descriptor; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -25,205 +35,197 @@ import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; -import cuchaz.enigma.analysis.BridgeMarker; -import cuchaz.enigma.analysis.JarIndex; -import cuchaz.enigma.bytecode.*; -import cuchaz.enigma.mapping.ClassEntry; -import cuchaz.enigma.mapping.Translator; -import javassist.*; -import javassist.bytecode.Descriptor; - public class TranslatingTypeLoader implements ITypeLoader { - private JarFile jar; - private JarIndex jarIndex; - private Translator obfuscatingTranslator; - private Translator deobfuscatingTranslator; - private Map cache; - private ClasspathTypeLoader defaultTypeLoader; - - public TranslatingTypeLoader(JarFile jar, JarIndex jarIndex, Translator obfuscatingTranslator, Translator deobfuscatingTranslator) { - this.jar = jar; - this.jarIndex = jarIndex; - this.obfuscatingTranslator = obfuscatingTranslator; - this.deobfuscatingTranslator = deobfuscatingTranslator; - this.cache = Maps.newHashMap(); - this.defaultTypeLoader = new ClasspathTypeLoader(); - } - - public void clearCache() { - this.cache.clear(); - } - - @Override - public boolean tryLoadType(String className, Buffer out) { - - // check the cache - byte[] data; - if (this.cache.containsKey(className)) { - data = this.cache.get(className); - } else { - data = loadType(className); - this.cache.put(className, data); - } - - if (data == null) { - // chain to default type loader - return this.defaultTypeLoader.tryLoadType(className, out); - } - - // send the class to the decompiler - out.reset(data.length); - System.arraycopy(data, 0, out.array(), out.position(), data.length); - out.position(0); - return true; - } - - public CtClass loadClass(String deobfClassName) { - - byte[] data = loadType(deobfClassName); - if (data == null) { - return null; - } - - // return a javassist handle for the class - String javaClassFileName = Descriptor.toJavaName(deobfClassName); - ClassPool classPool = new ClassPool(); - classPool.insertClassPath(new ByteArrayClassPath(javaClassFileName, data)); - try { - return classPool.get(javaClassFileName); - } catch (NotFoundException ex) { - throw new Error(ex); - } - } - - private byte[] loadType(String className) { - - // NOTE: don't know if class name is obf or deobf - ClassEntry classEntry = new ClassEntry(className); - ClassEntry obfClassEntry = this.obfuscatingTranslator.translateEntry(classEntry); - - // is this an inner class referenced directly? (ie trying to load b instead of a$b) - if (!obfClassEntry.isInnerClass()) { - List classChain = this.jarIndex.getObfClassChain(obfClassEntry); - if (classChain.size() > 1) { - System.err.println(String.format("WARNING: no class %s after inner class reconstruction. Try %s", - className, obfClassEntry.buildClassEntry(classChain) - )); - return null; - } - } - - // is this a class we should even know about? - if (!this.jarIndex.containsObfClass(obfClassEntry)) { - return null; - } - - // DEBUG - //System.out.println(String.format("Looking for %s (obf: %s)", classEntry.getName(), obfClassEntry.getName())); - - // find the class in the jar - String classInJarName = findClassInJar(obfClassEntry); - if (classInJarName == null) { - // couldn't find it - return null; - } - - try { - // read the class file into a buffer - ByteArrayOutputStream data = new ByteArrayOutputStream(); - byte[] buf = new byte[1024 * 1024]; // 1 KiB - InputStream in = this.jar.getInputStream(this.jar.getJarEntry(classInJarName + ".class")); - while (true) { - int bytesRead = in.read(buf); - if (bytesRead <= 0) { - break; - } - data.write(buf, 0, bytesRead); - } - data.close(); - in.close(); - buf = data.toByteArray(); - - // load the javassist handle to the raw class - ClassPool classPool = new ClassPool(); - String classInJarJavaName = Descriptor.toJavaName(classInJarName); - classPool.insertClassPath(new ByteArrayClassPath(classInJarJavaName, buf)); - CtClass c = classPool.get(classInJarJavaName); - - c = transformClass(c); - - // sanity checking - assertClassName(c, classEntry); - - // DEBUG - //Util.writeClass( c ); - - // we have a transformed class! - return c.toBytecode(); - } catch (IOException | NotFoundException | CannotCompileException ex) { - throw new Error(ex); - } - } - - private String findClassInJar(ClassEntry obfClassEntry) { - - // try to find the class in the jar - for (String className : getClassNamesToTry(obfClassEntry)) { - JarEntry jarEntry = this.jar.getJarEntry(className + ".class"); - if (jarEntry != null) { - return className; - } - } - - // didn't find it ;_; - return null; - } - - public List getClassNamesToTry(String className) { - return getClassNamesToTry(this.obfuscatingTranslator.translateEntry(new ClassEntry(className))); - } - - public List getClassNamesToTry(ClassEntry obfClassEntry) { - List classNamesToTry = Lists.newArrayList(); - classNamesToTry.add(obfClassEntry.getName()); - if (obfClassEntry.isInnerClass()) { - // try just the inner class name - classNamesToTry.add(obfClassEntry.getInnermostClassName()); - } - return classNamesToTry; - } - - public CtClass transformClass(CtClass c) - throws IOException, NotFoundException, CannotCompileException { - - // reconstruct inner classes - new InnerClassWriter(this.jarIndex, this.deobfuscatingTranslator).write(c); - - // re-get the javassist handle since we changed class names - ClassEntry obfClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); - String javaClassReconstructedName = Descriptor.toJavaName(obfClassEntry.getName()); - ClassPool classPool = new ClassPool(); - classPool.insertClassPath(new ByteArrayClassPath(javaClassReconstructedName, c.toBytecode())); - c = classPool.get(javaClassReconstructedName); - - // check that the file is correct after inner class reconstruction (ie cause Javassist to fail fast if something is wrong) - assertClassName(c, obfClassEntry); - - // do all kinds of deobfuscating transformations on the class - new BridgeMarker(this.jarIndex).markBridges(c); - new MethodParameterWriter(this.deobfuscatingTranslator).writeMethodArguments(c); - new LocalVariableRenamer(this.deobfuscatingTranslator).rename(c); - new ClassTranslator(this.deobfuscatingTranslator).translate(c); - - return c; - } - - private void assertClassName(CtClass c, ClassEntry obfClassEntry) { - String name1 = Descriptor.toJvmName(c.getName()); - assert (name1.equals(obfClassEntry.getName())) : String.format("Looking for %s, instead found %s", obfClassEntry.getName(), name1); - - String name2 = Descriptor.toJvmName(c.getClassFile().getName()); - assert (name2.equals(obfClassEntry.getName())) : String.format("Looking for %s, instead found %s", obfClassEntry.getName(), name2); - } + private JarFile jar; + private JarIndex jarIndex; + private Translator obfuscatingTranslator; + private Translator deobfuscatingTranslator; + private Map cache; + private ClasspathTypeLoader defaultTypeLoader; + + public TranslatingTypeLoader(JarFile jar, JarIndex jarIndex, Translator obfuscatingTranslator, Translator deobfuscatingTranslator) { + this.jar = jar; + this.jarIndex = jarIndex; + this.obfuscatingTranslator = obfuscatingTranslator; + this.deobfuscatingTranslator = deobfuscatingTranslator; + this.cache = Maps.newHashMap(); + this.defaultTypeLoader = new ClasspathTypeLoader(); + } + + public void clearCache() { + this.cache.clear(); + } + + @Override + public boolean tryLoadType(String className, Buffer out) { + + // check the cache + byte[] data; + if (this.cache.containsKey(className)) { + data = this.cache.get(className); + } else { + data = loadType(className); + this.cache.put(className, data); + } + + if (data == null) { + // chain to default type loader + return this.defaultTypeLoader.tryLoadType(className, out); + } + + // send the class to the decompiler + out.reset(data.length); + System.arraycopy(data, 0, out.array(), out.position(), data.length); + out.position(0); + return true; + } + + public CtClass loadClass(String deobfClassName) { + + byte[] data = loadType(deobfClassName); + if (data == null) { + return null; + } + + // return a javassist handle for the class + String javaClassFileName = Descriptor.toJavaName(deobfClassName); + ClassPool classPool = new ClassPool(); + classPool.insertClassPath(new ByteArrayClassPath(javaClassFileName, data)); + try { + return classPool.get(javaClassFileName); + } catch (NotFoundException ex) { + throw new Error(ex); + } + } + + private byte[] loadType(String className) { + + // NOTE: don't know if class name is obf or deobf + ClassEntry classEntry = new ClassEntry(className); + ClassEntry obfClassEntry = this.obfuscatingTranslator.translateEntry(classEntry); + + // is this an inner class referenced directly? (ie trying to load b instead of a$b) + if (!obfClassEntry.isInnerClass()) { + List classChain = this.jarIndex.getObfClassChain(obfClassEntry); + if (classChain.size() > 1) { + System.err.println(String.format("WARNING: no class %s after inner class reconstruction. Try %s", + className, obfClassEntry.buildClassEntry(classChain) + )); + return null; + } + } + + // is this a class we should even know about? + if (!this.jarIndex.containsObfClass(obfClassEntry)) { + return null; + } + + // DEBUG + //System.out.println(String.format("Looking for %s (obf: %s)", classEntry.getName(), obfClassEntry.getName())); + + // find the class in the jar + String classInJarName = findClassInJar(obfClassEntry); + if (classInJarName == null) { + // couldn't find it + return null; + } + + try { + // read the class file into a buffer + ByteArrayOutputStream data = new ByteArrayOutputStream(); + byte[] buf = new byte[1024 * 1024]; // 1 KiB + InputStream in = this.jar.getInputStream(this.jar.getJarEntry(classInJarName + ".class")); + while (true) { + int bytesRead = in.read(buf); + if (bytesRead <= 0) { + break; + } + data.write(buf, 0, bytesRead); + } + data.close(); + in.close(); + buf = data.toByteArray(); + + // load the javassist handle to the raw class + ClassPool classPool = new ClassPool(); + String classInJarJavaName = Descriptor.toJavaName(classInJarName); + classPool.insertClassPath(new ByteArrayClassPath(classInJarJavaName, buf)); + CtClass c = classPool.get(classInJarJavaName); + + c = transformClass(c); + + // sanity checking + assertClassName(c, classEntry); + + // DEBUG + //Util.writeClass( c ); + + // we have a transformed class! + return c.toBytecode(); + } catch (IOException | NotFoundException | CannotCompileException ex) { + throw new Error(ex); + } + } + + private String findClassInJar(ClassEntry obfClassEntry) { + + // try to find the class in the jar + for (String className : getClassNamesToTry(obfClassEntry)) { + JarEntry jarEntry = this.jar.getJarEntry(className + ".class"); + if (jarEntry != null) { + return className; + } + } + + // didn't find it ;_; + return null; + } + + public List getClassNamesToTry(String className) { + return getClassNamesToTry(this.obfuscatingTranslator.translateEntry(new ClassEntry(className))); + } + + public List getClassNamesToTry(ClassEntry obfClassEntry) { + List classNamesToTry = Lists.newArrayList(); + classNamesToTry.add(obfClassEntry.getName()); + if (obfClassEntry.isInnerClass()) { + // try just the inner class name + classNamesToTry.add(obfClassEntry.getInnermostClassName()); + } + return classNamesToTry; + } + + public CtClass transformClass(CtClass c) + throws IOException, NotFoundException, CannotCompileException { + + // reconstruct inner classes + new InnerClassWriter(this.jarIndex, this.deobfuscatingTranslator).write(c); + + // re-get the javassist handle since we changed class names + ClassEntry obfClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); + String javaClassReconstructedName = Descriptor.toJavaName(obfClassEntry.getName()); + ClassPool classPool = new ClassPool(); + classPool.insertClassPath(new ByteArrayClassPath(javaClassReconstructedName, c.toBytecode())); + c = classPool.get(javaClassReconstructedName); + + // check that the file is correct after inner class reconstruction (ie cause Javassist to fail fast if something is wrong) + assertClassName(c, obfClassEntry); + + // do all kinds of deobfuscating transformations on the class + new BridgeMarker(this.jarIndex).markBridges(c); + new MethodParameterWriter(this.deobfuscatingTranslator).writeMethodArguments(c); + new LocalVariableRenamer(this.deobfuscatingTranslator).rename(c); + new ClassTranslator(this.deobfuscatingTranslator).translate(c); + + return c; + } + + private void assertClassName(CtClass c, ClassEntry obfClassEntry) { + String name1 = Descriptor.toJvmName(c.getName()); + assert (name1.equals(obfClassEntry.getName())) : String.format("Looking for %s, instead found %s", obfClassEntry.getName(), name1); + + String name2 = Descriptor.toJvmName(c.getClassFile().getName()); + assert (name2.equals(obfClassEntry.getName())) : String.format("Looking for %s, instead found %s", obfClassEntry.getName(), name2); + } } diff --git a/src/main/java/cuchaz/enigma/analysis/Access.java b/src/main/java/cuchaz/enigma/analysis/Access.java index b8a7b2c8..547d85ef 100644 --- a/src/main/java/cuchaz/enigma/analysis/Access.java +++ b/src/main/java/cuchaz/enigma/analysis/Access.java @@ -8,40 +8,41 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ -package cuchaz.enigma.analysis; -import java.lang.reflect.Modifier; +package cuchaz.enigma.analysis; import javassist.CtBehavior; import javassist.CtField; +import java.lang.reflect.Modifier; + public enum Access { - PUBLIC, PROTECTED, PACKAGE, PRIVATE; - - public static Access get(CtBehavior behavior) { - return get(behavior.getModifiers()); - } - - public static Access get(CtField field) { - return get(field.getModifiers()); - } - - public static Access get(int modifiers) { - boolean isPublic = Modifier.isPublic(modifiers); - boolean isProtected = Modifier.isProtected(modifiers); - boolean isPrivate = Modifier.isPrivate(modifiers); - - if (isPublic && !isProtected && !isPrivate) { - return PUBLIC; - } else if (!isPublic && isProtected && !isPrivate) { - return PROTECTED; - } else if (!isPublic && !isProtected && isPrivate) { - return PRIVATE; - } else if (!isPublic && !isProtected && !isPrivate) { - return PACKAGE; - } - // assume public by default - return PUBLIC; - } + PUBLIC, PROTECTED, PACKAGE, PRIVATE; + + public static Access get(CtBehavior behavior) { + return get(behavior.getModifiers()); + } + + public static Access get(CtField field) { + return get(field.getModifiers()); + } + + public static Access get(int modifiers) { + boolean isPublic = Modifier.isPublic(modifiers); + boolean isProtected = Modifier.isProtected(modifiers); + boolean isPrivate = Modifier.isPrivate(modifiers); + + if (isPublic && !isProtected && !isPrivate) { + return PUBLIC; + } else if (!isPublic && isProtected && !isPrivate) { + return PROTECTED; + } else if (!isPublic && !isProtected && isPrivate) { + return PRIVATE; + } else if (!isPublic && !isProtected && !isPrivate) { + return PACKAGE; + } + // assume public by default + return PUBLIC; + } } diff --git a/src/main/java/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java index 52d5b311..6556b2cf 100644 --- a/src/main/java/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java +++ b/src/main/java/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java @@ -8,6 +8,7 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.analysis; import com.google.common.collect.Sets; @@ -20,85 +21,73 @@ import javax.swing.tree.TreeNode; import java.util.Set; public class BehaviorReferenceTreeNode extends DefaultMutableTreeNode - implements ReferenceTreeNode -{ + implements ReferenceTreeNode { - private Translator deobfuscatingTranslator; - private BehaviorEntry entry; - private EntryReference reference; - private Access access; + private Translator deobfuscatingTranslator; + private BehaviorEntry entry; + private EntryReference reference; + private Access access; - public BehaviorReferenceTreeNode(Translator deobfuscatingTranslator, BehaviorEntry entry) - { - this.deobfuscatingTranslator = deobfuscatingTranslator; - this.entry = entry; - this.reference = null; - } + public BehaviorReferenceTreeNode(Translator deobfuscatingTranslator, BehaviorEntry entry) { + this.deobfuscatingTranslator = deobfuscatingTranslator; + this.entry = entry; + this.reference = null; + } - public BehaviorReferenceTreeNode(Translator deobfuscatingTranslator, - EntryReference reference, Access access) - { - this.deobfuscatingTranslator = deobfuscatingTranslator; - this.entry = reference.entry; - this.reference = reference; - this.access = access; - } + public BehaviorReferenceTreeNode(Translator deobfuscatingTranslator, + EntryReference reference, Access access) { + this.deobfuscatingTranslator = deobfuscatingTranslator; + this.entry = reference.entry; + this.reference = reference; + this.access = access; + } - @Override public BehaviorEntry getEntry() - { - return this.entry; - } + @Override + public BehaviorEntry getEntry() { + return this.entry; + } - @Override public EntryReference getReference() - { - return this.reference; - } + @Override + public EntryReference getReference() { + return this.reference; + } - @Override public String toString() - { - if (this.reference != null) - { - return String.format("%s (%s)", this.deobfuscatingTranslator.translateEntry(this.reference.context), - this.access); - } - return this.deobfuscatingTranslator.translateEntry(this.entry).toString(); - } + @Override + public String toString() { + if (this.reference != null) { + return String.format("%s (%s)", this.deobfuscatingTranslator.translateEntry(this.reference.context), + this.access); + } + return this.deobfuscatingTranslator.translateEntry(this.entry).toString(); + } - public void load(JarIndex index, boolean recurse) - { - // get all the child nodes - for (EntryReference reference : index.getBehaviorReferences(this.entry)) - { - add(new BehaviorReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccess(this.entry))); - } + public void load(JarIndex index, boolean recurse) { + // get all the child nodes + for (EntryReference reference : index.getBehaviorReferences(this.entry)) { + add(new BehaviorReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccess(this.entry))); + } - if (recurse && this.children != null) - { - for (Object child : this.children) - { - if (child instanceof BehaviorReferenceTreeNode) - { - BehaviorReferenceTreeNode node = (BehaviorReferenceTreeNode) child; + if (recurse && this.children != null) { + for (Object child : this.children) { + if (child instanceof BehaviorReferenceTreeNode) { + BehaviorReferenceTreeNode node = (BehaviorReferenceTreeNode) child; - // don't recurse into ancestor - Set ancestors = Sets.newHashSet(); - TreeNode n = node; - while (n.getParent() != null) - { - n = n.getParent(); - if (n instanceof BehaviorReferenceTreeNode) - { - ancestors.add(((BehaviorReferenceTreeNode) n).getEntry()); - } - } - if (ancestors.contains(node.getEntry())) - { - continue; - } + // don't recurse into ancestor + Set ancestors = Sets.newHashSet(); + TreeNode n = node; + while (n.getParent() != null) { + n = n.getParent(); + if (n instanceof BehaviorReferenceTreeNode) { + ancestors.add(((BehaviorReferenceTreeNode) n).getEntry()); + } + } + if (ancestors.contains(node.getEntry())) { + continue; + } - node.load(index, true); - } - } - } - } + node.load(index, true); + } + } + } + } } diff --git a/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java b/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java index 0f4be7d9..81e750c1 100644 --- a/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java +++ b/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java @@ -8,6 +8,7 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.analysis; import cuchaz.enigma.mapping.EntryFactory; @@ -18,26 +19,26 @@ import javassist.bytecode.AccessFlag; public class BridgeMarker { - private JarIndex jarIndex; + private JarIndex jarIndex; - public BridgeMarker(JarIndex jarIndex) { - this.jarIndex = jarIndex; - } + public BridgeMarker(JarIndex jarIndex) { + this.jarIndex = jarIndex; + } - public void markBridges(CtClass c) { + public void markBridges(CtClass c) { - for (CtMethod method : c.getDeclaredMethods()) { - MethodEntry methodEntry = EntryFactory.getMethodEntry(method); + for (CtMethod method : c.getDeclaredMethods()) { + MethodEntry methodEntry = EntryFactory.getMethodEntry(method); - // is this a bridge method? - MethodEntry bridgedMethodEntry = this.jarIndex.getBridgedMethod(methodEntry); - if (bridgedMethodEntry != null) { + // is this a bridge method? + MethodEntry bridgedMethodEntry = this.jarIndex.getBridgedMethod(methodEntry); + if (bridgedMethodEntry != null) { - // it's a bridge method! add the bridge flag - int flags = method.getMethodInfo().getAccessFlags(); - flags |= AccessFlag.BRIDGE; - method.getMethodInfo().setAccessFlags(flags); - } - } - } + // it's a bridge method! add the bridge flag + int flags = method.getMethodInfo().getAccessFlags(); + flags |= AccessFlag.BRIDGE; + method.getMethodInfo().setAccessFlags(flags); + } + } + } } diff --git a/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java b/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java index 70ece243..f2fb2f8d 100644 --- a/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java +++ b/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java @@ -8,69 +8,68 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.analysis; import com.google.common.collect.Lists; - -import java.util.List; - -import javax.swing.tree.DefaultMutableTreeNode; - import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.MethodEntry; import cuchaz.enigma.mapping.Translator; +import javax.swing.tree.DefaultMutableTreeNode; +import java.util.List; + public class ClassImplementationsTreeNode extends DefaultMutableTreeNode { - private Translator deobfuscatingTranslator; - private ClassEntry entry; + private Translator deobfuscatingTranslator; + private ClassEntry entry; - public ClassImplementationsTreeNode(Translator deobfuscatingTranslator, ClassEntry entry) { - this.deobfuscatingTranslator = deobfuscatingTranslator; - this.entry = entry; - } + public ClassImplementationsTreeNode(Translator deobfuscatingTranslator, ClassEntry entry) { + this.deobfuscatingTranslator = deobfuscatingTranslator; + this.entry = entry; + } - public ClassEntry getClassEntry() { - return this.entry; - } + public static ClassImplementationsTreeNode findNode(ClassImplementationsTreeNode node, MethodEntry entry) { + // is this the node? + if (node.entry.equals(entry.getClassEntry())) { + return node; + } - public String getDeobfClassName() { - return this.deobfuscatingTranslator.translateClass(this.entry.getClassName()); - } + // recurse + for (int i = 0; i < node.getChildCount(); i++) { + ClassImplementationsTreeNode foundNode = findNode((ClassImplementationsTreeNode) node.getChildAt(i), entry); + if (foundNode != null) { + return foundNode; + } + } + return null; + } - @Override - public String toString() { - String className = getDeobfClassName(); - if (className == null) { - className = this.entry.getClassName(); - } - return className; - } + public ClassEntry getClassEntry() { + return this.entry; + } - public void load(JarIndex index) { - // get all method implementations - List nodes = Lists.newArrayList(); - for (String implementingClassName : index.getImplementingClasses(this.entry.getClassName())) { - nodes.add(new ClassImplementationsTreeNode(this.deobfuscatingTranslator, new ClassEntry(implementingClassName))); - } + public String getDeobfClassName() { + return this.deobfuscatingTranslator.translateClass(this.entry.getClassName()); + } - // add them to this node - nodes.forEach(this::add); - } + @Override + public String toString() { + String className = getDeobfClassName(); + if (className == null) { + className = this.entry.getClassName(); + } + return className; + } - public static ClassImplementationsTreeNode findNode(ClassImplementationsTreeNode node, MethodEntry entry) { - // is this the node? - if (node.entry.equals(entry.getClassEntry())) { - return node; - } + public void load(JarIndex index) { + // get all method implementations + List nodes = Lists.newArrayList(); + for (String implementingClassName : index.getImplementingClasses(this.entry.getClassName())) { + nodes.add(new ClassImplementationsTreeNode(this.deobfuscatingTranslator, new ClassEntry(implementingClassName))); + } - // recurse - for (int i = 0; i < node.getChildCount(); i++) { - ClassImplementationsTreeNode foundNode = findNode((ClassImplementationsTreeNode) node.getChildAt(i), entry); - if (foundNode != null) { - return foundNode; - } - } - return null; - } + // add them to this node + nodes.forEach(this::add); + } } diff --git a/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java index 8a60fc72..24e7cb0b 100644 --- a/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java +++ b/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java @@ -8,74 +8,73 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.analysis; import com.google.common.collect.Lists; - -import java.util.List; - -import javax.swing.tree.DefaultMutableTreeNode; - import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.Translator; +import javax.swing.tree.DefaultMutableTreeNode; +import java.util.List; + public class ClassInheritanceTreeNode extends DefaultMutableTreeNode { - private Translator deobfuscatingTranslator; - private String obfClassName; + private Translator deobfuscatingTranslator; + private String obfClassName; - public ClassInheritanceTreeNode(Translator deobfuscatingTranslator, String obfClassName) { - this.deobfuscatingTranslator = deobfuscatingTranslator; - this.obfClassName = obfClassName; - } + public ClassInheritanceTreeNode(Translator deobfuscatingTranslator, String obfClassName) { + this.deobfuscatingTranslator = deobfuscatingTranslator; + this.obfClassName = obfClassName; + } - public String getObfClassName() { - return this.obfClassName; - } + public static ClassInheritanceTreeNode findNode(ClassInheritanceTreeNode node, ClassEntry entry) { + // is this the node? + if (node.getObfClassName().equals(entry.getName())) { + return node; + } - public String getDeobfClassName() { - return this.deobfuscatingTranslator.translateClass(this.obfClassName); - } + // recurse + for (int i = 0; i < node.getChildCount(); i++) { + ClassInheritanceTreeNode foundNode = findNode((ClassInheritanceTreeNode) node.getChildAt(i), entry); + if (foundNode != null) { + return foundNode; + } + } + return null; + } - @Override - public String toString() { - String deobfClassName = getDeobfClassName(); - if (deobfClassName != null) { - return deobfClassName; - } - return this.obfClassName; - } + public String getObfClassName() { + return this.obfClassName; + } - public void load(TranslationIndex ancestries, boolean recurse) { - // get all the child nodes - List nodes = Lists.newArrayList(); - for (ClassEntry subclassEntry : ancestries.getSubclass(new ClassEntry(this.obfClassName))) { - nodes.add(new ClassInheritanceTreeNode(this.deobfuscatingTranslator, subclassEntry.getName())); - } + public String getDeobfClassName() { + return this.deobfuscatingTranslator.translateClass(this.obfClassName); + } - // add them to this node - nodes.forEach(this::add); + @Override + public String toString() { + String deobfClassName = getDeobfClassName(); + if (deobfClassName != null) { + return deobfClassName; + } + return this.obfClassName; + } - if (recurse) { - for (ClassInheritanceTreeNode node : nodes) { - node.load(ancestries, true); - } - } - } + public void load(TranslationIndex ancestries, boolean recurse) { + // get all the child nodes + List nodes = Lists.newArrayList(); + for (ClassEntry subclassEntry : ancestries.getSubclass(new ClassEntry(this.obfClassName))) { + nodes.add(new ClassInheritanceTreeNode(this.deobfuscatingTranslator, subclassEntry.getName())); + } - public static ClassInheritanceTreeNode findNode(ClassInheritanceTreeNode node, ClassEntry entry) { - // is this the node? - if (node.getObfClassName().equals(entry.getName())) { - return node; - } + // add them to this node + nodes.forEach(this::add); - // recurse - for (int i = 0; i < node.getChildCount(); i++) { - ClassInheritanceTreeNode foundNode = findNode((ClassInheritanceTreeNode) node.getChildAt(i), entry); - if (foundNode != null) { - return foundNode; - } - } - return null; - } + if (recurse) { + for (ClassInheritanceTreeNode node : nodes) { + node.load(ancestries, true); + } + } + } } diff --git a/src/main/java/cuchaz/enigma/analysis/EntryReference.java b/src/main/java/cuchaz/enigma/analysis/EntryReference.java index ad4baf81..3761fca8 100644 --- a/src/main/java/cuchaz/enigma/analysis/EntryReference.java +++ b/src/main/java/cuchaz/enigma/analysis/EntryReference.java @@ -8,116 +8,117 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ -package cuchaz.enigma.analysis; -import java.util.Arrays; -import java.util.List; +package cuchaz.enigma.analysis; import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.ConstructorEntry; import cuchaz.enigma.mapping.Entry; import cuchaz.enigma.utils.Utils; +import java.util.Arrays; +import java.util.List; + public class EntryReference { - private static final List ConstructorNonNames = Arrays.asList("this", "super", "static"); - public E entry; - public C context; - - private boolean sourceName; - - public EntryReference(E entry, String sourceName) { - this(entry, sourceName, null); - } - - public EntryReference(E entry, String sourceName, C context) { - if (entry == null) { - throw new IllegalArgumentException("Entry cannot be null!"); - } - - this.entry = entry; - this.context = context; - - this.sourceName = sourceName != null && sourceName.length() > 0; - if (entry instanceof ConstructorEntry && ConstructorNonNames.contains(sourceName)) { - this.sourceName = false; - } - } - - public EntryReference(E entry, C context, EntryReference other) { - this.entry = entry; - this.context = context; - this.sourceName = other.sourceName; - } - - public ClassEntry getLocationClassEntry() { - if (context != null) { - return context.getClassEntry(); - } - return entry.getClassEntry(); - } - - public boolean isNamed() { - return this.sourceName; - } - - public Entry getNameableEntry() { - if (entry instanceof ConstructorEntry) { - // renaming a constructor really means renaming the class - return entry.getClassEntry(); - } - return entry; - } - - public String getNamableName() { - if (getNameableEntry() instanceof ClassEntry) { - ClassEntry classEntry = (ClassEntry) getNameableEntry(); - if (classEntry.isInnerClass()) { - // make sure we only rename the inner class name - return classEntry.getInnermostClassName(); - } - } - - return getNameableEntry().getName(); - } - - @Override - public int hashCode() { - if (context != null) { - return Utils.combineHashesOrdered(entry.hashCode(), context.hashCode()); - } - return entry.hashCode(); - } - - @Override - public boolean equals(Object other) { - return other instanceof EntryReference && equals((EntryReference) other); - } - - public boolean equals(EntryReference other) { - // check entry first - boolean isEntrySame = entry.equals(other.entry); - if (!isEntrySame) { - return false; - } - - // check caller - if (context == null && other.context == null) { - return true; - } else if (context != null && other.context != null) { - return context.equals(other.context); - } - return false; - } - - @Override - public String toString() { - StringBuilder buf = new StringBuilder(); - buf.append(entry); - if (context != null) { - buf.append(" called from "); - buf.append(context); - } - return buf.toString(); - } + private static final List ConstructorNonNames = Arrays.asList("this", "super", "static"); + public E entry; + public C context; + + private boolean sourceName; + + public EntryReference(E entry, String sourceName) { + this(entry, sourceName, null); + } + + public EntryReference(E entry, String sourceName, C context) { + if (entry == null) { + throw new IllegalArgumentException("Entry cannot be null!"); + } + + this.entry = entry; + this.context = context; + + this.sourceName = sourceName != null && !sourceName.isEmpty(); + if (entry instanceof ConstructorEntry && ConstructorNonNames.contains(sourceName)) { + this.sourceName = false; + } + } + + public EntryReference(E entry, C context, EntryReference other) { + this.entry = entry; + this.context = context; + this.sourceName = other.sourceName; + } + + public ClassEntry getLocationClassEntry() { + if (context != null) { + return context.getClassEntry(); + } + return entry.getClassEntry(); + } + + public boolean isNamed() { + return this.sourceName; + } + + public Entry getNameableEntry() { + if (entry instanceof ConstructorEntry) { + // renaming a constructor really means renaming the class + return entry.getClassEntry(); + } + return entry; + } + + public String getNamableName() { + if (getNameableEntry() instanceof ClassEntry) { + ClassEntry classEntry = (ClassEntry) getNameableEntry(); + if (classEntry.isInnerClass()) { + // make sure we only rename the inner class name + return classEntry.getInnermostClassName(); + } + } + + return getNameableEntry().getName(); + } + + @Override + public int hashCode() { + if (context != null) { + return Utils.combineHashesOrdered(entry.hashCode(), context.hashCode()); + } + return entry.hashCode(); + } + + @Override + public boolean equals(Object other) { + return other instanceof EntryReference && equals((EntryReference) other); + } + + public boolean equals(EntryReference other) { + // check entry first + boolean isEntrySame = entry.equals(other.entry); + if (!isEntrySame) { + return false; + } + + // check caller + if (context == null && other.context == null) { + return true; + } else if (context != null && other.context != null) { + return context.equals(other.context); + } + return false; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append(entry); + if (context != null) { + buf.append(" called from "); + buf.append(context); + } + return buf.toString(); + } } diff --git a/src/main/java/cuchaz/enigma/analysis/EntryRenamer.java b/src/main/java/cuchaz/enigma/analysis/EntryRenamer.java index 7233fcf9..75806c33 100644 --- a/src/main/java/cuchaz/enigma/analysis/EntryRenamer.java +++ b/src/main/java/cuchaz/enigma/analysis/EntryRenamer.java @@ -8,140 +8,140 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.analysis; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; +import cuchaz.enigma.mapping.*; import java.util.AbstractMap; import java.util.List; import java.util.Map; import java.util.Set; -import cuchaz.enigma.mapping.*; - public class EntryRenamer { - public static void renameClassesInSet(Map renames, Set set) { - List entries = Lists.newArrayList(); - for (T val : set) { - entries.add(renameClassesInThing(renames, val)); - } - set.clear(); - set.addAll(entries); - } + public static void renameClassesInSet(Map renames, Set set) { + List entries = Lists.newArrayList(); + for (T val : set) { + entries.add(renameClassesInThing(renames, val)); + } + set.clear(); + set.addAll(entries); + } - public static void renameClassesInMap(Map renames, Map map) { - // for each key/value pair... - Set> entriesToAdd = Sets.newHashSet(); - for (Map.Entry entry : map.entrySet()) { - entriesToAdd.add(new AbstractMap.SimpleEntry<>(renameClassesInThing(renames, entry.getKey()), renameClassesInThing(renames, entry.getValue()))); - } - map.clear(); - for (Map.Entry entry : entriesToAdd) { - map.put(entry.getKey(), entry.getValue()); - } - } + public static void renameClassesInMap(Map renames, Map map) { + // for each key/value pair... + Set> entriesToAdd = Sets.newHashSet(); + for (Map.Entry entry : map.entrySet()) { + entriesToAdd.add(new AbstractMap.SimpleEntry<>(renameClassesInThing(renames, entry.getKey()), renameClassesInThing(renames, entry.getValue()))); + } + map.clear(); + for (Map.Entry entry : entriesToAdd) { + map.put(entry.getKey(), entry.getValue()); + } + } - public static void renameClassesInMultimap(Map renames, Multimap map) { - // for each key/value pair... - Set> entriesToAdd = Sets.newHashSet(); - for (Map.Entry entry : map.entries()) { - entriesToAdd.add(new AbstractMap.SimpleEntry<>(renameClassesInThing(renames, entry.getKey()), renameClassesInThing(renames, entry.getValue()))); - } - map.clear(); - for (Map.Entry entry : entriesToAdd) { - map.put(entry.getKey(), entry.getValue()); - } - } + public static void renameClassesInMultimap(Map renames, Multimap map) { + // for each key/value pair... + Set> entriesToAdd = Sets.newHashSet(); + for (Map.Entry entry : map.entries()) { + entriesToAdd.add(new AbstractMap.SimpleEntry<>(renameClassesInThing(renames, entry.getKey()), renameClassesInThing(renames, entry.getValue()))); + } + map.clear(); + for (Map.Entry entry : entriesToAdd) { + map.put(entry.getKey(), entry.getValue()); + } + } - public static void renameMethodsInMultimap(Map renames, Multimap map) { - // for each key/value pair... - Set> entriesToAdd = Sets.newHashSet(); - for (Map.Entry entry : map.entries()) { - entriesToAdd.add(new AbstractMap.SimpleEntry<>(renameMethodsInThing(renames, entry.getKey()), renameMethodsInThing(renames, entry.getValue()))); - } - map.clear(); - for (Map.Entry entry : entriesToAdd) { - map.put(entry.getKey(), entry.getValue()); - } - } + public static void renameMethodsInMultimap(Map renames, Multimap map) { + // for each key/value pair... + Set> entriesToAdd = Sets.newHashSet(); + for (Map.Entry entry : map.entries()) { + entriesToAdd.add(new AbstractMap.SimpleEntry<>(renameMethodsInThing(renames, entry.getKey()), renameMethodsInThing(renames, entry.getValue()))); + } + map.clear(); + for (Map.Entry entry : entriesToAdd) { + map.put(entry.getKey(), entry.getValue()); + } + } - public static void renameMethodsInMap(Map renames, Map map) { - // for each key/value pair... - Set> entriesToAdd = Sets.newHashSet(); - for (Map.Entry entry : map.entrySet()) { - entriesToAdd.add(new AbstractMap.SimpleEntry<>(renameMethodsInThing(renames, entry.getKey()), renameMethodsInThing(renames, entry.getValue()))); - } - map.clear(); - for (Map.Entry entry : entriesToAdd) { - map.put(entry.getKey(), entry.getValue()); - } - } + public static void renameMethodsInMap(Map renames, Map map) { + // for each key/value pair... + Set> entriesToAdd = Sets.newHashSet(); + for (Map.Entry entry : map.entrySet()) { + entriesToAdd.add(new AbstractMap.SimpleEntry<>(renameMethodsInThing(renames, entry.getKey()), renameMethodsInThing(renames, entry.getValue()))); + } + map.clear(); + for (Map.Entry entry : entriesToAdd) { + map.put(entry.getKey(), entry.getValue()); + } + } - @SuppressWarnings("unchecked") - public static T renameMethodsInThing(Map renames, T thing) { - if (thing instanceof MethodEntry) { - MethodEntry methodEntry = (MethodEntry)thing; - MethodEntry newMethodEntry = renames.get(methodEntry); - if (newMethodEntry != null) { - return (T)new MethodEntry( - methodEntry.getClassEntry(), - newMethodEntry.getName(), - methodEntry.getSignature() - ); - } - return thing; - } else if (thing instanceof ArgumentEntry) { - ArgumentEntry argumentEntry = (ArgumentEntry)thing; - return (T)new ArgumentEntry( - renameMethodsInThing(renames, argumentEntry.getBehaviorEntry()), - argumentEntry.getIndex(), - argumentEntry.getName() - ); - } else if (thing instanceof EntryReference) { - EntryReference reference = (EntryReference)thing; - reference.entry = renameMethodsInThing(renames, reference.entry); - reference.context = renameMethodsInThing(renames, reference.context); - return thing; - } - return thing; - } + @SuppressWarnings("unchecked") + public static T renameMethodsInThing(Map renames, T thing) { + if (thing instanceof MethodEntry) { + MethodEntry methodEntry = (MethodEntry) thing; + MethodEntry newMethodEntry = renames.get(methodEntry); + if (newMethodEntry != null) { + return (T) new MethodEntry( + methodEntry.getClassEntry(), + newMethodEntry.getName(), + methodEntry.getSignature() + ); + } + return thing; + } else if (thing instanceof ArgumentEntry) { + ArgumentEntry argumentEntry = (ArgumentEntry) thing; + return (T) new ArgumentEntry( + renameMethodsInThing(renames, argumentEntry.getBehaviorEntry()), + argumentEntry.getIndex(), + argumentEntry.getName() + ); + } else if (thing instanceof EntryReference) { + EntryReference reference = (EntryReference) thing; + reference.entry = renameMethodsInThing(renames, reference.entry); + reference.context = renameMethodsInThing(renames, reference.context); + return thing; + } + return thing; + } - @SuppressWarnings("unchecked") - public static T renameClassesInThing(final Map renames, T thing) { - if (thing instanceof String) { - String stringEntry = (String) thing; - if (renames.containsKey(stringEntry)) { - return (T) renames.get(stringEntry); - } - } else if (thing instanceof ClassEntry) { - ClassEntry classEntry = (ClassEntry) thing; - return (T) new ClassEntry(renameClassesInThing(renames, classEntry.getClassName())); - } else if (thing instanceof FieldEntry) { - FieldEntry fieldEntry = (FieldEntry) thing; - return (T) new FieldEntry(renameClassesInThing(renames, fieldEntry.getClassEntry()), fieldEntry.getName(), renameClassesInThing(renames, fieldEntry.getType())); - } else if (thing instanceof ConstructorEntry) { - ConstructorEntry constructorEntry = (ConstructorEntry) thing; - return (T) new ConstructorEntry(renameClassesInThing(renames, constructorEntry.getClassEntry()), renameClassesInThing(renames, constructorEntry.getSignature())); - } else if (thing instanceof MethodEntry) { - MethodEntry methodEntry = (MethodEntry) thing; - return (T) new MethodEntry(renameClassesInThing(renames, methodEntry.getClassEntry()), methodEntry.getName(), renameClassesInThing(renames, methodEntry.getSignature())); - } else if (thing instanceof ArgumentEntry) { - ArgumentEntry argumentEntry = (ArgumentEntry) thing; - return (T) new ArgumentEntry(renameClassesInThing(renames, argumentEntry.getBehaviorEntry()), argumentEntry.getIndex(), argumentEntry.getName()); - } else if (thing instanceof EntryReference) { - EntryReference reference = (EntryReference) thing; - reference.entry = renameClassesInThing(renames, reference.entry); - reference.context = renameClassesInThing(renames, reference.context); - return thing; - } else if (thing instanceof Signature) { - return (T) new Signature((Signature) thing, className -> renameClassesInThing(renames, className)); - } else if (thing instanceof Type) { - return (T) new Type((Type) thing, className -> renameClassesInThing(renames, className)); - } + @SuppressWarnings("unchecked") + public static T renameClassesInThing(final Map renames, T thing) { + if (thing instanceof String) { + String stringEntry = (String) thing; + if (renames.containsKey(stringEntry)) { + return (T) renames.get(stringEntry); + } + } else if (thing instanceof ClassEntry) { + ClassEntry classEntry = (ClassEntry) thing; + return (T) new ClassEntry(renameClassesInThing(renames, classEntry.getClassName())); + } else if (thing instanceof FieldEntry) { + FieldEntry fieldEntry = (FieldEntry) thing; + return (T) new FieldEntry(renameClassesInThing(renames, fieldEntry.getClassEntry()), fieldEntry.getName(), renameClassesInThing(renames, fieldEntry.getType())); + } else if (thing instanceof ConstructorEntry) { + ConstructorEntry constructorEntry = (ConstructorEntry) thing; + return (T) new ConstructorEntry(renameClassesInThing(renames, constructorEntry.getClassEntry()), renameClassesInThing(renames, constructorEntry.getSignature())); + } else if (thing instanceof MethodEntry) { + MethodEntry methodEntry = (MethodEntry) thing; + return (T) new MethodEntry(renameClassesInThing(renames, methodEntry.getClassEntry()), methodEntry.getName(), renameClassesInThing(renames, methodEntry.getSignature())); + } else if (thing instanceof ArgumentEntry) { + ArgumentEntry argumentEntry = (ArgumentEntry) thing; + return (T) new ArgumentEntry(renameClassesInThing(renames, argumentEntry.getBehaviorEntry()), argumentEntry.getIndex(), argumentEntry.getName()); + } else if (thing instanceof EntryReference) { + EntryReference reference = (EntryReference) thing; + reference.entry = renameClassesInThing(renames, reference.entry); + reference.context = renameClassesInThing(renames, reference.context); + return thing; + } else if (thing instanceof Signature) { + return (T) new Signature((Signature) thing, className -> renameClassesInThing(renames, className)); + } else if (thing instanceof Type) { + return (T) new Type((Type) thing, className -> renameClassesInThing(renames, className)); + } - return thing; - } + return thing; + } } diff --git a/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java index 70cd0590..34d2eff1 100644 --- a/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java +++ b/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java @@ -8,72 +8,73 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ -package cuchaz.enigma.analysis; -import javax.swing.tree.DefaultMutableTreeNode; +package cuchaz.enigma.analysis; import cuchaz.enigma.mapping.BehaviorEntry; import cuchaz.enigma.mapping.FieldEntry; import cuchaz.enigma.mapping.Translator; +import javax.swing.tree.DefaultMutableTreeNode; + public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode { - private Translator deobfuscatingTranslator; - private FieldEntry entry; - private EntryReference reference; - private Access access; + private Translator deobfuscatingTranslator; + private FieldEntry entry; + private EntryReference reference; + private Access access; - public FieldReferenceTreeNode(Translator deobfuscatingTranslator, FieldEntry entry) { - this.deobfuscatingTranslator = deobfuscatingTranslator; - this.entry = entry; - this.reference = null; - } + public FieldReferenceTreeNode(Translator deobfuscatingTranslator, FieldEntry entry) { + this.deobfuscatingTranslator = deobfuscatingTranslator; + this.entry = entry; + this.reference = null; + } - private FieldReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference reference, Access access) { - this.deobfuscatingTranslator = deobfuscatingTranslator; - this.entry = reference.entry; - this.reference = reference; - this.access = access; - } + private FieldReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference reference, Access access) { + this.deobfuscatingTranslator = deobfuscatingTranslator; + this.entry = reference.entry; + this.reference = reference; + this.access = access; + } - @Override - public FieldEntry getEntry() { - return this.entry; - } + @Override + public FieldEntry getEntry() { + return this.entry; + } - @Override - public EntryReference getReference() { - return this.reference; - } + @Override + public EntryReference getReference() { + return this.reference; + } - @Override - public String toString() { - if (this.reference != null) { - return String.format("%s (%s)", this.deobfuscatingTranslator.translateEntry(this.reference.context), this.access); - } - return this.deobfuscatingTranslator.translateEntry(this.entry).toString(); - } + @Override + public String toString() { + if (this.reference != null) { + return String.format("%s (%s)", this.deobfuscatingTranslator.translateEntry(this.reference.context), this.access); + } + return this.deobfuscatingTranslator.translateEntry(this.entry).toString(); + } - public void load(JarIndex index, boolean recurse) { - // get all the child nodes - if (this.reference == null) { - for (EntryReference reference : index.getFieldReferences(this.entry)) { - add(new FieldReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccess(this.entry))); - } - } else { - for (EntryReference reference : index.getBehaviorReferences(this.reference.context)) { - add(new BehaviorReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccess(this.reference.context))); - } - } + public void load(JarIndex index, boolean recurse) { + // get all the child nodes + if (this.reference == null) { + for (EntryReference reference : index.getFieldReferences(this.entry)) { + add(new FieldReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccess(this.entry))); + } + } else { + for (EntryReference reference : index.getBehaviorReferences(this.reference.context)) { + add(new BehaviorReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccess(this.reference.context))); + } + } - if (recurse && children != null) { - for (Object node : children) { - if (node instanceof BehaviorReferenceTreeNode) { - ((BehaviorReferenceTreeNode) node).load(index, true); - } else if (node instanceof FieldReferenceTreeNode) { - ((FieldReferenceTreeNode) node).load(index, true); - } - } - } - } + if (recurse && children != null) { + for (Object node : children) { + if (node instanceof BehaviorReferenceTreeNode) { + ((BehaviorReferenceTreeNode) node).load(index, true); + } else if (node instanceof FieldReferenceTreeNode) { + ((FieldReferenceTreeNode) node).load(index, true); + } + } + } + } } diff --git a/src/main/java/cuchaz/enigma/analysis/JarClassIterator.java b/src/main/java/cuchaz/enigma/analysis/JarClassIterator.java index 0d18105e..87d3797d 100644 --- a/src/main/java/cuchaz/enigma/analysis/JarClassIterator.java +++ b/src/main/java/cuchaz/enigma/analysis/JarClassIterator.java @@ -8,9 +8,17 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.analysis; import com.google.common.collect.Lists; +import cuchaz.enigma.Constants; +import cuchaz.enigma.mapping.ClassEntry; +import javassist.ByteArrayClassPath; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.NotFoundException; +import javassist.bytecode.Descriptor; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -21,103 +29,95 @@ import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarFile; -import cuchaz.enigma.Constants; -import cuchaz.enigma.mapping.ClassEntry; -import javassist.ByteArrayClassPath; -import javassist.ClassPool; -import javassist.CtClass; -import javassist.NotFoundException; -import javassist.bytecode.Descriptor; - public class JarClassIterator implements Iterator { - private JarFile jar; - private Iterator iter; - - public JarClassIterator(JarFile jar) { - this.jar = jar; - - // get the jar entries that correspond to classes - List classEntries = Lists.newArrayList(); - Enumeration entries = this.jar.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - - // is this a class file? - if (entry.getName().endsWith(".class")) { - classEntries.add(entry); - } - } - this.iter = classEntries.iterator(); - } - - @Override - public boolean hasNext() { - return this.iter.hasNext(); - } - - @Override - public CtClass next() { - JarEntry entry = this.iter.next(); - try { - return getClass(this.jar, entry); - } catch (IOException | NotFoundException ex) { - throw new Error("Unable to load class: " + entry.getName()); - } - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - - public static List getClassEntries(JarFile jar) { - List classEntries = Lists.newArrayList(); - Enumeration entries = jar.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - - // is this a class file? - if (!entry.isDirectory() && entry.getName().endsWith(".class")) { - classEntries.add(getClassEntry(entry)); - } - } - return classEntries; - } - - public static Iterable classes(final JarFile jar) { - return () -> new JarClassIterator(jar); - } - - private static CtClass getClass(JarFile jar, JarEntry entry) throws IOException, NotFoundException { - // read the class into a buffer - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - byte[] buf = new byte[Constants.KiB]; - int totalNumBytesRead = 0; - InputStream in = jar.getInputStream(entry); - while (in.available() > 0) { - int numBytesRead = in.read(buf); - if (numBytesRead < 0) { - break; - } - bos.write(buf, 0, numBytesRead); - - // sanity checking - totalNumBytesRead += numBytesRead; - if (totalNumBytesRead > Constants.MiB) { - throw new Error("Class file " + entry.getName() + " larger than 1 MiB! Something is wrong!"); - } - } - - // get a javassist handle for the class - String className = Descriptor.toJavaName(getClassEntry(entry).getName()); - ClassPool classPool = new ClassPool(); - classPool.appendSystemPath(); - classPool.insertClassPath(new ByteArrayClassPath(className, bos.toByteArray())); - return classPool.get(className); - } - - private static ClassEntry getClassEntry(JarEntry entry) { - return new ClassEntry(entry.getName().substring(0, entry.getName().length() - ".class".length())); - } + private JarFile jar; + private Iterator iter; + + public JarClassIterator(JarFile jar) { + this.jar = jar; + + // get the jar entries that correspond to classes + List classEntries = Lists.newArrayList(); + Enumeration entries = this.jar.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + + // is this a class file? + if (entry.getName().endsWith(".class")) { + classEntries.add(entry); + } + } + this.iter = classEntries.iterator(); + } + + public static List getClassEntries(JarFile jar) { + List classEntries = Lists.newArrayList(); + Enumeration entries = jar.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + + // is this a class file? + if (!entry.isDirectory() && entry.getName().endsWith(".class")) { + classEntries.add(getClassEntry(entry)); + } + } + return classEntries; + } + + public static Iterable classes(final JarFile jar) { + return () -> new JarClassIterator(jar); + } + + private static CtClass getClass(JarFile jar, JarEntry entry) throws IOException, NotFoundException { + // read the class into a buffer + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + byte[] buf = new byte[Constants.KiB]; + int totalNumBytesRead = 0; + InputStream in = jar.getInputStream(entry); + while (in.available() > 0) { + int numBytesRead = in.read(buf); + if (numBytesRead < 0) { + break; + } + bos.write(buf, 0, numBytesRead); + + // sanity checking + totalNumBytesRead += numBytesRead; + if (totalNumBytesRead > Constants.MiB) { + throw new Error("Class file " + entry.getName() + " larger than 1 MiB! Something is wrong!"); + } + } + + // get a javassist handle for the class + String className = Descriptor.toJavaName(getClassEntry(entry).getName()); + ClassPool classPool = new ClassPool(); + classPool.appendSystemPath(); + classPool.insertClassPath(new ByteArrayClassPath(className, bos.toByteArray())); + return classPool.get(className); + } + + private static ClassEntry getClassEntry(JarEntry entry) { + return new ClassEntry(entry.getName().substring(0, entry.getName().length() - ".class".length())); + } + + @Override + public boolean hasNext() { + return this.iter.hasNext(); + } + + @Override + public CtClass next() { + JarEntry entry = this.iter.next(); + try { + return getClass(this.jar, entry); + } catch (IOException | NotFoundException ex) { + throw new Error("Unable to load class: " + entry.getName()); + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } } diff --git a/src/main/java/cuchaz/enigma/analysis/JarIndex.java b/src/main/java/cuchaz/enigma/analysis/JarIndex.java index e8f74cc5..ea87dda5 100644 --- a/src/main/java/cuchaz/enigma/analysis/JarIndex.java +++ b/src/main/java/cuchaz/enigma/analysis/JarIndex.java @@ -8,825 +8,824 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.analysis; import com.google.common.collect.*; - -import java.lang.reflect.Modifier; -import java.util.*; -import java.util.jar.JarFile; - import cuchaz.enigma.mapping.*; import cuchaz.enigma.mapping.Translator; import javassist.*; import javassist.bytecode.*; import javassist.expr.*; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.jar.JarFile; + public class JarIndex { - private Set obfClassEntries; - private TranslationIndex translationIndex; - private Map access; - private Multimap fields; - private Multimap behaviors; - private Multimap methodImplementations; - private Multimap> behaviorReferences; - private Multimap> fieldReferences; - private Multimap innerClassesByOuter; - private Map outerClassesByInner; - private Map anonymousClasses; - private Map bridgedMethods; - private Set syntheticMethods; - - public JarIndex() { - this.obfClassEntries = Sets.newHashSet(); - this.translationIndex = new TranslationIndex(); - this.access = Maps.newHashMap(); - this.fields = HashMultimap.create(); - this.behaviors = HashMultimap.create(); - this.methodImplementations = HashMultimap.create(); - this.behaviorReferences = HashMultimap.create(); - this.fieldReferences = HashMultimap.create(); - this.innerClassesByOuter = HashMultimap.create(); - this.outerClassesByInner = Maps.newHashMap(); - this.anonymousClasses = Maps.newHashMap(); - this.bridgedMethods = Maps.newHashMap(); - this.syntheticMethods = Sets.newHashSet(); - } - - public void indexJar(JarFile jar, boolean buildInnerClasses) { - - // step 1: read the class names - this.obfClassEntries.addAll(JarClassIterator.getClassEntries(jar)); - - // step 2: index field/method/constructor access - for (CtClass c : JarClassIterator.classes(jar)) { - for (CtField field : c.getDeclaredFields()) { - FieldEntry fieldEntry = EntryFactory.getFieldEntry(field); - this.access.put(fieldEntry, Access.get(field)); - this.fields.put(fieldEntry.getClassEntry(), fieldEntry); - } - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); - this.access.put(behaviorEntry, Access.get(behavior)); - this.behaviors.put(behaviorEntry.getClassEntry(), behaviorEntry); - } - } - - // step 3: index extends, implements, fields, and methods - for (CtClass c : JarClassIterator.classes(jar)) { - this.translationIndex.indexClass(c); - String className = Descriptor.toJvmName(c.getName()); - for (String interfaceName : c.getClassFile().getInterfaces()) { - className = Descriptor.toJvmName(className); - interfaceName = Descriptor.toJvmName(interfaceName); - if (className.equals(interfaceName)) { - throw new IllegalArgumentException("Class cannot be its own interface! " + className); - } - } - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - indexBehavior(behavior); - } - } - - // step 4: index field, method, constructor references - for (CtClass c : JarClassIterator.classes(jar)) { - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - indexBehaviorReferences(behavior); - } - } - - if (buildInnerClasses) { - - // step 5: index inner classes and anonymous classes - for (CtClass c : JarClassIterator.classes(jar)) { - ClassEntry innerClassEntry = EntryFactory.getClassEntry(c); - ClassEntry outerClassEntry = findOuterClass(c); - if (outerClassEntry != null) { - this.innerClassesByOuter.put(outerClassEntry, innerClassEntry); - boolean innerWasAdded = this.outerClassesByInner.put(innerClassEntry, outerClassEntry) == null; - assert (innerWasAdded); - - BehaviorEntry enclosingBehavior = isAnonymousClass(c, outerClassEntry); - if (enclosingBehavior != null) { - this.anonymousClasses.put(innerClassEntry, enclosingBehavior); - - // DEBUG - //System.out.println("ANONYMOUS: " + outerClassEntry.getName() + "$" + innerClassEntry.getSimpleName()); - }/* else { - // DEBUG + private Set obfClassEntries; + private TranslationIndex translationIndex; + private Map access; + private Multimap fields; + private Multimap behaviors; + private Multimap methodImplementations; + private Multimap> behaviorReferences; + private Multimap> fieldReferences; + private Multimap innerClassesByOuter; + private Map outerClassesByInner; + private Map anonymousClasses; + private Map bridgedMethods; + private Set syntheticMethods; + + public JarIndex() { + this.obfClassEntries = Sets.newHashSet(); + this.translationIndex = new TranslationIndex(); + this.access = Maps.newHashMap(); + this.fields = HashMultimap.create(); + this.behaviors = HashMultimap.create(); + this.methodImplementations = HashMultimap.create(); + this.behaviorReferences = HashMultimap.create(); + this.fieldReferences = HashMultimap.create(); + this.innerClassesByOuter = HashMultimap.create(); + this.outerClassesByInner = Maps.newHashMap(); + this.anonymousClasses = Maps.newHashMap(); + this.bridgedMethods = Maps.newHashMap(); + this.syntheticMethods = Sets.newHashSet(); + } + + public void indexJar(JarFile jar, boolean buildInnerClasses) { + + // step 1: read the class names + this.obfClassEntries.addAll(JarClassIterator.getClassEntries(jar)); + + // step 2: index field/method/constructor access + for (CtClass c : JarClassIterator.classes(jar)) { + for (CtField field : c.getDeclaredFields()) { + FieldEntry fieldEntry = EntryFactory.getFieldEntry(field); + this.access.put(fieldEntry, Access.get(field)); + this.fields.put(fieldEntry.getClassEntry(), fieldEntry); + } + for (CtBehavior behavior : c.getDeclaredBehaviors()) { + BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); + this.access.put(behaviorEntry, Access.get(behavior)); + this.behaviors.put(behaviorEntry.getClassEntry(), behaviorEntry); + } + } + + // step 3: index extends, implements, fields, and methods + for (CtClass c : JarClassIterator.classes(jar)) { + this.translationIndex.indexClass(c); + String className = Descriptor.toJvmName(c.getName()); + for (String interfaceName : c.getClassFile().getInterfaces()) { + className = Descriptor.toJvmName(className); + interfaceName = Descriptor.toJvmName(interfaceName); + if (className.equals(interfaceName)) { + throw new IllegalArgumentException("Class cannot be its own interface! " + className); + } + } + for (CtBehavior behavior : c.getDeclaredBehaviors()) { + indexBehavior(behavior); + } + } + + // step 4: index field, method, constructor references + for (CtClass c : JarClassIterator.classes(jar)) { + for (CtBehavior behavior : c.getDeclaredBehaviors()) { + indexBehaviorReferences(behavior); + } + } + + if (buildInnerClasses) { + + // step 5: index inner classes and anonymous classes + for (CtClass c : JarClassIterator.classes(jar)) { + ClassEntry innerClassEntry = EntryFactory.getClassEntry(c); + ClassEntry outerClassEntry = findOuterClass(c); + if (outerClassEntry != null) { + this.innerClassesByOuter.put(outerClassEntry, innerClassEntry); + boolean innerWasAdded = this.outerClassesByInner.put(innerClassEntry, outerClassEntry) == null; + assert (innerWasAdded); + + BehaviorEntry enclosingBehavior = isAnonymousClass(c, outerClassEntry); + if (enclosingBehavior != null) { + this.anonymousClasses.put(innerClassEntry, enclosingBehavior); + + // DEBUG + //System.out.println("ANONYMOUS: " + outerClassEntry.getName() + "$" + innerClassEntry.getSimpleName()); + }/* else { + // DEBUG //System.out.println("INNER: " + outerClassEntry.getName() + "$" + innerClassEntry.getSimpleName()); }*/ - } - } - - // step 6: update other indices with inner class info - Map renames = Maps.newHashMap(); - for (ClassEntry innerClassEntry : this.innerClassesByOuter.values()) { - String newName = innerClassEntry.buildClassEntry(getObfClassChain(innerClassEntry)).getName(); - if (!innerClassEntry.getName().equals(newName)) { - // DEBUG - //System.out.println("REPLACE: " + innerClassEntry.getName() + " WITH " + newName); - renames.put(innerClassEntry.getName(), newName); - } - } - EntryRenamer.renameClassesInSet(renames, this.obfClassEntries); - this.translationIndex.renameClasses(renames); - EntryRenamer.renameClassesInMultimap(renames, this.methodImplementations); - EntryRenamer.renameClassesInMultimap(renames, this.behaviorReferences); - EntryRenamer.renameClassesInMultimap(renames, this.fieldReferences); - EntryRenamer.renameClassesInMap(renames, this.access); - } - } - - private void indexBehavior(CtBehavior behavior) { - // get the behavior entry - final BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); - if (behaviorEntry instanceof MethodEntry) { - MethodEntry methodEntry = (MethodEntry) behaviorEntry; - - // is synthetic - if ((behavior.getModifiers() & AccessFlag.SYNTHETIC) != 0) { - syntheticMethods.add(methodEntry); - } - - // index implementation - this.methodImplementations.put(behaviorEntry.getClassName(), methodEntry); - - // look for bridge and bridged methods - CtMethod bridgedMethod = getBridgedMethod((CtMethod) behavior); - if (bridgedMethod != null) { - this.bridgedMethods.put(methodEntry, EntryFactory.getMethodEntry(bridgedMethod)); - } - } - // looks like we don't care about constructors here - } - - private void indexBehaviorReferences(CtBehavior behavior) { - // index method calls - final BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); - try { - behavior.instrument(new ExprEditor() { - @Override - public void edit(MethodCall call) { - MethodEntry calledMethodEntry = EntryFactory.getMethodEntry(call); - ClassEntry resolvedClassEntry = translationIndex.resolveEntryClass(calledMethodEntry); - if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledMethodEntry.getClassEntry())) { - calledMethodEntry = new MethodEntry( - resolvedClassEntry, - calledMethodEntry.getName(), - calledMethodEntry.getSignature() - ); - } - EntryReference reference = new EntryReference<>( - calledMethodEntry, - call.getMethodName(), - behaviorEntry - ); - behaviorReferences.put(calledMethodEntry, reference); - } - - @Override - public void edit(FieldAccess call) { - FieldEntry calledFieldEntry = EntryFactory.getFieldEntry(call); - ClassEntry resolvedClassEntry = translationIndex.resolveEntryClass(calledFieldEntry); - if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledFieldEntry.getClassEntry())) { - calledFieldEntry = new FieldEntry(calledFieldEntry, resolvedClassEntry); - } - EntryReference reference = new EntryReference<>( - calledFieldEntry, - call.getFieldName(), - behaviorEntry - ); - fieldReferences.put(calledFieldEntry, reference); - } - - @Override - public void edit(ConstructorCall call) { - ConstructorEntry calledConstructorEntry = EntryFactory.getConstructorEntry(call); - EntryReference reference = new EntryReference<>( - calledConstructorEntry, - call.getMethodName(), - behaviorEntry - ); - behaviorReferences.put(calledConstructorEntry, reference); - } - - @Override - public void edit(NewExpr call) { - ConstructorEntry calledConstructorEntry = EntryFactory.getConstructorEntry(call); - EntryReference reference = new EntryReference<>( - calledConstructorEntry, - call.getClassName(), - behaviorEntry - ); - behaviorReferences.put(calledConstructorEntry, reference); - } - }); - } catch (CannotCompileException ex) { - throw new Error(ex); - } - } - - private CtMethod getBridgedMethod(CtMethod method) { - - // bridge methods just call another method, cast it to the return type, and return the result - // let's see if we can detect this scenario - - // skip non-synthetic methods - if ((method.getModifiers() & AccessFlag.SYNTHETIC) == 0) { - return null; - } - - // get all the called methods - final List methodCalls = Lists.newArrayList(); - try { - method.instrument(new ExprEditor() { - @Override - public void edit(MethodCall call) { - methodCalls.add(call); - } - }); - } catch (CannotCompileException ex) { - // this is stupid... we're not even compiling anything - throw new Error(ex); - } - - // is there just one? - if (methodCalls.size() != 1) { - return null; - } - MethodCall call = methodCalls.get(0); - - try { - // we have a bridge method! - return call.getMethod(); - } catch (NotFoundException ex) { - // can't find the type? not a bridge method - return null; - } - } - - private ClassEntry findOuterClass(CtClass c) { - - ClassEntry classEntry = EntryFactory.getClassEntry(c); - - // does this class already have an outer class? - if (classEntry.isInnerClass()) { - return classEntry.getOuterClassEntry(); - } - - // inner classes: - // have constructors that can (illegally) set synthetic fields - // the outer class is the only class that calls constructors - - // use the synthetic fields to find the synthetic constructors - for (CtConstructor constructor : c.getDeclaredConstructors()) { - Set syntheticFieldTypes = Sets.newHashSet(); - if (!isIllegalConstructor(syntheticFieldTypes, constructor)) { - continue; - } - - ConstructorEntry constructorEntry = EntryFactory.getConstructorEntry(constructor); - - // gather the classes from the illegally-set synthetic fields - Set illegallySetClasses = Sets.newHashSet(); - for (String type : syntheticFieldTypes) { - if (type.startsWith("L")) { - ClassEntry outerClassEntry = new ClassEntry(type.substring(1, type.length() - 1)); - if (isSaneOuterClass(outerClassEntry, classEntry)) { - illegallySetClasses.add(outerClassEntry); - } - } - } - - // who calls this constructor? - Set callerClasses = Sets.newHashSet(); - for (EntryReference reference : getBehaviorReferences(constructorEntry)) { - - // make sure it's not a call to super - if (reference.entry instanceof ConstructorEntry && reference.context instanceof ConstructorEntry) { - - // is the entry a superclass of the context? - ClassEntry calledClassEntry = reference.entry.getClassEntry(); - ClassEntry superclassEntry = this.translationIndex.getSuperclass(reference.context.getClassEntry()); - if (superclassEntry != null && superclassEntry.equals(calledClassEntry)) { - // it's a super call, skip - continue; - } - } - - if (isSaneOuterClass(reference.context.getClassEntry(), classEntry)) { - callerClasses.add(reference.context.getClassEntry()); - } - } - - // do we have an answer yet? - if (callerClasses.isEmpty()) { - if (illegallySetClasses.size() == 1) { - return illegallySetClasses.iterator().next(); - } else { - System.out.println(String.format("WARNING: Unable to find outer class for %s. No caller and no illegally set field classes.", classEntry)); - } - } else { - if (callerClasses.size() == 1) { - return callerClasses.iterator().next(); - } else { - // multiple callers, do the illegally set classes narrow it down? - Set intersection = Sets.newHashSet(callerClasses); - intersection.retainAll(illegallySetClasses); - if (intersection.size() == 1) { - return intersection.iterator().next(); - } else { - System.out.println(String.format("WARNING: Unable to choose outer class for %s among options: %s", classEntry, callerClasses)); - } - } - } - } - - return null; - } - - private boolean isSaneOuterClass(ClassEntry outerClassEntry, ClassEntry innerClassEntry) { - - // clearly this would be silly - if (outerClassEntry.equals(innerClassEntry)) { - return false; - } - - // is the outer class in the jar? - return this.obfClassEntries.contains(outerClassEntry); - - } - - @SuppressWarnings("unchecked") - private boolean isIllegalConstructor(Set syntheticFieldTypes, CtConstructor constructor) { - - // illegal constructors only set synthetic member fields, then call super() - String className = constructor.getDeclaringClass().getName(); - - // collect all the field accesses, constructor calls, and method calls - final List illegalFieldWrites = Lists.newArrayList(); - final List constructorCalls = Lists.newArrayList(); - try { - constructor.instrument(new ExprEditor() { - @Override - public void edit(FieldAccess fieldAccess) { - if (fieldAccess.isWriter() && constructorCalls.isEmpty()) { - illegalFieldWrites.add(fieldAccess); - } - } - - @Override - public void edit(ConstructorCall constructorCall) { - constructorCalls.add(constructorCall); - } - }); - } catch (CannotCompileException ex) { - // we're not compiling anything... this is stupid - throw new Error(ex); - } - - // are there any illegal field writes? - if (illegalFieldWrites.isEmpty()) { - return false; - } - - // are all the writes to synthetic fields? - for (FieldAccess fieldWrite : illegalFieldWrites) { - - // all illegal writes have to be to the local class - if (!fieldWrite.getClassName().equals(className)) { - System.err.println(String.format("WARNING: illegal write to non-member field %s.%s", fieldWrite.getClassName(), fieldWrite.getFieldName())); - return false; - } - - // find the field - FieldInfo fieldInfo = null; - for (FieldInfo info : (List) constructor.getDeclaringClass().getClassFile().getFields()) { - if (info.getName().equals(fieldWrite.getFieldName()) && info.getDescriptor().equals(fieldWrite.getSignature())) { - fieldInfo = info; - break; - } - } - if (fieldInfo == null) { - // field is in a superclass or something, can't be a local synthetic member - return false; - } - - // is this field synthetic? - boolean isSynthetic = (fieldInfo.getAccessFlags() & AccessFlag.SYNTHETIC) != 0; - if (isSynthetic) { - syntheticFieldTypes.add(fieldInfo.getDescriptor()); - } else { - System.err.println(String.format("WARNING: illegal write to non synthetic field %s %s.%s", fieldInfo.getDescriptor(), className, fieldInfo.getName())); - return false; - } - } - - // we passed all the tests! - return true; - } - - private BehaviorEntry isAnonymousClass(CtClass c, ClassEntry outerClassEntry) { - - // is this class already marked anonymous? - EnclosingMethodAttribute enclosingMethodAttribute = (EnclosingMethodAttribute) c.getClassFile().getAttribute(EnclosingMethodAttribute.tag); - if (enclosingMethodAttribute != null) { - if (enclosingMethodAttribute.methodIndex() > 0) { - return EntryFactory.getBehaviorEntry( - Descriptor.toJvmName(enclosingMethodAttribute.className()), - enclosingMethodAttribute.methodName(), - enclosingMethodAttribute.methodDescriptor() - ); - } else { - // an attribute but no method? assume not anonymous - return null; - } - } - - // if there's an inner class attribute, but not an enclosing method attribute, then it's not anonymous - InnerClassesAttribute innerClassesAttribute = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag); - if (innerClassesAttribute != null) { - return null; - } - - ClassEntry innerClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); - - // anonymous classes: - // can't be abstract - // have only one constructor - // it's called exactly once by the outer class - // the type the instance is assigned to can't be this type - - // is abstract? - if (Modifier.isAbstract(c.getModifiers())) { - return null; - } - - // is there exactly one constructor? - if (c.getDeclaredConstructors().length != 1) { - return null; - } - CtConstructor constructor = c.getDeclaredConstructors()[0]; - - // is this constructor called exactly once? - ConstructorEntry constructorEntry = EntryFactory.getConstructorEntry(constructor); - Collection> references = getBehaviorReferences(constructorEntry); - if (references.size() != 1) { - return null; - } - - // does the caller use this type? - BehaviorEntry caller = references.iterator().next().context; - for (FieldEntry fieldEntry : getReferencedFields(caller)) { - if (fieldEntry.getType().hasClass() && fieldEntry.getType().getClassEntry().equals(innerClassEntry)) { - // caller references this type, so it can't be anonymous - return null; - } - } - for (BehaviorEntry behaviorEntry : getReferencedBehaviors(caller)) { - if (behaviorEntry.getSignature().hasClass(innerClassEntry)) { - return null; - } - } - - return caller; - } - - public Set getObfClassEntries() { - return this.obfClassEntries; - } - - public Collection getObfFieldEntries() { - return this.fields.values(); - } - - public Collection getObfFieldEntries(ClassEntry classEntry) { - return this.fields.get(classEntry); - } - - public Collection getObfBehaviorEntries() { - return this.behaviors.values(); - } - - public Collection getObfBehaviorEntries(ClassEntry classEntry) { - return this.behaviors.get(classEntry); - } - - public TranslationIndex getTranslationIndex() { - return this.translationIndex; - } - - public Access getAccess(Entry entry) { - return this.access.get(entry); - } - - public ClassInheritanceTreeNode getClassInheritance(Translator deobfuscatingTranslator, ClassEntry obfClassEntry) { - - // get the root node - List ancestry = Lists.newArrayList(); - ancestry.add(obfClassEntry.getName()); - for (ClassEntry classEntry : this.translationIndex.getAncestry(obfClassEntry)) { - if (containsObfClass(classEntry)) { - ancestry.add(classEntry.getName()); - } - } - ClassInheritanceTreeNode rootNode = new ClassInheritanceTreeNode( - deobfuscatingTranslator, - ancestry.get(ancestry.size() - 1) - ); - - // expand all children recursively - rootNode.load(this.translationIndex, true); - - return rootNode; - } - - public ClassImplementationsTreeNode getClassImplementations(Translator deobfuscatingTranslator, ClassEntry obfClassEntry) { - - // is this even an interface? - if (isInterface(obfClassEntry.getClassName())) { - ClassImplementationsTreeNode node = new ClassImplementationsTreeNode(deobfuscatingTranslator, obfClassEntry); - node.load(this); - return node; - } - return null; - } - - public MethodInheritanceTreeNode getMethodInheritance(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) { - - // travel to the ancestor implementation - ClassEntry baseImplementationClassEntry = obfMethodEntry.getClassEntry(); - for (ClassEntry ancestorClassEntry : this.translationIndex.getAncestry(obfMethodEntry.getClassEntry())) { - MethodEntry ancestorMethodEntry = new MethodEntry( - new ClassEntry(ancestorClassEntry), - obfMethodEntry.getName(), - obfMethodEntry.getSignature() - ); - if (containsObfBehavior(ancestorMethodEntry)) { - baseImplementationClassEntry = ancestorClassEntry; - } - } - - // make a root node at the base - MethodEntry methodEntry = new MethodEntry( - baseImplementationClassEntry, - obfMethodEntry.getName(), - obfMethodEntry.getSignature() - ); - MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode( - deobfuscatingTranslator, - methodEntry, - containsObfBehavior(methodEntry) - ); - - // expand the full tree - rootNode.load(this, true); - - return rootNode; - } - - public List getMethodImplementations(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) { - - List interfaceMethodEntries = Lists.newArrayList(); - - // is this method on an interface? - if (isInterface(obfMethodEntry.getClassName())) { - interfaceMethodEntries.add(obfMethodEntry); - } else { - // get the interface class - for (ClassEntry interfaceEntry : getInterfaces(obfMethodEntry.getClassName())) { - - // is this method defined in this interface? - MethodEntry methodInterface = new MethodEntry( - interfaceEntry, - obfMethodEntry.getName(), - obfMethodEntry.getSignature() - ); - if (containsObfBehavior(methodInterface)) { - interfaceMethodEntries.add(methodInterface); - } - } - } - - List nodes = Lists.newArrayList(); - if (!interfaceMethodEntries.isEmpty()) { - for (MethodEntry interfaceMethodEntry : interfaceMethodEntries) { - MethodImplementationsTreeNode node = new MethodImplementationsTreeNode(deobfuscatingTranslator, interfaceMethodEntry); - node.load(this); - nodes.add(node); - } - } - return nodes; - } - - public Set getRelatedMethodImplementations(MethodEntry obfMethodEntry) { - Set methodEntries = Sets.newHashSet(); - getRelatedMethodImplementations(methodEntries, getMethodInheritance(new Translator(), obfMethodEntry)); - return methodEntries; - } - - private void getRelatedMethodImplementations(Set methodEntries, MethodInheritanceTreeNode node) { - MethodEntry methodEntry = node.getMethodEntry(); - - if (containsObfBehavior(methodEntry)) { - // collect the entry - methodEntries.add(methodEntry); - } - - // look at bridged methods! - MethodEntry bridgedEntry = getBridgedMethod(methodEntry); - while (bridgedEntry != null) { - methodEntries.addAll(getRelatedMethodImplementations(bridgedEntry)); - bridgedEntry = getBridgedMethod(bridgedEntry); - } - - // look at interface methods too - for (MethodImplementationsTreeNode implementationsNode : getMethodImplementations(new Translator(), methodEntry)) { - getRelatedMethodImplementations(methodEntries, implementationsNode); - } - - // recurse - for (int i = 0; i < node.getChildCount(); i++) { - getRelatedMethodImplementations(methodEntries, (MethodInheritanceTreeNode) node.getChildAt(i)); - } - } - - private void getRelatedMethodImplementations(Set methodEntries, MethodImplementationsTreeNode node) { - MethodEntry methodEntry = node.getMethodEntry(); - if (containsObfBehavior(methodEntry)) { - // collect the entry - methodEntries.add(methodEntry); - } - - // look at bridged methods! - MethodEntry bridgedEntry = getBridgedMethod(methodEntry); - while (bridgedEntry != null) { - methodEntries.addAll(getRelatedMethodImplementations(bridgedEntry)); - bridgedEntry = getBridgedMethod(bridgedEntry); - } - - // recurse - for (int i = 0; i < node.getChildCount(); i++) { - getRelatedMethodImplementations(methodEntries, (MethodImplementationsTreeNode) node.getChildAt(i)); - } - } - - public Collection> getFieldReferences(FieldEntry fieldEntry) { - return this.fieldReferences.get(fieldEntry); - } - - public Collection getReferencedFields(BehaviorEntry behaviorEntry) { - // linear search is fast enough for now - Set fieldEntries = Sets.newHashSet(); - for (EntryReference reference : this.fieldReferences.values()) { - if (reference.context == behaviorEntry) { - fieldEntries.add(reference.entry); - } - } - return fieldEntries; - } - - public Collection> getBehaviorReferences(BehaviorEntry behaviorEntry) { - return this.behaviorReferences.get(behaviorEntry); - } - - public Collection getReferencedBehaviors(BehaviorEntry behaviorEntry) { - // linear search is fast enough for now - Set behaviorEntries = Sets.newHashSet(); - for (EntryReference reference : this.behaviorReferences.values()) { - if (reference.context == behaviorEntry) { - behaviorEntries.add(reference.entry); - } - } - return behaviorEntries; - } - - public Collection getInnerClasses(ClassEntry obfOuterClassEntry) { - return this.innerClassesByOuter.get(obfOuterClassEntry); - } - - public ClassEntry getOuterClass(ClassEntry obfInnerClassEntry) { - return this.outerClassesByInner.get(obfInnerClassEntry); - } - - public boolean isAnonymousClass(ClassEntry obfInnerClassEntry) { - return this.anonymousClasses.containsKey(obfInnerClassEntry); - } - - public boolean isSyntheticMethod(MethodEntry methodEntry) { - return this.syntheticMethods.contains(methodEntry); - } - - public BehaviorEntry getAnonymousClassCaller(ClassEntry obfInnerClassName) { - return this.anonymousClasses.get(obfInnerClassName); - } - - public Set getInterfaces(String className) { - ClassEntry classEntry = new ClassEntry(className); - Set interfaces = new HashSet<>(); - interfaces.addAll(this.translationIndex.getInterfaces(classEntry)); - for (ClassEntry ancestor : this.translationIndex.getAncestry(classEntry)) { - interfaces.addAll(this.translationIndex.getInterfaces(ancestor)); - } - return interfaces; - } - - public Set getImplementingClasses(String targetInterfaceName) { - - // linear search is fast enough for now - Set classNames = Sets.newHashSet(); - for (Map.Entry entry : this.translationIndex.getClassInterfaces()) { - ClassEntry classEntry = entry.getKey(); - ClassEntry interfaceEntry = entry.getValue(); - if (interfaceEntry.getName().equals(targetInterfaceName)) { - String className = classEntry.getClassName(); - classNames.add(className); - if (isInterface(className)) { - classNames.addAll(getImplementingClasses(className)); - } - - this.translationIndex.getSubclassNamesRecursively(classNames, classEntry); - } - } - return classNames; - } - - public boolean isInterface(String className) { - return this.translationIndex.isInterface(new ClassEntry(className)); - } - - public boolean containsObfClass(ClassEntry obfClassEntry) { - return this.obfClassEntries.contains(obfClassEntry); - } - - public boolean containsObfField(FieldEntry obfFieldEntry) { - return this.access.containsKey(obfFieldEntry); - } - - public boolean containsObfBehavior(BehaviorEntry obfBehaviorEntry) { - return this.access.containsKey(obfBehaviorEntry); - } - - public boolean containsEntryWithSameName(Entry entry) - { - for (Entry target : this.access.keySet()) - if (target.getName().equals(entry.getName()) && entry.getClass().isInstance(target.getClass())) - return true; - return false; - } - - public boolean containsObfArgument(ArgumentEntry obfArgumentEntry) { - // check the behavior - if (!containsObfBehavior(obfArgumentEntry.getBehaviorEntry())) { - return false; - } - - // check the argument - return obfArgumentEntry.getIndex() < obfArgumentEntry.getBehaviorEntry().getSignature().getArgumentTypes().size(); - - } - - public boolean containsObfEntry(Entry obfEntry) { - if (obfEntry instanceof ClassEntry) { - return containsObfClass((ClassEntry) obfEntry); - } else if (obfEntry instanceof FieldEntry) { - return containsObfField((FieldEntry) obfEntry); - } else if (obfEntry instanceof BehaviorEntry) { - return containsObfBehavior((BehaviorEntry) obfEntry); - } else if (obfEntry instanceof ArgumentEntry) { - return containsObfArgument((ArgumentEntry) obfEntry); - } else if (obfEntry instanceof LocalVariableEntry) { - // TODO: Implement it - return false; - } else { - throw new Error("Entry type not supported: " + obfEntry.getClass().getName()); - } - } - - public MethodEntry getBridgedMethod(MethodEntry bridgeMethodEntry) { - return this.bridgedMethods.get(bridgeMethodEntry); - } - - public List getObfClassChain(ClassEntry obfClassEntry) { - - // build class chain in inner-to-outer order - List obfClassChain = Lists.newArrayList(obfClassEntry); - ClassEntry checkClassEntry = obfClassEntry; - while (true) { - ClassEntry obfOuterClassEntry = getOuterClass(checkClassEntry); - if (obfOuterClassEntry != null) { - obfClassChain.add(obfOuterClassEntry); - checkClassEntry = obfOuterClassEntry; - } else { - break; - } - } - - // switch to outer-to-inner order - Collections.reverse(obfClassChain); - - return obfClassChain; - } + } + } + + // step 6: update other indices with inner class info + Map renames = Maps.newHashMap(); + for (ClassEntry innerClassEntry : this.innerClassesByOuter.values()) { + String newName = innerClassEntry.buildClassEntry(getObfClassChain(innerClassEntry)).getName(); + if (!innerClassEntry.getName().equals(newName)) { + // DEBUG + //System.out.println("REPLACE: " + innerClassEntry.getName() + " WITH " + newName); + renames.put(innerClassEntry.getName(), newName); + } + } + EntryRenamer.renameClassesInSet(renames, this.obfClassEntries); + this.translationIndex.renameClasses(renames); + EntryRenamer.renameClassesInMultimap(renames, this.methodImplementations); + EntryRenamer.renameClassesInMultimap(renames, this.behaviorReferences); + EntryRenamer.renameClassesInMultimap(renames, this.fieldReferences); + EntryRenamer.renameClassesInMap(renames, this.access); + } + } + + private void indexBehavior(CtBehavior behavior) { + // get the behavior entry + final BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); + if (behaviorEntry instanceof MethodEntry) { + MethodEntry methodEntry = (MethodEntry) behaviorEntry; + + // is synthetic + if ((behavior.getModifiers() & AccessFlag.SYNTHETIC) != 0) { + syntheticMethods.add(methodEntry); + } + + // index implementation + this.methodImplementations.put(behaviorEntry.getClassName(), methodEntry); + + // look for bridge and bridged methods + CtMethod bridgedMethod = getBridgedMethod((CtMethod) behavior); + if (bridgedMethod != null) { + this.bridgedMethods.put(methodEntry, EntryFactory.getMethodEntry(bridgedMethod)); + } + } + // looks like we don't care about constructors here + } + + private void indexBehaviorReferences(CtBehavior behavior) { + // index method calls + final BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); + try { + behavior.instrument(new ExprEditor() { + @Override + public void edit(MethodCall call) { + MethodEntry calledMethodEntry = EntryFactory.getMethodEntry(call); + ClassEntry resolvedClassEntry = translationIndex.resolveEntryClass(calledMethodEntry); + if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledMethodEntry.getClassEntry())) { + calledMethodEntry = new MethodEntry( + resolvedClassEntry, + calledMethodEntry.getName(), + calledMethodEntry.getSignature() + ); + } + EntryReference reference = new EntryReference<>( + calledMethodEntry, + call.getMethodName(), + behaviorEntry + ); + behaviorReferences.put(calledMethodEntry, reference); + } + + @Override + public void edit(FieldAccess call) { + FieldEntry calledFieldEntry = EntryFactory.getFieldEntry(call); + ClassEntry resolvedClassEntry = translationIndex.resolveEntryClass(calledFieldEntry); + if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledFieldEntry.getClassEntry())) { + calledFieldEntry = new FieldEntry(calledFieldEntry, resolvedClassEntry); + } + EntryReference reference = new EntryReference<>( + calledFieldEntry, + call.getFieldName(), + behaviorEntry + ); + fieldReferences.put(calledFieldEntry, reference); + } + + @Override + public void edit(ConstructorCall call) { + ConstructorEntry calledConstructorEntry = EntryFactory.getConstructorEntry(call); + EntryReference reference = new EntryReference<>( + calledConstructorEntry, + call.getMethodName(), + behaviorEntry + ); + behaviorReferences.put(calledConstructorEntry, reference); + } + + @Override + public void edit(NewExpr call) { + ConstructorEntry calledConstructorEntry = EntryFactory.getConstructorEntry(call); + EntryReference reference = new EntryReference<>( + calledConstructorEntry, + call.getClassName(), + behaviorEntry + ); + behaviorReferences.put(calledConstructorEntry, reference); + } + }); + } catch (CannotCompileException ex) { + throw new Error(ex); + } + } + + private CtMethod getBridgedMethod(CtMethod method) { + + // bridge methods just call another method, cast it to the return type, and return the result + // let's see if we can detect this scenario + + // skip non-synthetic methods + if ((method.getModifiers() & AccessFlag.SYNTHETIC) == 0) { + return null; + } + + // get all the called methods + final List methodCalls = Lists.newArrayList(); + try { + method.instrument(new ExprEditor() { + @Override + public void edit(MethodCall call) { + methodCalls.add(call); + } + }); + } catch (CannotCompileException ex) { + // this is stupid... we're not even compiling anything + throw new Error(ex); + } + + // is there just one? + if (methodCalls.size() != 1) { + return null; + } + MethodCall call = methodCalls.get(0); + + try { + // we have a bridge method! + return call.getMethod(); + } catch (NotFoundException ex) { + // can't find the type? not a bridge method + return null; + } + } + + private ClassEntry findOuterClass(CtClass c) { + + ClassEntry classEntry = EntryFactory.getClassEntry(c); + + // does this class already have an outer class? + if (classEntry.isInnerClass()) { + return classEntry.getOuterClassEntry(); + } + + // inner classes: + // have constructors that can (illegally) set synthetic fields + // the outer class is the only class that calls constructors + + // use the synthetic fields to find the synthetic constructors + for (CtConstructor constructor : c.getDeclaredConstructors()) { + Set syntheticFieldTypes = Sets.newHashSet(); + if (!isIllegalConstructor(syntheticFieldTypes, constructor)) { + continue; + } + + ConstructorEntry constructorEntry = EntryFactory.getConstructorEntry(constructor); + + // gather the classes from the illegally-set synthetic fields + Set illegallySetClasses = Sets.newHashSet(); + for (String type : syntheticFieldTypes) { + if (type.startsWith("L")) { + ClassEntry outerClassEntry = new ClassEntry(type.substring(1, type.length() - 1)); + if (isSaneOuterClass(outerClassEntry, classEntry)) { + illegallySetClasses.add(outerClassEntry); + } + } + } + + // who calls this constructor? + Set callerClasses = Sets.newHashSet(); + for (EntryReference reference : getBehaviorReferences(constructorEntry)) { + + // make sure it's not a call to super + if (reference.entry instanceof ConstructorEntry && reference.context instanceof ConstructorEntry) { + + // is the entry a superclass of the context? + ClassEntry calledClassEntry = reference.entry.getClassEntry(); + ClassEntry superclassEntry = this.translationIndex.getSuperclass(reference.context.getClassEntry()); + if (superclassEntry != null && superclassEntry.equals(calledClassEntry)) { + // it's a super call, skip + continue; + } + } + + if (isSaneOuterClass(reference.context.getClassEntry(), classEntry)) { + callerClasses.add(reference.context.getClassEntry()); + } + } + + // do we have an answer yet? + if (callerClasses.isEmpty()) { + if (illegallySetClasses.size() == 1) { + return illegallySetClasses.iterator().next(); + } else { + System.out.println(String.format("WARNING: Unable to find outer class for %s. No caller and no illegally set field classes.", classEntry)); + } + } else { + if (callerClasses.size() == 1) { + return callerClasses.iterator().next(); + } else { + // multiple callers, do the illegally set classes narrow it down? + Set intersection = Sets.newHashSet(callerClasses); + intersection.retainAll(illegallySetClasses); + if (intersection.size() == 1) { + return intersection.iterator().next(); + } else { + System.out.println(String.format("WARNING: Unable to choose outer class for %s among options: %s", classEntry, callerClasses)); + } + } + } + } + + return null; + } + + private boolean isSaneOuterClass(ClassEntry outerClassEntry, ClassEntry innerClassEntry) { + + // clearly this would be silly + if (outerClassEntry.equals(innerClassEntry)) { + return false; + } + + // is the outer class in the jar? + return this.obfClassEntries.contains(outerClassEntry); + + } + + @SuppressWarnings("unchecked") + private boolean isIllegalConstructor(Set syntheticFieldTypes, CtConstructor constructor) { + + // illegal constructors only set synthetic member fields, then call super() + String className = constructor.getDeclaringClass().getName(); + + // collect all the field accesses, constructor calls, and method calls + final List illegalFieldWrites = Lists.newArrayList(); + final List constructorCalls = Lists.newArrayList(); + try { + constructor.instrument(new ExprEditor() { + @Override + public void edit(FieldAccess fieldAccess) { + if (fieldAccess.isWriter() && constructorCalls.isEmpty()) { + illegalFieldWrites.add(fieldAccess); + } + } + + @Override + public void edit(ConstructorCall constructorCall) { + constructorCalls.add(constructorCall); + } + }); + } catch (CannotCompileException ex) { + // we're not compiling anything... this is stupid + throw new Error(ex); + } + + // are there any illegal field writes? + if (illegalFieldWrites.isEmpty()) { + return false; + } + + // are all the writes to synthetic fields? + for (FieldAccess fieldWrite : illegalFieldWrites) { + + // all illegal writes have to be to the local class + if (!fieldWrite.getClassName().equals(className)) { + System.err.println(String.format("WARNING: illegal write to non-member field %s.%s", fieldWrite.getClassName(), fieldWrite.getFieldName())); + return false; + } + + // find the field + FieldInfo fieldInfo = null; + for (FieldInfo info : (List) constructor.getDeclaringClass().getClassFile().getFields()) { + if (info.getName().equals(fieldWrite.getFieldName()) && info.getDescriptor().equals(fieldWrite.getSignature())) { + fieldInfo = info; + break; + } + } + if (fieldInfo == null) { + // field is in a superclass or something, can't be a local synthetic member + return false; + } + + // is this field synthetic? + boolean isSynthetic = (fieldInfo.getAccessFlags() & AccessFlag.SYNTHETIC) != 0; + if (isSynthetic) { + syntheticFieldTypes.add(fieldInfo.getDescriptor()); + } else { + System.err.println(String.format("WARNING: illegal write to non synthetic field %s %s.%s", fieldInfo.getDescriptor(), className, fieldInfo.getName())); + return false; + } + } + + // we passed all the tests! + return true; + } + + private BehaviorEntry isAnonymousClass(CtClass c, ClassEntry outerClassEntry) { + + // is this class already marked anonymous? + EnclosingMethodAttribute enclosingMethodAttribute = (EnclosingMethodAttribute) c.getClassFile().getAttribute(EnclosingMethodAttribute.tag); + if (enclosingMethodAttribute != null) { + if (enclosingMethodAttribute.methodIndex() > 0) { + return EntryFactory.getBehaviorEntry( + Descriptor.toJvmName(enclosingMethodAttribute.className()), + enclosingMethodAttribute.methodName(), + enclosingMethodAttribute.methodDescriptor() + ); + } else { + // an attribute but no method? assume not anonymous + return null; + } + } + + // if there's an inner class attribute, but not an enclosing method attribute, then it's not anonymous + InnerClassesAttribute innerClassesAttribute = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag); + if (innerClassesAttribute != null) { + return null; + } + + ClassEntry innerClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); + + // anonymous classes: + // can't be abstract + // have only one constructor + // it's called exactly once by the outer class + // the type the instance is assigned to can't be this type + + // is abstract? + if (Modifier.isAbstract(c.getModifiers())) { + return null; + } + + // is there exactly one constructor? + if (c.getDeclaredConstructors().length != 1) { + return null; + } + CtConstructor constructor = c.getDeclaredConstructors()[0]; + + // is this constructor called exactly once? + ConstructorEntry constructorEntry = EntryFactory.getConstructorEntry(constructor); + Collection> references = getBehaviorReferences(constructorEntry); + if (references.size() != 1) { + return null; + } + + // does the caller use this type? + BehaviorEntry caller = references.iterator().next().context; + for (FieldEntry fieldEntry : getReferencedFields(caller)) { + if (fieldEntry.getType().hasClass() && fieldEntry.getType().getClassEntry().equals(innerClassEntry)) { + // caller references this type, so it can't be anonymous + return null; + } + } + for (BehaviorEntry behaviorEntry : getReferencedBehaviors(caller)) { + if (behaviorEntry.getSignature().hasClass(innerClassEntry)) { + return null; + } + } + + return caller; + } + + public Set getObfClassEntries() { + return this.obfClassEntries; + } + + public Collection getObfFieldEntries() { + return this.fields.values(); + } + + public Collection getObfFieldEntries(ClassEntry classEntry) { + return this.fields.get(classEntry); + } + + public Collection getObfBehaviorEntries() { + return this.behaviors.values(); + } + + public Collection getObfBehaviorEntries(ClassEntry classEntry) { + return this.behaviors.get(classEntry); + } + + public TranslationIndex getTranslationIndex() { + return this.translationIndex; + } + + public Access getAccess(Entry entry) { + return this.access.get(entry); + } + + public ClassInheritanceTreeNode getClassInheritance(Translator deobfuscatingTranslator, ClassEntry obfClassEntry) { + + // get the root node + List ancestry = Lists.newArrayList(); + ancestry.add(obfClassEntry.getName()); + for (ClassEntry classEntry : this.translationIndex.getAncestry(obfClassEntry)) { + if (containsObfClass(classEntry)) { + ancestry.add(classEntry.getName()); + } + } + ClassInheritanceTreeNode rootNode = new ClassInheritanceTreeNode( + deobfuscatingTranslator, + ancestry.get(ancestry.size() - 1) + ); + + // expand all children recursively + rootNode.load(this.translationIndex, true); + + return rootNode; + } + + public ClassImplementationsTreeNode getClassImplementations(Translator deobfuscatingTranslator, ClassEntry obfClassEntry) { + + // is this even an interface? + if (isInterface(obfClassEntry.getClassName())) { + ClassImplementationsTreeNode node = new ClassImplementationsTreeNode(deobfuscatingTranslator, obfClassEntry); + node.load(this); + return node; + } + return null; + } + + public MethodInheritanceTreeNode getMethodInheritance(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) { + + // travel to the ancestor implementation + ClassEntry baseImplementationClassEntry = obfMethodEntry.getClassEntry(); + for (ClassEntry ancestorClassEntry : this.translationIndex.getAncestry(obfMethodEntry.getClassEntry())) { + MethodEntry ancestorMethodEntry = new MethodEntry( + new ClassEntry(ancestorClassEntry), + obfMethodEntry.getName(), + obfMethodEntry.getSignature() + ); + if (containsObfBehavior(ancestorMethodEntry)) { + baseImplementationClassEntry = ancestorClassEntry; + } + } + + // make a root node at the base + MethodEntry methodEntry = new MethodEntry( + baseImplementationClassEntry, + obfMethodEntry.getName(), + obfMethodEntry.getSignature() + ); + MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode( + deobfuscatingTranslator, + methodEntry, + containsObfBehavior(methodEntry) + ); + + // expand the full tree + rootNode.load(this, true); + + return rootNode; + } + + public List getMethodImplementations(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) { + + List interfaceMethodEntries = Lists.newArrayList(); + + // is this method on an interface? + if (isInterface(obfMethodEntry.getClassName())) { + interfaceMethodEntries.add(obfMethodEntry); + } else { + // get the interface class + for (ClassEntry interfaceEntry : getInterfaces(obfMethodEntry.getClassName())) { + + // is this method defined in this interface? + MethodEntry methodInterface = new MethodEntry( + interfaceEntry, + obfMethodEntry.getName(), + obfMethodEntry.getSignature() + ); + if (containsObfBehavior(methodInterface)) { + interfaceMethodEntries.add(methodInterface); + } + } + } + + List nodes = Lists.newArrayList(); + if (!interfaceMethodEntries.isEmpty()) { + for (MethodEntry interfaceMethodEntry : interfaceMethodEntries) { + MethodImplementationsTreeNode node = new MethodImplementationsTreeNode(deobfuscatingTranslator, interfaceMethodEntry); + node.load(this); + nodes.add(node); + } + } + return nodes; + } + + public Set getRelatedMethodImplementations(MethodEntry obfMethodEntry) { + Set methodEntries = Sets.newHashSet(); + getRelatedMethodImplementations(methodEntries, getMethodInheritance(new Translator(), obfMethodEntry)); + return methodEntries; + } + + private void getRelatedMethodImplementations(Set methodEntries, MethodInheritanceTreeNode node) { + MethodEntry methodEntry = node.getMethodEntry(); + + if (containsObfBehavior(methodEntry)) { + // collect the entry + methodEntries.add(methodEntry); + } + + // look at bridged methods! + MethodEntry bridgedEntry = getBridgedMethod(methodEntry); + while (bridgedEntry != null) { + methodEntries.addAll(getRelatedMethodImplementations(bridgedEntry)); + bridgedEntry = getBridgedMethod(bridgedEntry); + } + + // look at interface methods too + for (MethodImplementationsTreeNode implementationsNode : getMethodImplementations(new Translator(), methodEntry)) { + getRelatedMethodImplementations(methodEntries, implementationsNode); + } + + // recurse + for (int i = 0; i < node.getChildCount(); i++) { + getRelatedMethodImplementations(methodEntries, (MethodInheritanceTreeNode) node.getChildAt(i)); + } + } + + private void getRelatedMethodImplementations(Set methodEntries, MethodImplementationsTreeNode node) { + MethodEntry methodEntry = node.getMethodEntry(); + if (containsObfBehavior(methodEntry)) { + // collect the entry + methodEntries.add(methodEntry); + } + + // look at bridged methods! + MethodEntry bridgedEntry = getBridgedMethod(methodEntry); + while (bridgedEntry != null) { + methodEntries.addAll(getRelatedMethodImplementations(bridgedEntry)); + bridgedEntry = getBridgedMethod(bridgedEntry); + } + + // recurse + for (int i = 0; i < node.getChildCount(); i++) { + getRelatedMethodImplementations(methodEntries, (MethodImplementationsTreeNode) node.getChildAt(i)); + } + } + + public Collection> getFieldReferences(FieldEntry fieldEntry) { + return this.fieldReferences.get(fieldEntry); + } + + public Collection getReferencedFields(BehaviorEntry behaviorEntry) { + // linear search is fast enough for now + Set fieldEntries = Sets.newHashSet(); + for (EntryReference reference : this.fieldReferences.values()) { + if (reference.context == behaviorEntry) { + fieldEntries.add(reference.entry); + } + } + return fieldEntries; + } + + public Collection> getBehaviorReferences(BehaviorEntry behaviorEntry) { + return this.behaviorReferences.get(behaviorEntry); + } + + public Collection getReferencedBehaviors(BehaviorEntry behaviorEntry) { + // linear search is fast enough for now + Set behaviorEntries = Sets.newHashSet(); + for (EntryReference reference : this.behaviorReferences.values()) { + if (reference.context == behaviorEntry) { + behaviorEntries.add(reference.entry); + } + } + return behaviorEntries; + } + + public Collection getInnerClasses(ClassEntry obfOuterClassEntry) { + return this.innerClassesByOuter.get(obfOuterClassEntry); + } + + public ClassEntry getOuterClass(ClassEntry obfInnerClassEntry) { + return this.outerClassesByInner.get(obfInnerClassEntry); + } + + public boolean isAnonymousClass(ClassEntry obfInnerClassEntry) { + return this.anonymousClasses.containsKey(obfInnerClassEntry); + } + + public boolean isSyntheticMethod(MethodEntry methodEntry) { + return this.syntheticMethods.contains(methodEntry); + } + + public BehaviorEntry getAnonymousClassCaller(ClassEntry obfInnerClassName) { + return this.anonymousClasses.get(obfInnerClassName); + } + + public Set getInterfaces(String className) { + ClassEntry classEntry = new ClassEntry(className); + Set interfaces = new HashSet<>(); + interfaces.addAll(this.translationIndex.getInterfaces(classEntry)); + for (ClassEntry ancestor : this.translationIndex.getAncestry(classEntry)) { + interfaces.addAll(this.translationIndex.getInterfaces(ancestor)); + } + return interfaces; + } + + public Set getImplementingClasses(String targetInterfaceName) { + + // linear search is fast enough for now + Set classNames = Sets.newHashSet(); + for (Map.Entry entry : this.translationIndex.getClassInterfaces()) { + ClassEntry classEntry = entry.getKey(); + ClassEntry interfaceEntry = entry.getValue(); + if (interfaceEntry.getName().equals(targetInterfaceName)) { + String className = classEntry.getClassName(); + classNames.add(className); + if (isInterface(className)) { + classNames.addAll(getImplementingClasses(className)); + } + + this.translationIndex.getSubclassNamesRecursively(classNames, classEntry); + } + } + return classNames; + } + + public boolean isInterface(String className) { + return this.translationIndex.isInterface(new ClassEntry(className)); + } + + public boolean containsObfClass(ClassEntry obfClassEntry) { + return this.obfClassEntries.contains(obfClassEntry); + } + + public boolean containsObfField(FieldEntry obfFieldEntry) { + return this.access.containsKey(obfFieldEntry); + } + + public boolean containsObfBehavior(BehaviorEntry obfBehaviorEntry) { + return this.access.containsKey(obfBehaviorEntry); + } + + public boolean containsEntryWithSameName(Entry entry) { + for (Entry target : this.access.keySet()) + if (target.getName().equals(entry.getName()) && entry.getClass().isInstance(target.getClass())) + return true; + return false; + } + + public boolean containsObfArgument(ArgumentEntry obfArgumentEntry) { + // check the behavior + if (!containsObfBehavior(obfArgumentEntry.getBehaviorEntry())) { + return false; + } + + // check the argument + return obfArgumentEntry.getIndex() < obfArgumentEntry.getBehaviorEntry().getSignature().getArgumentTypes().size(); + + } + + public boolean containsObfEntry(Entry obfEntry) { + if (obfEntry instanceof ClassEntry) { + return containsObfClass((ClassEntry) obfEntry); + } else if (obfEntry instanceof FieldEntry) { + return containsObfField((FieldEntry) obfEntry); + } else if (obfEntry instanceof BehaviorEntry) { + return containsObfBehavior((BehaviorEntry) obfEntry); + } else if (obfEntry instanceof ArgumentEntry) { + return containsObfArgument((ArgumentEntry) obfEntry); + } else if (obfEntry instanceof LocalVariableEntry) { + // TODO: Implement it + return false; + } else { + throw new Error("Entry type not supported: " + obfEntry.getClass().getName()); + } + } + + public MethodEntry getBridgedMethod(MethodEntry bridgeMethodEntry) { + return this.bridgedMethods.get(bridgeMethodEntry); + } + + public List getObfClassChain(ClassEntry obfClassEntry) { + + // build class chain in inner-to-outer order + List obfClassChain = Lists.newArrayList(obfClassEntry); + ClassEntry checkClassEntry = obfClassEntry; + while (true) { + ClassEntry obfOuterClassEntry = getOuterClass(checkClassEntry); + if (obfOuterClassEntry != null) { + obfClassChain.add(obfOuterClassEntry); + checkClassEntry = obfOuterClassEntry; + } else { + break; + } + } + + // switch to outer-to-inner order + Collections.reverse(obfClassChain); + + return obfClassChain; + } } diff --git a/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java b/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java index 9bd6219a..bacb1aac 100644 --- a/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java +++ b/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java @@ -8,87 +8,86 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.analysis; import com.google.common.collect.Lists; - -import java.util.List; - -import javax.swing.tree.DefaultMutableTreeNode; - import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.MethodEntry; import cuchaz.enigma.mapping.Translator; +import javax.swing.tree.DefaultMutableTreeNode; +import java.util.List; + public class MethodImplementationsTreeNode extends DefaultMutableTreeNode { - private Translator deobfuscatingTranslator; - private MethodEntry entry; - - public MethodImplementationsTreeNode(Translator deobfuscatingTranslator, MethodEntry entry) { - if (entry == null) { - throw new IllegalArgumentException("Entry cannot be null!"); - } - - this.deobfuscatingTranslator = deobfuscatingTranslator; - this.entry = entry; - } - - public MethodEntry getMethodEntry() { - return this.entry; - } - - public String getDeobfClassName() { - return this.deobfuscatingTranslator.translateClass(this.entry.getClassName()); - } - - public String getDeobfMethodName() { - return this.deobfuscatingTranslator.translate(this.entry); - } - - @Override - public String toString() { - String className = getDeobfClassName(); - if (className == null) { - className = this.entry.getClassName(); - } - - String methodName = getDeobfMethodName(); - if (methodName == null) { - methodName = this.entry.getName(); - } - return className + "." + methodName + "()"; - } - - public void load(JarIndex index) { - - // get all method implementations - List nodes = Lists.newArrayList(); - for (String implementingClassName : index.getImplementingClasses(this.entry.getClassName())) { - MethodEntry methodEntry = new MethodEntry(new ClassEntry(implementingClassName), this.entry.getName(), this.entry.getSignature() - ); - if (index.containsObfBehavior(methodEntry)) { - nodes.add(new MethodImplementationsTreeNode(this.deobfuscatingTranslator, methodEntry)); - } - } - - // add them to this node - nodes.forEach(this::add); - } - - public static MethodImplementationsTreeNode findNode(MethodImplementationsTreeNode node, MethodEntry entry) { - // is this the node? - if (node.getMethodEntry().equals(entry)) { - return node; - } - - // recurse - for (int i = 0; i < node.getChildCount(); i++) { - MethodImplementationsTreeNode foundNode = findNode((MethodImplementationsTreeNode) node.getChildAt(i), entry); - if (foundNode != null) { - return foundNode; - } - } - return null; - } + private Translator deobfuscatingTranslator; + private MethodEntry entry; + + public MethodImplementationsTreeNode(Translator deobfuscatingTranslator, MethodEntry entry) { + if (entry == null) { + throw new IllegalArgumentException("Entry cannot be null!"); + } + + this.deobfuscatingTranslator = deobfuscatingTranslator; + this.entry = entry; + } + + public static MethodImplementationsTreeNode findNode(MethodImplementationsTreeNode node, MethodEntry entry) { + // is this the node? + if (node.getMethodEntry().equals(entry)) { + return node; + } + + // recurse + for (int i = 0; i < node.getChildCount(); i++) { + MethodImplementationsTreeNode foundNode = findNode((MethodImplementationsTreeNode) node.getChildAt(i), entry); + if (foundNode != null) { + return foundNode; + } + } + return null; + } + + public MethodEntry getMethodEntry() { + return this.entry; + } + + public String getDeobfClassName() { + return this.deobfuscatingTranslator.translateClass(this.entry.getClassName()); + } + + public String getDeobfMethodName() { + return this.deobfuscatingTranslator.translate(this.entry); + } + + @Override + public String toString() { + String className = getDeobfClassName(); + if (className == null) { + className = this.entry.getClassName(); + } + + String methodName = getDeobfMethodName(); + if (methodName == null) { + methodName = this.entry.getName(); + } + return className + "." + methodName + "()"; + } + + public void load(JarIndex index) { + + // get all method implementations + List nodes = Lists.newArrayList(); + for (String implementingClassName : index.getImplementingClasses(this.entry.getClassName())) { + MethodEntry methodEntry = new MethodEntry(new ClassEntry(implementingClassName), this.entry.getName(), this.entry.getSignature() + ); + if (index.containsObfBehavior(methodEntry)) { + nodes.add(new MethodImplementationsTreeNode(this.deobfuscatingTranslator, methodEntry)); + } + } + + // add them to this node + nodes.forEach(this::add); + } } diff --git a/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java index b65b8c10..4f84dd09 100644 --- a/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java +++ b/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java @@ -8,97 +8,96 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.analysis; import com.google.common.collect.Lists; - -import java.util.List; - -import javax.swing.tree.DefaultMutableTreeNode; - import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.MethodEntry; import cuchaz.enigma.mapping.Translator; +import javax.swing.tree.DefaultMutableTreeNode; +import java.util.List; + public class MethodInheritanceTreeNode extends DefaultMutableTreeNode { - private Translator deobfuscatingTranslator; - private MethodEntry entry; - private boolean isImplemented; - - public MethodInheritanceTreeNode(Translator deobfuscatingTranslator, MethodEntry entry, boolean isImplemented) { - this.deobfuscatingTranslator = deobfuscatingTranslator; - this.entry = entry; - this.isImplemented = isImplemented; - } - - public MethodEntry getMethodEntry() { - return this.entry; - } - - public String getDeobfClassName() { - return this.deobfuscatingTranslator.translateClass(this.entry.getClassName()); - } - - public String getDeobfMethodName() { - return this.deobfuscatingTranslator.translate(this.entry); - } - - public boolean isImplemented() { - return this.isImplemented; - } - - @Override - public String toString() { - String className = getDeobfClassName(); - if (className == null) { - className = this.entry.getClassName(); - } - - if (!this.isImplemented) { - return className; - } else { - String methodName = getDeobfMethodName(); - if (methodName == null) { - methodName = this.entry.getName(); - } - return className + "." + methodName + "()"; - } - } - - public void load(JarIndex index, boolean recurse) { - // get all the child nodes - List nodes = Lists.newArrayList(); - for (ClassEntry subclassEntry : index.getTranslationIndex().getSubclass(this.entry.getClassEntry())) { - MethodEntry methodEntry = new MethodEntry(subclassEntry, this.entry.getName(), this.entry.getSignature() - ); - nodes.add(new MethodInheritanceTreeNode(this.deobfuscatingTranslator, methodEntry, index.containsObfBehavior(methodEntry) - )); - } - - // add them to this node - nodes.forEach(this::add); - - if (recurse) { - for (MethodInheritanceTreeNode node : nodes) { - node.load(index, true); - } - } - } - - public static MethodInheritanceTreeNode findNode(MethodInheritanceTreeNode node, MethodEntry entry) { - // is this the node? - if (node.getMethodEntry().equals(entry)) { - return node; - } - - // recurse - for (int i = 0; i < node.getChildCount(); i++) { - MethodInheritanceTreeNode foundNode = findNode((MethodInheritanceTreeNode) node.getChildAt(i), entry); - if (foundNode != null) { - return foundNode; - } - } - return null; - } + private Translator deobfuscatingTranslator; + private MethodEntry entry; + private boolean isImplemented; + + public MethodInheritanceTreeNode(Translator deobfuscatingTranslator, MethodEntry entry, boolean isImplemented) { + this.deobfuscatingTranslator = deobfuscatingTranslator; + this.entry = entry; + this.isImplemented = isImplemented; + } + + public static MethodInheritanceTreeNode findNode(MethodInheritanceTreeNode node, MethodEntry entry) { + // is this the node? + if (node.getMethodEntry().equals(entry)) { + return node; + } + + // recurse + for (int i = 0; i < node.getChildCount(); i++) { + MethodInheritanceTreeNode foundNode = findNode((MethodInheritanceTreeNode) node.getChildAt(i), entry); + if (foundNode != null) { + return foundNode; + } + } + return null; + } + + public MethodEntry getMethodEntry() { + return this.entry; + } + + public String getDeobfClassName() { + return this.deobfuscatingTranslator.translateClass(this.entry.getClassName()); + } + + public String getDeobfMethodName() { + return this.deobfuscatingTranslator.translate(this.entry); + } + + public boolean isImplemented() { + return this.isImplemented; + } + + @Override + public String toString() { + String className = getDeobfClassName(); + if (className == null) { + className = this.entry.getClassName(); + } + + if (!this.isImplemented) { + return className; + } else { + String methodName = getDeobfMethodName(); + if (methodName == null) { + methodName = this.entry.getName(); + } + return className + "." + methodName + "()"; + } + } + + public void load(JarIndex index, boolean recurse) { + // get all the child nodes + List nodes = Lists.newArrayList(); + for (ClassEntry subclassEntry : index.getTranslationIndex().getSubclass(this.entry.getClassEntry())) { + MethodEntry methodEntry = new MethodEntry(subclassEntry, this.entry.getName(), this.entry.getSignature() + ); + nodes.add(new MethodInheritanceTreeNode(this.deobfuscatingTranslator, methodEntry, index.containsObfBehavior(methodEntry) + )); + } + + // add them to this node + nodes.forEach(this::add); + + if (recurse) { + for (MethodInheritanceTreeNode node : nodes) { + node.load(index, true); + } + } + } } diff --git a/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java index 93923467..04693637 100644 --- a/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java +++ b/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java @@ -8,12 +8,13 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.analysis; import cuchaz.enigma.mapping.Entry; public interface ReferenceTreeNode { - E getEntry(); + E getEntry(); - EntryReference getReference(); + EntryReference getReference(); } diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndex.java b/src/main/java/cuchaz/enigma/analysis/SourceIndex.java index 719930e9..19250c8d 100644 --- a/src/main/java/cuchaz/enigma/analysis/SourceIndex.java +++ b/src/main/java/cuchaz/enigma/analysis/SourceIndex.java @@ -8,174 +8,173 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.analysis; import com.google.common.collect.HashMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; - import com.strobel.decompiler.languages.Region; import com.strobel.decompiler.languages.java.ast.AstNode; import com.strobel.decompiler.languages.java.ast.Identifier; +import cuchaz.enigma.mapping.Entry; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.TreeMap; -import cuchaz.enigma.mapping.Entry; - public class SourceIndex { - private String source; - private TreeMap> tokenToReference; - private Multimap, Token> referenceToTokens; - private Map declarationToToken; - private List lineOffsets; - private boolean ignoreBadTokens; - - public SourceIndex(String source) { - this(source, true); - } - - public SourceIndex(String source, boolean ignoreBadTokens) { - this.source = source; - this.ignoreBadTokens = ignoreBadTokens; - this.tokenToReference = Maps.newTreeMap(); - this.referenceToTokens = HashMultimap.create(); - this.declarationToToken = Maps.newHashMap(); - this.lineOffsets = Lists.newArrayList(); - - // count the lines - this.lineOffsets.add(0); - for (int i = 0; i < source.length(); i++) { - if (source.charAt(i) == '\n') { - this.lineOffsets.add(i + 1); - } - } - } - - public String getSource() { - return this.source; - } - - public Token getToken(AstNode node) { - - // get the text of the node - String name = ""; - if (node instanceof Identifier) { - name = ((Identifier) node).getName(); - } - - // get a token for this node's region - Region region = node.getRegion(); - if (region.getBeginLine() == 0 || region.getEndLine() == 0) { - // DEBUG - System.err.println(String.format("WARNING: %s \"%s\" has invalid region: %s", node.getNodeType(), name, region)); - return null; - } - Token token = new Token(toPos(region.getBeginLine(), region.getBeginColumn()), toPos(region.getEndLine(), region.getEndColumn()), this.source); - if (token.start == 0) { - // DEBUG - System.err.println(String.format("WARNING: %s \"%s\" has invalid start: %s", node.getNodeType(), name, region)); - return null; - } - - // DEBUG - // 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 && this.ignoreBadTokens) { - // DEBUG - System.err.println(String.format("WARNING: %s \"%s\" is probably a bad token. It was ignored", node.getNodeType(), name)); - return null; - } - - return token; - } - - public void addReference(AstNode node, Entry deobfEntry, Entry deobfContext) { - Token token = getToken(node); - if (token != null) { - EntryReference deobfReference = new EntryReference<>(deobfEntry, token.text, deobfContext); - this.tokenToReference.put(token, deobfReference); - this.referenceToTokens.put(deobfReference, token); - } - } - - public void addDeclaration(AstNode node, Entry deobfEntry) { - Token token = getToken(node); - if (token != null) { - EntryReference reference = new EntryReference<>(deobfEntry, token.text); - this.tokenToReference.put(token, reference); - this.referenceToTokens.put(reference, token); - this.declarationToToken.put(deobfEntry, token); - } - } - - public Token getReferenceToken(int pos) { - Token token = this.tokenToReference.floorKey(new Token(pos, pos, null)); - if (token != null && token.contains(pos)) { - return token; - } - return null; - } - - public Collection getReferenceTokens(EntryReference deobfReference) { - return this.referenceToTokens.get(deobfReference); - } - - public EntryReference getDeobfReference(Token token) { - if (token == null) { - return null; - } - return this.tokenToReference.get(token); - } - - public void replaceDeobfReference(Token token, EntryReference newDeobfReference) { - EntryReference oldDeobfReference = this.tokenToReference.get(token); - this.tokenToReference.put(token, newDeobfReference); - Collection tokens = this.referenceToTokens.get(oldDeobfReference); - this.referenceToTokens.removeAll(oldDeobfReference); - this.referenceToTokens.putAll(newDeobfReference, tokens); - } - - public Iterable referenceTokens() { - return this.tokenToReference.keySet(); - } - - public Iterable declarationTokens() { - return this.declarationToToken.values(); - } - - public Iterable declarations() { - return this.declarationToToken.keySet(); - } - - public Token getDeclarationToken(Entry deobfEntry) { - return this.declarationToToken.get(deobfEntry); - } - - public int getLineNumber(int pos) { - // line number is 1-based - int line = 0; - for (Integer offset : this.lineOffsets) { - if (offset > pos) { - break; - } - line++; - } - return line; - } - - public int getColumnNumber(int pos) { - // column number is 1-based - return pos - this.lineOffsets.get(getLineNumber(pos) - 1) + 1; - } - - private int toPos(int line, int col) { - // line and col are 1-based - return this.lineOffsets.get(line - 1) + col - 1; - } + private String source; + private TreeMap> tokenToReference; + private Multimap, Token> referenceToTokens; + private Map declarationToToken; + private List lineOffsets; + private boolean ignoreBadTokens; + + public SourceIndex(String source) { + this(source, true); + } + + public SourceIndex(String source, boolean ignoreBadTokens) { + this.source = source; + this.ignoreBadTokens = ignoreBadTokens; + this.tokenToReference = Maps.newTreeMap(); + this.referenceToTokens = HashMultimap.create(); + this.declarationToToken = Maps.newHashMap(); + this.lineOffsets = Lists.newArrayList(); + + // count the lines + this.lineOffsets.add(0); + for (int i = 0; i < source.length(); i++) { + if (source.charAt(i) == '\n') { + this.lineOffsets.add(i + 1); + } + } + } + + public String getSource() { + return this.source; + } + + public Token getToken(AstNode node) { + + // get the text of the node + String name = ""; + if (node instanceof Identifier) { + name = ((Identifier) node).getName(); + } + + // get a token for this node's region + Region region = node.getRegion(); + if (region.getBeginLine() == 0 || region.getEndLine() == 0) { + // DEBUG + System.err.println(String.format("WARNING: %s \"%s\" has invalid region: %s", node.getNodeType(), name, region)); + return null; + } + Token token = new Token(toPos(region.getBeginLine(), region.getBeginColumn()), toPos(region.getEndLine(), region.getEndColumn()), this.source); + if (token.start == 0) { + // DEBUG + System.err.println(String.format("WARNING: %s \"%s\" has invalid start: %s", node.getNodeType(), name, region)); + return null; + } + + // DEBUG + // 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 && this.ignoreBadTokens) { + // DEBUG + System.err.println(String.format("WARNING: %s \"%s\" is probably a bad token. It was ignored", node.getNodeType(), name)); + return null; + } + + return token; + } + + public void addReference(AstNode node, Entry deobfEntry, Entry deobfContext) { + Token token = getToken(node); + if (token != null) { + EntryReference deobfReference = new EntryReference<>(deobfEntry, token.text, deobfContext); + this.tokenToReference.put(token, deobfReference); + this.referenceToTokens.put(deobfReference, token); + } + } + + public void addDeclaration(AstNode node, Entry deobfEntry) { + Token token = getToken(node); + if (token != null) { + EntryReference reference = new EntryReference<>(deobfEntry, token.text); + this.tokenToReference.put(token, reference); + this.referenceToTokens.put(reference, token); + this.declarationToToken.put(deobfEntry, token); + } + } + + public Token getReferenceToken(int pos) { + Token token = this.tokenToReference.floorKey(new Token(pos, pos, null)); + if (token != null && token.contains(pos)) { + return token; + } + return null; + } + + public Collection getReferenceTokens(EntryReference deobfReference) { + return this.referenceToTokens.get(deobfReference); + } + + public EntryReference getDeobfReference(Token token) { + if (token == null) { + return null; + } + return this.tokenToReference.get(token); + } + + public void replaceDeobfReference(Token token, EntryReference newDeobfReference) { + EntryReference oldDeobfReference = this.tokenToReference.get(token); + this.tokenToReference.put(token, newDeobfReference); + Collection tokens = this.referenceToTokens.get(oldDeobfReference); + this.referenceToTokens.removeAll(oldDeobfReference); + this.referenceToTokens.putAll(newDeobfReference, tokens); + } + + public Iterable referenceTokens() { + return this.tokenToReference.keySet(); + } + + public Iterable declarationTokens() { + return this.declarationToToken.values(); + } + + public Iterable declarations() { + return this.declarationToToken.keySet(); + } + + public Token getDeclarationToken(Entry deobfEntry) { + return this.declarationToToken.get(deobfEntry); + } + + public int getLineNumber(int pos) { + // line number is 1-based + int line = 0; + for (Integer offset : this.lineOffsets) { + if (offset > pos) { + break; + } + line++; + } + return line; + } + + public int getColumnNumber(int pos) { + // column number is 1-based + return pos - this.lineOffsets.get(getLineNumber(pos) - 1) + 1; + } + + private int toPos(int line, int col) { + // line and col are 1-based + return this.lineOffsets.get(line - 1) + col - 1; + } } diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java b/src/main/java/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java index bfd5a562..4febf256 100644 --- a/src/main/java/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java +++ b/src/main/java/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java @@ -8,6 +8,7 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.analysis; import com.google.common.collect.HashMultimap; @@ -26,186 +27,179 @@ import java.util.Map; public class SourceIndexBehaviorVisitor extends SourceIndexVisitor { - private BehaviorEntry behaviorEntry; - - // TODO: Really fix Procyon index problem with inner classes - private int argumentPosition; - private int localsPosition; - private Multimap unmatchedIdentifier = HashMultimap.create(); - private Map identifierEntryCache = new HashMap<>(); - - public SourceIndexBehaviorVisitor(BehaviorEntry behaviorEntry) { - this.behaviorEntry = behaviorEntry; - this.argumentPosition = 0; - this.localsPosition = 0; - } - - @Override - public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) { - MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); - - // get the behavior entry - ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); - BehaviorEntry behaviorEntry = null; - if (ref instanceof MethodReference) { - MethodReference methodRef = (MethodReference) ref; - if (methodRef.isConstructor()) { - behaviorEntry = new ConstructorEntry(classEntry, new Signature(ref.getErasedSignature())); - } else if (methodRef.isTypeInitializer()) { - behaviorEntry = new ConstructorEntry(classEntry); - } else { - behaviorEntry = new MethodEntry(classEntry, ref.getName(), new Signature(ref.getErasedSignature())); - } - } - if (behaviorEntry != null) { - // get the node for the token - AstNode tokenNode = null; - if (node.getTarget() instanceof MemberReferenceExpression) { - tokenNode = ((MemberReferenceExpression) node.getTarget()).getMemberNameToken(); - } else if (node.getTarget() instanceof SuperReferenceExpression) { - tokenNode = node.getTarget(); - } else if (node.getTarget() instanceof ThisReferenceExpression) { - tokenNode = node.getTarget(); - } - if (tokenNode != null) { - index.addReference(tokenNode, behaviorEntry, this.behaviorEntry); - } - } - - // Check for identifier - node.getArguments().stream().filter(expression -> expression instanceof IdentifierExpression) - .forEach(expression -> this.checkIdentifier((IdentifierExpression) expression, index)); - return recurse(node, index); - } - - @Override - public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) { - MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); - if (ref != null) { - // make sure this is actually a field - if (ref.getErasedSignature().indexOf('(') >= 0) { - throw new Error("Expected a field here! got " + ref); - } - - ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); - FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new Type(ref.getErasedSignature())); - index.addReference(node.getMemberNameToken(), fieldEntry, this.behaviorEntry); - } - - return recurse(node, index); - } - - @Override - public Void visitSimpleType(SimpleType node, SourceIndex index) { - TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE); - if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) { - ClassEntry classEntry = new ClassEntry(ref.getInternalName()); - index.addReference(node.getIdentifierToken(), classEntry, this.behaviorEntry); - } - - return recurse(node, index); - } - - @Override - public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) { - ParameterDefinition def = node.getUserData(Keys.PARAMETER_DEFINITION); - if (def.getMethod() instanceof MemberReference && def.getMethod() instanceof MethodReference) - { - ArgumentEntry argumentEntry = new ArgumentEntry(ProcyonEntryFactory.getBehaviorEntry((MethodReference) def.getMethod()), - argumentPosition++, node.getName()); - Identifier identifier = node.getNameToken(); - // cache the argument entry and the identifier - identifierEntryCache.put(identifier.getName(), argumentEntry); - index.addDeclaration(identifier, argumentEntry); - } - - return recurse(node, index); - } - - @Override - public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) { - MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); - if (ref != null) { - ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); - FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new Type(ref.getErasedSignature())); - index.addReference(node.getIdentifierToken(), fieldEntry, this.behaviorEntry); - } - else - this.checkIdentifier(node, index); - return recurse(node, index); - } - - private void checkIdentifier(IdentifierExpression node, SourceIndex index) - { - if (identifierEntryCache.containsKey(node.getIdentifier())) // If it's in the argument cache, create a token! - index.addDeclaration(node.getIdentifierToken(), identifierEntryCache.get(node.getIdentifier())); - else - unmatchedIdentifier.put(node.getIdentifier(), node.getIdentifierToken()); // Not matched actually, put it! - } - - private void addDeclarationToUnmatched(String key, SourceIndex index) - { - Entry entry = identifierEntryCache.get(key); - - // This cannot happened in theory - if (entry == null) - return; - for (Identifier identifier : unmatchedIdentifier.get(key)) - index.addDeclaration(identifier, entry); - unmatchedIdentifier.removeAll(key); - } - - @Override - public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) { - MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); - if (ref != null) { - ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); - ConstructorEntry constructorEntry = new ConstructorEntry(classEntry, new Signature(ref.getErasedSignature())); - if (node.getType() instanceof SimpleType) { - SimpleType simpleTypeNode = (SimpleType) node.getType(); - index.addReference(simpleTypeNode.getIdentifierToken(), constructorEntry, this.behaviorEntry); - } - } - - return recurse(node, index); - } - - @Override - public Void visitForEachStatement(ForEachStatement node, SourceIndex index) { - if (node.getVariableType() instanceof SimpleType) - { - SimpleType type = (SimpleType) node.getVariableType(); - TypeReference typeReference = type.getUserData(Keys.TYPE_REFERENCE); - Identifier identifier = node.getVariableNameToken(); - String signature = Descriptor.of(typeReference.getErasedDescription()); - LocalVariableEntry localVariableEntry = new LocalVariableEntry(behaviorEntry, localsPosition++, identifier.getName(), new Type(signature)); - identifierEntryCache.put(identifier.getName(), localVariableEntry); - addDeclarationToUnmatched(identifier.getName(), index); - index.addDeclaration(identifier, localVariableEntry); - } - return recurse(node, index); - } - - @Override - public Void visitVariableDeclaration(VariableDeclarationStatement node, SourceIndex index) { - AstNodeCollection variables = node.getVariables(); - - // Single assignation - if (variables.size() == 1) - { - VariableInitializer initializer = variables.firstOrNullObject(); - if (initializer != null && node.getType() instanceof SimpleType) - { - SimpleType type = (SimpleType) node.getType(); - TypeReference typeReference = type.getUserData(Keys.TYPE_REFERENCE); - String signature = Descriptor.of(typeReference.getErasedDescription()); - Identifier identifier = initializer.getNameToken(); - LocalVariableEntry localVariableEntry = new LocalVariableEntry(behaviorEntry, localsPosition++, initializer.getName(), new Type(signature)); - identifierEntryCache.put(identifier.getName(), localVariableEntry); - addDeclarationToUnmatched(identifier.getName(), index); - index.addDeclaration(identifier, localVariableEntry); - } - } - return recurse(node, index); - } + private BehaviorEntry behaviorEntry; + + // TODO: Really fix Procyon index problem with inner classes + private int argumentPosition; + private int localsPosition; + private Multimap unmatchedIdentifier = HashMultimap.create(); + private Map identifierEntryCache = new HashMap<>(); + + public SourceIndexBehaviorVisitor(BehaviorEntry behaviorEntry) { + this.behaviorEntry = behaviorEntry; + this.argumentPosition = 0; + this.localsPosition = 0; + } + + @Override + public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) { + MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); + + // get the behavior entry + ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); + BehaviorEntry behaviorEntry = null; + if (ref instanceof MethodReference) { + MethodReference methodRef = (MethodReference) ref; + if (methodRef.isConstructor()) { + behaviorEntry = new ConstructorEntry(classEntry, new Signature(ref.getErasedSignature())); + } else if (methodRef.isTypeInitializer()) { + behaviorEntry = new ConstructorEntry(classEntry); + } else { + behaviorEntry = new MethodEntry(classEntry, ref.getName(), new Signature(ref.getErasedSignature())); + } + } + if (behaviorEntry != null) { + // get the node for the token + AstNode tokenNode = null; + if (node.getTarget() instanceof MemberReferenceExpression) { + tokenNode = ((MemberReferenceExpression) node.getTarget()).getMemberNameToken(); + } else if (node.getTarget() instanceof SuperReferenceExpression) { + tokenNode = node.getTarget(); + } else if (node.getTarget() instanceof ThisReferenceExpression) { + tokenNode = node.getTarget(); + } + if (tokenNode != null) { + index.addReference(tokenNode, behaviorEntry, this.behaviorEntry); + } + } + + // Check for identifier + node.getArguments().stream().filter(expression -> expression instanceof IdentifierExpression) + .forEach(expression -> this.checkIdentifier((IdentifierExpression) expression, index)); + return recurse(node, index); + } + + @Override + public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) { + MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); + if (ref != null) { + // make sure this is actually a field + if (ref.getErasedSignature().indexOf('(') >= 0) { + throw new Error("Expected a field here! got " + ref); + } + + ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); + FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new Type(ref.getErasedSignature())); + index.addReference(node.getMemberNameToken(), fieldEntry, this.behaviorEntry); + } + + return recurse(node, index); + } + + @Override + public Void visitSimpleType(SimpleType node, SourceIndex index) { + TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE); + if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) { + ClassEntry classEntry = new ClassEntry(ref.getInternalName()); + index.addReference(node.getIdentifierToken(), classEntry, this.behaviorEntry); + } + + return recurse(node, index); + } + + @Override + public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) { + ParameterDefinition def = node.getUserData(Keys.PARAMETER_DEFINITION); + if (def.getMethod() instanceof MemberReference && def.getMethod() instanceof MethodReference) { + ArgumentEntry argumentEntry = new ArgumentEntry(ProcyonEntryFactory.getBehaviorEntry((MethodReference) def.getMethod()), + argumentPosition++, node.getName()); + Identifier identifier = node.getNameToken(); + // cache the argument entry and the identifier + identifierEntryCache.put(identifier.getName(), argumentEntry); + index.addDeclaration(identifier, argumentEntry); + } + + return recurse(node, index); + } + + @Override + public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) { + MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); + if (ref != null) { + ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); + FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new Type(ref.getErasedSignature())); + index.addReference(node.getIdentifierToken(), fieldEntry, this.behaviorEntry); + } else + this.checkIdentifier(node, index); + return recurse(node, index); + } + + private void checkIdentifier(IdentifierExpression node, SourceIndex index) { + if (identifierEntryCache.containsKey(node.getIdentifier())) // If it's in the argument cache, create a token! + index.addDeclaration(node.getIdentifierToken(), identifierEntryCache.get(node.getIdentifier())); + else + unmatchedIdentifier.put(node.getIdentifier(), node.getIdentifierToken()); // Not matched actually, put it! + } + + private void addDeclarationToUnmatched(String key, SourceIndex index) { + Entry entry = identifierEntryCache.get(key); + + // This cannot happened in theory + if (entry == null) + return; + for (Identifier identifier : unmatchedIdentifier.get(key)) + index.addDeclaration(identifier, entry); + unmatchedIdentifier.removeAll(key); + } + + @Override + public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) { + MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); + if (ref != null) { + ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); + ConstructorEntry constructorEntry = new ConstructorEntry(classEntry, new Signature(ref.getErasedSignature())); + if (node.getType() instanceof SimpleType) { + SimpleType simpleTypeNode = (SimpleType) node.getType(); + index.addReference(simpleTypeNode.getIdentifierToken(), constructorEntry, this.behaviorEntry); + } + } + + return recurse(node, index); + } + + @Override + public Void visitForEachStatement(ForEachStatement node, SourceIndex index) { + if (node.getVariableType() instanceof SimpleType) { + SimpleType type = (SimpleType) node.getVariableType(); + TypeReference typeReference = type.getUserData(Keys.TYPE_REFERENCE); + Identifier identifier = node.getVariableNameToken(); + String signature = Descriptor.of(typeReference.getErasedDescription()); + LocalVariableEntry localVariableEntry = new LocalVariableEntry(behaviorEntry, localsPosition++, identifier.getName(), new Type(signature)); + identifierEntryCache.put(identifier.getName(), localVariableEntry); + addDeclarationToUnmatched(identifier.getName(), index); + index.addDeclaration(identifier, localVariableEntry); + } + return recurse(node, index); + } + + @Override + public Void visitVariableDeclaration(VariableDeclarationStatement node, SourceIndex index) { + AstNodeCollection variables = node.getVariables(); + + // Single assignation + if (variables.size() == 1) { + VariableInitializer initializer = variables.firstOrNullObject(); + if (initializer != null && node.getType() instanceof SimpleType) { + SimpleType type = (SimpleType) node.getType(); + TypeReference typeReference = type.getUserData(Keys.TYPE_REFERENCE); + String signature = Descriptor.of(typeReference.getErasedDescription()); + Identifier identifier = initializer.getNameToken(); + LocalVariableEntry localVariableEntry = new LocalVariableEntry(behaviorEntry, localsPosition++, initializer.getName(), new Type(signature)); + identifierEntryCache.put(identifier.getName(), localVariableEntry); + addDeclarationToUnmatched(identifier.getName(), index); + index.addDeclaration(identifier, localVariableEntry); + } + } + return recurse(node, index); + } } diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java b/src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java index 2a212222..11482163 100644 --- a/src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java +++ b/src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java @@ -8,6 +8,7 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.analysis; import com.strobel.assembler.metadata.FieldDefinition; @@ -20,79 +21,79 @@ import cuchaz.enigma.mapping.*; public class SourceIndexClassVisitor extends SourceIndexVisitor { - private ClassEntry classEntry; + private ClassEntry classEntry; - public SourceIndexClassVisitor(ClassEntry classEntry) { - this.classEntry = classEntry; - } + public SourceIndexClassVisitor(ClassEntry classEntry) { + this.classEntry = classEntry; + } - @Override - public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) { - // is this this class, or a subtype? - TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION); - ClassEntry classEntry = new ClassEntry(def.getInternalName()); - if (!classEntry.equals(this.classEntry)) { - // it's a sub-type, recurse - index.addDeclaration(node.getNameToken(), classEntry); - return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index); - } + @Override + public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) { + // is this this class, or a subtype? + TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION); + ClassEntry classEntry = new ClassEntry(def.getInternalName()); + if (!classEntry.equals(this.classEntry)) { + // it's a sub-type, recurse + index.addDeclaration(node.getNameToken(), classEntry); + return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index); + } - return recurse(node, index); - } + return recurse(node, index); + } - @Override - public Void visitSimpleType(SimpleType node, SourceIndex index) { - TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE); - if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) { - ClassEntry classEntry = new ClassEntry(ref.getInternalName()); - index.addReference(node.getIdentifierToken(), classEntry, this.classEntry); - } + @Override + public Void visitSimpleType(SimpleType node, SourceIndex index) { + TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE); + if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) { + ClassEntry classEntry = new ClassEntry(ref.getInternalName()); + index.addReference(node.getIdentifierToken(), classEntry, this.classEntry); + } - return recurse(node, index); - } + return recurse(node, index); + } - @Override - public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) { - MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION); - BehaviorEntry behaviorEntry = ProcyonEntryFactory.getBehaviorEntry(def); - AstNode tokenNode = node.getNameToken(); - if (behaviorEntry instanceof ConstructorEntry) { - ConstructorEntry constructorEntry = (ConstructorEntry) behaviorEntry; - if (constructorEntry.isStatic()) { - // for static initializers, check elsewhere for the token node - tokenNode = node.getModifiers().firstOrNullObject(); - } - } - index.addDeclaration(tokenNode, behaviorEntry); - return node.acceptVisitor(new SourceIndexBehaviorVisitor(behaviorEntry), index); - } + @Override + public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) { + MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION); + BehaviorEntry behaviorEntry = ProcyonEntryFactory.getBehaviorEntry(def); + AstNode tokenNode = node.getNameToken(); + if (behaviorEntry instanceof ConstructorEntry) { + ConstructorEntry constructorEntry = (ConstructorEntry) behaviorEntry; + if (constructorEntry.isStatic()) { + // for static initializers, check elsewhere for the token node + tokenNode = node.getModifiers().firstOrNullObject(); + } + } + index.addDeclaration(tokenNode, behaviorEntry); + return node.acceptVisitor(new SourceIndexBehaviorVisitor(behaviorEntry), index); + } - @Override - public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) { - MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION); - ConstructorEntry constructorEntry = ProcyonEntryFactory.getConstructorEntry(def); - index.addDeclaration(node.getNameToken(), constructorEntry); - return node.acceptVisitor(new SourceIndexBehaviorVisitor(constructorEntry), index); - } + @Override + public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) { + MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION); + ConstructorEntry constructorEntry = ProcyonEntryFactory.getConstructorEntry(def); + index.addDeclaration(node.getNameToken(), constructorEntry); + return node.acceptVisitor(new SourceIndexBehaviorVisitor(constructorEntry), index); + } - @Override - public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) { - FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); - FieldEntry fieldEntry = ProcyonEntryFactory.getFieldEntry(def); - assert (node.getVariables().size() == 1); - VariableInitializer variable = node.getVariables().firstOrNullObject(); - index.addDeclaration(variable.getNameToken(), fieldEntry); + @Override + public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) { + FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); + FieldEntry fieldEntry = ProcyonEntryFactory.getFieldEntry(def); + assert (node.getVariables().size() == 1); + VariableInitializer variable = node.getVariables().firstOrNullObject(); + index.addDeclaration(variable.getNameToken(), fieldEntry); - return recurse(node, index); - } + return recurse(node, index); + } - @Override - public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) { - // treat enum declarations as field declarations - FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); - FieldEntry fieldEntry = ProcyonEntryFactory.getFieldEntry(def); - index.addDeclaration(node.getNameToken(), fieldEntry); + @Override + public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) { + // treat enum declarations as field declarations + FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); + FieldEntry fieldEntry = ProcyonEntryFactory.getFieldEntry(def); + index.addDeclaration(node.getNameToken(), fieldEntry); - return recurse(node, index); - } + return recurse(node, index); + } } diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java b/src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java index 40381f43..a94a55b7 100644 --- a/src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java +++ b/src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java @@ -8,374 +8,374 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.analysis; import com.strobel.assembler.metadata.TypeDefinition; import com.strobel.decompiler.languages.java.ast.*; import com.strobel.decompiler.patterns.Pattern; - import cuchaz.enigma.mapping.ClassEntry; public class SourceIndexVisitor implements IAstVisitor { - @Override - public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) { - TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION); - ClassEntry classEntry = new ClassEntry(def.getInternalName()); - index.addDeclaration(node.getNameToken(), classEntry); - - return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index); - } - - protected Void recurse(AstNode node, SourceIndex index) { - for (final AstNode child : node.getChildren()) { - child.acceptVisitor(this, index); - } - return null; - } - - @Override - public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitSimpleType(SimpleType node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitComment(Comment node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitPatternPlaceholder(AstNode node, Pattern pattern, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitTypeReference(TypeReferenceExpression node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitJavaTokenNode(JavaTokenNode node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitIdentifier(Identifier node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitNullReferenceExpression(NullReferenceExpression node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitThisReferenceExpression(ThisReferenceExpression node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitSuperReferenceExpression(SuperReferenceExpression node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitClassOfExpression(ClassOfExpression node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitBlockStatement(BlockStatement node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitExpressionStatement(ExpressionStatement node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitBreakStatement(BreakStatement node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitContinueStatement(ContinueStatement node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitDoWhileStatement(DoWhileStatement node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitEmptyStatement(EmptyStatement node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitIfElseStatement(IfElseStatement node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitLabelStatement(LabelStatement node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitLabeledStatement(LabeledStatement node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitReturnStatement(ReturnStatement node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitSwitchStatement(SwitchStatement node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitSwitchSection(SwitchSection node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitCaseLabel(CaseLabel node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitThrowStatement(ThrowStatement node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitCatchClause(CatchClause node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitAnnotation(Annotation node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitNewLine(NewLineNode node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitVariableDeclaration(VariableDeclarationStatement node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitVariableInitializer(VariableInitializer node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitText(TextNode node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitImportDeclaration(ImportDeclaration node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitInitializerBlock(InstanceInitializer node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitTypeParameterDeclaration(TypeParameterDeclaration node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitCompilationUnit(CompilationUnit node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitPackageDeclaration(PackageDeclaration node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitArraySpecifier(ArraySpecifier node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitComposedType(ComposedType node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitWhileStatement(WhileStatement node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitPrimitiveExpression(PrimitiveExpression node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitCastExpression(CastExpression node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitBinaryOperatorExpression(BinaryOperatorExpression node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitInstanceOfExpression(InstanceOfExpression node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitIndexerExpression(IndexerExpression node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitUnaryOperatorExpression(UnaryOperatorExpression node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitConditionalExpression(ConditionalExpression node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitArrayInitializerExpression(ArrayInitializerExpression node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitArrayCreationExpression(ArrayCreationExpression node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitAssignmentExpression(AssignmentExpression node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitForStatement(ForStatement node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitForEachStatement(ForEachStatement node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitTryCatchStatement(TryCatchStatement node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitGotoStatement(GotoStatement node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitParenthesizedExpression(ParenthesizedExpression node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitSynchronizedStatement(SynchronizedStatement node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitAnonymousObjectCreationExpression(AnonymousObjectCreationExpression node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitWildcardType(WildcardType node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitMethodGroupExpression(MethodGroupExpression node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitAssertStatement(AssertStatement node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitLambdaExpression(LambdaExpression node, SourceIndex index) { - return recurse(node, index); - } - - @Override - public Void visitLocalTypeDeclarationStatement(LocalTypeDeclarationStatement node, SourceIndex index) { - return recurse(node, index); - } + @Override + public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) { + TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION); + ClassEntry classEntry = new ClassEntry(def.getInternalName()); + index.addDeclaration(node.getNameToken(), classEntry); + + return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index); + } + + protected Void recurse(AstNode node, SourceIndex index) { + for (final AstNode child : node.getChildren()) { + child.acceptVisitor(this, index); + } + return null; + } + + @Override + public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitSimpleType(SimpleType node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitComment(Comment node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitPatternPlaceholder(AstNode node, Pattern pattern, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitTypeReference(TypeReferenceExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitJavaTokenNode(JavaTokenNode node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitIdentifier(Identifier node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitNullReferenceExpression(NullReferenceExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitThisReferenceExpression(ThisReferenceExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitSuperReferenceExpression(SuperReferenceExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitClassOfExpression(ClassOfExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitBlockStatement(BlockStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitExpressionStatement(ExpressionStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitBreakStatement(BreakStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitContinueStatement(ContinueStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitDoWhileStatement(DoWhileStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitEmptyStatement(EmptyStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitIfElseStatement(IfElseStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitLabelStatement(LabelStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitLabeledStatement(LabeledStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitReturnStatement(ReturnStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitSwitchStatement(SwitchStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitSwitchSection(SwitchSection node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitCaseLabel(CaseLabel node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitThrowStatement(ThrowStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitCatchClause(CatchClause node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitAnnotation(Annotation node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitNewLine(NewLineNode node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitVariableDeclaration(VariableDeclarationStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitVariableInitializer(VariableInitializer node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitText(TextNode node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitImportDeclaration(ImportDeclaration node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitInitializerBlock(InstanceInitializer node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitTypeParameterDeclaration(TypeParameterDeclaration node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitCompilationUnit(CompilationUnit node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitPackageDeclaration(PackageDeclaration node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitArraySpecifier(ArraySpecifier node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitComposedType(ComposedType node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitWhileStatement(WhileStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitPrimitiveExpression(PrimitiveExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitCastExpression(CastExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitBinaryOperatorExpression(BinaryOperatorExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitInstanceOfExpression(InstanceOfExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitIndexerExpression(IndexerExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitUnaryOperatorExpression(UnaryOperatorExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitConditionalExpression(ConditionalExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitArrayInitializerExpression(ArrayInitializerExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitArrayCreationExpression(ArrayCreationExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitAssignmentExpression(AssignmentExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitForStatement(ForStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitForEachStatement(ForEachStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitTryCatchStatement(TryCatchStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitGotoStatement(GotoStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitParenthesizedExpression(ParenthesizedExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitSynchronizedStatement(SynchronizedStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitAnonymousObjectCreationExpression(AnonymousObjectCreationExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitWildcardType(WildcardType node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitMethodGroupExpression(MethodGroupExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitAssertStatement(AssertStatement node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitLambdaExpression(LambdaExpression node, SourceIndex index) { + return recurse(node, index); + } + + @Override + public Void visitLocalTypeDeclarationStatement(LocalTypeDeclarationStatement node, SourceIndex index) { + return recurse(node, index); + } } diff --git a/src/main/java/cuchaz/enigma/analysis/Token.java b/src/main/java/cuchaz/enigma/analysis/Token.java index 42f4660a..266d2027 100644 --- a/src/main/java/cuchaz/enigma/analysis/Token.java +++ b/src/main/java/cuchaz/enigma/analysis/Token.java @@ -8,48 +8,48 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.analysis; public class Token implements Comparable { - public int start; - public int end; - public String text; - - public Token(int start, int end, String source) { - this.start = start; - this.end = end; - if (source != null) { - this.text = source.substring(start, end); - } - } - - public boolean contains(int pos) { - return pos >= start && pos <= end; - } - - @Override - public int compareTo(Token other) { - return start - other.start; - } - - @Override - public boolean equals(Object other) { - return other instanceof Token && equals((Token) other); - } - - @Override - public int hashCode() - { - return Integer.hashCode(start) + Integer.hashCode(end) + (text != null ? text.hashCode() : 0); - } - - public boolean equals(Token other) { - return start == other.start && end == other.end; - } - - @Override - public String toString() { - return String.format("[%d,%d]", start, end); - } + public int start; + public int end; + public String text; + + public Token(int start, int end, String source) { + this.start = start; + this.end = end; + if (source != null) { + this.text = source.substring(start, end); + } + } + + public boolean contains(int pos) { + return pos >= start && pos <= end; + } + + @Override + public int compareTo(Token other) { + return start - other.start; + } + + @Override + public boolean equals(Object other) { + return other instanceof Token && equals((Token) other); + } + + @Override + public int hashCode() { + return Integer.hashCode(start) + Integer.hashCode(end) + (text != null ? text.hashCode() : 0); + } + + public boolean equals(Token other) { + return start == other.start && end == other.end; + } + + @Override + public String toString() { + return String.format("[%d,%d]", start, end); + } } diff --git a/src/main/java/cuchaz/enigma/analysis/TranslationIndex.java b/src/main/java/cuchaz/enigma/analysis/TranslationIndex.java index d51131f6..26be05b4 100644 --- a/src/main/java/cuchaz/enigma/analysis/TranslationIndex.java +++ b/src/main/java/cuchaz/enigma/analysis/TranslationIndex.java @@ -8,291 +8,288 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.analysis; import com.google.common.collect.HashMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; - -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Set; - import cuchaz.enigma.mapping.*; import javassist.CtBehavior; import javassist.CtClass; import javassist.CtField; import javassist.bytecode.Descriptor; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + public class TranslationIndex { - private Map superclasses; - private Multimap fieldEntries; - private Multimap behaviorEntries; - private Multimap interfaces; - - public TranslationIndex() { - this.superclasses = Maps.newHashMap(); - this.fieldEntries = HashMultimap.create(); - this.behaviorEntries = HashMultimap.create(); - this.interfaces = HashMultimap.create(); - } - - public TranslationIndex(TranslationIndex other, Translator translator) { - // translate the superclasses - this.superclasses = Maps.newHashMap(); - for (Map.Entry mapEntry : other.superclasses.entrySet()) { - this.superclasses.put(translator.translateEntry(mapEntry.getKey()), translator.translateEntry(mapEntry.getValue())); - } - - // translate the interfaces - this.interfaces = HashMultimap.create(); - for (Map.Entry mapEntry : other.interfaces.entries()) { - this.interfaces.put( - translator.translateEntry(mapEntry.getKey()), - translator.translateEntry(mapEntry.getValue()) - ); - } - - // translate the fields - this.fieldEntries = HashMultimap.create(); - for (Map.Entry mapEntry : other.fieldEntries.entries()) { - this.fieldEntries.put( - translator.translateEntry(mapEntry.getKey()), - translator.translateEntry(mapEntry.getValue()) - ); - } - - this.behaviorEntries = HashMultimap.create(); - for (Map.Entry mapEntry : other.behaviorEntries.entries()) { - this.behaviorEntries.put( - translator.translateEntry(mapEntry.getKey()), - translator.translateEntry(mapEntry.getValue()) - ); - } - } - - public void indexClass(CtClass c) { - indexClass(c, true); - } - - public void indexClass(CtClass c, boolean indexMembers) { - ClassEntry classEntry = EntryFactory.getClassEntry(c); - if (isJre(classEntry)) { - return; - } - - // add the superclass - ClassEntry superclassEntry = EntryFactory.getSuperclassEntry(c); - if (superclassEntry != null) { - this.superclasses.put(classEntry, superclassEntry); - } - - // add the interfaces - for (String interfaceClassName : c.getClassFile().getInterfaces()) { - ClassEntry interfaceClassEntry = new ClassEntry(Descriptor.toJvmName(interfaceClassName)); - if (!isJre(interfaceClassEntry)) { - - this.interfaces.put(classEntry, interfaceClassEntry); - } - } - - if (indexMembers) { - // add fields - for (CtField field : c.getDeclaredFields()) { - FieldEntry fieldEntry = EntryFactory.getFieldEntry(field); - this.fieldEntries.put(fieldEntry.getClassEntry(), fieldEntry); - } - - // add behaviors - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); - this.behaviorEntries.put(behaviorEntry.getClassEntry(), behaviorEntry); - } - } - } - - public void renameClasses(Map renames) { - EntryRenamer.renameClassesInMap(renames, this.superclasses); - EntryRenamer.renameClassesInMultimap(renames, this.fieldEntries); - EntryRenamer.renameClassesInMultimap(renames, this.behaviorEntries); - } - - public ClassEntry getSuperclass(ClassEntry classEntry) { - return this.superclasses.get(classEntry); - } - - public List getAncestry(ClassEntry classEntry) { - List ancestors = Lists.newArrayList(); - while (classEntry != null) { - classEntry = getSuperclass(classEntry); - if (classEntry != null) { - ancestors.add(classEntry); - } - } - return ancestors; - } - - public List getSubclass(ClassEntry classEntry) { - // linear search is fast enough for now - List subclasses = Lists.newArrayList(); - for (Map.Entry entry : this.superclasses.entrySet()) { - ClassEntry subclass = entry.getKey(); - ClassEntry superclass = entry.getValue(); - if (classEntry.equals(superclass)) { - subclasses.add(subclass); - } - } - return subclasses; - } - - public void getSubclassesRecursively(Set out, ClassEntry classEntry) { - for (ClassEntry subclassEntry : getSubclass(classEntry)) { - out.add(subclassEntry); - getSubclassesRecursively(out, subclassEntry); - } - } - - public void getSubclassNamesRecursively(Set out, ClassEntry classEntry) { - for (ClassEntry subclassEntry : getSubclass(classEntry)) { - out.add(subclassEntry.getName()); - getSubclassNamesRecursively(out, subclassEntry); - } - } - - public Collection> getClassInterfaces() { - return this.interfaces.entries(); - } - - public Collection getInterfaces(ClassEntry classEntry) { - return this.interfaces.get(classEntry); - } - - public boolean isInterface(ClassEntry classEntry) { - return this.interfaces.containsValue(classEntry); - } - - public boolean entryExists(Entry entry) { - if (entry instanceof FieldEntry) { - return fieldExists((FieldEntry) entry); - } else if (entry instanceof BehaviorEntry) { - return behaviorExists((BehaviorEntry) entry); - } else if (entry instanceof ArgumentEntry) { - return behaviorExists(((ArgumentEntry) entry).getBehaviorEntry()); - } else if (entry instanceof LocalVariableEntry) { - return behaviorExists(((LocalVariableEntry) entry).getBehaviorEntry()); - } - throw new IllegalArgumentException("Cannot check existence for " + entry.getClass()); - } - - public boolean fieldExists(FieldEntry fieldEntry) { - return this.fieldEntries.containsEntry(fieldEntry.getClassEntry(), fieldEntry); - } - - public boolean behaviorExists(BehaviorEntry behaviorEntry) { - return this.behaviorEntries.containsEntry(behaviorEntry.getClassEntry(), behaviorEntry); - } - - public ClassEntry resolveEntryClass(Entry entry) { - return resolveEntryClass(entry, false); - } - - public ClassEntry resolveEntryClass(Entry entry, boolean checkSuperclassBeforeChild) { - if (entry instanceof ClassEntry) { - return (ClassEntry) entry; - } - - ClassEntry superclassEntry = resolveSuperclass(entry, checkSuperclassBeforeChild); - if (superclassEntry != null) { - return superclassEntry; - } - - ClassEntry interfaceEntry = resolveInterface(entry); - if (interfaceEntry != null) { - return interfaceEntry; - } - - return null; - } - - public ClassEntry resolveSuperclass(Entry entry, boolean checkSuperclassBeforeChild) { - - // Default case - if (!checkSuperclassBeforeChild) - return resolveSuperclass(entry); - - // Save the original entry - Entry originalEntry = entry; - - // Get all possible superclasses and reverse the list - List superclasses = Lists.reverse(getAncestry(originalEntry.getClassEntry())); - - boolean existInEntry = false; - - for (ClassEntry classEntry : superclasses) - { - entry = entry.cloneToNewClass(classEntry); - existInEntry = entryExists(entry); - - // Check for possible entry in interfaces of superclasses - ClassEntry interfaceEntry = resolveInterface(entry); - if (interfaceEntry != null) - return interfaceEntry; - if (existInEntry) - break; - } - - // Doesn't exists in superclasses? check the child or return null - if (!existInEntry) - return !entryExists(originalEntry) ? null : originalEntry.getClassEntry(); - - return entry.getClassEntry(); - } - - public ClassEntry resolveSuperclass(Entry entry) - { - // this entry could refer to a method on a class where the method is not actually implemented - // travel up the inheritance tree to find the closest implementation - - while (!entryExists(entry)) { - // is there a parent class? - ClassEntry superclassEntry = getSuperclass(entry.getClassEntry()); - if (superclassEntry == null) { - // this is probably a method from a class in a library - // we can't trace the implementation up any higher unless we index the library - return null; - } - - // move up to the parent class - entry = entry.cloneToNewClass(superclassEntry); - } - return entry.getClassEntry(); - } - - public ClassEntry resolveInterface(Entry entry) { - // the interfaces for any class is a forest - // so let's look at all the trees - - for (ClassEntry interfaceEntry : this.interfaces.get(entry.getClassEntry())) { - Collection subInterface = this.interfaces.get(interfaceEntry); - if (subInterface != null && !subInterface.isEmpty()) - { - ClassEntry result = resolveInterface(entry.cloneToNewClass(interfaceEntry)); - if (result != null) - return result; - } - ClassEntry resolvedClassEntry = resolveSuperclass(entry.cloneToNewClass(interfaceEntry)); - if (resolvedClassEntry != null) { - return resolvedClassEntry; - } - } - return null; - } - - private boolean isJre(ClassEntry classEntry) { - String packageName = classEntry.getPackageName(); - return packageName != null && (packageName.startsWith("java") || packageName.startsWith("javax")); - } + private Map superclasses; + private Multimap fieldEntries; + private Multimap behaviorEntries; + private Multimap interfaces; + + public TranslationIndex() { + this.superclasses = Maps.newHashMap(); + this.fieldEntries = HashMultimap.create(); + this.behaviorEntries = HashMultimap.create(); + this.interfaces = HashMultimap.create(); + } + + public TranslationIndex(TranslationIndex other, Translator translator) { + // translate the superclasses + this.superclasses = Maps.newHashMap(); + for (Map.Entry mapEntry : other.superclasses.entrySet()) { + this.superclasses.put(translator.translateEntry(mapEntry.getKey()), translator.translateEntry(mapEntry.getValue())); + } + + // translate the interfaces + this.interfaces = HashMultimap.create(); + for (Map.Entry mapEntry : other.interfaces.entries()) { + this.interfaces.put( + translator.translateEntry(mapEntry.getKey()), + translator.translateEntry(mapEntry.getValue()) + ); + } + + // translate the fields + this.fieldEntries = HashMultimap.create(); + for (Map.Entry mapEntry : other.fieldEntries.entries()) { + this.fieldEntries.put( + translator.translateEntry(mapEntry.getKey()), + translator.translateEntry(mapEntry.getValue()) + ); + } + + this.behaviorEntries = HashMultimap.create(); + for (Map.Entry mapEntry : other.behaviorEntries.entries()) { + this.behaviorEntries.put( + translator.translateEntry(mapEntry.getKey()), + translator.translateEntry(mapEntry.getValue()) + ); + } + } + + public void indexClass(CtClass c) { + indexClass(c, true); + } + + public void indexClass(CtClass c, boolean indexMembers) { + ClassEntry classEntry = EntryFactory.getClassEntry(c); + if (isJre(classEntry)) { + return; + } + + // add the superclass + ClassEntry superclassEntry = EntryFactory.getSuperclassEntry(c); + if (superclassEntry != null) { + this.superclasses.put(classEntry, superclassEntry); + } + + // add the interfaces + for (String interfaceClassName : c.getClassFile().getInterfaces()) { + ClassEntry interfaceClassEntry = new ClassEntry(Descriptor.toJvmName(interfaceClassName)); + if (!isJre(interfaceClassEntry)) { + + this.interfaces.put(classEntry, interfaceClassEntry); + } + } + + if (indexMembers) { + // add fields + for (CtField field : c.getDeclaredFields()) { + FieldEntry fieldEntry = EntryFactory.getFieldEntry(field); + this.fieldEntries.put(fieldEntry.getClassEntry(), fieldEntry); + } + + // add behaviors + for (CtBehavior behavior : c.getDeclaredBehaviors()) { + BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); + this.behaviorEntries.put(behaviorEntry.getClassEntry(), behaviorEntry); + } + } + } + + public void renameClasses(Map renames) { + EntryRenamer.renameClassesInMap(renames, this.superclasses); + EntryRenamer.renameClassesInMultimap(renames, this.fieldEntries); + EntryRenamer.renameClassesInMultimap(renames, this.behaviorEntries); + } + + public ClassEntry getSuperclass(ClassEntry classEntry) { + return this.superclasses.get(classEntry); + } + + public List getAncestry(ClassEntry classEntry) { + List ancestors = Lists.newArrayList(); + while (classEntry != null) { + classEntry = getSuperclass(classEntry); + if (classEntry != null) { + ancestors.add(classEntry); + } + } + return ancestors; + } + + public List getSubclass(ClassEntry classEntry) { + // linear search is fast enough for now + List subclasses = Lists.newArrayList(); + for (Map.Entry entry : this.superclasses.entrySet()) { + ClassEntry subclass = entry.getKey(); + ClassEntry superclass = entry.getValue(); + if (classEntry.equals(superclass)) { + subclasses.add(subclass); + } + } + return subclasses; + } + + public void getSubclassesRecursively(Set out, ClassEntry classEntry) { + for (ClassEntry subclassEntry : getSubclass(classEntry)) { + out.add(subclassEntry); + getSubclassesRecursively(out, subclassEntry); + } + } + + public void getSubclassNamesRecursively(Set out, ClassEntry classEntry) { + for (ClassEntry subclassEntry : getSubclass(classEntry)) { + out.add(subclassEntry.getName()); + getSubclassNamesRecursively(out, subclassEntry); + } + } + + public Collection> getClassInterfaces() { + return this.interfaces.entries(); + } + + public Collection getInterfaces(ClassEntry classEntry) { + return this.interfaces.get(classEntry); + } + + public boolean isInterface(ClassEntry classEntry) { + return this.interfaces.containsValue(classEntry); + } + + public boolean entryExists(Entry entry) { + if (entry instanceof FieldEntry) { + return fieldExists((FieldEntry) entry); + } else if (entry instanceof BehaviorEntry) { + return behaviorExists((BehaviorEntry) entry); + } else if (entry instanceof ArgumentEntry) { + return behaviorExists(((ArgumentEntry) entry).getBehaviorEntry()); + } else if (entry instanceof LocalVariableEntry) { + return behaviorExists(((LocalVariableEntry) entry).getBehaviorEntry()); + } + throw new IllegalArgumentException("Cannot check existence for " + entry.getClass()); + } + + public boolean fieldExists(FieldEntry fieldEntry) { + return this.fieldEntries.containsEntry(fieldEntry.getClassEntry(), fieldEntry); + } + + public boolean behaviorExists(BehaviorEntry behaviorEntry) { + return this.behaviorEntries.containsEntry(behaviorEntry.getClassEntry(), behaviorEntry); + } + + public ClassEntry resolveEntryClass(Entry entry) { + return resolveEntryClass(entry, false); + } + + public ClassEntry resolveEntryClass(Entry entry, boolean checkSuperclassBeforeChild) { + if (entry instanceof ClassEntry) { + return (ClassEntry) entry; + } + + ClassEntry superclassEntry = resolveSuperclass(entry, checkSuperclassBeforeChild); + if (superclassEntry != null) { + return superclassEntry; + } + + ClassEntry interfaceEntry = resolveInterface(entry); + if (interfaceEntry != null) { + return interfaceEntry; + } + + return null; + } + + public ClassEntry resolveSuperclass(Entry entry, boolean checkSuperclassBeforeChild) { + + // Default case + if (!checkSuperclassBeforeChild) + return resolveSuperclass(entry); + + // Save the original entry + Entry originalEntry = entry; + + // Get all possible superclasses and reverse the list + List superclasses = Lists.reverse(getAncestry(originalEntry.getClassEntry())); + + boolean existInEntry = false; + + for (ClassEntry classEntry : superclasses) { + entry = entry.cloneToNewClass(classEntry); + existInEntry = entryExists(entry); + + // Check for possible entry in interfaces of superclasses + ClassEntry interfaceEntry = resolveInterface(entry); + if (interfaceEntry != null) + return interfaceEntry; + if (existInEntry) + break; + } + + // Doesn't exists in superclasses? check the child or return null + if (!existInEntry) + return !entryExists(originalEntry) ? null : originalEntry.getClassEntry(); + + return entry.getClassEntry(); + } + + public ClassEntry resolveSuperclass(Entry entry) { + // this entry could refer to a method on a class where the method is not actually implemented + // travel up the inheritance tree to find the closest implementation + + while (!entryExists(entry)) { + // is there a parent class? + ClassEntry superclassEntry = getSuperclass(entry.getClassEntry()); + if (superclassEntry == null) { + // this is probably a method from a class in a library + // we can't trace the implementation up any higher unless we index the library + return null; + } + + // move up to the parent class + entry = entry.cloneToNewClass(superclassEntry); + } + return entry.getClassEntry(); + } + + public ClassEntry resolveInterface(Entry entry) { + // the interfaces for any class is a forest + // so let's look at all the trees + + for (ClassEntry interfaceEntry : this.interfaces.get(entry.getClassEntry())) { + Collection subInterface = this.interfaces.get(interfaceEntry); + if (subInterface != null && !subInterface.isEmpty()) { + ClassEntry result = resolveInterface(entry.cloneToNewClass(interfaceEntry)); + if (result != null) + return result; + } + ClassEntry resolvedClassEntry = resolveSuperclass(entry.cloneToNewClass(interfaceEntry)); + if (resolvedClassEntry != null) { + return resolvedClassEntry; + } + } + return null; + } + + private boolean isJre(ClassEntry classEntry) { + String packageName = classEntry.getPackageName(); + return packageName != null && (packageName.startsWith("java") || packageName.startsWith("javax")); + } } diff --git a/src/main/java/cuchaz/enigma/analysis/TreeDumpVisitor.java b/src/main/java/cuchaz/enigma/analysis/TreeDumpVisitor.java index cc025da7..c98fb9ef 100644 --- a/src/main/java/cuchaz/enigma/analysis/TreeDumpVisitor.java +++ b/src/main/java/cuchaz/enigma/analysis/TreeDumpVisitor.java @@ -8,6 +8,7 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.analysis; import com.strobel.componentmodel.Key; @@ -19,420 +20,420 @@ import java.nio.charset.Charset; public class TreeDumpVisitor implements IAstVisitor { - private File file; - private Writer out; - - public TreeDumpVisitor(File file) { - this.file = file; - } - - @Override - public Void visitCompilationUnit(CompilationUnit node, Void ignored) { - try { - out = new OutputStreamWriter(new FileOutputStream(file), Charset.forName("UTF-8")); - recurse(node, ignored); - out.close(); - return null; - } catch (IOException ex) { - throw new Error(ex); - } - } - - private Void recurse(AstNode node, Void ignored) { - // show the tree - try { - out.write(getIndent(node) + node.getClass().getSimpleName() + " " + getText(node) + " " + dumpUserData(node) + " " + node.getRegion() + "\n"); - } catch (IOException ex) { - throw new Error(ex); - } - - // recurse - for (final AstNode child : node.getChildren()) { - child.acceptVisitor(this, ignored); - } - return null; - } - - private String getText(AstNode node) { - if (node instanceof Identifier) { - return "\"" + ((Identifier) node).getName() + "\""; - } - return ""; - } - - private String dumpUserData(AstNode node) { - StringBuilder buf = new StringBuilder(); - for (Key key : Keys.ALL_KEYS) { - Object val = node.getUserData(key); - if (val != null) { - buf.append(String.format(" [%s=%s]", key, val)); - } - } - return buf.toString(); - } - - private String getIndent(AstNode node) { - StringBuilder buf = new StringBuilder(); - int depth = getDepth(node); - for (int i = 0; i < depth; i++) { - buf.append("\t"); - } - return buf.toString(); - } - - private int getDepth(AstNode node) { - int depth = -1; - while (node != null) { - depth++; - node = node.getParent(); - } - return depth; - } - - // OVERRIDES WE DON'T CARE ABOUT - - @Override - public Void visitInvocationExpression(InvocationExpression node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitMemberReferenceExpression(MemberReferenceExpression node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitSimpleType(SimpleType node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitMethodDeclaration(MethodDeclaration node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitConstructorDeclaration(ConstructorDeclaration node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitParameterDeclaration(ParameterDeclaration node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitFieldDeclaration(FieldDeclaration node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitTypeDeclaration(TypeDeclaration node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitComment(Comment node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitPatternPlaceholder(AstNode node, Pattern pattern, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitTypeReference(TypeReferenceExpression node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitJavaTokenNode(JavaTokenNode node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitIdentifier(Identifier node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitNullReferenceExpression(NullReferenceExpression node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitThisReferenceExpression(ThisReferenceExpression node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitSuperReferenceExpression(SuperReferenceExpression node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitClassOfExpression(ClassOfExpression node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitBlockStatement(BlockStatement node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitExpressionStatement(ExpressionStatement node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitBreakStatement(BreakStatement node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitContinueStatement(ContinueStatement node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitDoWhileStatement(DoWhileStatement node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitEmptyStatement(EmptyStatement node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitIfElseStatement(IfElseStatement node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitLabelStatement(LabelStatement node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitLabeledStatement(LabeledStatement node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitReturnStatement(ReturnStatement node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitSwitchStatement(SwitchStatement node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitSwitchSection(SwitchSection node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitCaseLabel(CaseLabel node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitThrowStatement(ThrowStatement node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitCatchClause(CatchClause node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitAnnotation(Annotation node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitNewLine(NewLineNode node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitVariableDeclaration(VariableDeclarationStatement node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitVariableInitializer(VariableInitializer node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitText(TextNode node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitImportDeclaration(ImportDeclaration node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitInitializerBlock(InstanceInitializer node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitTypeParameterDeclaration(TypeParameterDeclaration node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitPackageDeclaration(PackageDeclaration node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitArraySpecifier(ArraySpecifier node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitComposedType(ComposedType node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitWhileStatement(WhileStatement node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitPrimitiveExpression(PrimitiveExpression node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitCastExpression(CastExpression node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitBinaryOperatorExpression(BinaryOperatorExpression node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitInstanceOfExpression(InstanceOfExpression node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitIndexerExpression(IndexerExpression node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitIdentifierExpression(IdentifierExpression node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitUnaryOperatorExpression(UnaryOperatorExpression node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitConditionalExpression(ConditionalExpression node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitArrayInitializerExpression(ArrayInitializerExpression node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitObjectCreationExpression(ObjectCreationExpression node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitArrayCreationExpression(ArrayCreationExpression node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitAssignmentExpression(AssignmentExpression node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitForStatement(ForStatement node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitForEachStatement(ForEachStatement node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitTryCatchStatement(TryCatchStatement node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitGotoStatement(GotoStatement node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitParenthesizedExpression(ParenthesizedExpression node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitSynchronizedStatement(SynchronizedStatement node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitAnonymousObjectCreationExpression(AnonymousObjectCreationExpression node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitWildcardType(WildcardType node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitMethodGroupExpression(MethodGroupExpression node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitEnumValueDeclaration(EnumValueDeclaration node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitAssertStatement(AssertStatement node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitLambdaExpression(LambdaExpression node, Void ignored) { - return recurse(node, ignored); - } - - @Override - public Void visitLocalTypeDeclarationStatement(LocalTypeDeclarationStatement node, Void ignored) { - return recurse(node, ignored); - } + private File file; + private Writer out; + + public TreeDumpVisitor(File file) { + this.file = file; + } + + @Override + public Void visitCompilationUnit(CompilationUnit node, Void ignored) { + try { + out = new OutputStreamWriter(new FileOutputStream(file), Charset.forName("UTF-8")); + recurse(node, ignored); + out.close(); + return null; + } catch (IOException ex) { + throw new Error(ex); + } + } + + private Void recurse(AstNode node, Void ignored) { + // show the tree + try { + out.write(getIndent(node) + node.getClass().getSimpleName() + " " + getText(node) + " " + dumpUserData(node) + " " + node.getRegion() + "\n"); + } catch (IOException ex) { + throw new Error(ex); + } + + // recurse + for (final AstNode child : node.getChildren()) { + child.acceptVisitor(this, ignored); + } + return null; + } + + private String getText(AstNode node) { + if (node instanceof Identifier) { + return "\"" + ((Identifier) node).getName() + "\""; + } + return ""; + } + + private String dumpUserData(AstNode node) { + StringBuilder buf = new StringBuilder(); + for (Key key : Keys.ALL_KEYS) { + Object val = node.getUserData(key); + if (val != null) { + buf.append(String.format(" [%s=%s]", key, val)); + } + } + return buf.toString(); + } + + private String getIndent(AstNode node) { + StringBuilder buf = new StringBuilder(); + int depth = getDepth(node); + for (int i = 0; i < depth; i++) { + buf.append("\t"); + } + return buf.toString(); + } + + private int getDepth(AstNode node) { + int depth = -1; + while (node != null) { + depth++; + node = node.getParent(); + } + return depth; + } + + // OVERRIDES WE DON'T CARE ABOUT + + @Override + public Void visitInvocationExpression(InvocationExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitMemberReferenceExpression(MemberReferenceExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitSimpleType(SimpleType node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitMethodDeclaration(MethodDeclaration node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitConstructorDeclaration(ConstructorDeclaration node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitParameterDeclaration(ParameterDeclaration node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitFieldDeclaration(FieldDeclaration node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitTypeDeclaration(TypeDeclaration node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitComment(Comment node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitPatternPlaceholder(AstNode node, Pattern pattern, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitTypeReference(TypeReferenceExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitJavaTokenNode(JavaTokenNode node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitIdentifier(Identifier node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitNullReferenceExpression(NullReferenceExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitThisReferenceExpression(ThisReferenceExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitSuperReferenceExpression(SuperReferenceExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitClassOfExpression(ClassOfExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitBlockStatement(BlockStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitExpressionStatement(ExpressionStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitBreakStatement(BreakStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitContinueStatement(ContinueStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitDoWhileStatement(DoWhileStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitEmptyStatement(EmptyStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitIfElseStatement(IfElseStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitLabelStatement(LabelStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitLabeledStatement(LabeledStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitReturnStatement(ReturnStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitSwitchStatement(SwitchStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitSwitchSection(SwitchSection node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitCaseLabel(CaseLabel node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitThrowStatement(ThrowStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitCatchClause(CatchClause node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitAnnotation(Annotation node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitNewLine(NewLineNode node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitVariableDeclaration(VariableDeclarationStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitVariableInitializer(VariableInitializer node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitText(TextNode node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitImportDeclaration(ImportDeclaration node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitInitializerBlock(InstanceInitializer node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitTypeParameterDeclaration(TypeParameterDeclaration node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitPackageDeclaration(PackageDeclaration node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitArraySpecifier(ArraySpecifier node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitComposedType(ComposedType node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitWhileStatement(WhileStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitPrimitiveExpression(PrimitiveExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitCastExpression(CastExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitBinaryOperatorExpression(BinaryOperatorExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitInstanceOfExpression(InstanceOfExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitIndexerExpression(IndexerExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitIdentifierExpression(IdentifierExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitUnaryOperatorExpression(UnaryOperatorExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitConditionalExpression(ConditionalExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitArrayInitializerExpression(ArrayInitializerExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitObjectCreationExpression(ObjectCreationExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitArrayCreationExpression(ArrayCreationExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitAssignmentExpression(AssignmentExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitForStatement(ForStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitForEachStatement(ForEachStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitTryCatchStatement(TryCatchStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitGotoStatement(GotoStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitParenthesizedExpression(ParenthesizedExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitSynchronizedStatement(SynchronizedStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitAnonymousObjectCreationExpression(AnonymousObjectCreationExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitWildcardType(WildcardType node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitMethodGroupExpression(MethodGroupExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitEnumValueDeclaration(EnumValueDeclaration node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitAssertStatement(AssertStatement node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitLambdaExpression(LambdaExpression node, Void ignored) { + return recurse(node, ignored); + } + + @Override + public Void visitLocalTypeDeclarationStatement(LocalTypeDeclarationStatement node, Void ignored) { + return recurse(node, ignored); + } } diff --git a/src/main/java/cuchaz/enigma/bytecode/ClassProtectifier.java b/src/main/java/cuchaz/enigma/bytecode/ClassProtectifier.java index ad5bab0c..6ec576e0 100644 --- a/src/main/java/cuchaz/enigma/bytecode/ClassProtectifier.java +++ b/src/main/java/cuchaz/enigma/bytecode/ClassProtectifier.java @@ -8,6 +8,7 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.bytecode; import javassist.CtBehavior; @@ -16,36 +17,35 @@ import javassist.CtField; import javassist.bytecode.AccessFlag; import javassist.bytecode.InnerClassesAttribute; - public class ClassProtectifier { - public static CtClass protectify(CtClass c) { - - // protectify all the fields - for (CtField field : c.getDeclaredFields()) { - field.setModifiers(protectify(field.getModifiers())); - } - - // protectify all the methods and constructors - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - behavior.setModifiers(protectify(behavior.getModifiers())); - } - - // protectify all the inner classes - InnerClassesAttribute attr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag); - if (attr != null) { - for (int i = 0; i < attr.tableLength(); i++) { - attr.setAccessFlags(i, protectify(attr.accessFlags(i))); - } - } - - return c; - } - - private static int protectify(int flags) { - if (AccessFlag.isPrivate(flags)) { - flags = AccessFlag.setProtected(flags); - } - return flags; - } + public static CtClass protectify(CtClass c) { + + // protectify all the fields + for (CtField field : c.getDeclaredFields()) { + field.setModifiers(protectify(field.getModifiers())); + } + + // protectify all the methods and constructors + for (CtBehavior behavior : c.getDeclaredBehaviors()) { + behavior.setModifiers(protectify(behavior.getModifiers())); + } + + // protectify all the inner classes + InnerClassesAttribute attr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag); + if (attr != null) { + for (int i = 0; i < attr.tableLength(); i++) { + attr.setAccessFlags(i, protectify(attr.accessFlags(i))); + } + } + + return c; + } + + private static int protectify(int flags) { + if (AccessFlag.isPrivate(flags)) { + flags = AccessFlag.setProtected(flags); + } + return flags; + } } diff --git a/src/main/java/cuchaz/enigma/bytecode/ClassPublifier.java b/src/main/java/cuchaz/enigma/bytecode/ClassPublifier.java index 0aa7fac7..d627fe91 100644 --- a/src/main/java/cuchaz/enigma/bytecode/ClassPublifier.java +++ b/src/main/java/cuchaz/enigma/bytecode/ClassPublifier.java @@ -8,6 +8,7 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.bytecode; import javassist.CtBehavior; @@ -16,36 +17,35 @@ import javassist.CtField; import javassist.bytecode.AccessFlag; import javassist.bytecode.InnerClassesAttribute; - public class ClassPublifier { - public static CtClass publify(CtClass c) { - - // publify all the fields - for (CtField field : c.getDeclaredFields()) { - field.setModifiers(publify(field.getModifiers())); - } - - // publify all the methods and constructors - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - behavior.setModifiers(publify(behavior.getModifiers())); - } - - // publify all the inner classes - InnerClassesAttribute attr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag); - if (attr != null) { - for (int i = 0; i < attr.tableLength(); i++) { - attr.setAccessFlags(i, publify(attr.accessFlags(i))); - } - } - - return c; - } - - private static int publify(int flags) { - if (!AccessFlag.isPublic(flags)) { - flags = AccessFlag.setPublic(flags); - } - return flags; - } + public static CtClass publify(CtClass c) { + + // publify all the fields + for (CtField field : c.getDeclaredFields()) { + field.setModifiers(publify(field.getModifiers())); + } + + // publify all the methods and constructors + for (CtBehavior behavior : c.getDeclaredBehaviors()) { + behavior.setModifiers(publify(behavior.getModifiers())); + } + + // publify all the inner classes + InnerClassesAttribute attr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag); + if (attr != null) { + for (int i = 0; i < attr.tableLength(); i++) { + attr.setAccessFlags(i, publify(attr.accessFlags(i))); + } + } + + return c; + } + + private static int publify(int flags) { + if (!AccessFlag.isPublic(flags)) { + flags = AccessFlag.setPublic(flags); + } + return flags; + } } diff --git a/src/main/java/cuchaz/enigma/bytecode/ClassRenamer.java b/src/main/java/cuchaz/enigma/bytecode/ClassRenamer.java index d874633d..a52cab6d 100644 --- a/src/main/java/cuchaz/enigma/bytecode/ClassRenamer.java +++ b/src/main/java/cuchaz/enigma/bytecode/ClassRenamer.java @@ -8,532 +8,532 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ -package cuchaz.enigma.bytecode; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +package cuchaz.enigma.bytecode; import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.ClassNameReplacer; import cuchaz.enigma.mapping.Mappings; import cuchaz.enigma.mapping.Translator; -import javassist.*; +import javassist.CtBehavior; +import javassist.CtClass; +import javassist.CtField; +import javassist.Modifier; import javassist.bytecode.*; import javassist.bytecode.SignatureAttribute.*; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + public class ClassRenamer { - private enum SignatureType { - Class { - @Override - public String rename(String signature, ReplacerClassMap map) { - return renameClassSignature(signature, map); - } - }, - Field { - @Override - public String rename(String signature, ReplacerClassMap map) { - return renameFieldSignature(signature, map); - } - }, - Method { - @Override - public String rename(String signature, ReplacerClassMap map) { - return renameMethodSignature(signature, map); - } - }; - - public abstract String rename(String signature, ReplacerClassMap map); - } - - private static class ReplacerClassMap extends HashMap { - - private ClassNameReplacer replacer; - - public ReplacerClassMap(ClassNameReplacer replacer) { - this.replacer = replacer; - } - - @Override - public String get(Object obj) { - if (obj instanceof String) { - return get((String) obj); - } - return null; - } - - public String get(String className) { - return replacer.replace(className); - } - } - - public static void applyModifier(Object obj, Mappings.EntryModifier modifier) - { - int mod = -1; - if (obj instanceof CtField) - mod = ((CtField) obj).getModifiers(); - else if (obj instanceof CtBehavior) - mod = ((CtBehavior) obj).getModifiers(); - else if (obj instanceof CtClass) - mod = ((CtClass) obj).getModifiers(); - - if (mod != -1) - { - switch (modifier) - { - case PRIVATE: - mod = Modifier.setPrivate(mod); - break; - case PROTECTED: - mod = Modifier.setProtected(mod); - break; - case PUBLIC: - mod = Modifier.setPublic(mod); - break; - default: - break; - } - if (obj instanceof CtField) - ((CtField) obj).setModifiers(mod); - else if (obj instanceof CtBehavior) - ((CtBehavior) obj).setModifiers(mod); - else - ((CtClass) obj).setModifiers(mod); - } - } - - public static void renameClasses(CtClass c, final Translator translator) { - renameClasses(c, className -> { - ClassEntry entry = translator.translateEntry(new ClassEntry(className)); - if (entry != null) { - return entry.getName(); - } - return null; - }); - } - - public static void moveAllClassesOutOfDefaultPackage(CtClass c, final String newPackageName) { - renameClasses(c, className -> { - ClassEntry entry = new ClassEntry(className); - if (entry.isInDefaultPackage()) { - return newPackageName + "/" + entry.getName(); - } - return null; - }); - } - - public static void moveAllClassesIntoDefaultPackage(CtClass c, final String oldPackageName) { - renameClasses(c, className -> { - ClassEntry entry = new ClassEntry(className); - if (entry.getPackageName().equals(oldPackageName)) { - return entry.getSimpleName(); - } - return null; - }); - } - - @SuppressWarnings("unchecked") - public static void renameClasses(CtClass c, ClassNameReplacer replacer) { - - // sadly, we can't use CtClass.renameClass() because SignatureAttribute.renameClass() is extremely buggy =( - - ReplacerClassMap map = new ReplacerClassMap(replacer); - ClassFile classFile = c.getClassFile(); - - // rename the constant pool (covers ClassInfo, MethodTypeInfo, and NameAndTypeInfo) - ConstPool constPool = c.getClassFile().getConstPool(); - constPool.renameClass(map); - - // rename class attributes - renameAttributes(classFile.getAttributes(), map, SignatureType.Class); - - // rename methods - for (MethodInfo methodInfo : (List) classFile.getMethods()) { - methodInfo.setDescriptor(Descriptor.rename(methodInfo.getDescriptor(), map)); - renameAttributes(methodInfo.getAttributes(), map, SignatureType.Method); - } - - // rename fields - for (FieldInfo fieldInfo : (List) classFile.getFields()) { - fieldInfo.setDescriptor(Descriptor.rename(fieldInfo.getDescriptor(), map)); - renameAttributes(fieldInfo.getAttributes(), map, SignatureType.Field); - } - - // rename the class name itself last - // NOTE: don't use the map here, because setName() calls the buggy SignatureAttribute.renameClass() - // we only want to replace exactly this class name - String newName = renameClassName(c.getName(), map); - if (newName != null) { - c.setName(newName); - } - - // replace simple names in the InnerClasses attribute too - InnerClassesAttribute attr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag); - if (attr != null) { - for (int i = 0; i < attr.tableLength(); i++) { - - String innerName = attr.innerClass(i); - // get the inner class full name (which has already been translated) - ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(innerName)); - - if (attr.innerNameIndex(i) != 0) { - // update the inner name - attr.setInnerNameIndex(i, constPool.addUtf8Info(classEntry.getInnermostClassName())); - } + public static void applyModifier(Object obj, Mappings.EntryModifier modifier) { + int mod = -1; + if (obj instanceof CtField) + mod = ((CtField) obj).getModifiers(); + else if (obj instanceof CtBehavior) + mod = ((CtBehavior) obj).getModifiers(); + else if (obj instanceof CtClass) + mod = ((CtClass) obj).getModifiers(); + + if (mod != -1) { + switch (modifier) { + case PRIVATE: + mod = Modifier.setPrivate(mod); + break; + case PROTECTED: + mod = Modifier.setProtected(mod); + break; + case PUBLIC: + mod = Modifier.setPublic(mod); + break; + default: + break; + } + if (obj instanceof CtField) + ((CtField) obj).setModifiers(mod); + else if (obj instanceof CtBehavior) + ((CtBehavior) obj).setModifiers(mod); + else + ((CtClass) obj).setModifiers(mod); + } + } + + public static void renameClasses(CtClass c, final Translator translator) { + renameClasses(c, className -> { + ClassEntry entry = translator.translateEntry(new ClassEntry(className)); + if (entry != null) { + return entry.getName(); + } + return null; + }); + } + + public static void moveAllClassesOutOfDefaultPackage(CtClass c, final String newPackageName) { + renameClasses(c, className -> { + ClassEntry entry = new ClassEntry(className); + if (entry.isInDefaultPackage()) { + return newPackageName + "/" + entry.getName(); + } + return null; + }); + } + + public static void moveAllClassesIntoDefaultPackage(CtClass c, final String oldPackageName) { + renameClasses(c, className -> { + ClassEntry entry = new ClassEntry(className); + if (entry.getPackageName().equals(oldPackageName)) { + return entry.getSimpleName(); + } + return null; + }); + } + + @SuppressWarnings("unchecked") + public static void renameClasses(CtClass c, ClassNameReplacer replacer) { + + // sadly, we can't use CtClass.renameClass() because SignatureAttribute.renameClass() is extremely buggy =( + + ReplacerClassMap map = new ReplacerClassMap(replacer); + ClassFile classFile = c.getClassFile(); + + // rename the constant pool (covers ClassInfo, MethodTypeInfo, and NameAndTypeInfo) + ConstPool constPool = c.getClassFile().getConstPool(); + constPool.renameClass(map); + + // rename class attributes + renameAttributes(classFile.getAttributes(), map, SignatureType.Class); + + // rename methods + for (MethodInfo methodInfo : (List) classFile.getMethods()) { + methodInfo.setDescriptor(Descriptor.rename(methodInfo.getDescriptor(), map)); + renameAttributes(methodInfo.getAttributes(), map, SignatureType.Method); + } + + // rename fields + for (FieldInfo fieldInfo : (List) classFile.getFields()) { + fieldInfo.setDescriptor(Descriptor.rename(fieldInfo.getDescriptor(), map)); + renameAttributes(fieldInfo.getAttributes(), map, SignatureType.Field); + } + + // rename the class name itself last + // NOTE: don't use the map here, because setName() calls the buggy SignatureAttribute.renameClass() + // we only want to replace exactly this class name + String newName = renameClassName(c.getName(), map); + if (newName != null) { + c.setName(newName); + } + + // replace simple names in the InnerClasses attribute too + InnerClassesAttribute attr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag); + if (attr != null) { + for (int i = 0; i < attr.tableLength(); i++) { + + String innerName = attr.innerClass(i); + // get the inner class full name (which has already been translated) + ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(innerName)); + + if (attr.innerNameIndex(i) != 0) { + // update the inner name + attr.setInnerNameIndex(i, constPool.addUtf8Info(classEntry.getInnermostClassName())); + } /* DEBUG - System.out.println(String.format("\tDEOBF: %s-> ATTR: %s,%s,%s", classEntry, attr.outerClass(i), attr.innerClass(i), attr.innerName(i))); + System.out.println(String.format("\tDEOBF: %s-> ATTR: %s,%s,%s", classEntry, attr.outerClass(i), attr.innerClass(i), attr.innerName(i))); */ - } - } - } - - @SuppressWarnings("unchecked") - private static void renameAttributes(List attributes, ReplacerClassMap map, SignatureType type) { - try { - - // make the rename class method accessible - Method renameClassMethod = AttributeInfo.class.getDeclaredMethod("renameClass", Map.class); - renameClassMethod.setAccessible(true); - - for (AttributeInfo attribute : attributes) { - if (attribute instanceof SignatureAttribute) { - // this has to be handled specially because SignatureAttribute.renameClass() is buggy as hell - SignatureAttribute signatureAttribute = (SignatureAttribute) attribute; - String newSignature = type.rename(signatureAttribute.getSignature(), map); - if (newSignature != null) { - signatureAttribute.setSignature(newSignature); - } - } else if (attribute instanceof CodeAttribute) { - // code attributes have signature attributes too (indirectly) - CodeAttribute codeAttribute = (CodeAttribute) attribute; - renameAttributes(codeAttribute.getAttributes(), map, type); - } else if (attribute instanceof LocalVariableTypeAttribute) { - // lvt attributes have signature attributes too - LocalVariableTypeAttribute localVariableAttribute = (LocalVariableTypeAttribute) attribute; - renameLocalVariableTypeAttribute(localVariableAttribute, map); - } else { - renameClassMethod.invoke(attribute, map); - } - } - - } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { - throw new Error("Unable to call javassist methods by reflection!", ex); - } - } - - private static void renameLocalVariableTypeAttribute(LocalVariableTypeAttribute attribute, ReplacerClassMap map) { - - // adapted from LocalVariableAttribute.renameClass() - ConstPool cp = attribute.getConstPool(); - int n = attribute.tableLength(); - byte[] info = attribute.get(); - for (int i = 0; i < n; ++i) { - int pos = i * 10 + 2; - int index = ByteArray.readU16bit(info, pos + 6); - if (index != 0) { - String signature = cp.getUtf8Info(index); - String newSignature = renameLocalVariableSignature(signature, map); - if (newSignature != null) { - ByteArray.write16bit(cp.addUtf8Info(newSignature), info, pos + 6); - } - } - } - } - - private static String renameLocalVariableSignature(String signature, ReplacerClassMap map) { - - // for some reason, signatures with . in them don't count as field signatures - // looks like anonymous classes delimit with . in stead of $ - // convert the . to $, but keep track of how many we replace - // we need to put them back after we translate - int start = signature.lastIndexOf('$') + 1; - int numConverted = 0; - StringBuilder buf = new StringBuilder(signature); - for (int i = buf.length() - 1; i >= start; i--) { - char c = buf.charAt(i); - if (c == '.') { - buf.setCharAt(i, '$'); - numConverted++; - } - } - signature = buf.toString(); - - // translate - String newSignature = renameFieldSignature(signature, map); - if (newSignature != null) { - - // put the delimiters back - buf = new StringBuilder(newSignature); - for (int i = buf.length() - 1; i >= 0 && numConverted > 0; i--) { - char c = buf.charAt(i); - if (c == '$') { - buf.setCharAt(i, '.'); - numConverted--; - } - } - assert (numConverted == 0); - newSignature = buf.toString(); - - return newSignature; - } - - return null; - } - - private static String renameClassSignature(String signature, ReplacerClassMap map) { - try { - ClassSignature type = renameType(SignatureAttribute.toClassSignature(signature), map); - return type.encode(); - } catch (BadBytecode ex) { - throw new Error("Can't parse field signature: " + signature); - } - } - - private static String renameFieldSignature(String signature, ReplacerClassMap map) { - try { - ObjectType type = renameType(SignatureAttribute.toFieldSignature(signature), map); - if (type != null) { - return type.encode(); - } - return null; - } catch (BadBytecode ex) { - throw new Error("Can't parse class signature: " + signature); - } - } - - private static String renameMethodSignature(String signature, ReplacerClassMap map) { - try { - MethodSignature type = renameType(SignatureAttribute.toMethodSignature(signature), map); - return type.encode(); - } catch (BadBytecode ex) { - throw new Error("Can't parse method signature: " + signature); - } - } - - private static TypeParameter[] renameTypeParameter(TypeParameter[] typeParamTypes, ReplacerClassMap map) - { - if (typeParamTypes != null) { - typeParamTypes = Arrays.copyOf(typeParamTypes, typeParamTypes.length); - for (int i = 0; i < typeParamTypes.length; i++) { - TypeParameter newParamType = renameType(typeParamTypes[i], map); - if (newParamType != null) { - typeParamTypes[i] = newParamType; - } - } - } - return typeParamTypes; - } - - private static ClassSignature renameType(ClassSignature type, ReplacerClassMap map) { - - TypeParameter[] typeParamTypes = renameTypeParameter(type.getParameters(), map); - - ClassType superclassType = type.getSuperClass(); - if (superclassType != ClassType.OBJECT) { - ClassType newSuperclassType = renameType(superclassType, map); - if (newSuperclassType != null) { - superclassType = newSuperclassType; - } - } - - ClassType[] interfaceTypes = type.getInterfaces(); - if (interfaceTypes != null) { - interfaceTypes = Arrays.copyOf(interfaceTypes, interfaceTypes.length); - for (int i = 0; i < interfaceTypes.length; i++) { - ClassType newInterfaceType = renameType(interfaceTypes[i], map); - if (newInterfaceType != null) { - interfaceTypes[i] = newInterfaceType; - } - } - } - - return new ClassSignature(typeParamTypes, superclassType, interfaceTypes); - } - - private static MethodSignature renameType(MethodSignature type, ReplacerClassMap map) { - - TypeParameter[] typeParamTypes = renameTypeParameter(type.getTypeParameters(), map); - - Type[] paramTypes = type.getParameterTypes(); - if (paramTypes != null) { - paramTypes = Arrays.copyOf(paramTypes, paramTypes.length); - for (int i = 0; i < paramTypes.length; i++) { - Type newParamType = renameType(paramTypes[i], map); - if (newParamType != null) { - paramTypes[i] = newParamType; - } - } - } - - Type returnType = type.getReturnType(); - if (returnType != null) { - Type newReturnType = renameType(returnType, map); - if (newReturnType != null) { - returnType = newReturnType; - } - } - - ObjectType[] exceptionTypes = type.getExceptionTypes(); - if (exceptionTypes != null) { - exceptionTypes = Arrays.copyOf(exceptionTypes, exceptionTypes.length); - for (int i = 0; i < exceptionTypes.length; i++) { - ObjectType newExceptionType = renameType(exceptionTypes[i], map); - if (newExceptionType != null) { - exceptionTypes[i] = newExceptionType; - } - } - } - - return new MethodSignature(typeParamTypes, paramTypes, returnType, exceptionTypes); - } - - private static Type renameType(Type type, ReplacerClassMap map) { - if (type instanceof ObjectType) { - return renameType((ObjectType) type, map); - } else if (type instanceof BaseType) { - return renameType((BaseType) type, map); - } else { - throw new Error("Don't know how to rename type " + type.getClass()); - } - } - - private static ObjectType renameType(ObjectType type, ReplacerClassMap map) { - if (type instanceof ArrayType) { - return renameType((ArrayType) type, map); - } else if (type instanceof ClassType) { - return renameType((ClassType) type, map); - } else if (type instanceof TypeVariable) { - return renameType((TypeVariable) type, map); - } else { - throw new Error("Don't know how to rename type " + type.getClass()); - } - } - - private static BaseType renameType(BaseType type, ReplacerClassMap map) { - // don't have to rename primitives - return null; - } - - private static TypeVariable renameType(TypeVariable type, ReplacerClassMap map) { - // don't have to rename template args - return null; - } - - private static ClassType renameType(ClassType type, ReplacerClassMap map) { - - // translate type args - TypeArgument[] args = type.getTypeArguments(); - if (args != null) { - args = Arrays.copyOf(args, args.length); - for (int i = 0; i < args.length; i++) { - TypeArgument newType = renameType(args[i], map); - if (newType != null) { - args[i] = newType; - } - } - } - - if (type instanceof NestedClassType) { - NestedClassType nestedType = (NestedClassType) type; - - // translate the name - String name = getClassName(type); - String newName = map.get(name); - if (newName != null) { - name = new ClassEntry(newName).getInnermostClassName(); - } - - // translate the parent class too - ClassType parent = renameType(nestedType.getDeclaringClass(), map); - if (parent == null) { - parent = nestedType.getDeclaringClass(); - } - - return new NestedClassType(parent, name, args); - } else { - - // translate the name - String name = type.getName(); - String newName = renameClassName(name, map); - if (newName != null) { - name = newName; - } - - return new ClassType(name, args); - } - } - - private static String getClassName(ClassType type) { - if (type instanceof NestedClassType) { - NestedClassType nestedType = (NestedClassType) type; - return getClassName(nestedType.getDeclaringClass()) + "$" + Descriptor.toJvmName(type.getName().replace('.', '$')); - } else { - return Descriptor.toJvmName(type.getName()); - } - } - - private static String renameClassName(String name, ReplacerClassMap map) { - String newName = map.get(Descriptor.toJvmName(name)); - if (newName != null) { - return Descriptor.toJavaName(newName); - } - return null; - } - - private static TypeArgument renameType(TypeArgument type, ReplacerClassMap map) { - ObjectType subType = type.getType(); - if (subType != null) { - ObjectType newSubType = renameType(subType, map); - if (newSubType != null) { - switch (type.getKind()) { - case ' ': - return new TypeArgument(newSubType); - case '+': - return TypeArgument.subclassOf(newSubType); - case '-': - return TypeArgument.superOf(newSubType); - default: - throw new Error("Unknown type kind: " + type.getKind()); - } - } - } - return null; - } - - private static ArrayType renameType(ArrayType type, ReplacerClassMap map) { - Type newSubType = renameType(type.getComponentType(), map); - if (newSubType != null) { - return new ArrayType(type.getDimension(), newSubType); - } - return null; - } - - private static TypeParameter renameType(TypeParameter type, ReplacerClassMap map) { - - ObjectType superclassType = type.getClassBound(); - if (superclassType != null) { - ObjectType newSuperclassType = renameType(superclassType, map); - if (newSuperclassType != null) { - superclassType = newSuperclassType; - } - } - - ObjectType[] interfaceTypes = type.getInterfaceBound(); - if (interfaceTypes != null) { - interfaceTypes = Arrays.copyOf(interfaceTypes, interfaceTypes.length); - for (int i = 0; i < interfaceTypes.length; i++) { - ObjectType newInterfaceType = renameType(interfaceTypes[i], map); - if (newInterfaceType != null) { - interfaceTypes[i] = newInterfaceType; - } - } - } - - return new TypeParameter(type.getName(), superclassType, interfaceTypes); - } + } + } + } + + @SuppressWarnings("unchecked") + private static void renameAttributes(List attributes, ReplacerClassMap map, SignatureType type) { + try { + + // make the rename class method accessible + Method renameClassMethod = AttributeInfo.class.getDeclaredMethod("renameClass", Map.class); + renameClassMethod.setAccessible(true); + + for (AttributeInfo attribute : attributes) { + if (attribute instanceof SignatureAttribute) { + // this has to be handled specially because SignatureAttribute.renameClass() is buggy as hell + SignatureAttribute signatureAttribute = (SignatureAttribute) attribute; + String newSignature = type.rename(signatureAttribute.getSignature(), map); + if (newSignature != null) { + signatureAttribute.setSignature(newSignature); + } + } else if (attribute instanceof CodeAttribute) { + // code attributes have signature attributes too (indirectly) + CodeAttribute codeAttribute = (CodeAttribute) attribute; + renameAttributes(codeAttribute.getAttributes(), map, type); + } else if (attribute instanceof LocalVariableTypeAttribute) { + // lvt attributes have signature attributes too + LocalVariableTypeAttribute localVariableAttribute = (LocalVariableTypeAttribute) attribute; + renameLocalVariableTypeAttribute(localVariableAttribute, map); + } else { + renameClassMethod.invoke(attribute, map); + } + } + + } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + throw new Error("Unable to call javassist methods by reflection!", ex); + } + } + + private static void renameLocalVariableTypeAttribute(LocalVariableTypeAttribute attribute, ReplacerClassMap map) { + + // adapted from LocalVariableAttribute.renameClass() + ConstPool cp = attribute.getConstPool(); + int n = attribute.tableLength(); + byte[] info = attribute.get(); + for (int i = 0; i < n; ++i) { + int pos = i * 10 + 2; + int index = ByteArray.readU16bit(info, pos + 6); + if (index != 0) { + String signature = cp.getUtf8Info(index); + String newSignature = renameLocalVariableSignature(signature, map); + if (newSignature != null) { + ByteArray.write16bit(cp.addUtf8Info(newSignature), info, pos + 6); + } + } + } + } + + private static String renameLocalVariableSignature(String signature, ReplacerClassMap map) { + + // for some reason, signatures with . in them don't count as field signatures + // looks like anonymous classes delimit with . in stead of $ + // convert the . to $, but keep track of how many we replace + // we need to put them back after we translate + int start = signature.lastIndexOf('$') + 1; + int numConverted = 0; + StringBuilder buf = new StringBuilder(signature); + for (int i = buf.length() - 1; i >= start; i--) { + char c = buf.charAt(i); + if (c == '.') { + buf.setCharAt(i, '$'); + numConverted++; + } + } + signature = buf.toString(); + + // translate + String newSignature = renameFieldSignature(signature, map); + if (newSignature != null) { + + // put the delimiters back + buf = new StringBuilder(newSignature); + for (int i = buf.length() - 1; i >= 0 && numConverted > 0; i--) { + char c = buf.charAt(i); + if (c == '$') { + buf.setCharAt(i, '.'); + numConverted--; + } + } + assert (numConverted == 0); + newSignature = buf.toString(); + + return newSignature; + } + + return null; + } + + private static String renameClassSignature(String signature, ReplacerClassMap map) { + try { + ClassSignature type = renameType(SignatureAttribute.toClassSignature(signature), map); + return type.encode(); + } catch (BadBytecode ex) { + throw new Error("Can't parse field signature: " + signature); + } + } + + private static String renameFieldSignature(String signature, ReplacerClassMap map) { + try { + ObjectType type = renameType(SignatureAttribute.toFieldSignature(signature), map); + if (type != null) { + return type.encode(); + } + return null; + } catch (BadBytecode ex) { + throw new Error("Can't parse class signature: " + signature); + } + } + + private static String renameMethodSignature(String signature, ReplacerClassMap map) { + try { + MethodSignature type = renameType(SignatureAttribute.toMethodSignature(signature), map); + return type.encode(); + } catch (BadBytecode ex) { + throw new Error("Can't parse method signature: " + signature); + } + } + + private static TypeParameter[] renameTypeParameter(TypeParameter[] typeParamTypes, ReplacerClassMap map) { + if (typeParamTypes != null) { + typeParamTypes = Arrays.copyOf(typeParamTypes, typeParamTypes.length); + for (int i = 0; i < typeParamTypes.length; i++) { + TypeParameter newParamType = renameType(typeParamTypes[i], map); + if (newParamType != null) { + typeParamTypes[i] = newParamType; + } + } + } + return typeParamTypes; + } + + private static ClassSignature renameType(ClassSignature type, ReplacerClassMap map) { + + TypeParameter[] typeParamTypes = renameTypeParameter(type.getParameters(), map); + + ClassType superclassType = type.getSuperClass(); + if (superclassType != ClassType.OBJECT) { + ClassType newSuperclassType = renameType(superclassType, map); + if (newSuperclassType != null) { + superclassType = newSuperclassType; + } + } + + ClassType[] interfaceTypes = type.getInterfaces(); + if (interfaceTypes != null) { + interfaceTypes = Arrays.copyOf(interfaceTypes, interfaceTypes.length); + for (int i = 0; i < interfaceTypes.length; i++) { + ClassType newInterfaceType = renameType(interfaceTypes[i], map); + if (newInterfaceType != null) { + interfaceTypes[i] = newInterfaceType; + } + } + } + + return new ClassSignature(typeParamTypes, superclassType, interfaceTypes); + } + + private static MethodSignature renameType(MethodSignature type, ReplacerClassMap map) { + + TypeParameter[] typeParamTypes = renameTypeParameter(type.getTypeParameters(), map); + + Type[] paramTypes = type.getParameterTypes(); + if (paramTypes != null) { + paramTypes = Arrays.copyOf(paramTypes, paramTypes.length); + for (int i = 0; i < paramTypes.length; i++) { + Type newParamType = renameType(paramTypes[i], map); + if (newParamType != null) { + paramTypes[i] = newParamType; + } + } + } + + Type returnType = type.getReturnType(); + if (returnType != null) { + Type newReturnType = renameType(returnType, map); + if (newReturnType != null) { + returnType = newReturnType; + } + } + + ObjectType[] exceptionTypes = type.getExceptionTypes(); + if (exceptionTypes != null) { + exceptionTypes = Arrays.copyOf(exceptionTypes, exceptionTypes.length); + for (int i = 0; i < exceptionTypes.length; i++) { + ObjectType newExceptionType = renameType(exceptionTypes[i], map); + if (newExceptionType != null) { + exceptionTypes[i] = newExceptionType; + } + } + } + + return new MethodSignature(typeParamTypes, paramTypes, returnType, exceptionTypes); + } + + private static Type renameType(Type type, ReplacerClassMap map) { + if (type instanceof ObjectType) { + return renameType((ObjectType) type, map); + } else if (type instanceof BaseType) { + return renameType((BaseType) type, map); + } else { + throw new Error("Don't know how to rename type " + type.getClass()); + } + } + + private static ObjectType renameType(ObjectType type, ReplacerClassMap map) { + if (type instanceof ArrayType) { + return renameType((ArrayType) type, map); + } else if (type instanceof ClassType) { + return renameType((ClassType) type, map); + } else if (type instanceof TypeVariable) { + return renameType((TypeVariable) type, map); + } else { + throw new Error("Don't know how to rename type " + type.getClass()); + } + } + + private static BaseType renameType(BaseType type, ReplacerClassMap map) { + // don't have to rename primitives + return null; + } + + private static TypeVariable renameType(TypeVariable type, ReplacerClassMap map) { + // don't have to rename template args + return null; + } + + private static ClassType renameType(ClassType type, ReplacerClassMap map) { + + // translate type args + TypeArgument[] args = type.getTypeArguments(); + if (args != null) { + args = Arrays.copyOf(args, args.length); + for (int i = 0; i < args.length; i++) { + TypeArgument newType = renameType(args[i], map); + if (newType != null) { + args[i] = newType; + } + } + } + + if (type instanceof NestedClassType) { + NestedClassType nestedType = (NestedClassType) type; + + // translate the name + String name = getClassName(type); + String newName = map.get(name); + if (newName != null) { + name = new ClassEntry(newName).getInnermostClassName(); + } + + // translate the parent class too + ClassType parent = renameType(nestedType.getDeclaringClass(), map); + if (parent == null) { + parent = nestedType.getDeclaringClass(); + } + + return new NestedClassType(parent, name, args); + } else { + + // translate the name + String name = type.getName(); + String newName = renameClassName(name, map); + if (newName != null) { + name = newName; + } + + return new ClassType(name, args); + } + } + + private static String getClassName(ClassType type) { + if (type instanceof NestedClassType) { + NestedClassType nestedType = (NestedClassType) type; + return getClassName(nestedType.getDeclaringClass()) + "$" + Descriptor.toJvmName(type.getName().replace('.', '$')); + } else { + return Descriptor.toJvmName(type.getName()); + } + } + + private static String renameClassName(String name, ReplacerClassMap map) { + String newName = map.get(Descriptor.toJvmName(name)); + if (newName != null) { + return Descriptor.toJavaName(newName); + } + return null; + } + + private static TypeArgument renameType(TypeArgument type, ReplacerClassMap map) { + ObjectType subType = type.getType(); + if (subType != null) { + ObjectType newSubType = renameType(subType, map); + if (newSubType != null) { + switch (type.getKind()) { + case ' ': + return new TypeArgument(newSubType); + case '+': + return TypeArgument.subclassOf(newSubType); + case '-': + return TypeArgument.superOf(newSubType); + default: + throw new Error("Unknown type kind: " + type.getKind()); + } + } + } + return null; + } + + private static ArrayType renameType(ArrayType type, ReplacerClassMap map) { + Type newSubType = renameType(type.getComponentType(), map); + if (newSubType != null) { + return new ArrayType(type.getDimension(), newSubType); + } + return null; + } + + private static TypeParameter renameType(TypeParameter type, ReplacerClassMap map) { + + ObjectType superclassType = type.getClassBound(); + if (superclassType != null) { + ObjectType newSuperclassType = renameType(superclassType, map); + if (newSuperclassType != null) { + superclassType = newSuperclassType; + } + } + + ObjectType[] interfaceTypes = type.getInterfaceBound(); + if (interfaceTypes != null) { + interfaceTypes = Arrays.copyOf(interfaceTypes, interfaceTypes.length); + for (int i = 0; i < interfaceTypes.length; i++) { + ObjectType newInterfaceType = renameType(interfaceTypes[i], map); + if (newInterfaceType != null) { + interfaceTypes[i] = newInterfaceType; + } + } + } + + return new TypeParameter(type.getName(), superclassType, interfaceTypes); + } + + private enum SignatureType { + Class { + @Override + public String rename(String signature, ReplacerClassMap map) { + return renameClassSignature(signature, map); + } + }, + Field { + @Override + public String rename(String signature, ReplacerClassMap map) { + return renameFieldSignature(signature, map); + } + }, + Method { + @Override + public String rename(String signature, ReplacerClassMap map) { + return renameMethodSignature(signature, map); + } + }; + + public abstract String rename(String signature, ReplacerClassMap map); + } + + private static class ReplacerClassMap extends HashMap { + + private ClassNameReplacer replacer; + + public ReplacerClassMap(ClassNameReplacer replacer) { + this.replacer = replacer; + } + + @Override + public String get(Object obj) { + if (obj instanceof String) { + return get((String) obj); + } + return null; + } + + public String get(String className) { + return replacer.replace(className); + } + } } diff --git a/src/main/java/cuchaz/enigma/bytecode/ClassTranslator.java b/src/main/java/cuchaz/enigma/bytecode/ClassTranslator.java index 62ebfafb..1ebf6561 100644 --- a/src/main/java/cuchaz/enigma/bytecode/ClassTranslator.java +++ b/src/main/java/cuchaz/enigma/bytecode/ClassTranslator.java @@ -8,155 +8,158 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.bytecode; import cuchaz.enigma.mapping.*; -import cuchaz.enigma.mapping.Translator; -import javassist.*; +import javassist.CtBehavior; +import javassist.CtClass; +import javassist.CtField; +import javassist.CtMethod; import javassist.bytecode.*; public class ClassTranslator { - private Translator translator; - - public ClassTranslator(Translator translator) { - this.translator = translator; - } - - public void translate(CtClass c) { - - // NOTE: the order of these translations is very important - - // translate all the field and method references in the code by editing the constant pool - ConstPool constants = c.getClassFile().getConstPool(); - ConstPoolEditor editor = new ConstPoolEditor(constants); - for (int i = 1; i < constants.getSize(); i++) { - switch (constants.getTag(i)) { - - case ConstPool.CONST_Fieldref: { - - // translate the name and type - FieldEntry entry = EntryFactory.getFieldEntry( - Descriptor.toJvmName(constants.getFieldrefClassName(i)), - constants.getFieldrefName(i), - constants.getFieldrefType(i) - ); - FieldEntry translatedEntry = this.translator.translateEntry(entry); - if (!entry.equals(translatedEntry)) { - editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedEntry.getType().toString()); - } - } - break; - - case ConstPool.CONST_Methodref: - case ConstPool.CONST_InterfaceMethodref: { - - // translate the name and type (ie signature) - BehaviorEntry entry = EntryFactory.getBehaviorEntry( - Descriptor.toJvmName(editor.getMemberrefClassname(i)), - editor.getMemberrefName(i), - editor.getMemberrefType(i) - ); - BehaviorEntry translatedEntry = this.translator.translateEntry(entry); - if (!entry.equals(translatedEntry)) { - editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedEntry.getSignature().toString()); - } - } - break; - default: - break; - } - } - - ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); - Mappings.EntryModifier modifier = this.translator.getModifier(classEntry); - if (modifier != null && modifier != Mappings.EntryModifier.UNCHANGED) - ClassRenamer.applyModifier(c, modifier); - - // translate all the fields - for (CtField field : c.getDeclaredFields()) { - - // translate the name - FieldEntry entry = EntryFactory.getFieldEntry(field); - String translatedName = this.translator.translate(entry); - modifier = this.translator.getModifier(entry); - if (modifier != null && modifier != Mappings.EntryModifier.UNCHANGED) - ClassRenamer.applyModifier(field, modifier); - - if (translatedName != null) { - field.setName(translatedName); - } - - // translate the type - Type translatedType = this.translator.translateType(entry.getType()); - field.getFieldInfo().setDescriptor(translatedType.toString()); - } - - // translate all the methods and constructors - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - - BehaviorEntry entry = EntryFactory.getBehaviorEntry(behavior); - - modifier = this.translator.getModifier(entry); - if (modifier != null && modifier != Mappings.EntryModifier.UNCHANGED) - ClassRenamer.applyModifier(behavior, modifier); - - if (behavior instanceof CtMethod) { - CtMethod method = (CtMethod) behavior; - - // translate the name - String translatedName = this.translator.translate(entry); - if (translatedName != null) { - method.setName(translatedName); - } - } - - if (entry.getSignature() != null) { - // translate the signature - Signature translatedSignature = this.translator.translateSignature(entry.getSignature()); - behavior.getMethodInfo().setDescriptor(translatedSignature.toString()); - } - } - - // translate the EnclosingMethod attribute - EnclosingMethodAttribute enclosingMethodAttr = (EnclosingMethodAttribute) c.getClassFile().getAttribute(EnclosingMethodAttribute.tag); - if (enclosingMethodAttr != null) { - - if (enclosingMethodAttr.methodIndex() == 0) { - BehaviorEntry obfBehaviorEntry = EntryFactory.getBehaviorEntry(Descriptor.toJvmName(enclosingMethodAttr.className())); - BehaviorEntry deobfBehaviorEntry = this.translator.translateEntry(obfBehaviorEntry); - c.getClassFile().addAttribute(new EnclosingMethodAttribute( - constants, - deobfBehaviorEntry.getClassName() - )); - } else { - BehaviorEntry obfBehaviorEntry = EntryFactory.getBehaviorEntry( - Descriptor.toJvmName(enclosingMethodAttr.className()), - enclosingMethodAttr.methodName(), - enclosingMethodAttr.methodDescriptor() - ); - BehaviorEntry deobfBehaviorEntry = this.translator.translateEntry(obfBehaviorEntry); - c.getClassFile().addAttribute(new EnclosingMethodAttribute( - constants, - deobfBehaviorEntry.getClassName(), - deobfBehaviorEntry.getName(), - deobfBehaviorEntry.getSignature().toString() - )); - } - } - - // translate all the class names referenced in the code - // the above code only changed method/field/reference names and types, but not the rest of the class references - ClassRenamer.renameClasses(c, this.translator); - - // translate the source file attribute too - ClassEntry deobfClassEntry = this.translator.translateEntry(classEntry); - if (deobfClassEntry != null) { - String sourceFile = Descriptor.toJvmName(deobfClassEntry.getOutermostClassEntry().getSimpleName()) + ".java"; - c.getClassFile().addAttribute(new SourceFileAttribute(constants, sourceFile)); - } - InnerClassesAttribute attr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag); - if (attr != null) - InnerClassWriter.changeModifier(c, attr, translator); - } + private Translator translator; + + public ClassTranslator(Translator translator) { + this.translator = translator; + } + + public void translate(CtClass c) { + + // NOTE: the order of these translations is very important + + // translate all the field and method references in the code by editing the constant pool + ConstPool constants = c.getClassFile().getConstPool(); + ConstPoolEditor editor = new ConstPoolEditor(constants); + for (int i = 1; i < constants.getSize(); i++) { + switch (constants.getTag(i)) { + + case ConstPool.CONST_Fieldref: { + + // translate the name and type + FieldEntry entry = EntryFactory.getFieldEntry( + Descriptor.toJvmName(constants.getFieldrefClassName(i)), + constants.getFieldrefName(i), + constants.getFieldrefType(i) + ); + FieldEntry translatedEntry = this.translator.translateEntry(entry); + if (!entry.equals(translatedEntry)) { + editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedEntry.getType().toString()); + } + } + break; + + case ConstPool.CONST_Methodref: + case ConstPool.CONST_InterfaceMethodref: { + + // translate the name and type (ie signature) + BehaviorEntry entry = EntryFactory.getBehaviorEntry( + Descriptor.toJvmName(editor.getMemberrefClassname(i)), + editor.getMemberrefName(i), + editor.getMemberrefType(i) + ); + BehaviorEntry translatedEntry = this.translator.translateEntry(entry); + if (!entry.equals(translatedEntry)) { + editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedEntry.getSignature().toString()); + } + } + break; + default: + break; + } + } + + ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); + Mappings.EntryModifier modifier = this.translator.getModifier(classEntry); + if (modifier != null && modifier != Mappings.EntryModifier.UNCHANGED) + ClassRenamer.applyModifier(c, modifier); + + // translate all the fields + for (CtField field : c.getDeclaredFields()) { + + // translate the name + FieldEntry entry = EntryFactory.getFieldEntry(field); + String translatedName = this.translator.translate(entry); + modifier = this.translator.getModifier(entry); + if (modifier != null && modifier != Mappings.EntryModifier.UNCHANGED) + ClassRenamer.applyModifier(field, modifier); + + if (translatedName != null) { + field.setName(translatedName); + } + + // translate the type + Type translatedType = this.translator.translateType(entry.getType()); + field.getFieldInfo().setDescriptor(translatedType.toString()); + } + + // translate all the methods and constructors + for (CtBehavior behavior : c.getDeclaredBehaviors()) { + + BehaviorEntry entry = EntryFactory.getBehaviorEntry(behavior); + + modifier = this.translator.getModifier(entry); + if (modifier != null && modifier != Mappings.EntryModifier.UNCHANGED) + ClassRenamer.applyModifier(behavior, modifier); + + if (behavior instanceof CtMethod) { + CtMethod method = (CtMethod) behavior; + + // translate the name + String translatedName = this.translator.translate(entry); + if (translatedName != null) { + method.setName(translatedName); + } + } + + if (entry.getSignature() != null) { + // translate the signature + Signature translatedSignature = this.translator.translateSignature(entry.getSignature()); + behavior.getMethodInfo().setDescriptor(translatedSignature.toString()); + } + } + + // translate the EnclosingMethod attribute + EnclosingMethodAttribute enclosingMethodAttr = (EnclosingMethodAttribute) c.getClassFile().getAttribute(EnclosingMethodAttribute.tag); + if (enclosingMethodAttr != null) { + + if (enclosingMethodAttr.methodIndex() == 0) { + BehaviorEntry obfBehaviorEntry = EntryFactory.getBehaviorEntry(Descriptor.toJvmName(enclosingMethodAttr.className())); + BehaviorEntry deobfBehaviorEntry = this.translator.translateEntry(obfBehaviorEntry); + c.getClassFile().addAttribute(new EnclosingMethodAttribute( + constants, + deobfBehaviorEntry.getClassName() + )); + } else { + BehaviorEntry obfBehaviorEntry = EntryFactory.getBehaviorEntry( + Descriptor.toJvmName(enclosingMethodAttr.className()), + enclosingMethodAttr.methodName(), + enclosingMethodAttr.methodDescriptor() + ); + BehaviorEntry deobfBehaviorEntry = this.translator.translateEntry(obfBehaviorEntry); + c.getClassFile().addAttribute(new EnclosingMethodAttribute( + constants, + deobfBehaviorEntry.getClassName(), + deobfBehaviorEntry.getName(), + deobfBehaviorEntry.getSignature().toString() + )); + } + } + + // translate all the class names referenced in the code + // the above code only changed method/field/reference names and types, but not the rest of the class references + ClassRenamer.renameClasses(c, this.translator); + + // translate the source file attribute too + ClassEntry deobfClassEntry = this.translator.translateEntry(classEntry); + if (deobfClassEntry != null) { + String sourceFile = Descriptor.toJvmName(deobfClassEntry.getOutermostClassEntry().getSimpleName()) + ".java"; + c.getClassFile().addAttribute(new SourceFileAttribute(constants, sourceFile)); + } + InnerClassesAttribute attr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag); + if (attr != null) + InnerClassWriter.changeModifier(c, attr, translator); + } } diff --git a/src/main/java/cuchaz/enigma/bytecode/ConstPoolEditor.java b/src/main/java/cuchaz/enigma/bytecode/ConstPoolEditor.java index 256df61e..1932730d 100644 --- a/src/main/java/cuchaz/enigma/bytecode/ConstPoolEditor.java +++ b/src/main/java/cuchaz/enigma/bytecode/ConstPoolEditor.java @@ -8,8 +8,15 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.bytecode; +import cuchaz.enigma.bytecode.accessors.ClassInfoAccessor; +import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor; +import cuchaz.enigma.bytecode.accessors.MemberRefInfoAccessor; +import javassist.bytecode.ConstPool; +import javassist.bytecode.Descriptor; + import java.io.DataInputStream; import java.io.DataOutputStream; import java.lang.reflect.Constructor; @@ -17,247 +24,241 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.HashMap; -import cuchaz.enigma.bytecode.accessors.ClassInfoAccessor; -import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor; -import cuchaz.enigma.bytecode.accessors.MemberRefInfoAccessor; -import javassist.bytecode.ConstPool; -import javassist.bytecode.Descriptor; - public class ConstPoolEditor { - private static Method getItem; - private static Method addItem; - private static Method addItem0; - private static Field items; - private static Field cache; - private static Field numItems; - private static Field objects; - private static Field elements; - private static Method methodWritePool; - private static Constructor constructorPool; - - static { - try { - getItem = ConstPool.class.getDeclaredMethod("getItem", int.class); - getItem.setAccessible(true); - - addItem = ConstPool.class.getDeclaredMethod("addItem", Class.forName("javassist.bytecode.ConstInfo")); - addItem.setAccessible(true); - - addItem0 = ConstPool.class.getDeclaredMethod("addItem0", Class.forName("javassist.bytecode.ConstInfo")); - addItem0.setAccessible(true); - - items = ConstPool.class.getDeclaredField("items"); - items.setAccessible(true); - - cache = ConstPool.class.getDeclaredField("itemsCache"); - cache.setAccessible(true); - - numItems = ConstPool.class.getDeclaredField("numOfItems"); - numItems.setAccessible(true); - - objects = Class.forName("javassist.bytecode.LongVector").getDeclaredField("objects"); - objects.setAccessible(true); - - elements = Class.forName("javassist.bytecode.LongVector").getDeclaredField("elements"); - elements.setAccessible(true); - - methodWritePool = ConstPool.class.getDeclaredMethod("write", DataOutputStream.class); - methodWritePool.setAccessible(true); - - constructorPool = ConstPool.class.getDeclaredConstructor(DataInputStream.class); - constructorPool.setAccessible(true); - } catch (Exception ex) { - throw new Error(ex); - } - } - - private ConstPool pool; - - public ConstPoolEditor(ConstPool pool) { - this.pool = pool; - } - - public void writePool(DataOutputStream out) { - try { - methodWritePool.invoke(this.pool, out); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public static ConstPool readPool(DataInputStream in) { - try { - return constructorPool.newInstance(in); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public String getMemberrefClassname(int memberrefIndex) { - return Descriptor.toJvmName(this.pool.getClassInfo(this.pool.getMemberClass(memberrefIndex))); - } - - public String getMemberrefName(int memberrefIndex) { - return this.pool.getUtf8Info(this.pool.getNameAndTypeName(this.pool.getMemberNameAndType(memberrefIndex))); - } - - public String getMemberrefType(int memberrefIndex) { - return this.pool.getUtf8Info(this.pool.getNameAndTypeDescriptor(this.pool.getMemberNameAndType(memberrefIndex))); - } - - public ConstInfoAccessor getItem(int index) { - try { - Object entry = getItem.invoke(this.pool, index); - if (entry == null) { - return null; - } - return new ConstInfoAccessor(entry); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public int addItem(Object item) { - try { - return (Integer) addItem.invoke(this.pool, item); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public int addItemForceNew(Object item) { - try { - return (Integer) addItem0.invoke(this.pool, item); - } catch (Exception ex) { - throw new Error(ex); - } - } - - @SuppressWarnings("rawtypes") - public void removeLastItem() { - try { - // remove the item from the cache - HashMap cache = getCache(); - if (cache != null) { - Object item = getItem(this.pool.getSize() - 1); - cache.remove(item); - } - - // remove the actual item - // based off of LongVector.addElement() - Object item = items.get(this.pool); - Object[][] object = (Object[][]) objects.get(items); - int numElements = (Integer) elements.get(items) - 1; - int nth = numElements >> 7; - int offset = numElements & (128 - 1); - object[nth][offset] = null; - - // decrement the number of items - elements.set(item, numElements); - numItems.set(this.pool, (Integer) numItems.get(this.pool) - 1); - } catch (Exception ex) { - throw new Error(ex); - } - } - - @SuppressWarnings("rawtypes") - public HashMap getCache() { - try { - return (HashMap) cache.get(this.pool); - } catch (Exception ex) { - throw new Error(ex); - } - } - - @SuppressWarnings({"rawtypes", "unchecked"}) - public void changeMemberrefNameAndType(int memberrefIndex, String newName, String newType) { - // NOTE: when changing values, we always need to copy-on-write - try { - // get the memberref item - Object item = getItem(memberrefIndex).getItem(); - - // update the cache - HashMap cache = getCache(); - if (cache != null) { - cache.remove(item); - } - - new MemberRefInfoAccessor(item).setNameAndTypeIndex(this.pool.addNameAndTypeInfo(newName, newType)); - - // update the cache - if (cache != null) { - cache.put(item, item); - } - } catch (Exception ex) { - throw new Error(ex); - } - - // make sure the change worked - assert (newName.equals(getMemberrefName(memberrefIndex))); - assert (newType.equals(getMemberrefType(memberrefIndex))); - } - - @SuppressWarnings({"rawtypes", "unchecked"}) - public void changeClassName(int classNameIndex, String newName) { - // NOTE: when changing values, we always need to copy-on-write - try { - // get the class item - Object item = getItem(classNameIndex).getItem(); - - // update the cache - HashMap cache = getCache(); - if (cache != null) { - cache.remove(item); - } - - // add the new name and repoint the name-and-type to it - new ClassInfoAccessor(item).setNameIndex(this.pool.addUtf8Info(newName)); - - // update the cache - if (cache != null) { - cache.put(item, item); - } - } catch (Exception ex) { - throw new Error(ex); - } - } - - public static ConstPool newConstPool() { - // const pool expects the name of a class to initialize itself - // but we want an empty pool - // so give it a bogus name, and then clear the entries afterwards - ConstPool pool = new ConstPool("a"); - - ConstPoolEditor editor = new ConstPoolEditor(pool); - int size = pool.getSize(); - for (int i = 0; i < size - 1; i++) { - editor.removeLastItem(); - } - - // make sure the pool is actually empty - // although, in this case "empty" means one thing in it - // the JVM spec says index 0 should be reserved - assert (pool.getSize() == 1); - assert (editor.getItem(0) == null); - assert (editor.getItem(1) == null); - assert (editor.getItem(2) == null); - assert (editor.getItem(3) == null); - - // also, clear the cache - editor.getCache().clear(); - - return pool; - } - - public String dump() { - StringBuilder buf = new StringBuilder(); - for (int i = 1; i < this.pool.getSize(); i++) { - buf.append(String.format("%4d", i)); - buf.append(" "); - buf.append(getItem(i).toString()); - buf.append("\n"); - } - return buf.toString(); - } + private static Method getItem; + private static Method addItem; + private static Method addItem0; + private static Field items; + private static Field cache; + private static Field numItems; + private static Field objects; + private static Field elements; + private static Method methodWritePool; + private static Constructor constructorPool; + + static { + try { + getItem = ConstPool.class.getDeclaredMethod("getItem", int.class); + getItem.setAccessible(true); + + addItem = ConstPool.class.getDeclaredMethod("addItem", Class.forName("javassist.bytecode.ConstInfo")); + addItem.setAccessible(true); + + addItem0 = ConstPool.class.getDeclaredMethod("addItem0", Class.forName("javassist.bytecode.ConstInfo")); + addItem0.setAccessible(true); + + items = ConstPool.class.getDeclaredField("items"); + items.setAccessible(true); + + cache = ConstPool.class.getDeclaredField("itemsCache"); + cache.setAccessible(true); + + numItems = ConstPool.class.getDeclaredField("numOfItems"); + numItems.setAccessible(true); + + objects = Class.forName("javassist.bytecode.LongVector").getDeclaredField("objects"); + objects.setAccessible(true); + + elements = Class.forName("javassist.bytecode.LongVector").getDeclaredField("elements"); + elements.setAccessible(true); + + methodWritePool = ConstPool.class.getDeclaredMethod("write", DataOutputStream.class); + methodWritePool.setAccessible(true); + + constructorPool = ConstPool.class.getDeclaredConstructor(DataInputStream.class); + constructorPool.setAccessible(true); + } catch (Exception ex) { + throw new Error(ex); + } + } + + private ConstPool pool; + + public ConstPoolEditor(ConstPool pool) { + this.pool = pool; + } + + public static ConstPool readPool(DataInputStream in) { + try { + return constructorPool.newInstance(in); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public static ConstPool newConstPool() { + // const pool expects the name of a class to initialize itself + // but we want an empty pool + // so give it a bogus name, and then clear the entries afterwards + ConstPool pool = new ConstPool("a"); + + ConstPoolEditor editor = new ConstPoolEditor(pool); + int size = pool.getSize(); + for (int i = 0; i < size - 1; i++) { + editor.removeLastItem(); + } + + // make sure the pool is actually empty + // although, in this case "empty" means one thing in it + // the JVM spec says index 0 should be reserved + assert (pool.getSize() == 1); + assert (editor.getItem(0) == null); + assert (editor.getItem(1) == null); + assert (editor.getItem(2) == null); + assert (editor.getItem(3) == null); + + // also, clear the cache + editor.getCache().clear(); + + return pool; + } + + public void writePool(DataOutputStream out) { + try { + methodWritePool.invoke(this.pool, out); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public String getMemberrefClassname(int memberrefIndex) { + return Descriptor.toJvmName(this.pool.getClassInfo(this.pool.getMemberClass(memberrefIndex))); + } + + public String getMemberrefName(int memberrefIndex) { + return this.pool.getUtf8Info(this.pool.getNameAndTypeName(this.pool.getMemberNameAndType(memberrefIndex))); + } + + public String getMemberrefType(int memberrefIndex) { + return this.pool.getUtf8Info(this.pool.getNameAndTypeDescriptor(this.pool.getMemberNameAndType(memberrefIndex))); + } + + public ConstInfoAccessor getItem(int index) { + try { + Object entry = getItem.invoke(this.pool, index); + if (entry == null) { + return null; + } + return new ConstInfoAccessor(entry); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public int addItem(Object item) { + try { + return (Integer) addItem.invoke(this.pool, item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public int addItemForceNew(Object item) { + try { + return (Integer) addItem0.invoke(this.pool, item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + @SuppressWarnings("rawtypes") + public void removeLastItem() { + try { + // remove the item from the cache + HashMap cache = getCache(); + if (cache != null) { + Object item = getItem(this.pool.getSize() - 1); + cache.remove(item); + } + + // remove the actual item + // based off of LongVector.addElement() + Object item = items.get(this.pool); + Object[][] object = (Object[][]) objects.get(items); + int numElements = (Integer) elements.get(items) - 1; + int nth = numElements >> 7; + int offset = numElements & (128 - 1); + object[nth][offset] = null; + + // decrement the number of items + elements.set(item, numElements); + numItems.set(this.pool, (Integer) numItems.get(this.pool) - 1); + } catch (Exception ex) { + throw new Error(ex); + } + } + + @SuppressWarnings("rawtypes") + public HashMap getCache() { + try { + return (HashMap) cache.get(this.pool); + } catch (Exception ex) { + throw new Error(ex); + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void changeMemberrefNameAndType(int memberrefIndex, String newName, String newType) { + // NOTE: when changing values, we always need to copy-on-write + try { + // get the memberref item + Object item = getItem(memberrefIndex).getItem(); + + // update the cache + HashMap cache = getCache(); + if (cache != null) { + cache.remove(item); + } + + new MemberRefInfoAccessor(item).setNameAndTypeIndex(this.pool.addNameAndTypeInfo(newName, newType)); + + // update the cache + if (cache != null) { + cache.put(item, item); + } + } catch (Exception ex) { + throw new Error(ex); + } + + // make sure the change worked + assert (newName.equals(getMemberrefName(memberrefIndex))); + assert (newType.equals(getMemberrefType(memberrefIndex))); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void changeClassName(int classNameIndex, String newName) { + // NOTE: when changing values, we always need to copy-on-write + try { + // get the class item + Object item = getItem(classNameIndex).getItem(); + + // update the cache + HashMap cache = getCache(); + if (cache != null) { + cache.remove(item); + } + + // add the new name and repoint the name-and-type to it + new ClassInfoAccessor(item).setNameIndex(this.pool.addUtf8Info(newName)); + + // update the cache + if (cache != null) { + cache.put(item, item); + } + } catch (Exception ex) { + throw new Error(ex); + } + } + + public String dump() { + StringBuilder buf = new StringBuilder(); + for (int i = 1; i < this.pool.getSize(); i++) { + buf.append(String.format("%4d", i)); + buf.append(" "); + buf.append(getItem(i)); + buf.append("\n"); + } + return buf.toString(); + } } diff --git a/src/main/java/cuchaz/enigma/bytecode/InfoType.java b/src/main/java/cuchaz/enigma/bytecode/InfoType.java index 21b04173..9013d581 100644 --- a/src/main/java/cuchaz/enigma/bytecode/InfoType.java +++ b/src/main/java/cuchaz/enigma/bytecode/InfoType.java @@ -8,259 +8,259 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.bytecode; import com.google.common.collect.Maps; +import cuchaz.enigma.bytecode.accessors.*; import java.util.Collection; import java.util.Map; -import cuchaz.enigma.bytecode.accessors.*; - public enum InfoType { - Utf8Info(1), - IntegerInfo(3), - FloatInfo(4), - LongInfo(5), - DoubleInfo(6), - ClassInfo(7) { - @Override - public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { - ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem()); - gatherIndexTree(indices, editor, accessor.getNameIndex()); - } - - @Override - public void remapIndices(Map map, ConstInfoAccessor entry) { - ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem()); - accessor.setNameIndex(remapIndex(map, accessor.getNameIndex())); - } - - @Override - public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { - ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem()); - ConstInfoAccessor nameEntry = pool.getItem(accessor.getNameIndex()); - return nameEntry != null && nameEntry.getTag() == Utf8Info.getTag(); - } - }, - StringInfo(8) { - @Override - public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { - StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem()); - gatherIndexTree(indices, editor, accessor.getStringIndex()); - } - - @Override - public void remapIndices(Map map, ConstInfoAccessor entry) { - StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem()); - accessor.setStringIndex(remapIndex(map, accessor.getStringIndex())); - } - - @Override - public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { - StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem()); - ConstInfoAccessor stringEntry = pool.getItem(accessor.getStringIndex()); - return stringEntry != null && stringEntry.getTag() == Utf8Info.getTag(); - } - }, - FieldRefInfo(9) { - @Override - public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { - MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem()); - gatherIndexTree(indices, editor, accessor.getClassIndex()); - gatherIndexTree(indices, editor, accessor.getNameAndTypeIndex()); - } - - @Override - public void remapIndices(Map map, ConstInfoAccessor entry) { - MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem()); - accessor.setClassIndex(remapIndex(map, accessor.getClassIndex())); - accessor.setNameAndTypeIndex(remapIndex(map, accessor.getNameAndTypeIndex())); - } - - @Override - public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { - MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem()); - ConstInfoAccessor classEntry = pool.getItem(accessor.getClassIndex()); - ConstInfoAccessor nameAndTypeEntry = pool.getItem(accessor.getNameAndTypeIndex()); - return classEntry != null && classEntry.getTag() == ClassInfo.getTag() && nameAndTypeEntry != null && nameAndTypeEntry.getTag() == NameAndTypeInfo.getTag(); - } - }, - // same as FieldRefInfo - MethodRefInfo(10) { - @Override - public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { - FieldRefInfo.gatherIndexTree(indices, editor, entry); - } - - @Override - public void remapIndices(Map map, ConstInfoAccessor entry) { - FieldRefInfo.remapIndices(map, entry); - } - - @Override - public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { - return FieldRefInfo.subIndicesAreValid(entry, pool); - } - }, - // same as FieldRefInfo - InterfaceMethodRefInfo(11) { - @Override - public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { - FieldRefInfo.gatherIndexTree(indices, editor, entry); - } - - @Override - public void remapIndices(Map map, ConstInfoAccessor entry) { - FieldRefInfo.remapIndices(map, entry); - } - - @Override - public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { - return FieldRefInfo.subIndicesAreValid(entry, pool); - } - }, - NameAndTypeInfo(12) { - @Override - public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { - NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem()); - gatherIndexTree(indices, editor, accessor.getNameIndex()); - gatherIndexTree(indices, editor, accessor.getTypeIndex()); - } - - @Override - public void remapIndices(Map map, ConstInfoAccessor entry) { - NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem()); - accessor.setNameIndex(remapIndex(map, accessor.getNameIndex())); - accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex())); - } - - @Override - public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { - NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem()); - ConstInfoAccessor nameEntry = pool.getItem(accessor.getNameIndex()); - ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex()); - return nameEntry != null && nameEntry.getTag() == Utf8Info.getTag() && typeEntry != null && typeEntry.getTag() == Utf8Info.getTag(); - } - }, - MethodHandleInfo(15) { - @Override - public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { - MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem()); - gatherIndexTree(indices, editor, accessor.getTypeIndex()); - gatherIndexTree(indices, editor, accessor.getMethodRefIndex()); - } - - @Override - public void remapIndices(Map map, ConstInfoAccessor entry) { - MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem()); - accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex())); - accessor.setMethodRefIndex(remapIndex(map, accessor.getMethodRefIndex())); - } - - @Override - public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { - MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem()); - ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex()); - ConstInfoAccessor methodRefEntry = pool.getItem(accessor.getMethodRefIndex()); - return typeEntry != null && typeEntry.getTag() == Utf8Info.getTag() && methodRefEntry != null && methodRefEntry.getTag() == MethodRefInfo.getTag(); - } - }, - MethodTypeInfo(16) { - @Override - public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { - MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem()); - gatherIndexTree(indices, editor, accessor.getTypeIndex()); - } - - @Override - public void remapIndices(Map map, ConstInfoAccessor entry) { - MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem()); - accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex())); - } - - @Override - public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { - MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem()); - ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex()); - return typeEntry != null && typeEntry.getTag() == Utf8Info.getTag(); - } - }, - InvokeDynamicInfo(18) { - @Override - public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { - InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem()); - gatherIndexTree(indices, editor, accessor.getBootstrapIndex()); - gatherIndexTree(indices, editor, accessor.getNameAndTypeIndex()); - } - - @Override - public void remapIndices(Map map, ConstInfoAccessor entry) { - InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem()); - accessor.setBootstrapIndex(remapIndex(map, accessor.getBootstrapIndex())); - accessor.setNameAndTypeIndex(remapIndex(map, accessor.getNameAndTypeIndex())); - } - - @Override - public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { - InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem()); - ConstInfoAccessor bootstrapEntry = pool.getItem(accessor.getBootstrapIndex()); - ConstInfoAccessor nameAndTypeEntry = pool.getItem(accessor.getNameAndTypeIndex()); - return bootstrapEntry != null && bootstrapEntry.getTag() == Utf8Info.getTag() && nameAndTypeEntry != null && nameAndTypeEntry.getTag() == NameAndTypeInfo.getTag(); - } - }; - - private static Map types; - - static { - types = Maps.newTreeMap(); - for (InfoType type : values()) { - types.put(type.getTag(), type); - } - } - - private int tag; - - InfoType(int tag) { - this.tag = tag; - } - - public int getTag() { - return this.tag; - } - - public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { - // by default, do nothing - } - - public void remapIndices(Map map, ConstInfoAccessor entry) { - // by default, do nothing - } - - public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { - // by default, everything is good - return true; - } - - public static InfoType getByTag(int tag) { - return types.get(tag); - } - - public static void gatherIndexTree(Collection indices, ConstPoolEditor editor, int index) { - // add own index - indices.add(index); - - // recurse - ConstInfoAccessor entry = editor.getItem(index); - entry.getType().gatherIndexTree(indices, editor, entry); - } - - private static int remapIndex(Map map, int index) { - Integer newIndex = map.get(index); - if (newIndex == null) { - newIndex = index; - } - return newIndex; - } + Utf8Info(1), + IntegerInfo(3), + FloatInfo(4), + LongInfo(5), + DoubleInfo(6), + ClassInfo(7) { + @Override + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem()); + gatherIndexTree(indices, editor, accessor.getNameIndex()); + } + + @Override + public void remapIndices(Map map, ConstInfoAccessor entry) { + ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem()); + accessor.setNameIndex(remapIndex(map, accessor.getNameIndex())); + } + + @Override + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem()); + ConstInfoAccessor nameEntry = pool.getItem(accessor.getNameIndex()); + return nameEntry != null && nameEntry.getTag() == Utf8Info.getTag(); + } + }, + StringInfo(8) { + @Override + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem()); + gatherIndexTree(indices, editor, accessor.getStringIndex()); + } + + @Override + public void remapIndices(Map map, ConstInfoAccessor entry) { + StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem()); + accessor.setStringIndex(remapIndex(map, accessor.getStringIndex())); + } + + @Override + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem()); + ConstInfoAccessor stringEntry = pool.getItem(accessor.getStringIndex()); + return stringEntry != null && stringEntry.getTag() == Utf8Info.getTag(); + } + }, + FieldRefInfo(9) { + @Override + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem()); + gatherIndexTree(indices, editor, accessor.getClassIndex()); + gatherIndexTree(indices, editor, accessor.getNameAndTypeIndex()); + } + + @Override + public void remapIndices(Map map, ConstInfoAccessor entry) { + MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem()); + accessor.setClassIndex(remapIndex(map, accessor.getClassIndex())); + accessor.setNameAndTypeIndex(remapIndex(map, accessor.getNameAndTypeIndex())); + } + + @Override + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem()); + ConstInfoAccessor classEntry = pool.getItem(accessor.getClassIndex()); + ConstInfoAccessor nameAndTypeEntry = pool.getItem(accessor.getNameAndTypeIndex()); + return classEntry != null && classEntry.getTag() == ClassInfo.getTag() && nameAndTypeEntry != null && nameAndTypeEntry.getTag() == NameAndTypeInfo.getTag(); + } + }, + // same as FieldRefInfo + MethodRefInfo(10) { + @Override + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + FieldRefInfo.gatherIndexTree(indices, editor, entry); + } + + @Override + public void remapIndices(Map map, ConstInfoAccessor entry) { + FieldRefInfo.remapIndices(map, entry); + } + + @Override + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + return FieldRefInfo.subIndicesAreValid(entry, pool); + } + }, + // same as FieldRefInfo + InterfaceMethodRefInfo(11) { + @Override + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + FieldRefInfo.gatherIndexTree(indices, editor, entry); + } + + @Override + public void remapIndices(Map map, ConstInfoAccessor entry) { + FieldRefInfo.remapIndices(map, entry); + } + + @Override + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + return FieldRefInfo.subIndicesAreValid(entry, pool); + } + }, + NameAndTypeInfo(12) { + @Override + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem()); + gatherIndexTree(indices, editor, accessor.getNameIndex()); + gatherIndexTree(indices, editor, accessor.getTypeIndex()); + } + + @Override + public void remapIndices(Map map, ConstInfoAccessor entry) { + NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem()); + accessor.setNameIndex(remapIndex(map, accessor.getNameIndex())); + accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex())); + } + + @Override + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem()); + ConstInfoAccessor nameEntry = pool.getItem(accessor.getNameIndex()); + ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex()); + return nameEntry != null && nameEntry.getTag() == Utf8Info.getTag() && typeEntry != null && typeEntry.getTag() == Utf8Info.getTag(); + } + }, + MethodHandleInfo(15) { + @Override + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem()); + gatherIndexTree(indices, editor, accessor.getTypeIndex()); + gatherIndexTree(indices, editor, accessor.getMethodRefIndex()); + } + + @Override + public void remapIndices(Map map, ConstInfoAccessor entry) { + MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem()); + accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex())); + accessor.setMethodRefIndex(remapIndex(map, accessor.getMethodRefIndex())); + } + + @Override + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem()); + ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex()); + ConstInfoAccessor methodRefEntry = pool.getItem(accessor.getMethodRefIndex()); + return typeEntry != null && typeEntry.getTag() == Utf8Info.getTag() && methodRefEntry != null && methodRefEntry.getTag() == MethodRefInfo.getTag(); + } + }, + MethodTypeInfo(16) { + @Override + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem()); + gatherIndexTree(indices, editor, accessor.getTypeIndex()); + } + + @Override + public void remapIndices(Map map, ConstInfoAccessor entry) { + MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem()); + accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex())); + } + + @Override + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem()); + ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex()); + return typeEntry != null && typeEntry.getTag() == Utf8Info.getTag(); + } + }, + InvokeDynamicInfo(18) { + @Override + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem()); + gatherIndexTree(indices, editor, accessor.getBootstrapIndex()); + gatherIndexTree(indices, editor, accessor.getNameAndTypeIndex()); + } + + @Override + public void remapIndices(Map map, ConstInfoAccessor entry) { + InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem()); + accessor.setBootstrapIndex(remapIndex(map, accessor.getBootstrapIndex())); + accessor.setNameAndTypeIndex(remapIndex(map, accessor.getNameAndTypeIndex())); + } + + @Override + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem()); + ConstInfoAccessor bootstrapEntry = pool.getItem(accessor.getBootstrapIndex()); + ConstInfoAccessor nameAndTypeEntry = pool.getItem(accessor.getNameAndTypeIndex()); + return bootstrapEntry != null && bootstrapEntry.getTag() == Utf8Info.getTag() && nameAndTypeEntry != null && nameAndTypeEntry.getTag() == NameAndTypeInfo.getTag(); + } + }; + + private static Map types; + + static { + types = Maps.newTreeMap(); + for (InfoType type : values()) { + types.put(type.getTag(), type); + } + } + + private int tag; + + InfoType(int tag) { + this.tag = tag; + } + + public static InfoType getByTag(int tag) { + return types.get(tag); + } + + public static void gatherIndexTree(Collection indices, ConstPoolEditor editor, int index) { + // add own index + indices.add(index); + + // recurse + ConstInfoAccessor entry = editor.getItem(index); + entry.getType().gatherIndexTree(indices, editor, entry); + } + + private static int remapIndex(Map map, int index) { + Integer newIndex = map.get(index); + if (newIndex == null) { + newIndex = index; + } + return newIndex; + } + + public int getTag() { + return this.tag; + } + + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + // by default, do nothing + } + + public void remapIndices(Map map, ConstInfoAccessor entry) { + // by default, do nothing + } + + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + // by default, everything is good + return true; + } } diff --git a/src/main/java/cuchaz/enigma/bytecode/InnerClassWriter.java b/src/main/java/cuchaz/enigma/bytecode/InnerClassWriter.java index eb70c23d..5f8be908 100644 --- a/src/main/java/cuchaz/enigma/bytecode/InnerClassWriter.java +++ b/src/main/java/cuchaz/enigma/bytecode/InnerClassWriter.java @@ -8,13 +8,10 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.bytecode; import com.google.common.collect.Lists; - -import java.util.Collection; -import java.util.List; - import cuchaz.enigma.analysis.JarIndex; import cuchaz.enigma.mapping.*; import javassist.ClassPool; @@ -22,126 +19,126 @@ import javassist.CtClass; import javassist.NotFoundException; import javassist.bytecode.*; +import java.util.Collection; +import java.util.List; + public class InnerClassWriter { - private JarIndex index; - private Translator deobfuscatorTranslator; - - public InnerClassWriter(JarIndex index, Translator deobfuscatorTranslator) { - this.index = index; - this.deobfuscatorTranslator = deobfuscatorTranslator; - } - - public void write(CtClass c) { - - // don't change anything if there's already an attribute there - InnerClassesAttribute oldAttr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag); - if (oldAttr != null) { - // bail! - return; - } - - ClassEntry obfClassEntry = EntryFactory.getClassEntry(c); - List obfClassChain = this.index.getObfClassChain(obfClassEntry); - - boolean isInnerClass = obfClassChain.size() > 1; - if (isInnerClass) { - - // it's an inner class, rename it to the fully qualified name - c.setName(obfClassEntry.buildClassEntry(obfClassChain).getName()); - - BehaviorEntry caller = this.index.getAnonymousClassCaller(obfClassEntry); - if (caller != null) { - - // write the enclosing method attribute - if (caller.getName().equals("")) { - c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName())); - } else { - c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName(), caller.getName(), caller.getSignature().toString())); - } - } - } - - // does this class have any inner classes? - Collection obfInnerClassEntries = this.index.getInnerClasses(obfClassEntry); - - if (isInnerClass || !obfInnerClassEntries.isEmpty()) { - - // create an inner class attribute - InnerClassesAttribute attr = new InnerClassesAttribute(c.getClassFile().getConstPool()); - c.getClassFile().addAttribute(attr); - - // write the ancestry, but not the outermost class - for (int i = 1; i < obfClassChain.size(); i++) { - ClassEntry obfInnerClassEntry = obfClassChain.get(i); - writeInnerClass(attr, obfClassChain, obfInnerClassEntry); - - // update references to use the fully qualified inner class name - c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(obfClassChain).getName()); - } - - // write the inner classes - for (ClassEntry obfInnerClassEntry : obfInnerClassEntries) { - - // extend the class chain - List extendedObfClassChain = Lists.newArrayList(obfClassChain); - extendedObfClassChain.add(obfInnerClassEntry); - - writeInnerClass(attr, extendedObfClassChain, obfInnerClassEntry); - - // update references to use the fully qualified inner class name - c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(extendedObfClassChain).getName()); - } - } - } - - // FIXME: modiffier is not applied to inner class - public static void changeModifier(CtClass c, InnerClassesAttribute attr, Translator translator) - { - ClassPool pool = c.getClassPool(); - for (int i = 0; i < attr.tableLength(); i++) { - - String innerName = attr.innerClass(i); - // get the inner class full name (which has already been translated) - ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(innerName)); - try - { - CtClass innerClass = pool.get(innerName); - Mappings.EntryModifier modifier = translator.getModifier(classEntry); - if (modifier != null && modifier != Mappings.EntryModifier.UNCHANGED) - ClassRenamer.applyModifier(innerClass, modifier); - } catch (NotFoundException e) - { - // This shouldn't be possible in theory - //e.printStackTrace(); - } - } - } - - private void writeInnerClass(InnerClassesAttribute attr, List obfClassChain, ClassEntry obfClassEntry) { - - // get the new inner class name - ClassEntry obfInnerClassEntry = obfClassEntry.buildClassEntry(obfClassChain); - ClassEntry obfOuterClassEntry = obfInnerClassEntry.getOuterClassEntry(); - - // here's what the JVM spec says about the InnerClasses attribute - // append(inner, parent, 0 if anonymous else simple name, flags); - - // update the attribute with this inner class - ConstPool constPool = attr.getConstPool(); - int innerClassIndex = constPool.addClassInfo(obfInnerClassEntry.getName()); - int parentClassIndex = constPool.addClassInfo(obfOuterClassEntry.getName()); - int innerClassNameIndex = 0; - int accessFlags = AccessFlag.PUBLIC; - // TODO: need to figure out if we can put static or not - if (!this.index.isAnonymousClass(obfClassEntry)) { - innerClassNameIndex = constPool.addUtf8Info(obfInnerClassEntry.getInnermostClassName()); - } - - attr.append(innerClassIndex, parentClassIndex, innerClassNameIndex, accessFlags); + private JarIndex index; + private Translator deobfuscatorTranslator; + + public InnerClassWriter(JarIndex index, Translator deobfuscatorTranslator) { + this.index = index; + this.deobfuscatorTranslator = deobfuscatorTranslator; + } + + // FIXME: modiffier is not applied to inner class + public static void changeModifier(CtClass c, InnerClassesAttribute attr, Translator translator) { + ClassPool pool = c.getClassPool(); + for (int i = 0; i < attr.tableLength(); i++) { + + String innerName = attr.innerClass(i); + // get the inner class full name (which has already been translated) + ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(innerName)); + try { + CtClass innerClass = pool.get(innerName); + Mappings.EntryModifier modifier = translator.getModifier(classEntry); + if (modifier != null && modifier != Mappings.EntryModifier.UNCHANGED) + ClassRenamer.applyModifier(innerClass, modifier); + } catch (NotFoundException e) { + // This shouldn't be possible in theory + //e.printStackTrace(); + } + } + } + + public void write(CtClass c) { + + // don't change anything if there's already an attribute there + InnerClassesAttribute oldAttr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag); + if (oldAttr != null) { + // bail! + return; + } + + ClassEntry obfClassEntry = EntryFactory.getClassEntry(c); + List obfClassChain = this.index.getObfClassChain(obfClassEntry); + + boolean isInnerClass = obfClassChain.size() > 1; + if (isInnerClass) { + + // it's an inner class, rename it to the fully qualified name + c.setName(obfClassEntry.buildClassEntry(obfClassChain).getName()); + + BehaviorEntry caller = this.index.getAnonymousClassCaller(obfClassEntry); + if (caller != null) { + + // write the enclosing method attribute + if (caller.getName().equals("")) { + c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName())); + } else { + c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName(), caller.getName(), caller.getSignature().toString())); + } + } + } + + // does this class have any inner classes? + Collection obfInnerClassEntries = this.index.getInnerClasses(obfClassEntry); + + if (isInnerClass || !obfInnerClassEntries.isEmpty()) { + + // create an inner class attribute + InnerClassesAttribute attr = new InnerClassesAttribute(c.getClassFile().getConstPool()); + c.getClassFile().addAttribute(attr); + + // write the ancestry, but not the outermost class + for (int i = 1; i < obfClassChain.size(); i++) { + ClassEntry obfInnerClassEntry = obfClassChain.get(i); + writeInnerClass(attr, obfClassChain, obfInnerClassEntry); + + // update references to use the fully qualified inner class name + c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(obfClassChain).getName()); + } + + // write the inner classes + for (ClassEntry obfInnerClassEntry : obfInnerClassEntries) { + + // extend the class chain + List extendedObfClassChain = Lists.newArrayList(obfClassChain); + extendedObfClassChain.add(obfInnerClassEntry); + + writeInnerClass(attr, extendedObfClassChain, obfInnerClassEntry); + + // update references to use the fully qualified inner class name + c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(extendedObfClassChain).getName()); + } + } + } + + private void writeInnerClass(InnerClassesAttribute attr, List obfClassChain, ClassEntry obfClassEntry) { + + // get the new inner class name + ClassEntry obfInnerClassEntry = obfClassEntry.buildClassEntry(obfClassChain); + ClassEntry obfOuterClassEntry = obfInnerClassEntry.getOuterClassEntry(); + + // here's what the JVM spec says about the InnerClasses attribute + // append(inner, parent, 0 if anonymous else simple name, flags); + + // update the attribute with this inner class + ConstPool constPool = attr.getConstPool(); + int innerClassIndex = constPool.addClassInfo(obfInnerClassEntry.getName()); + int parentClassIndex = constPool.addClassInfo(obfOuterClassEntry.getName()); + int innerClassNameIndex = 0; + int accessFlags = AccessFlag.PUBLIC; + // TODO: need to figure out if we can put static or not + if (!this.index.isAnonymousClass(obfClassEntry)) { + innerClassNameIndex = constPool.addUtf8Info(obfInnerClassEntry.getInnermostClassName()); + } + + attr.append(innerClassIndex, parentClassIndex, innerClassNameIndex, accessFlags); /* DEBUG - System.out.println(String.format("\tOBF: %s -> ATTR: %s,%s,%s (replace %s with %s)", + System.out.println(String.format("\tOBF: %s -> ATTR: %s,%s,%s (replace %s with %s)", obfClassEntry, attr.innerClass(attr.tableLength() - 1), attr.outerClass(attr.tableLength() - 1), @@ -150,5 +147,5 @@ public class InnerClassWriter { obfClassEntry.getName() )); */ - } + } } diff --git a/src/main/java/cuchaz/enigma/bytecode/LocalVariableRenamer.java b/src/main/java/cuchaz/enigma/bytecode/LocalVariableRenamer.java index 24b5f363..8909d816 100644 --- a/src/main/java/cuchaz/enigma/bytecode/LocalVariableRenamer.java +++ b/src/main/java/cuchaz/enigma/bytecode/LocalVariableRenamer.java @@ -8,6 +8,7 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.bytecode; import cuchaz.enigma.mapping.*; @@ -15,131 +16,129 @@ import javassist.CtBehavior; import javassist.CtClass; import javassist.bytecode.*; - public class LocalVariableRenamer { - private Translator translator; - - public LocalVariableRenamer(Translator translator) { - this.translator = translator; - } - - public void rename(CtClass c) { - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - - // if there's a local variable table, just rename everything to v1, v2, v3, ... for now - CodeAttribute codeAttribute = behavior.getMethodInfo().getCodeAttribute(); - if (codeAttribute == null) { - continue; - } - - BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); - ConstPool constants = c.getClassFile().getConstPool(); - - LocalVariableAttribute table = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag); - if (table != null) { - renameLVT(behaviorEntry, constants, table); - } - - LocalVariableTypeAttribute typeTable = (LocalVariableTypeAttribute) codeAttribute.getAttribute(LocalVariableAttribute.typeTag); - if (typeTable != null) { - renameLVTT(typeTable, table); - } - } - } - - // DEBUG - @SuppressWarnings("unused") - private void dumpTable(LocalVariableAttribute table) { - for (int i = 0; i < table.tableLength(); i++) { - System.out.println(String.format("\t%d (%d): %s %s", - i, table.index(i), table.variableName(i), table.descriptor(i) - )); - } - } - - private void renameLVT(BehaviorEntry behaviorEntry, ConstPool constants, LocalVariableAttribute table) { - - // skip empty tables - if (table.tableLength() <= 0) { - return; - } - - // where do we start counting variables? - int starti = 0; - if (table.variableName(0).equals("this")) { - // skip the "this" variable - starti = 1; - } - - // rename method arguments first - int numArgs = 0; - if (behaviorEntry.getSignature() != null) { - numArgs = behaviorEntry.getSignature().getArgumentTypes().size(); - - boolean isNestedClassConstructor = false; - - // If the behavior is a constructor and if it have more than one arg, it's probably from a nested! - if (behaviorEntry instanceof ConstructorEntry && behaviorEntry.getClassEntry() != null && behaviorEntry.getClassEntry().isInnerClass() && numArgs >= 1) - { - // Get the first arg type - Type firstArg = behaviorEntry.getSignature().getArgumentTypes().get(0); - - // If the arg is a class and if the class name match the outer class name of the constructor, it's definitely a constructor of a nested class - if (firstArg.isClass() && firstArg.getClassEntry().equals(behaviorEntry.getClassEntry().getOuterClassEntry())) { - isNestedClassConstructor = true; - numArgs--; - } - } - - for (int i = starti; i < starti + numArgs && i < table.tableLength(); i++) { - int argi = i - starti; - String argName = this.translator.translate(new ArgumentEntry(behaviorEntry, argi, "")); - if (argName == null) { - Type argType = behaviorEntry.getSignature().getArgumentTypes().get(isNestedClassConstructor ? argi + 1 : argi); - // Unfortunately each of these have different name getters, so they have different code paths - if (argType.isPrimitive()) { - Type.Primitive argCls = argType.getPrimitive(); - argName = "a" + argCls.name() + (argi + 1); - } else if (argType.isArray()) { - // List types would require this whole block again, so just go with aListx - argName = "aList" + (argi + 1); - } else if (argType.isClass()) { - ClassEntry argClsTrans = this.translator.translateEntry(argType.getClassEntry()); - argName = "a" + argClsTrans.getSimpleName().replace("$", "") + (argi + 1); - } else { - argName = "a" + (argi + 1); - } - } - renameVariable(table, i, constants.addUtf8Info(argName)); - } - } - - // then rename the rest of the args, if any - for (int i = starti + numArgs; i < table.tableLength(); i++) { - int firstIndex = table.index(starti + numArgs); - renameVariable(table, i, constants.addUtf8Info("v" + (table.index(i) - firstIndex + 1))); - } - } - - private void renameLVTT(LocalVariableTypeAttribute typeTable, LocalVariableAttribute table) { - // rename args to the same names as in the LVT - for (int i = 0; i < typeTable.tableLength(); i++) { - renameVariable(typeTable, i, getNameIndex(table, typeTable.index(i))); - } - } - - private void renameVariable(LocalVariableAttribute table, int i, int stringId) { - // based off of LocalVariableAttribute.nameIndex() - ByteArray.write16bit(stringId, table.get(), i * 10 + 6); - } - - private int getNameIndex(LocalVariableAttribute table, int index) { - for (int i = 0; i < table.tableLength(); i++) { - if (table.index(i) == index) { - return table.nameIndex(i); - } - } - return 0; - } + private Translator translator; + + public LocalVariableRenamer(Translator translator) { + this.translator = translator; + } + + public void rename(CtClass c) { + for (CtBehavior behavior : c.getDeclaredBehaviors()) { + + // if there's a local variable table, just rename everything to v1, v2, v3, ... for now + CodeAttribute codeAttribute = behavior.getMethodInfo().getCodeAttribute(); + if (codeAttribute == null) { + continue; + } + + BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); + ConstPool constants = c.getClassFile().getConstPool(); + + LocalVariableAttribute table = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag); + if (table != null) { + renameLVT(behaviorEntry, constants, table); + } + + LocalVariableTypeAttribute typeTable = (LocalVariableTypeAttribute) codeAttribute.getAttribute(LocalVariableAttribute.typeTag); + if (typeTable != null) { + renameLVTT(typeTable, table); + } + } + } + + // DEBUG + @SuppressWarnings("unused") + private void dumpTable(LocalVariableAttribute table) { + for (int i = 0; i < table.tableLength(); i++) { + System.out.println(String.format("\t%d (%d): %s %s", + i, table.index(i), table.variableName(i), table.descriptor(i) + )); + } + } + + private void renameLVT(BehaviorEntry behaviorEntry, ConstPool constants, LocalVariableAttribute table) { + + // skip empty tables + if (table.tableLength() <= 0) { + return; + } + + // where do we start counting variables? + int starti = 0; + if (table.variableName(0).equals("this")) { + // skip the "this" variable + starti = 1; + } + + // rename method arguments first + int numArgs = 0; + if (behaviorEntry.getSignature() != null) { + numArgs = behaviorEntry.getSignature().getArgumentTypes().size(); + + boolean isNestedClassConstructor = false; + + // If the behavior is a constructor and if it have more than one arg, it's probably from a nested! + if (behaviorEntry instanceof ConstructorEntry && behaviorEntry.getClassEntry() != null && behaviorEntry.getClassEntry().isInnerClass() && numArgs >= 1) { + // Get the first arg type + Type firstArg = behaviorEntry.getSignature().getArgumentTypes().get(0); + + // If the arg is a class and if the class name match the outer class name of the constructor, it's definitely a constructor of a nested class + if (firstArg.isClass() && firstArg.getClassEntry().equals(behaviorEntry.getClassEntry().getOuterClassEntry())) { + isNestedClassConstructor = true; + numArgs--; + } + } + + for (int i = starti; i < starti + numArgs && i < table.tableLength(); i++) { + int argi = i - starti; + String argName = this.translator.translate(new ArgumentEntry(behaviorEntry, argi, "")); + if (argName == null) { + Type argType = behaviorEntry.getSignature().getArgumentTypes().get(isNestedClassConstructor ? argi + 1 : argi); + // Unfortunately each of these have different name getters, so they have different code paths + if (argType.isPrimitive()) { + Type.Primitive argCls = argType.getPrimitive(); + argName = "a" + argCls.name() + (argi + 1); + } else if (argType.isArray()) { + // List types would require this whole block again, so just go with aListx + argName = "aList" + (argi + 1); + } else if (argType.isClass()) { + ClassEntry argClsTrans = this.translator.translateEntry(argType.getClassEntry()); + argName = "a" + argClsTrans.getSimpleName().replace("$", "") + (argi + 1); + } else { + argName = "a" + (argi + 1); + } + } + renameVariable(table, i, constants.addUtf8Info(argName)); + } + } + + // then rename the rest of the args, if any + for (int i = starti + numArgs; i < table.tableLength(); i++) { + int firstIndex = table.index(starti + numArgs); + renameVariable(table, i, constants.addUtf8Info("v" + (table.index(i) - firstIndex + 1))); + } + } + + private void renameLVTT(LocalVariableTypeAttribute typeTable, LocalVariableAttribute table) { + // rename args to the same names as in the LVT + for (int i = 0; i < typeTable.tableLength(); i++) { + renameVariable(typeTable, i, getNameIndex(table, typeTable.index(i))); + } + } + + private void renameVariable(LocalVariableAttribute table, int i, int stringId) { + // based off of LocalVariableAttribute.nameIndex() + ByteArray.write16bit(stringId, table.get(), i * 10 + 6); + } + + private int getNameIndex(LocalVariableAttribute table, int index) { + for (int i = 0; i < table.tableLength(); i++) { + if (table.index(i) == index) { + return table.nameIndex(i); + } + } + return 0; + } } diff --git a/src/main/java/cuchaz/enigma/bytecode/MethodParameterWriter.java b/src/main/java/cuchaz/enigma/bytecode/MethodParameterWriter.java index 28ad04ad..d63572e9 100644 --- a/src/main/java/cuchaz/enigma/bytecode/MethodParameterWriter.java +++ b/src/main/java/cuchaz/enigma/bytecode/MethodParameterWriter.java @@ -8,10 +8,8 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ -package cuchaz.enigma.bytecode; -import java.util.ArrayList; -import java.util.List; +package cuchaz.enigma.bytecode; import cuchaz.enigma.mapping.*; import javassist.CtBehavior; @@ -19,48 +17,51 @@ import javassist.CtClass; import javassist.bytecode.CodeAttribute; import javassist.bytecode.LocalVariableAttribute; +import java.util.ArrayList; +import java.util.List; + public class MethodParameterWriter { - private Translator translator; + private Translator translator; - public MethodParameterWriter(Translator translator) { - this.translator = translator; - } + public MethodParameterWriter(Translator translator) { + this.translator = translator; + } - public void writeMethodArguments(CtClass c) { + public void writeMethodArguments(CtClass c) { - // Procyon will read method arguments from the "MethodParameters" attribute, so write those - for (CtBehavior behavior : c.getDeclaredBehaviors()) { + // Procyon will read method arguments from the "MethodParameters" attribute, so write those + for (CtBehavior behavior : c.getDeclaredBehaviors()) { - // if there's a local variable table here, don't write a MethodParameters attribute - // let the local variable writer deal with it instead - // procyon starts doing really weird things if we give it both attributes - CodeAttribute codeAttribute = behavior.getMethodInfo().getCodeAttribute(); - if (codeAttribute != null && codeAttribute.getAttribute(LocalVariableAttribute.tag) != null) { - continue; - } + // if there's a local variable table here, don't write a MethodParameters attribute + // let the local variable writer deal with it instead + // procyon starts doing really weird things if we give it both attributes + CodeAttribute codeAttribute = behavior.getMethodInfo().getCodeAttribute(); + if (codeAttribute != null && codeAttribute.getAttribute(LocalVariableAttribute.tag) != null) { + continue; + } - BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); + BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); - // get the number of arguments - Signature signature = behaviorEntry.getSignature(); - if (signature == null) { - // static initializers have no signatures, or arguments - continue; - } - int numParams = signature.getArgumentTypes().size(); - if (numParams <= 0) { - continue; - } + // get the number of arguments + Signature signature = behaviorEntry.getSignature(); + if (signature == null) { + // static initializers have no signatures, or arguments + continue; + } + int numParams = signature.getArgumentTypes().size(); + if (numParams <= 0) { + continue; + } - // get the list of argument names - List names = new ArrayList<>(numParams); - for (int i = 0; i < numParams; i++) { - names.add(this.translator.translate(new ArgumentEntry(behaviorEntry, i, ""))); - } + // get the list of argument names + List names = new ArrayList<>(numParams); + for (int i = 0; i < numParams; i++) { + names.add(this.translator.translate(new ArgumentEntry(behaviorEntry, i, ""))); + } - // save the mappings to the class - MethodParametersAttribute.updateClass(behavior.getMethodInfo(), names); - } - } + // save the mappings to the class + MethodParametersAttribute.updateClass(behavior.getMethodInfo(), names); + } + } } diff --git a/src/main/java/cuchaz/enigma/bytecode/MethodParametersAttribute.java b/src/main/java/cuchaz/enigma/bytecode/MethodParametersAttribute.java index bace3a0d..3f819abd 100644 --- a/src/main/java/cuchaz/enigma/bytecode/MethodParametersAttribute.java +++ b/src/main/java/cuchaz/enigma/bytecode/MethodParametersAttribute.java @@ -8,79 +8,80 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.bytecode; +import javassist.bytecode.AttributeInfo; +import javassist.bytecode.ConstPool; +import javassist.bytecode.MethodInfo; + import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; -import javassist.bytecode.AttributeInfo; -import javassist.bytecode.ConstPool; -import javassist.bytecode.MethodInfo; - public class MethodParametersAttribute extends AttributeInfo { - private MethodParametersAttribute(ConstPool pool, List parameterNameIndices) { - super(pool, "MethodParameters", writeStruct(parameterNameIndices)); - } + private MethodParametersAttribute(ConstPool pool, List parameterNameIndices) { + super(pool, "MethodParameters", writeStruct(parameterNameIndices)); + } - public static void updateClass(MethodInfo info, List names) { + public static void updateClass(MethodInfo info, List names) { - // add the names to the class const pool - ConstPool constPool = info.getConstPool(); - List parameterNameIndices = new ArrayList<>(); - for (String name : names) { - if (name != null) { - parameterNameIndices.add(constPool.addUtf8Info(name)); - } else { - parameterNameIndices.add(0); - } - } + // add the names to the class const pool + ConstPool constPool = info.getConstPool(); + List parameterNameIndices = new ArrayList<>(); + for (String name : names) { + if (name != null) { + parameterNameIndices.add(constPool.addUtf8Info(name)); + } else { + parameterNameIndices.add(0); + } + } - // add the attribute to the method - info.addAttribute(new MethodParametersAttribute(constPool, parameterNameIndices)); - } + // add the attribute to the method + info.addAttribute(new MethodParametersAttribute(constPool, parameterNameIndices)); + } - private static byte[] writeStruct(List parameterNameIndices) { - // JVM 8 Spec says the struct looks like this: - // http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.24 - // uint8 num_params - // for each param: - // uint16 name_index -> points to UTF8 entry in constant pool, or 0 for no entry - // uint16 access_flags -> don't care, just set to 0 + private static byte[] writeStruct(List parameterNameIndices) { + // JVM 8 Spec says the struct looks like this: + // http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.24 + // uint8 num_params + // for each param: + // uint16 name_index -> points to UTF8 entry in constant pool, or 0 for no entry + // uint16 access_flags -> don't care, just set to 0 - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - DataOutputStream out = new DataOutputStream(buf); + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(buf); - // NOTE: java hates unsigned integers, so we have to be careful here - // the writeShort(), writeByte() methods will read 16,8 low-order bits from the int argument - // as long as the int argument is in range of the unsigned short/byte type, it will be written as an unsigned short/byte - // if the int is out of range, the byte stream won't look the way we want and weird things will happen - final int SIZEOF_UINT8 = 1; - final int SIZEOF_UINT16 = 2; - final int MAX_UINT8 = (1 << 8) - 1; - final int MAX_UINT16 = (1 << 16) - 1; + // NOTE: java hates unsigned integers, so we have to be careful here + // the writeShort(), writeByte() methods will read 16,8 low-order bits from the int argument + // as long as the int argument is in range of the unsigned short/byte type, it will be written as an unsigned short/byte + // if the int is out of range, the byte stream won't look the way we want and weird things will happen + final int SIZEOF_UINT8 = 1; + final int SIZEOF_UINT16 = 2; + final int MAX_UINT8 = (1 << 8) - 1; + final int MAX_UINT16 = (1 << 16) - 1; - try { - assert (parameterNameIndices.size() >= 0 && parameterNameIndices.size() <= MAX_UINT8); - out.writeByte(parameterNameIndices.size()); + try { + assert (parameterNameIndices.size() >= 0 && parameterNameIndices.size() <= MAX_UINT8); + out.writeByte(parameterNameIndices.size()); - for (Integer index : parameterNameIndices) { - assert (index >= 0 && index <= MAX_UINT16); - out.writeShort(index); + for (Integer index : parameterNameIndices) { + assert (index >= 0 && index <= MAX_UINT16); + out.writeShort(index); - // just write 0 for the access flags - out.writeShort(0); - } + // just write 0 for the access flags + out.writeShort(0); + } - out.close(); - byte[] data = buf.toByteArray(); - assert (data.length == SIZEOF_UINT8 + parameterNameIndices.size() * (SIZEOF_UINT16 + SIZEOF_UINT16)); - return data; - } catch (IOException ex) { - throw new Error(ex); - } - } + out.close(); + byte[] data = buf.toByteArray(); + assert (data.length == SIZEOF_UINT8 + parameterNameIndices.size() * (SIZEOF_UINT16 + SIZEOF_UINT16)); + return data; + } catch (IOException ex) { + throw new Error(ex); + } + } } diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java index 66f22839..eaa6e901 100644 --- a/src/main/java/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java +++ b/src/main/java/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java @@ -8,48 +8,49 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.bytecode.accessors; import java.lang.reflect.Field; public class ClassInfoAccessor { - private Object item; - - private static Class clazz; - private static Field nameIndex; - - public ClassInfoAccessor(Object item) { - this.item = item; - } - - public int getNameIndex() { - try { - return (Integer) nameIndex.get(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public void setNameIndex(int val) { - try { - nameIndex.set(this.item, val); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public static boolean isType(ConstInfoAccessor accessor) { - return clazz.isAssignableFrom(accessor.getItem().getClass()); - } - - static { - try { - clazz = Class.forName("javassist.bytecode.ClassInfo"); - nameIndex = clazz.getDeclaredField("name"); - nameIndex.setAccessible(true); - } catch (Exception ex) { - throw new Error(ex); - } - } + private static Class clazz; + private static Field nameIndex; + + static { + try { + clazz = Class.forName("javassist.bytecode.ClassInfo"); + nameIndex = clazz.getDeclaredField("name"); + nameIndex.setAccessible(true); + } catch (Exception ex) { + throw new Error(ex); + } + } + + private Object item; + + public ClassInfoAccessor(Object item) { + this.item = item; + } + + public static boolean isType(ConstInfoAccessor accessor) { + return clazz.isAssignableFrom(accessor.getItem().getClass()); + } + + public int getNameIndex() { + try { + return (Integer) nameIndex.get(this.item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setNameIndex(int val) { + try { + nameIndex.set(this.item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } } diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java index aa363d2a..27d991a3 100644 --- a/src/main/java/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java +++ b/src/main/java/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java @@ -8,122 +8,117 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ -package cuchaz.enigma.bytecode.accessors; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.lang.reflect.Field; -import java.lang.reflect.Method; +package cuchaz.enigma.bytecode.accessors; import com.google.common.base.Charsets; import cuchaz.enigma.bytecode.InfoType; +import java.io.*; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + public class ConstInfoAccessor { - private static Class clazz; - private static Field index; - private static Method getTag; - - private Object item; - - public ConstInfoAccessor(Object item) { - if (item == null) { - throw new IllegalArgumentException("item cannot be null!"); - } - this.item = item; - } - - public Object getItem() { - return this.item; - } - - public int getIndex() { - try { - return (Integer) index.get(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public int getTag() { - try { - return (Integer) getTag.invoke(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public ConstInfoAccessor copy() { - return new ConstInfoAccessor(copyItem()); - } - - public Object copyItem() { - // I don't know of a simpler way to copy one of these silly things... - try { - // serialize the item - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - DataOutputStream out = new DataOutputStream(buf); - write(out); - - // deserialize the item - DataInputStream in = new DataInputStream(new ByteArrayInputStream(buf.toByteArray())); - Object item = new ConstInfoAccessor(in).getItem(); - in.close(); - - return item; - } catch (Exception ex) { - throw new Error(ex); - } - } - - public void write(DataOutputStream out) throws IOException { - try { - out.writeUTF(this.item.getClass().getName()); - out.writeInt(getIndex()); - - Method method = this.item.getClass().getMethod("write", DataOutputStream.class); - method.setAccessible(true); - method.invoke(this.item, out); - } catch (IOException ex) { - throw ex; - } catch (Exception ex) { - throw new Error(ex); - } - } - - @Override - public String toString() { - try { - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - PrintWriter out = new PrintWriter(new OutputStreamWriter(buf, Charsets.UTF_8)); - Method print = this.item.getClass().getMethod("print", PrintWriter.class); - print.setAccessible(true); - print.invoke(this.item, out); - out.close(); - return buf.toString("UTF-8").replace("\n", ""); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public InfoType getType() { - return InfoType.getByTag(getTag()); - } - - static { - try { - clazz = Class.forName("javassist.bytecode.ConstInfo"); - index = clazz.getDeclaredField("index"); - index.setAccessible(true); - getTag = clazz.getMethod("getTag"); - getTag.setAccessible(true); - } catch (Exception ex) { - throw new Error(ex); - } - } + private static Class clazz; + private static Field index; + private static Method getTag; + + static { + try { + clazz = Class.forName("javassist.bytecode.ConstInfo"); + index = clazz.getDeclaredField("index"); + index.setAccessible(true); + getTag = clazz.getMethod("getTag"); + getTag.setAccessible(true); + } catch (Exception ex) { + throw new Error(ex); + } + } + + private Object item; + + public ConstInfoAccessor(Object item) { + if (item == null) { + throw new IllegalArgumentException("item cannot be null!"); + } + this.item = item; + } + + public Object getItem() { + return this.item; + } + + public int getIndex() { + try { + return (Integer) index.get(this.item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public int getTag() { + try { + return (Integer) getTag.invoke(this.item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public ConstInfoAccessor copy() { + return new ConstInfoAccessor(copyItem()); + } + + public Object copyItem() { + // I don't know of a simpler way to copy one of these silly things... + try { + // serialize the item + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(buf); + write(out); + + // deserialize the item + DataInputStream in = new DataInputStream(new ByteArrayInputStream(buf.toByteArray())); + Object item = new ConstInfoAccessor(in).getItem(); + in.close(); + + return item; + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void write(DataOutputStream out) throws IOException { + try { + out.writeUTF(this.item.getClass().getName()); + out.writeInt(getIndex()); + + Method method = this.item.getClass().getMethod("write", DataOutputStream.class); + method.setAccessible(true); + method.invoke(this.item, out); + } catch (IOException ex) { + throw ex; + } catch (Exception ex) { + throw new Error(ex); + } + } + + @Override + public String toString() { + try { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + PrintWriter out = new PrintWriter(new OutputStreamWriter(buf, Charsets.UTF_8)); + Method print = this.item.getClass().getMethod("print", PrintWriter.class); + print.setAccessible(true); + print.invoke(this.item, out); + out.close(); + return buf.toString("UTF-8").replace("\n", ""); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public InfoType getType() { + return InfoType.getByTag(getTag()); + } } diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java index 69aee160..aef35321 100644 --- a/src/main/java/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java +++ b/src/main/java/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java @@ -8,68 +8,68 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.bytecode.accessors; import java.lang.reflect.Field; public class InvokeDynamicInfoAccessor { - private static Class clazz; - private static Field bootstrapIndex; - private static Field nameAndTypeIndex; - + private static Class clazz; + private static Field bootstrapIndex; + private static Field nameAndTypeIndex; - private Object item; + static { + try { + clazz = Class.forName("javassist.bytecode.InvokeDynamicInfo"); + bootstrapIndex = clazz.getDeclaredField("bootstrap"); + bootstrapIndex.setAccessible(true); + nameAndTypeIndex = clazz.getDeclaredField("nameAndType"); + nameAndTypeIndex.setAccessible(true); + } catch (Exception ex) { + throw new Error(ex); + } + } - public InvokeDynamicInfoAccessor(Object item) { - this.item = item; - } + private Object item; - public int getBootstrapIndex() { - try { - return (Integer) bootstrapIndex.get(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } + public InvokeDynamicInfoAccessor(Object item) { + this.item = item; + } - public void setBootstrapIndex(int val) { - try { - bootstrapIndex.set(this.item, val); - } catch (Exception ex) { - throw new Error(ex); - } - } + public static boolean isType(ConstInfoAccessor accessor) { + return clazz.isAssignableFrom(accessor.getItem().getClass()); + } - public int getNameAndTypeIndex() { - try { - return (Integer) nameAndTypeIndex.get(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } + public int getBootstrapIndex() { + try { + return (Integer) bootstrapIndex.get(this.item); + } catch (Exception ex) { + throw new Error(ex); + } + } - public void setNameAndTypeIndex(int val) { - try { - nameAndTypeIndex.set(this.item, val); - } catch (Exception ex) { - throw new Error(ex); - } - } + public void setBootstrapIndex(int val) { + try { + bootstrapIndex.set(this.item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } - public static boolean isType(ConstInfoAccessor accessor) { - return clazz.isAssignableFrom(accessor.getItem().getClass()); - } + public int getNameAndTypeIndex() { + try { + return (Integer) nameAndTypeIndex.get(this.item); + } catch (Exception ex) { + throw new Error(ex); + } + } - static { - try { - clazz = Class.forName("javassist.bytecode.InvokeDynamicInfo"); - bootstrapIndex = clazz.getDeclaredField("bootstrap"); - bootstrapIndex.setAccessible(true); - nameAndTypeIndex = clazz.getDeclaredField("nameAndType"); - nameAndTypeIndex.setAccessible(true); - } catch (Exception ex) { - throw new Error(ex); - } - } + public void setNameAndTypeIndex(int val) { + try { + nameAndTypeIndex.set(this.item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } } diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java index 0e0297be..058bb454 100644 --- a/src/main/java/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java +++ b/src/main/java/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java @@ -8,67 +8,68 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.bytecode.accessors; import java.lang.reflect.Field; public class MemberRefInfoAccessor { - private static Class clazz; - private static Field classIndex; - private static Field nameAndTypeIndex; + private static Class clazz; + private static Field classIndex; + private static Field nameAndTypeIndex; - private Object item; + static { + try { + clazz = Class.forName("javassist.bytecode.MemberrefInfo"); + classIndex = clazz.getDeclaredField("classIndex"); + classIndex.setAccessible(true); + nameAndTypeIndex = clazz.getDeclaredField("nameAndTypeIndex"); + nameAndTypeIndex.setAccessible(true); + } catch (Exception ex) { + throw new Error(ex); + } + } - public MemberRefInfoAccessor(Object item) { - this.item = item; - } + private Object item; - public int getClassIndex() { - try { - return (Integer) classIndex.get(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } + public MemberRefInfoAccessor(Object item) { + this.item = item; + } - public void setClassIndex(int val) { - try { - classIndex.set(this.item, val); - } catch (Exception ex) { - throw new Error(ex); - } - } + public static boolean isType(ConstInfoAccessor accessor) { + return clazz.isAssignableFrom(accessor.getItem().getClass()); + } - public int getNameAndTypeIndex() { - try { - return (Integer) nameAndTypeIndex.get(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } + public int getClassIndex() { + try { + return (Integer) classIndex.get(this.item); + } catch (Exception ex) { + throw new Error(ex); + } + } - public void setNameAndTypeIndex(int val) { - try { - nameAndTypeIndex.set(this.item, val); - } catch (Exception ex) { - throw new Error(ex); - } - } + public void setClassIndex(int val) { + try { + classIndex.set(this.item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } - public static boolean isType(ConstInfoAccessor accessor) { - return clazz.isAssignableFrom(accessor.getItem().getClass()); - } + public int getNameAndTypeIndex() { + try { + return (Integer) nameAndTypeIndex.get(this.item); + } catch (Exception ex) { + throw new Error(ex); + } + } - static { - try { - clazz = Class.forName("javassist.bytecode.MemberrefInfo"); - classIndex = clazz.getDeclaredField("classIndex"); - classIndex.setAccessible(true); - nameAndTypeIndex = clazz.getDeclaredField("nameAndTypeIndex"); - nameAndTypeIndex.setAccessible(true); - } catch (Exception ex) { - throw new Error(ex); - } - } + public void setNameAndTypeIndex(int val) { + try { + nameAndTypeIndex.set(this.item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } } diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java index 9a7dd698..985e792e 100644 --- a/src/main/java/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java +++ b/src/main/java/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java @@ -8,67 +8,68 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.bytecode.accessors; import java.lang.reflect.Field; public class MethodHandleInfoAccessor { - private static Class clazz; - private static Field kindIndex; - private static Field indexIndex; + private static Class clazz; + private static Field kindIndex; + private static Field indexIndex; - private Object item; + static { + try { + clazz = Class.forName("javassist.bytecode.MethodHandleInfo"); + kindIndex = clazz.getDeclaredField("refKind"); + kindIndex.setAccessible(true); + indexIndex = clazz.getDeclaredField("refIndex"); + indexIndex.setAccessible(true); + } catch (Exception ex) { + throw new Error(ex); + } + } - public MethodHandleInfoAccessor(Object item) { - this.item = item; - } + private Object item; - public int getTypeIndex() { - try { - return (Integer) kindIndex.get(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } + public MethodHandleInfoAccessor(Object item) { + this.item = item; + } - public void setTypeIndex(int val) { - try { - kindIndex.set(this.item, val); - } catch (Exception ex) { - throw new Error(ex); - } - } + public static boolean isType(ConstInfoAccessor accessor) { + return clazz.isAssignableFrom(accessor.getItem().getClass()); + } - public int getMethodRefIndex() { - try { - return (Integer) indexIndex.get(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } + public int getTypeIndex() { + try { + return (Integer) kindIndex.get(this.item); + } catch (Exception ex) { + throw new Error(ex); + } + } - public void setMethodRefIndex(int val) { - try { - indexIndex.set(this.item, val); - } catch (Exception ex) { - throw new Error(ex); - } - } + public void setTypeIndex(int val) { + try { + kindIndex.set(this.item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } - public static boolean isType(ConstInfoAccessor accessor) { - return clazz.isAssignableFrom(accessor.getItem().getClass()); - } + public int getMethodRefIndex() { + try { + return (Integer) indexIndex.get(this.item); + } catch (Exception ex) { + throw new Error(ex); + } + } - static { - try { - clazz = Class.forName("javassist.bytecode.MethodHandleInfo"); - kindIndex = clazz.getDeclaredField("refKind"); - kindIndex.setAccessible(true); - indexIndex = clazz.getDeclaredField("refIndex"); - indexIndex.setAccessible(true); - } catch (Exception ex) { - throw new Error(ex); - } - } + public void setMethodRefIndex(int val) { + try { + indexIndex.set(this.item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } } diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java index 5ec9c3b4..10b0cb0c 100644 --- a/src/main/java/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java +++ b/src/main/java/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java @@ -8,49 +8,50 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.bytecode.accessors; import java.lang.reflect.Field; public class MethodTypeInfoAccessor { - private static Class clazz; - private static Field descriptorIndex; - - private Object item; - - public MethodTypeInfoAccessor(Object item) { - this.item = item; - } - - public int getTypeIndex() { - try { - return (Integer) descriptorIndex.get(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public void setTypeIndex(int val) { - try { - descriptorIndex.set(this.item, val); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public static boolean isType(ConstInfoAccessor accessor) { - return clazz.isAssignableFrom(accessor.getItem().getClass()); - } - - static { - try { - clazz = Class.forName("javassist.bytecode.MethodTypeInfo"); - descriptorIndex = clazz.getDeclaredField("descriptor"); - descriptorIndex.setAccessible(true); - } catch (Exception ex) { - throw new Error(ex); - } - } + private static Class clazz; + private static Field descriptorIndex; + + static { + try { + clazz = Class.forName("javassist.bytecode.MethodTypeInfo"); + descriptorIndex = clazz.getDeclaredField("descriptor"); + descriptorIndex.setAccessible(true); + } catch (Exception ex) { + throw new Error(ex); + } + } + + private Object item; + + public MethodTypeInfoAccessor(Object item) { + this.item = item; + } + + public static boolean isType(ConstInfoAccessor accessor) { + return clazz.isAssignableFrom(accessor.getItem().getClass()); + } + + public int getTypeIndex() { + try { + return (Integer) descriptorIndex.get(this.item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setTypeIndex(int val) { + try { + descriptorIndex.set(this.item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } } diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java index 95df37c1..cc7fdbe8 100644 --- a/src/main/java/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java +++ b/src/main/java/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java @@ -8,67 +8,68 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.bytecode.accessors; import java.lang.reflect.Field; public class NameAndTypeInfoAccessor { - private static Class clazz; - private static Field nameIndex; - private static Field typeIndex; + private static Class clazz; + private static Field nameIndex; + private static Field typeIndex; - private Object item; + static { + try { + clazz = Class.forName("javassist.bytecode.NameAndTypeInfo"); + nameIndex = clazz.getDeclaredField("memberName"); + nameIndex.setAccessible(true); + typeIndex = clazz.getDeclaredField("typeDescriptor"); + typeIndex.setAccessible(true); + } catch (Exception ex) { + throw new Error(ex); + } + } - public NameAndTypeInfoAccessor(Object item) { - this.item = item; - } + private Object item; - public int getNameIndex() { - try { - return (Integer) nameIndex.get(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } + public NameAndTypeInfoAccessor(Object item) { + this.item = item; + } - public void setNameIndex(int val) { - try { - nameIndex.set(this.item, val); - } catch (Exception ex) { - throw new Error(ex); - } - } + public static boolean isType(ConstInfoAccessor accessor) { + return clazz.isAssignableFrom(accessor.getItem().getClass()); + } - public int getTypeIndex() { - try { - return (Integer) typeIndex.get(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } + public int getNameIndex() { + try { + return (Integer) nameIndex.get(this.item); + } catch (Exception ex) { + throw new Error(ex); + } + } - public void setTypeIndex(int val) { - try { - typeIndex.set(this.item, val); - } catch (Exception ex) { - throw new Error(ex); - } - } + public void setNameIndex(int val) { + try { + nameIndex.set(this.item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } - public static boolean isType(ConstInfoAccessor accessor) { - return clazz.isAssignableFrom(accessor.getItem().getClass()); - } + public int getTypeIndex() { + try { + return (Integer) typeIndex.get(this.item); + } catch (Exception ex) { + throw new Error(ex); + } + } - static { - try { - clazz = Class.forName("javassist.bytecode.NameAndTypeInfo"); - nameIndex = clazz.getDeclaredField("memberName"); - nameIndex.setAccessible(true); - typeIndex = clazz.getDeclaredField("typeDescriptor"); - typeIndex.setAccessible(true); - } catch (Exception ex) { - throw new Error(ex); - } - } + public void setTypeIndex(int val) { + try { + typeIndex.set(this.item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } } diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java index 1c55a443..5c68d4af 100644 --- a/src/main/java/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java +++ b/src/main/java/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java @@ -8,48 +8,49 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.bytecode.accessors; import java.lang.reflect.Field; public class StringInfoAccessor { - private static Class clazz; - private static Field stringIndex; - - private Object item; - - public StringInfoAccessor(Object item) { - this.item = item; - } - - public int getStringIndex() { - try { - return (Integer) stringIndex.get(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public void setStringIndex(int val) { - try { - stringIndex.set(this.item, val); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public static boolean isType(ConstInfoAccessor accessor) { - return clazz.isAssignableFrom(accessor.getItem().getClass()); - } - - static { - try { - clazz = Class.forName("javassist.bytecode.StringInfo"); - stringIndex = clazz.getDeclaredField("string"); - stringIndex.setAccessible(true); - } catch (Exception ex) { - throw new Error(ex); - } - } + private static Class clazz; + private static Field stringIndex; + + static { + try { + clazz = Class.forName("javassist.bytecode.StringInfo"); + stringIndex = clazz.getDeclaredField("string"); + stringIndex.setAccessible(true); + } catch (Exception ex) { + throw new Error(ex); + } + } + + private Object item; + + public StringInfoAccessor(Object item) { + this.item = item; + } + + public static boolean isType(ConstInfoAccessor accessor) { + return clazz.isAssignableFrom(accessor.getItem().getClass()); + } + + public int getStringIndex() { + try { + return (Integer) stringIndex.get(this.item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setStringIndex(int val) { + try { + stringIndex.set(this.item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } } diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java index 7a2cb667..cc3b41bc 100644 --- a/src/main/java/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java +++ b/src/main/java/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java @@ -8,21 +8,22 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.bytecode.accessors; public class Utf8InfoAccessor { - private static Class clazz; + private static Class clazz; - static { - try { - clazz = Class.forName("javassist.bytecode.Utf8Info"); - } catch (Exception ex) { - throw new Error(ex); - } - } + static { + try { + clazz = Class.forName("javassist.bytecode.Utf8Info"); + } catch (Exception ex) { + throw new Error(ex); + } + } - public static boolean isType(ConstInfoAccessor accessor) { - return clazz.isAssignableFrom(accessor.getItem().getClass()); - } + public static boolean isType(ConstInfoAccessor accessor) { + return clazz.isAssignableFrom(accessor.getItem().getClass()); + } } diff --git a/src/main/java/cuchaz/enigma/convert/ClassForest.java b/src/main/java/cuchaz/enigma/convert/ClassForest.java index b08d48fb..4542fb33 100644 --- a/src/main/java/cuchaz/enigma/convert/ClassForest.java +++ b/src/main/java/cuchaz/enigma/convert/ClassForest.java @@ -8,53 +8,52 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.convert; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; - -import java.util.Collection; - import cuchaz.enigma.mapping.ClassEntry; +import java.util.Collection; public class ClassForest { - private ClassIdentifier identifier; - private Multimap forest; - - public ClassForest(ClassIdentifier identifier) { - this.identifier = identifier; - this.forest = HashMultimap.create(); - } - - public void addAll(Iterable entries) { - for (ClassEntry entry : entries) { - add(entry); - } - } - - public void add(ClassEntry entry) { - try { - this.forest.put(this.identifier.identify(entry), entry); - } catch (ClassNotFoundException ex) { - throw new Error("Unable to find class " + entry.getName()); - } - } - - public Collection identities() { - return this.forest.keySet(); - } - - public Collection classes() { - return this.forest.values(); - } - - public Collection getClasses(ClassIdentity identity) { - return this.forest.get(identity); - } - - public boolean containsIdentity(ClassIdentity identity) { - return this.forest.containsKey(identity); - } + private ClassIdentifier identifier; + private Multimap forest; + + public ClassForest(ClassIdentifier identifier) { + this.identifier = identifier; + this.forest = HashMultimap.create(); + } + + public void addAll(Iterable entries) { + for (ClassEntry entry : entries) { + add(entry); + } + } + + public void add(ClassEntry entry) { + try { + this.forest.put(this.identifier.identify(entry), entry); + } catch (ClassNotFoundException ex) { + throw new Error("Unable to find class " + entry.getName()); + } + } + + public Collection identities() { + return this.forest.keySet(); + } + + public Collection classes() { + return this.forest.values(); + } + + public Collection getClasses(ClassIdentity identity) { + return this.forest.get(identity); + } + + public boolean containsIdentity(ClassIdentity identity) { + return this.forest.containsKey(identity); + } } diff --git a/src/main/java/cuchaz/enigma/convert/ClassIdentifier.java b/src/main/java/cuchaz/enigma/convert/ClassIdentifier.java index 557e6083..0a72073c 100644 --- a/src/main/java/cuchaz/enigma/convert/ClassIdentifier.java +++ b/src/main/java/cuchaz/enigma/convert/ClassIdentifier.java @@ -8,13 +8,10 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.convert; import com.google.common.collect.Maps; - -import java.util.Map; -import java.util.jar.JarFile; - import cuchaz.enigma.TranslatingTypeLoader; import cuchaz.enigma.analysis.JarIndex; import cuchaz.enigma.convert.ClassNamer.SidedClassNamer; @@ -22,34 +19,36 @@ import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.Translator; import javassist.CtClass; +import java.util.Map; +import java.util.jar.JarFile; public class ClassIdentifier { - private JarIndex index; - private SidedClassNamer namer; - private boolean useReferences; - private TranslatingTypeLoader loader; - private Map cache; - - public ClassIdentifier(JarFile jar, JarIndex index, SidedClassNamer namer, boolean useReferences) { - this.index = index; - this.namer = namer; - this.useReferences = useReferences; - this.loader = new TranslatingTypeLoader(jar, index, new Translator(), new Translator()); - this.cache = Maps.newHashMap(); - } - - public ClassIdentity identify(ClassEntry classEntry) - throws ClassNotFoundException { - ClassIdentity identity = this.cache.get(classEntry); - if (identity == null) { - CtClass c = this.loader.loadClass(classEntry.getName()); - if (c == null) { - throw new ClassNotFoundException(classEntry.getName()); - } - identity = new ClassIdentity(c, this.namer, this.index, this.useReferences); - this.cache.put(classEntry, identity); - } - return identity; - } + private JarIndex index; + private SidedClassNamer namer; + private boolean useReferences; + private TranslatingTypeLoader loader; + private Map cache; + + public ClassIdentifier(JarFile jar, JarIndex index, SidedClassNamer namer, boolean useReferences) { + this.index = index; + this.namer = namer; + this.useReferences = useReferences; + this.loader = new TranslatingTypeLoader(jar, index, new Translator(), new Translator()); + this.cache = Maps.newHashMap(); + } + + public ClassIdentity identify(ClassEntry classEntry) + throws ClassNotFoundException { + ClassIdentity identity = this.cache.get(classEntry); + if (identity == null) { + CtClass c = this.loader.loadClass(classEntry.getName()); + if (c == null) { + throw new ClassNotFoundException(classEntry.getName()); + } + identity = new ClassIdentity(c, this.namer, this.index, this.useReferences); + this.cache.put(classEntry, identity); + } + return identity; + } } diff --git a/src/main/java/cuchaz/enigma/convert/ClassIdentity.java b/src/main/java/cuchaz/enigma/convert/ClassIdentity.java index f72bf703..a395b755 100644 --- a/src/main/java/cuchaz/enigma/convert/ClassIdentity.java +++ b/src/main/java/cuchaz/enigma/convert/ClassIdentity.java @@ -8,18 +8,10 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.convert; import com.google.common.collect.*; - -import java.io.UnsupportedEncodingException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Enumeration; -import java.util.List; -import java.util.Map; -import java.util.Set; - import cuchaz.enigma.analysis.ClassImplementationsTreeNode; import cuchaz.enigma.analysis.EntryReference; import cuchaz.enigma.analysis.JarIndex; @@ -33,408 +25,415 @@ import javassist.*; import javassist.bytecode.*; import javassist.expr.*; +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Enumeration; +import java.util.List; +import java.util.Map; +import java.util.Set; + public class ClassIdentity { - private ClassEntry classEntry; - private SidedClassNamer namer; - private Multiset fields; - private Multiset methods; - private Multiset constructors; - private String staticInitializer; - private String extendz; - private Multiset implementz; - private Set stringLiterals; - private Multiset implementations; - private Multiset references; - private String outer; - - private final ClassNameReplacer classNameReplacer = new ClassNameReplacer() { - - private Map classNames = Maps.newHashMap(); - - @Override - public String replace(String className) { - - // classes not in the none package can be passed through - ClassEntry classEntry = new ClassEntry(className); - if (classEntry.getPackageName() != null) { - return className; - } - - // is this class ourself? - if (className.equals(classEntry.getName())) { - return "CSelf"; - } - - // try the namer - if (namer != null) { - String newName = namer.getName(className); - if (newName != null) { - return newName; - } - } - - // otherwise, use local naming - if (!classNames.containsKey(className)) { - classNames.put(className, getNewClassName()); - } - return classNames.get(className); - } - - private String getNewClassName() { - return String.format("C%03d", classNames.size()); - } - }; - - public ClassIdentity(CtClass c, SidedClassNamer namer, JarIndex index, boolean useReferences) { - this.namer = namer; - - // stuff from the bytecode - - this.classEntry = EntryFactory.getClassEntry(c); - this.fields = HashMultiset.create(); - for (CtField field : c.getDeclaredFields()) { - this.fields.add(scrubType(field.getSignature())); - } - this.methods = HashMultiset.create(); - for (CtMethod method : c.getDeclaredMethods()) { - this.methods.add(scrubSignature(method.getSignature()) + "0x" + getBehaviorSignature(method)); - } - this.constructors = HashMultiset.create(); - for (CtConstructor constructor : c.getDeclaredConstructors()) { - this.constructors.add(scrubSignature(constructor.getSignature()) + "0x" + getBehaviorSignature(constructor)); - } - this.staticInitializer = ""; - if (c.getClassInitializer() != null) { - this.staticInitializer = getBehaviorSignature(c.getClassInitializer()); - } - this.extendz = ""; - if (c.getClassFile().getSuperclass() != null) { - this.extendz = scrubClassName(Descriptor.toJvmName(c.getClassFile().getSuperclass())); - } - this.implementz = HashMultiset.create(); - for (String interfaceName : c.getClassFile().getInterfaces()) { - this.implementz.add(scrubClassName(Descriptor.toJvmName(interfaceName))); - } - - this.stringLiterals = Sets.newHashSet(); - ConstPool constants = c.getClassFile().getConstPool(); - for (int i = 1; i < constants.getSize(); i++) { - if (constants.getTag(i) == ConstPool.CONST_String) { - this.stringLiterals.add(constants.getStringInfo(i)); - } - } - - // stuff from the jar index - - this.implementations = HashMultiset.create(); - ClassImplementationsTreeNode implementationsNode = index.getClassImplementations(null, this.classEntry); - if (implementationsNode != null) { - @SuppressWarnings("unchecked") - Enumeration implementations = implementationsNode.children(); - while (implementations.hasMoreElements()) { - ClassImplementationsTreeNode node = implementations.nextElement(); - this.implementations.add(scrubClassName(node.getClassEntry().getName())); - } - } - - this.references = HashMultiset.create(); - if (useReferences) { - for (CtField field : c.getDeclaredFields()) { - FieldEntry fieldEntry = EntryFactory.getFieldEntry(field); - index.getFieldReferences(fieldEntry).forEach(this::addReference); - } - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); - index.getBehaviorReferences(behaviorEntry).forEach(this::addReference); - } - } - - this.outer = null; - if (this.classEntry.isInnerClass()) { - this.outer = this.classEntry.getOuterClassName(); - } - } - - private void addReference(EntryReference reference) { - if (reference.context.getSignature() != null) { - this.references.add(String.format("%s_%s", - scrubClassName(reference.context.getClassName()), - scrubSignature(reference.context.getSignature()) - )); - } else { - this.references.add(String.format("%s_", - scrubClassName(reference.context.getClassName()) - )); - } - } - - public ClassEntry getClassEntry() { - return this.classEntry; - } - - @Override - public String toString() { - StringBuilder buf = new StringBuilder(); - buf.append("class: "); - buf.append(this.classEntry.getName()); - buf.append(" "); - buf.append(hashCode()); - buf.append("\n"); - for (String field : this.fields) { - buf.append("\tfield "); - buf.append(field); - buf.append("\n"); - } - for (String method : this.methods) { - buf.append("\tmethod "); - buf.append(method); - buf.append("\n"); - } - for (String constructor : this.constructors) { - buf.append("\tconstructor "); - buf.append(constructor); - buf.append("\n"); - } - if (this.staticInitializer.length() > 0) { - buf.append("\tinitializer "); - buf.append(this.staticInitializer); - buf.append("\n"); - } - if (this.extendz.length() > 0) { - buf.append("\textends "); - buf.append(this.extendz); - buf.append("\n"); - } - for (String interfaceName : this.implementz) { - buf.append("\timplements "); - buf.append(interfaceName); - buf.append("\n"); - } - for (String implementation : this.implementations) { - buf.append("\timplemented by "); - buf.append(implementation); - buf.append("\n"); - } - for (String reference : this.references) { - buf.append("\treference "); - buf.append(reference); - buf.append("\n"); - } - buf.append("\touter "); - buf.append(this.outer); - buf.append("\n"); - return buf.toString(); - } - - private String scrubClassName(String className) { - return classNameReplacer.replace(className); - } - - private String scrubType(String typeName) { - return scrubType(new Type(typeName)).toString(); - } - - private Type scrubType(Type type) { - if (type.hasClass()) { - return new Type(type, classNameReplacer); - } else { - return type; - } - } - - private String scrubSignature(String signature) { - return scrubSignature(new Signature(signature)).toString(); - } - - private Signature scrubSignature(Signature signature) { - return new Signature(signature, classNameReplacer); - } - - private boolean isClassMatchedUniquely(String className) { - return this.namer != null && this.namer.getName(Descriptor.toJvmName(className)) != null; - } - - private String getBehaviorSignature(CtBehavior behavior) { - try { - // does this method have an implementation? - if (behavior.getMethodInfo().getCodeAttribute() == null) { - return "(none)"; - } - - // compute the hash from the opcodes - ConstPool constants = behavior.getMethodInfo().getConstPool(); - final MessageDigest digest = MessageDigest.getInstance("MD5"); - CodeIterator iter = behavior.getMethodInfo().getCodeAttribute().iterator(); - while (iter.hasNext()) { - int pos = iter.next(); - - // update the hash with the opcode - int opcode = iter.byteAt(pos); - digest.update((byte) opcode); - int constIndex; - switch (opcode) { - case Opcode.LDC: - constIndex = iter.byteAt(pos + 1); - updateHashWithConstant(digest, constants, constIndex); - break; - - case Opcode.LDC_W: - case Opcode.LDC2_W: - constIndex = (iter.byteAt(pos + 1) << 8) | iter.byteAt(pos + 2); - updateHashWithConstant(digest, constants, constIndex); - break; - default: - break; - } - } - - // update hash with method and field accesses - behavior.instrument(new ExprEditor() { - @Override - public void edit(MethodCall call) { - updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName()))); - updateHashWithString(digest, scrubSignature(call.getSignature())); - if (isClassMatchedUniquely(call.getClassName())) { - updateHashWithString(digest, call.getMethodName()); - } - } - - @Override - public void edit(FieldAccess access) { - updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(access.getClassName()))); - updateHashWithString(digest, scrubType(access.getSignature())); - if (isClassMatchedUniquely(access.getClassName())) { - updateHashWithString(digest, access.getFieldName()); - } - } - - @Override - public void edit(ConstructorCall call) { - updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName()))); - updateHashWithString(digest, scrubSignature(call.getSignature())); - } - - @Override - public void edit(NewExpr expr) { - updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(expr.getClassName()))); - } - }); - - // convert the hash to a hex string - return toHex(digest.digest()); - } catch (BadBytecode | NoSuchAlgorithmException | CannotCompileException ex) { - throw new Error(ex); - } - } - - private void updateHashWithConstant(MessageDigest digest, ConstPool constants, int index) { - ConstPoolEditor editor = new ConstPoolEditor(constants); - ConstInfoAccessor item = editor.getItem(index); - if (item.getType() == InfoType.StringInfo) { - updateHashWithString(digest, constants.getStringInfo(index)); - } - // TODO: other constants - } - - private void updateHashWithString(MessageDigest digest, String val) { - try { - digest.update(val.getBytes("UTF8")); - } catch (UnsupportedEncodingException ex) { - throw new Error(ex); - } - } - - private String toHex(byte[] bytes) { - // function taken from: - // http://stackoverflow.com/questions/9655181/convert-from-byte-array-to-hex-string-in-java - final char[] hexArray = "0123456789ABCDEF".toCharArray(); - char[] hexChars = new char[bytes.length * 2]; - for (int j = 0; j < bytes.length; j++) { - int v = bytes[j] & 0xFF; - hexChars[j * 2] = hexArray[v >>> 4]; - hexChars[j * 2 + 1] = hexArray[v & 0x0F]; - } - return new String(hexChars); - } - - @Override - public boolean equals(Object other) { - return other instanceof ClassIdentity && equals((ClassIdentity) other); - } - - public boolean equals(ClassIdentity other) { - return this.fields.equals(other.fields) - && this.methods.equals(other.methods) - && this.constructors.equals(other.constructors) - && this.staticInitializer.equals(other.staticInitializer) - && this.extendz.equals(other.extendz) - && this.implementz.equals(other.implementz) - && this.implementations.equals(other.implementations) - && this.references.equals(other.references); - } - - @Override - public int hashCode() { - List objs = Lists.newArrayList(); - objs.addAll(this.fields); - objs.addAll(this.methods); - objs.addAll(this.constructors); - objs.add(this.staticInitializer); - objs.add(this.extendz); - objs.addAll(this.implementz); - objs.addAll(this.implementations); - objs.addAll(this.references); - return Utils.combineHashesOrdered(objs); - } - - public int getMatchScore(ClassIdentity other) { - return 2 * getNumMatches(this.extendz, other.extendz) - + 2 * getNumMatches(this.outer, other.outer) - + 2 * getNumMatches(this.implementz, other.implementz) - + getNumMatches(this.stringLiterals, other.stringLiterals) - + getNumMatches(this.fields, other.fields) - + getNumMatches(this.methods, other.methods) - + getNumMatches(this.constructors, other.constructors); - } - - public int getMaxMatchScore() { - return 2 + 2 + 2 * this.implementz.size() + this.stringLiterals.size() + this.fields.size() + this.methods.size() + this.constructors.size(); - } - - public boolean matches(CtClass c) { - // just compare declaration counts - return this.fields.size() == c.getDeclaredFields().length - && this.methods.size() == c.getDeclaredMethods().length - && this.constructors.size() == c.getDeclaredConstructors().length; - } - - private int getNumMatches(Set a, Set b) { - int numMatches = 0; - for (String val : a) { - if (b.contains(val)) { - numMatches++; - } - } - return numMatches; - } - - private int getNumMatches(Multiset a, Multiset b) { - int numMatches = 0; - for (String val : a) { - if (b.contains(val)) { - numMatches++; - } - } - return numMatches; - } - - private int getNumMatches(String a, String b) { - if (a == null && b == null) { - return 1; - } else if (a != null && b != null && a.equals(b)) { - return 1; - } - return 0; - } + private ClassEntry classEntry; + private SidedClassNamer namer; + private final ClassNameReplacer classNameReplacer = new ClassNameReplacer() { + + private Map classNames = Maps.newHashMap(); + + @Override + public String replace(String className) { + + // classes not in the none package can be passed through + ClassEntry classEntry = new ClassEntry(className); + if (classEntry.getPackageName() != null) { + return className; + } + + // is this class ourself? + if (className.equals(classEntry.getName())) { + return "CSelf"; + } + + // try the namer + if (namer != null) { + String newName = namer.getName(className); + if (newName != null) { + return newName; + } + } + + // otherwise, use local naming + if (!classNames.containsKey(className)) { + classNames.put(className, getNewClassName()); + } + return classNames.get(className); + } + + private String getNewClassName() { + return String.format("C%03d", classNames.size()); + } + }; + private Multiset fields; + private Multiset methods; + private Multiset constructors; + private String staticInitializer; + private String extendz; + private Multiset implementz; + private Set stringLiterals; + private Multiset implementations; + private Multiset references; + private String outer; + + public ClassIdentity(CtClass c, SidedClassNamer namer, JarIndex index, boolean useReferences) { + this.namer = namer; + + // stuff from the bytecode + + this.classEntry = EntryFactory.getClassEntry(c); + this.fields = HashMultiset.create(); + for (CtField field : c.getDeclaredFields()) { + this.fields.add(scrubType(field.getSignature())); + } + this.methods = HashMultiset.create(); + for (CtMethod method : c.getDeclaredMethods()) { + this.methods.add(scrubSignature(method.getSignature()) + "0x" + getBehaviorSignature(method)); + } + this.constructors = HashMultiset.create(); + for (CtConstructor constructor : c.getDeclaredConstructors()) { + this.constructors.add(scrubSignature(constructor.getSignature()) + "0x" + getBehaviorSignature(constructor)); + } + this.staticInitializer = ""; + if (c.getClassInitializer() != null) { + this.staticInitializer = getBehaviorSignature(c.getClassInitializer()); + } + this.extendz = ""; + if (c.getClassFile().getSuperclass() != null) { + this.extendz = scrubClassName(Descriptor.toJvmName(c.getClassFile().getSuperclass())); + } + this.implementz = HashMultiset.create(); + for (String interfaceName : c.getClassFile().getInterfaces()) { + this.implementz.add(scrubClassName(Descriptor.toJvmName(interfaceName))); + } + + this.stringLiterals = Sets.newHashSet(); + ConstPool constants = c.getClassFile().getConstPool(); + for (int i = 1; i < constants.getSize(); i++) { + if (constants.getTag(i) == ConstPool.CONST_String) { + this.stringLiterals.add(constants.getStringInfo(i)); + } + } + + // stuff from the jar index + + this.implementations = HashMultiset.create(); + ClassImplementationsTreeNode implementationsNode = index.getClassImplementations(null, this.classEntry); + if (implementationsNode != null) { + @SuppressWarnings("unchecked") + Enumeration implementations = implementationsNode.children(); + while (implementations.hasMoreElements()) { + ClassImplementationsTreeNode node = implementations.nextElement(); + this.implementations.add(scrubClassName(node.getClassEntry().getName())); + } + } + + this.references = HashMultiset.create(); + if (useReferences) { + for (CtField field : c.getDeclaredFields()) { + FieldEntry fieldEntry = EntryFactory.getFieldEntry(field); + index.getFieldReferences(fieldEntry).forEach(this::addReference); + } + for (CtBehavior behavior : c.getDeclaredBehaviors()) { + BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); + index.getBehaviorReferences(behaviorEntry).forEach(this::addReference); + } + } + + this.outer = null; + if (this.classEntry.isInnerClass()) { + this.outer = this.classEntry.getOuterClassName(); + } + } + + private void addReference(EntryReference reference) { + if (reference.context.getSignature() != null) { + this.references.add(String.format("%s_%s", + scrubClassName(reference.context.getClassName()), + scrubSignature(reference.context.getSignature()) + )); + } else { + this.references.add(String.format("%s_", + scrubClassName(reference.context.getClassName()) + )); + } + } + + public ClassEntry getClassEntry() { + return this.classEntry; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append("class: "); + buf.append(this.classEntry.getName()); + buf.append(" "); + buf.append(hashCode()); + buf.append("\n"); + for (String field : this.fields) { + buf.append("\tfield "); + buf.append(field); + buf.append("\n"); + } + for (String method : this.methods) { + buf.append("\tmethod "); + buf.append(method); + buf.append("\n"); + } + for (String constructor : this.constructors) { + buf.append("\tconstructor "); + buf.append(constructor); + buf.append("\n"); + } + if (!this.staticInitializer.isEmpty()) { + buf.append("\tinitializer "); + buf.append(this.staticInitializer); + buf.append("\n"); + } + if (!this.extendz.isEmpty()) { + buf.append("\textends "); + buf.append(this.extendz); + buf.append("\n"); + } + for (String interfaceName : this.implementz) { + buf.append("\timplements "); + buf.append(interfaceName); + buf.append("\n"); + } + for (String implementation : this.implementations) { + buf.append("\timplemented by "); + buf.append(implementation); + buf.append("\n"); + } + for (String reference : this.references) { + buf.append("\treference "); + buf.append(reference); + buf.append("\n"); + } + buf.append("\touter "); + buf.append(this.outer); + buf.append("\n"); + return buf.toString(); + } + + private String scrubClassName(String className) { + return classNameReplacer.replace(className); + } + + private String scrubType(String typeName) { + return scrubType(new Type(typeName)).toString(); + } + + private Type scrubType(Type type) { + if (type.hasClass()) { + return new Type(type, classNameReplacer); + } else { + return type; + } + } + + private String scrubSignature(String signature) { + return scrubSignature(new Signature(signature)).toString(); + } + + private Signature scrubSignature(Signature signature) { + return new Signature(signature, classNameReplacer); + } + + private boolean isClassMatchedUniquely(String className) { + return this.namer != null && this.namer.getName(Descriptor.toJvmName(className)) != null; + } + + private String getBehaviorSignature(CtBehavior behavior) { + try { + // does this method have an implementation? + if (behavior.getMethodInfo().getCodeAttribute() == null) { + return "(none)"; + } + + // compute the hash from the opcodes + ConstPool constants = behavior.getMethodInfo().getConstPool(); + final MessageDigest digest = MessageDigest.getInstance("MD5"); + CodeIterator iter = behavior.getMethodInfo().getCodeAttribute().iterator(); + while (iter.hasNext()) { + int pos = iter.next(); + + // update the hash with the opcode + int opcode = iter.byteAt(pos); + digest.update((byte) opcode); + int constIndex; + switch (opcode) { + case Opcode.LDC: + constIndex = iter.byteAt(pos + 1); + updateHashWithConstant(digest, constants, constIndex); + break; + + case Opcode.LDC_W: + case Opcode.LDC2_W: + constIndex = (iter.byteAt(pos + 1) << 8) | iter.byteAt(pos + 2); + updateHashWithConstant(digest, constants, constIndex); + break; + default: + break; + } + } + + // update hash with method and field accesses + behavior.instrument(new ExprEditor() { + @Override + public void edit(MethodCall call) { + updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName()))); + updateHashWithString(digest, scrubSignature(call.getSignature())); + if (isClassMatchedUniquely(call.getClassName())) { + updateHashWithString(digest, call.getMethodName()); + } + } + + @Override + public void edit(FieldAccess access) { + updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(access.getClassName()))); + updateHashWithString(digest, scrubType(access.getSignature())); + if (isClassMatchedUniquely(access.getClassName())) { + updateHashWithString(digest, access.getFieldName()); + } + } + + @Override + public void edit(ConstructorCall call) { + updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName()))); + updateHashWithString(digest, scrubSignature(call.getSignature())); + } + + @Override + public void edit(NewExpr expr) { + updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(expr.getClassName()))); + } + }); + + // convert the hash to a hex string + return toHex(digest.digest()); + } catch (BadBytecode | NoSuchAlgorithmException | CannotCompileException ex) { + throw new Error(ex); + } + } + + private void updateHashWithConstant(MessageDigest digest, ConstPool constants, int index) { + ConstPoolEditor editor = new ConstPoolEditor(constants); + ConstInfoAccessor item = editor.getItem(index); + if (item.getType() == InfoType.StringInfo) { + updateHashWithString(digest, constants.getStringInfo(index)); + } + // TODO: other constants + } + + private void updateHashWithString(MessageDigest digest, String val) { + try { + digest.update(val.getBytes("UTF8")); + } catch (UnsupportedEncodingException ex) { + throw new Error(ex); + } + } + + private String toHex(byte[] bytes) { + // function taken from: + // http://stackoverflow.com/questions/9655181/convert-from-byte-array-to-hex-string-in-java + final char[] hexArray = "0123456789ABCDEF".toCharArray(); + char[] hexChars = new char[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = hexArray[v >>> 4]; + hexChars[j * 2 + 1] = hexArray[v & 0x0F]; + } + return new String(hexChars); + } + + @Override + public boolean equals(Object other) { + return other instanceof ClassIdentity && equals((ClassIdentity) other); + } + + public boolean equals(ClassIdentity other) { + return this.fields.equals(other.fields) + && this.methods.equals(other.methods) + && this.constructors.equals(other.constructors) + && this.staticInitializer.equals(other.staticInitializer) + && this.extendz.equals(other.extendz) + && this.implementz.equals(other.implementz) + && this.implementations.equals(other.implementations) + && this.references.equals(other.references); + } + + @Override + public int hashCode() { + List objs = Lists.newArrayList(); + objs.addAll(this.fields); + objs.addAll(this.methods); + objs.addAll(this.constructors); + objs.add(this.staticInitializer); + objs.add(this.extendz); + objs.addAll(this.implementz); + objs.addAll(this.implementations); + objs.addAll(this.references); + return Utils.combineHashesOrdered(objs); + } + + public int getMatchScore(ClassIdentity other) { + return 2 * getNumMatches(this.extendz, other.extendz) + + 2 * getNumMatches(this.outer, other.outer) + + 2 * getNumMatches(this.implementz, other.implementz) + + getNumMatches(this.stringLiterals, other.stringLiterals) + + getNumMatches(this.fields, other.fields) + + getNumMatches(this.methods, other.methods) + + getNumMatches(this.constructors, other.constructors); + } + + public int getMaxMatchScore() { + return 2 + 2 + 2 * this.implementz.size() + this.stringLiterals.size() + this.fields.size() + this.methods.size() + this.constructors.size(); + } + + public boolean matches(CtClass c) { + // just compare declaration counts + return this.fields.size() == c.getDeclaredFields().length + && this.methods.size() == c.getDeclaredMethods().length + && this.constructors.size() == c.getDeclaredConstructors().length; + } + + private int getNumMatches(Set a, Set b) { + int numMatches = 0; + for (String val : a) { + if (b.contains(val)) { + numMatches++; + } + } + return numMatches; + } + + private int getNumMatches(Multiset a, Multiset b) { + int numMatches = 0; + for (String val : a) { + if (b.contains(val)) { + numMatches++; + } + } + return numMatches; + } + + private int getNumMatches(String a, String b) { + if (a == null && b == null) { + return 1; + } else if (a != null && b != null && a.equals(b)) { + return 1; + } + return 0; + } } diff --git a/src/main/java/cuchaz/enigma/convert/ClassMatch.java b/src/main/java/cuchaz/enigma/convert/ClassMatch.java index 9fa35f03..bb3e4f43 100644 --- a/src/main/java/cuchaz/enigma/convert/ClassMatch.java +++ b/src/main/java/cuchaz/enigma/convert/ClassMatch.java @@ -8,76 +8,76 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.convert; import com.google.common.collect.Sets; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.utils.Utils; import java.util.Collection; import java.util.Set; -import cuchaz.enigma.mapping.ClassEntry; -import cuchaz.enigma.utils.Utils; - public class ClassMatch { - public Set sourceClasses; - public Set destClasses; + public Set sourceClasses; + public Set destClasses; - public ClassMatch(Collection sourceClasses, Collection destClasses) { - this.sourceClasses = Sets.newHashSet(sourceClasses); - this.destClasses = Sets.newHashSet(destClasses); - } + public ClassMatch(Collection sourceClasses, Collection destClasses) { + this.sourceClasses = Sets.newHashSet(sourceClasses); + this.destClasses = Sets.newHashSet(destClasses); + } - public ClassMatch(ClassEntry sourceClass, ClassEntry destClass) { - sourceClasses = Sets.newHashSet(); - if (sourceClass != null) { - sourceClasses.add(sourceClass); - } - destClasses = Sets.newHashSet(); - if (destClass != null) { - destClasses.add(destClass); - } - } + public ClassMatch(ClassEntry sourceClass, ClassEntry destClass) { + sourceClasses = Sets.newHashSet(); + if (sourceClass != null) { + sourceClasses.add(sourceClass); + } + destClasses = Sets.newHashSet(); + if (destClass != null) { + destClasses.add(destClass); + } + } - public boolean isMatched() { - return sourceClasses.size() > 0 && destClasses.size() > 0; - } + public boolean isMatched() { + return !sourceClasses.isEmpty() && !destClasses.isEmpty(); + } - public boolean isAmbiguous() { - return sourceClasses.size() > 1 || destClasses.size() > 1; - } + public boolean isAmbiguous() { + return sourceClasses.size() > 1 || destClasses.size() > 1; + } - public ClassEntry getUniqueSource() { - if (sourceClasses.size() != 1) { - throw new IllegalStateException("Match has ambiguous source!"); - } - return sourceClasses.iterator().next(); - } + public ClassEntry getUniqueSource() { + if (sourceClasses.size() != 1) { + throw new IllegalStateException("Match has ambiguous source!"); + } + return sourceClasses.iterator().next(); + } - public ClassEntry getUniqueDest() { - if (destClasses.size() != 1) { - throw new IllegalStateException("Match has ambiguous source!"); - } - return destClasses.iterator().next(); - } + public ClassEntry getUniqueDest() { + if (destClasses.size() != 1) { + throw new IllegalStateException("Match has ambiguous source!"); + } + return destClasses.iterator().next(); + } - public Set intersectSourceClasses(Set classes) { - Set intersection = Sets.newHashSet(sourceClasses); - intersection.retainAll(classes); - return intersection; - } + public Set intersectSourceClasses(Set classes) { + Set intersection = Sets.newHashSet(sourceClasses); + intersection.retainAll(classes); + return intersection; + } - @Override - public int hashCode() { - return Utils.combineHashesOrdered(sourceClasses, destClasses); - } + @Override + public int hashCode() { + return Utils.combineHashesOrdered(sourceClasses, destClasses); + } - @Override - public boolean equals(Object other) { - return other instanceof ClassMatch && equals((ClassMatch) other); - } + @Override + public boolean equals(Object other) { + return other instanceof ClassMatch && equals((ClassMatch) other); + } - public boolean equals(ClassMatch other) { - return this.sourceClasses.equals(other.sourceClasses) && this.destClasses.equals(other.destClasses); - } + public boolean equals(ClassMatch other) { + return this.sourceClasses.equals(other.sourceClasses) && this.destClasses.equals(other.destClasses); + } } diff --git a/src/main/java/cuchaz/enigma/convert/ClassMatches.java b/src/main/java/cuchaz/enigma/convert/ClassMatches.java index 431c4f24..db2c550f 100644 --- a/src/main/java/cuchaz/enigma/convert/ClassMatches.java +++ b/src/main/java/cuchaz/enigma/convert/ClassMatches.java @@ -8,152 +8,151 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.convert; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import com.google.common.collect.Maps; import com.google.common.collect.Sets; - -import java.util.*; - import cuchaz.enigma.mapping.ClassEntry; +import java.util.*; public class ClassMatches implements Iterable { - private Collection matches; - private Map matchesBySource; - private Map matchesByDest; - private BiMap uniqueMatches; - private Map ambiguousMatchesBySource; - private Map ambiguousMatchesByDest; - private Set unmatchedSourceClasses; - private Set unmatchedDestClasses; - - public ClassMatches() { - this(new ArrayList<>()); - } - - public ClassMatches(Collection matches) { - this.matches = matches; - matchesBySource = Maps.newHashMap(); - matchesByDest = Maps.newHashMap(); - uniqueMatches = HashBiMap.create(); - ambiguousMatchesBySource = Maps.newHashMap(); - ambiguousMatchesByDest = Maps.newHashMap(); - unmatchedSourceClasses = Sets.newHashSet(); - unmatchedDestClasses = Sets.newHashSet(); - - for (ClassMatch match : matches) { - indexMatch(match); - } - } - - public void add(ClassMatch match) { - matches.add(match); - indexMatch(match); - } - - public void remove(ClassMatch match) { - for (ClassEntry sourceClass : match.sourceClasses) { - matchesBySource.remove(sourceClass); - uniqueMatches.remove(sourceClass); - ambiguousMatchesBySource.remove(sourceClass); - unmatchedSourceClasses.remove(sourceClass); - } - for (ClassEntry destClass : match.destClasses) { - matchesByDest.remove(destClass); - uniqueMatches.inverse().remove(destClass); - ambiguousMatchesByDest.remove(destClass); - unmatchedDestClasses.remove(destClass); - } - matches.remove(match); - } - - public int size() { - return matches.size(); - } - - @Override - public Iterator iterator() { - return matches.iterator(); - } - - private void indexMatch(ClassMatch match) { - if (!match.isMatched()) { - // unmatched - unmatchedSourceClasses.addAll(match.sourceClasses); - unmatchedDestClasses.addAll(match.destClasses); - } else { - if (match.isAmbiguous()) { - // ambiguously matched - for (ClassEntry entry : match.sourceClasses) { - ambiguousMatchesBySource.put(entry, match); - } - for (ClassEntry entry : match.destClasses) { - ambiguousMatchesByDest.put(entry, match); - } - } else { - // uniquely matched - uniqueMatches.put(match.getUniqueSource(), match.getUniqueDest()); - } - } - for (ClassEntry entry : match.sourceClasses) { - matchesBySource.put(entry, match); - } - for (ClassEntry entry : match.destClasses) { - matchesByDest.put(entry, match); - } - } - - public BiMap getUniqueMatches() { - return uniqueMatches; - } - - public Set getUnmatchedSourceClasses() { - return unmatchedSourceClasses; - } - - public Set getUnmatchedDestClasses() { - return unmatchedDestClasses; - } - - public Set getAmbiguouslyMatchedSourceClasses() { - return ambiguousMatchesBySource.keySet(); - } - - public ClassMatch getAmbiguousMatchBySource(ClassEntry sourceClass) { - return ambiguousMatchesBySource.get(sourceClass); - } - - public ClassMatch getMatchBySource(ClassEntry sourceClass) { - return matchesBySource.get(sourceClass); - } - - public ClassMatch getMatchByDest(ClassEntry destClass) { - return matchesByDest.get(destClass); - } - - public void removeSource(ClassEntry sourceClass) { - ClassMatch match = matchesBySource.get(sourceClass); - if (match != null) { - remove(match); - match.sourceClasses.remove(sourceClass); - if (!match.sourceClasses.isEmpty() || !match.destClasses.isEmpty()) { - add(match); - } - } - } - - public void removeDest(ClassEntry destClass) { - ClassMatch match = matchesByDest.get(destClass); - if (match != null) { - remove(match); - match.destClasses.remove(destClass); - if (!match.sourceClasses.isEmpty() || !match.destClasses.isEmpty()) { - add(match); - } - } - } + private Collection matches; + private Map matchesBySource; + private Map matchesByDest; + private BiMap uniqueMatches; + private Map ambiguousMatchesBySource; + private Map ambiguousMatchesByDest; + private Set unmatchedSourceClasses; + private Set unmatchedDestClasses; + + public ClassMatches() { + this(new ArrayList<>()); + } + + public ClassMatches(Collection matches) { + this.matches = matches; + matchesBySource = Maps.newHashMap(); + matchesByDest = Maps.newHashMap(); + uniqueMatches = HashBiMap.create(); + ambiguousMatchesBySource = Maps.newHashMap(); + ambiguousMatchesByDest = Maps.newHashMap(); + unmatchedSourceClasses = Sets.newHashSet(); + unmatchedDestClasses = Sets.newHashSet(); + + for (ClassMatch match : matches) { + indexMatch(match); + } + } + + public void add(ClassMatch match) { + matches.add(match); + indexMatch(match); + } + + public void remove(ClassMatch match) { + for (ClassEntry sourceClass : match.sourceClasses) { + matchesBySource.remove(sourceClass); + uniqueMatches.remove(sourceClass); + ambiguousMatchesBySource.remove(sourceClass); + unmatchedSourceClasses.remove(sourceClass); + } + for (ClassEntry destClass : match.destClasses) { + matchesByDest.remove(destClass); + uniqueMatches.inverse().remove(destClass); + ambiguousMatchesByDest.remove(destClass); + unmatchedDestClasses.remove(destClass); + } + matches.remove(match); + } + + public int size() { + return matches.size(); + } + + @Override + public Iterator iterator() { + return matches.iterator(); + } + + private void indexMatch(ClassMatch match) { + if (!match.isMatched()) { + // unmatched + unmatchedSourceClasses.addAll(match.sourceClasses); + unmatchedDestClasses.addAll(match.destClasses); + } else { + if (match.isAmbiguous()) { + // ambiguously matched + for (ClassEntry entry : match.sourceClasses) { + ambiguousMatchesBySource.put(entry, match); + } + for (ClassEntry entry : match.destClasses) { + ambiguousMatchesByDest.put(entry, match); + } + } else { + // uniquely matched + uniqueMatches.put(match.getUniqueSource(), match.getUniqueDest()); + } + } + for (ClassEntry entry : match.sourceClasses) { + matchesBySource.put(entry, match); + } + for (ClassEntry entry : match.destClasses) { + matchesByDest.put(entry, match); + } + } + + public BiMap getUniqueMatches() { + return uniqueMatches; + } + + public Set getUnmatchedSourceClasses() { + return unmatchedSourceClasses; + } + + public Set getUnmatchedDestClasses() { + return unmatchedDestClasses; + } + + public Set getAmbiguouslyMatchedSourceClasses() { + return ambiguousMatchesBySource.keySet(); + } + + public ClassMatch getAmbiguousMatchBySource(ClassEntry sourceClass) { + return ambiguousMatchesBySource.get(sourceClass); + } + + public ClassMatch getMatchBySource(ClassEntry sourceClass) { + return matchesBySource.get(sourceClass); + } + + public ClassMatch getMatchByDest(ClassEntry destClass) { + return matchesByDest.get(destClass); + } + + public void removeSource(ClassEntry sourceClass) { + ClassMatch match = matchesBySource.get(sourceClass); + if (match != null) { + remove(match); + match.sourceClasses.remove(sourceClass); + if (!match.sourceClasses.isEmpty() || !match.destClasses.isEmpty()) { + add(match); + } + } + } + + public void removeDest(ClassEntry destClass) { + ClassMatch match = matchesByDest.get(destClass); + if (match != null) { + remove(match); + match.destClasses.remove(destClass); + if (!match.sourceClasses.isEmpty() || !match.destClasses.isEmpty()) { + add(match); + } + } + } } diff --git a/src/main/java/cuchaz/enigma/convert/ClassMatching.java b/src/main/java/cuchaz/enigma/convert/ClassMatching.java index b05df871..f302f130 100644 --- a/src/main/java/cuchaz/enigma/convert/ClassMatching.java +++ b/src/main/java/cuchaz/enigma/convert/ClassMatching.java @@ -8,12 +8,14 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.convert; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import com.google.common.collect.Lists; import com.google.common.collect.Sets; +import cuchaz.enigma.mapping.ClassEntry; import java.util.ArrayList; import java.util.Collection; @@ -21,134 +23,132 @@ import java.util.List; import java.util.Map.Entry; import java.util.Set; -import cuchaz.enigma.mapping.ClassEntry; - public class ClassMatching { - private ClassForest sourceClasses; - private ClassForest destClasses; - private BiMap knownMatches; - - public ClassMatching(ClassIdentifier sourceIdentifier, ClassIdentifier destIdentifier) { - sourceClasses = new ClassForest(sourceIdentifier); - destClasses = new ClassForest(destIdentifier); - knownMatches = HashBiMap.create(); - } - - public void addKnownMatches(BiMap knownMatches) { - this.knownMatches.putAll(knownMatches); - } - - public void match(Iterable sourceClasses, Iterable destClasses) { - for (ClassEntry sourceClass : sourceClasses) { - if (!knownMatches.containsKey(sourceClass)) { - this.sourceClasses.add(sourceClass); - } - } - for (ClassEntry destClass : destClasses) { - if (!knownMatches.containsValue(destClass)) { - this.destClasses.add(destClass); - } - } - } - - public Collection matches() { - List matches = Lists.newArrayList(); - for (Entry entry : knownMatches.entrySet()) { - matches.add(new ClassMatch( - entry.getKey(), - entry.getValue() - )); - } - for (ClassIdentity identity : sourceClasses.identities()) { - matches.add(new ClassMatch( - sourceClasses.getClasses(identity), - destClasses.getClasses(identity) - )); - } - for (ClassIdentity identity : destClasses.identities()) { - if (!sourceClasses.containsIdentity(identity)) { - matches.add(new ClassMatch( - new ArrayList<>(), - destClasses.getClasses(identity) - )); - } - } - return matches; - } - - public Collection sourceClasses() { - Set classes = Sets.newHashSet(); - for (ClassMatch match : matches()) { - classes.addAll(match.sourceClasses); - } - return classes; - } - - public Collection destClasses() { - Set classes = Sets.newHashSet(); - for (ClassMatch match : matches()) { - classes.addAll(match.destClasses); - } - return classes; - } - - public BiMap uniqueMatches() { - BiMap uniqueMatches = HashBiMap.create(); - for (ClassMatch match : matches()) { - if (match.isMatched() && !match.isAmbiguous()) { - uniqueMatches.put(match.getUniqueSource(), match.getUniqueDest()); - } - } - return uniqueMatches; - } - - public Collection ambiguousMatches() { - List ambiguousMatches = Lists.newArrayList(); - for (ClassMatch match : matches()) { - if (match.isMatched() && match.isAmbiguous()) { - ambiguousMatches.add(match); - } - } - return ambiguousMatches; - } - - public Collection unmatchedSourceClasses() { - List classes = Lists.newArrayList(); - for (ClassMatch match : matches()) { - if (!match.isMatched() && !match.sourceClasses.isEmpty()) { - classes.addAll(match.sourceClasses); - } - } - return classes; - } - - public Collection unmatchedDestClasses() { - List classes = Lists.newArrayList(); - for (ClassMatch match : matches()) { - if (!match.isMatched() && !match.destClasses.isEmpty()) { - classes.addAll(match.destClasses); - } - } - return classes; - } - - @Override - public String toString() { - - // count the ambiguous classes - int numAmbiguousSource = 0; - int numAmbiguousDest = 0; - for (ClassMatch match : ambiguousMatches()) { - numAmbiguousSource += match.sourceClasses.size(); - numAmbiguousDest += match.destClasses.size(); - } - - String buf = String.format("%20s%8s%8s\n", "", "Source", "Dest") + String - .format("%20s%8d%8d\n", "Classes", sourceClasses().size(), destClasses().size()) + String - .format("%20s%8d%8d\n", "Uniquely matched", uniqueMatches().size(), uniqueMatches().size()) + String - .format("%20s%8d%8d\n", "Ambiguously matched", numAmbiguousSource, numAmbiguousDest) + String - .format("%20s%8d%8d\n", "Unmatched", unmatchedSourceClasses().size(), unmatchedDestClasses().size()); - return buf; - } + private ClassForest sourceClasses; + private ClassForest destClasses; + private BiMap knownMatches; + + public ClassMatching(ClassIdentifier sourceIdentifier, ClassIdentifier destIdentifier) { + sourceClasses = new ClassForest(sourceIdentifier); + destClasses = new ClassForest(destIdentifier); + knownMatches = HashBiMap.create(); + } + + public void addKnownMatches(BiMap knownMatches) { + this.knownMatches.putAll(knownMatches); + } + + public void match(Iterable sourceClasses, Iterable destClasses) { + for (ClassEntry sourceClass : sourceClasses) { + if (!knownMatches.containsKey(sourceClass)) { + this.sourceClasses.add(sourceClass); + } + } + for (ClassEntry destClass : destClasses) { + if (!knownMatches.containsValue(destClass)) { + this.destClasses.add(destClass); + } + } + } + + public Collection matches() { + List matches = Lists.newArrayList(); + for (Entry entry : knownMatches.entrySet()) { + matches.add(new ClassMatch( + entry.getKey(), + entry.getValue() + )); + } + for (ClassIdentity identity : sourceClasses.identities()) { + matches.add(new ClassMatch( + sourceClasses.getClasses(identity), + destClasses.getClasses(identity) + )); + } + for (ClassIdentity identity : destClasses.identities()) { + if (!sourceClasses.containsIdentity(identity)) { + matches.add(new ClassMatch( + new ArrayList<>(), + destClasses.getClasses(identity) + )); + } + } + return matches; + } + + public Collection sourceClasses() { + Set classes = Sets.newHashSet(); + for (ClassMatch match : matches()) { + classes.addAll(match.sourceClasses); + } + return classes; + } + + public Collection destClasses() { + Set classes = Sets.newHashSet(); + for (ClassMatch match : matches()) { + classes.addAll(match.destClasses); + } + return classes; + } + + public BiMap uniqueMatches() { + BiMap uniqueMatches = HashBiMap.create(); + for (ClassMatch match : matches()) { + if (match.isMatched() && !match.isAmbiguous()) { + uniqueMatches.put(match.getUniqueSource(), match.getUniqueDest()); + } + } + return uniqueMatches; + } + + public Collection ambiguousMatches() { + List ambiguousMatches = Lists.newArrayList(); + for (ClassMatch match : matches()) { + if (match.isMatched() && match.isAmbiguous()) { + ambiguousMatches.add(match); + } + } + return ambiguousMatches; + } + + public Collection unmatchedSourceClasses() { + List classes = Lists.newArrayList(); + for (ClassMatch match : matches()) { + if (!match.isMatched() && !match.sourceClasses.isEmpty()) { + classes.addAll(match.sourceClasses); + } + } + return classes; + } + + public Collection unmatchedDestClasses() { + List classes = Lists.newArrayList(); + for (ClassMatch match : matches()) { + if (!match.isMatched() && !match.destClasses.isEmpty()) { + classes.addAll(match.destClasses); + } + } + return classes; + } + + @Override + public String toString() { + + // count the ambiguous classes + int numAmbiguousSource = 0; + int numAmbiguousDest = 0; + for (ClassMatch match : ambiguousMatches()) { + numAmbiguousSource += match.sourceClasses.size(); + numAmbiguousDest += match.destClasses.size(); + } + + String buf = String.format("%20s%8s%8s\n", "", "Source", "Dest") + String + .format("%20s%8d%8d\n", "Classes", sourceClasses().size(), destClasses().size()) + String + .format("%20s%8d%8d\n", "Uniquely matched", uniqueMatches().size(), uniqueMatches().size()) + String + .format("%20s%8d%8d\n", "Ambiguously matched", numAmbiguousSource, numAmbiguousDest) + String + .format("%20s%8d%8d\n", "Unmatched", unmatchedSourceClasses().size(), unmatchedDestClasses().size()); + return buf; + } } diff --git a/src/main/java/cuchaz/enigma/convert/ClassNamer.java b/src/main/java/cuchaz/enigma/convert/ClassNamer.java index e471c7dd..39699101 100644 --- a/src/main/java/cuchaz/enigma/convert/ClassNamer.java +++ b/src/main/java/cuchaz/enigma/convert/ClassNamer.java @@ -8,49 +8,48 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.convert; import com.google.common.collect.BiMap; import com.google.common.collect.Maps; +import cuchaz.enigma.mapping.ClassEntry; import java.util.Map; -import cuchaz.enigma.mapping.ClassEntry; - public class ClassNamer { - public interface SidedClassNamer { - String getName(String name); - } - - private Map sourceNames; - private Map destNames; - - public ClassNamer(BiMap mappings) { - // convert the identity mappings to name maps - this.sourceNames = Maps.newHashMap(); - this.destNames = Maps.newHashMap(); - int i = 0; - for (Map.Entry entry : mappings.entrySet()) { - String name = String.format("M%04d", i++); - this.sourceNames.put(entry.getKey().getName(), name); - this.destNames.put(entry.getValue().getName(), name); - } - } - - public String getSourceName(String name) { - return this.sourceNames.get(name); - } - - public String getDestName(String name) { - return this.destNames.get(name); - } - - public SidedClassNamer getSourceNamer() { - return this::getSourceName; - } - - public SidedClassNamer getDestNamer() { - return this::getDestName; - } + private Map sourceNames; + private Map destNames; + public ClassNamer(BiMap mappings) { + // convert the identity mappings to name maps + this.sourceNames = Maps.newHashMap(); + this.destNames = Maps.newHashMap(); + int i = 0; + for (Map.Entry entry : mappings.entrySet()) { + String name = String.format("M%04d", i++); + this.sourceNames.put(entry.getKey().getName(), name); + this.destNames.put(entry.getValue().getName(), name); + } + } + + public String getSourceName(String name) { + return this.sourceNames.get(name); + } + + public String getDestName(String name) { + return this.destNames.get(name); + } + + public SidedClassNamer getSourceNamer() { + return this::getSourceName; + } + + public SidedClassNamer getDestNamer() { + return this::getDestName; + } + + public interface SidedClassNamer { + String getName(String name); + } } diff --git a/src/main/java/cuchaz/enigma/convert/FieldMatches.java b/src/main/java/cuchaz/enigma/convert/FieldMatches.java index 236cd4d1..a528b276 100644 --- a/src/main/java/cuchaz/enigma/convert/FieldMatches.java +++ b/src/main/java/cuchaz/enigma/convert/FieldMatches.java @@ -8,144 +8,143 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.convert; import com.google.common.collect.*; - -import java.util.Collection; -import java.util.Set; - import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.FieldEntry; +import java.util.Collection; +import java.util.Set; public class FieldMatches { - private BiMap matches; - private Multimap matchedSourceFields; - private Multimap unmatchedSourceFields; - private Multimap unmatchedDestFields; - private Multimap unmatchableSourceFields; - - public FieldMatches() { - matches = HashBiMap.create(); - matchedSourceFields = HashMultimap.create(); - unmatchedSourceFields = HashMultimap.create(); - unmatchedDestFields = HashMultimap.create(); - unmatchableSourceFields = HashMultimap.create(); - } - - public void addMatch(FieldEntry srcField, FieldEntry destField) { - boolean wasAdded = matches.put(srcField, destField) == null; - assert (wasAdded); - wasAdded = matchedSourceFields.put(srcField.getClassEntry(), srcField); - assert (wasAdded); - } - - public void addUnmatchedSourceField(FieldEntry fieldEntry) { - boolean wasAdded = unmatchedSourceFields.put(fieldEntry.getClassEntry(), fieldEntry); - assert (wasAdded); - } - - public void addUnmatchedSourceFields(Iterable fieldEntries) { - for (FieldEntry fieldEntry : fieldEntries) { - addUnmatchedSourceField(fieldEntry); - } - } - - public void addUnmatchedDestField(FieldEntry fieldEntry) { - boolean wasAdded = unmatchedDestFields.put(fieldEntry.getClassEntry(), fieldEntry); - assert (wasAdded); - } - - public void addUnmatchedDestFields(Iterable fieldEntries) { - for (FieldEntry fieldEntry : fieldEntries) { - addUnmatchedDestField(fieldEntry); - } - } - - public void addUnmatchableSourceField(FieldEntry sourceField) { - boolean wasAdded = unmatchableSourceFields.put(sourceField.getClassEntry(), sourceField); - assert (wasAdded); - } - - public Set getSourceClassesWithUnmatchedFields() { - return unmatchedSourceFields.keySet(); - } - - public Collection getSourceClassesWithoutUnmatchedFields() { - Set out = Sets.newHashSet(); - out.addAll(matchedSourceFields.keySet()); - out.removeAll(unmatchedSourceFields.keySet()); - return out; - } - - public Collection getUnmatchedSourceFields() { - return unmatchedSourceFields.values(); - } - - public Collection getUnmatchedSourceFields(ClassEntry sourceClass) { - return unmatchedSourceFields.get(sourceClass); - } - - public Collection getUnmatchedDestFields() { - return unmatchedDestFields.values(); - } - - public Collection getUnmatchedDestFields(ClassEntry destClass) { - return unmatchedDestFields.get(destClass); - } - - public Collection getUnmatchableSourceFields() { - return unmatchableSourceFields.values(); - } - - public boolean hasSource(FieldEntry fieldEntry) { - return matches.containsKey(fieldEntry) || unmatchedSourceFields.containsValue(fieldEntry); - } - - public boolean hasDest(FieldEntry fieldEntry) { - return matches.containsValue(fieldEntry) || unmatchedDestFields.containsValue(fieldEntry); - } - - public BiMap matches() { - return matches; - } - - public boolean isMatchedSourceField(FieldEntry sourceField) { - return matches.containsKey(sourceField); - } - - public boolean isMatchedDestField(FieldEntry destField) { - return matches.containsValue(destField); - } - - public void makeMatch(FieldEntry sourceField, FieldEntry destField) { - boolean wasRemoved = unmatchedSourceFields.remove(sourceField.getClassEntry(), sourceField); - assert (wasRemoved); - wasRemoved = unmatchedDestFields.remove(destField.getClassEntry(), destField); - assert (wasRemoved); - addMatch(sourceField, destField); - } - - public boolean isMatched(FieldEntry sourceField, FieldEntry destField) { - FieldEntry match = matches.get(sourceField); - return match != null && match.equals(destField); - } - - public void unmakeMatch(FieldEntry sourceField, FieldEntry destField) { - boolean wasRemoved = matches.remove(sourceField) != null; - assert (wasRemoved); - wasRemoved = matchedSourceFields.remove(sourceField.getClassEntry(), sourceField); - assert (wasRemoved); - addUnmatchedSourceField(sourceField); - addUnmatchedDestField(destField); - } - - public void makeSourceUnmatchable(FieldEntry sourceField) { - assert (!isMatchedSourceField(sourceField)); - boolean wasRemoved = unmatchedSourceFields.remove(sourceField.getClassEntry(), sourceField); - assert (wasRemoved); - addUnmatchableSourceField(sourceField); - } + private BiMap matches; + private Multimap matchedSourceFields; + private Multimap unmatchedSourceFields; + private Multimap unmatchedDestFields; + private Multimap unmatchableSourceFields; + + public FieldMatches() { + matches = HashBiMap.create(); + matchedSourceFields = HashMultimap.create(); + unmatchedSourceFields = HashMultimap.create(); + unmatchedDestFields = HashMultimap.create(); + unmatchableSourceFields = HashMultimap.create(); + } + + public void addMatch(FieldEntry srcField, FieldEntry destField) { + boolean wasAdded = matches.put(srcField, destField) == null; + assert (wasAdded); + wasAdded = matchedSourceFields.put(srcField.getClassEntry(), srcField); + assert (wasAdded); + } + + public void addUnmatchedSourceField(FieldEntry fieldEntry) { + boolean wasAdded = unmatchedSourceFields.put(fieldEntry.getClassEntry(), fieldEntry); + assert (wasAdded); + } + + public void addUnmatchedSourceFields(Iterable fieldEntries) { + for (FieldEntry fieldEntry : fieldEntries) { + addUnmatchedSourceField(fieldEntry); + } + } + + public void addUnmatchedDestField(FieldEntry fieldEntry) { + boolean wasAdded = unmatchedDestFields.put(fieldEntry.getClassEntry(), fieldEntry); + assert (wasAdded); + } + + public void addUnmatchedDestFields(Iterable fieldEntries) { + for (FieldEntry fieldEntry : fieldEntries) { + addUnmatchedDestField(fieldEntry); + } + } + + public void addUnmatchableSourceField(FieldEntry sourceField) { + boolean wasAdded = unmatchableSourceFields.put(sourceField.getClassEntry(), sourceField); + assert (wasAdded); + } + + public Set getSourceClassesWithUnmatchedFields() { + return unmatchedSourceFields.keySet(); + } + + public Collection getSourceClassesWithoutUnmatchedFields() { + Set out = Sets.newHashSet(); + out.addAll(matchedSourceFields.keySet()); + out.removeAll(unmatchedSourceFields.keySet()); + return out; + } + + public Collection getUnmatchedSourceFields() { + return unmatchedSourceFields.values(); + } + + public Collection getUnmatchedSourceFields(ClassEntry sourceClass) { + return unmatchedSourceFields.get(sourceClass); + } + + public Collection getUnmatchedDestFields() { + return unmatchedDestFields.values(); + } + + public Collection getUnmatchedDestFields(ClassEntry destClass) { + return unmatchedDestFields.get(destClass); + } + + public Collection getUnmatchableSourceFields() { + return unmatchableSourceFields.values(); + } + + public boolean hasSource(FieldEntry fieldEntry) { + return matches.containsKey(fieldEntry) || unmatchedSourceFields.containsValue(fieldEntry); + } + + public boolean hasDest(FieldEntry fieldEntry) { + return matches.containsValue(fieldEntry) || unmatchedDestFields.containsValue(fieldEntry); + } + + public BiMap matches() { + return matches; + } + + public boolean isMatchedSourceField(FieldEntry sourceField) { + return matches.containsKey(sourceField); + } + + public boolean isMatchedDestField(FieldEntry destField) { + return matches.containsValue(destField); + } + + public void makeMatch(FieldEntry sourceField, FieldEntry destField) { + boolean wasRemoved = unmatchedSourceFields.remove(sourceField.getClassEntry(), sourceField); + assert (wasRemoved); + wasRemoved = unmatchedDestFields.remove(destField.getClassEntry(), destField); + assert (wasRemoved); + addMatch(sourceField, destField); + } + + public boolean isMatched(FieldEntry sourceField, FieldEntry destField) { + FieldEntry match = matches.get(sourceField); + return match != null && match.equals(destField); + } + + public void unmakeMatch(FieldEntry sourceField, FieldEntry destField) { + boolean wasRemoved = matches.remove(sourceField) != null; + assert (wasRemoved); + wasRemoved = matchedSourceFields.remove(sourceField.getClassEntry(), sourceField); + assert (wasRemoved); + addUnmatchedSourceField(sourceField); + addUnmatchedDestField(destField); + } + + public void makeSourceUnmatchable(FieldEntry sourceField) { + assert (!isMatchedSourceField(sourceField)); + boolean wasRemoved = unmatchedSourceFields.remove(sourceField.getClassEntry(), sourceField); + assert (wasRemoved); + addUnmatchableSourceField(sourceField); + } } diff --git a/src/main/java/cuchaz/enigma/convert/MappingsConverter.java b/src/main/java/cuchaz/enigma/convert/MappingsConverter.java index a5ded677..fa3e9362 100644 --- a/src/main/java/cuchaz/enigma/convert/MappingsConverter.java +++ b/src/main/java/cuchaz/enigma/convert/MappingsConverter.java @@ -8,6 +8,7 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.convert; import com.google.common.collect.*; @@ -29,688 +30,682 @@ import java.util.jar.JarFile; public class MappingsConverter { - public static ClassMatches computeClassMatches(JarFile sourceJar, JarFile destJar, Mappings mappings) { - - // index jars - System.out.println("Indexing source jar..."); - JarIndex sourceIndex = new JarIndex(); - sourceIndex.indexJar(sourceJar, false); - System.out.println("Indexing dest jar..."); - JarIndex destIndex = new JarIndex(); - destIndex.indexJar(destJar, false); - - // compute the matching - ClassMatching matching = computeMatching(sourceJar, sourceIndex, destJar, destIndex, null); - return new ClassMatches(matching.matches()); - } - - public static ClassMatching computeMatching(JarFile sourceJar, JarIndex sourceIndex, JarFile destJar, JarIndex destIndex, BiMap knownMatches) { - - System.out.println("Iteratively matching classes"); - - ClassMatching lastMatching = null; - int round = 0; - SidedClassNamer sourceNamer = null; - SidedClassNamer destNamer = null; - for (boolean useReferences : Arrays.asList(false, true)) { - - int numUniqueMatchesLastTime = 0; - if (lastMatching != null) { - numUniqueMatchesLastTime = lastMatching.uniqueMatches().size(); - } - - while (true) { - - System.out.println("Round " + (++round) + "..."); - - // init the matching with identity settings - ClassMatching matching = new ClassMatching( - new ClassIdentifier(sourceJar, sourceIndex, sourceNamer, useReferences), - new ClassIdentifier(destJar, destIndex, destNamer, useReferences) - ); - - if (knownMatches != null) { - matching.addKnownMatches(knownMatches); - } - - if (lastMatching == null) { - // search all classes - matching.match(sourceIndex.getObfClassEntries(), destIndex.getObfClassEntries()); - } else { - // we already know about these matches from last time - matching.addKnownMatches(lastMatching.uniqueMatches()); - - // search unmatched and ambiguously-matched classes - matching.match(lastMatching.unmatchedSourceClasses(), lastMatching.unmatchedDestClasses()); - for (ClassMatch match : lastMatching.ambiguousMatches()) { - matching.match(match.sourceClasses, match.destClasses); - } - } - System.out.println(matching); - BiMap uniqueMatches = matching.uniqueMatches(); - - // did we match anything new this time? - if (uniqueMatches.size() > numUniqueMatchesLastTime) { - numUniqueMatchesLastTime = uniqueMatches.size(); - lastMatching = matching; - } else { - break; - } - - // update the namers - ClassNamer namer = new ClassNamer(uniqueMatches); - sourceNamer = namer.getSourceNamer(); - destNamer = namer.getDestNamer(); - } - } - - return lastMatching; - } - - public static Mappings newMappings(ClassMatches matches, Mappings oldMappings, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) - throws MappingConflict { - // sort the unique matches by size of inner class chain - 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); - } - - // build the mappings (in order of small-to-large inner chains) - Mappings newMappings = new Mappings(); - List chainSizes = Lists.newArrayList(matchesByDestChainSize.keySet()); - Collections.sort(chainSizes); - for (int chainSize : chainSizes) { - for (java.util.Map.Entry match : matchesByDestChainSize.get(chainSize)) { - // get class info - ClassEntry obfSourceClassEntry = match.getKey(); - ClassEntry obfDestClassEntry = match.getValue(); - List destClassChain = destDeobfuscator.getJarIndex().getObfClassChain(obfDestClassEntry); - - ClassMapping sourceMapping; - if (obfSourceClassEntry.isInnerClass()) { - List srcClassChain = sourceDeobfuscator.getMappings().getClassMappingChain(obfSourceClassEntry); - sourceMapping = srcClassChain.get(srcClassChain.size() - 1); - } else { - sourceMapping = sourceDeobfuscator.getMappings().getClassByObf(obfSourceClassEntry); - } - - if (sourceMapping == null) { - // if this class was never deobfuscated, don't try to match it - continue; - } - - // find out where to make the dest class mapping - if (destClassChain.size() == 1) { - // not an inner class, add directly to mappings - newMappings.addClassMapping(migrateClassMapping(obfDestClassEntry, sourceMapping, matches, false)); - } else { - // inner class, find the outer class mapping - ClassMapping destMapping = null; - for (int i = 0; i < destClassChain.size() - 1; i++) { - ClassEntry destChainClassEntry = destClassChain.get(i); - if (destMapping == null) { - destMapping = newMappings.getClassByObf(destChainClassEntry); - if (destMapping == null) { - destMapping = new ClassMapping(destChainClassEntry.getName()); - newMappings.addClassMapping(destMapping); - } - } else { - destMapping = destMapping.getInnerClassByObfSimple(destChainClassEntry.getInnermostClassName()); - if (destMapping == null) { - destMapping = new ClassMapping(destChainClassEntry.getName()); - destMapping.addInnerClassMapping(destMapping); - } - } - } - destMapping.addInnerClassMapping(migrateClassMapping(obfDestClassEntry, sourceMapping, matches, true)); - } - } - } - return newMappings; - } - - private static ClassMapping migrateClassMapping(ClassEntry newObfClass, ClassMapping oldClassMapping, final ClassMatches matches, boolean useSimpleName) { - - ClassNameReplacer replacer = className -> - { - ClassEntry newClassEntry = matches.getUniqueMatches().get(new ClassEntry(className)); - if (newClassEntry != null) { - return newClassEntry.getName(); - } - return null; - }; - - ClassMapping newClassMapping; - String deobfName = oldClassMapping.getDeobfName(); - if (deobfName != null) { - if (useSimpleName) { - deobfName = new ClassEntry(deobfName).getSimpleName(); - } - newClassMapping = new ClassMapping(newObfClass.getName(), deobfName); - } else { - newClassMapping = new ClassMapping(newObfClass.getName()); - } - - // migrate fields - for (FieldMapping oldFieldMapping : oldClassMapping.fields()) { - if (canMigrate(oldFieldMapping.getObfType(), matches)) { - newClassMapping.addFieldMapping(new FieldMapping(oldFieldMapping, replacer)); - } else { - System.out.println(String.format("Can't map field, dropping: %s.%s %s", - oldClassMapping.getDeobfName(), - oldFieldMapping.getDeobfName(), - oldFieldMapping.getObfType() - )); - } - } - - // migrate methods - for (MethodMapping oldMethodMapping : oldClassMapping.methods()) { - if (canMigrate(oldMethodMapping.getObfSignature(), matches)) { - newClassMapping.addMethodMapping(new MethodMapping(oldMethodMapping, replacer)); - } else { - System.out.println(String.format("Can't map method, dropping: %s.%s %s", - oldClassMapping.getDeobfName(), - oldMethodMapping.getDeobfName(), - oldMethodMapping.getObfSignature() - )); - } - } - - return newClassMapping; - } - - private static boolean canMigrate(Signature oldObfSignature, ClassMatches classMatches) { - for (Type oldObfType : oldObfSignature.types()) { - if (!canMigrate(oldObfType, classMatches)) { - return false; - } - } - return true; - } - - private static boolean canMigrate(Type oldObfType, ClassMatches classMatches) { - - // non classes can be migrated - if (!oldObfType.hasClass()) { - return true; - } - - // non obfuscated classes can be migrated - ClassEntry classEntry = oldObfType.getClassEntry(); - if (classEntry.getPackageName() != null) { - return true; - } - - // obfuscated classes with mappings can be migrated - return classMatches.getUniqueMatches().containsKey(classEntry); - } - - public static void convertMappings(Mappings mappings, BiMap changes) { - - // sort the changes so classes are renamed in the correct order - // ie. if we have the mappings a->b, b->c, we have to apply b->c before a->b - LinkedHashMap sortedChanges = Maps.newLinkedHashMap(); - int numChangesLeft = changes.size(); - while (!changes.isEmpty()) { - Iterator> iter = changes.entrySet().iterator(); - while (iter.hasNext()) { - Map.Entry change = iter.next(); - if (changes.containsKey(change.getValue())) { - sortedChanges.put(change.getKey(), change.getValue()); - iter.remove(); - } - } - - // did we remove any changes? - if (numChangesLeft - changes.size() > 0) { - // keep going - numChangesLeft = changes.size(); - } else { - // can't sort anymore. There must be a loop - break; - } - } - if (!changes.isEmpty()) { - throw new Error("Unable to sort class changes! There must be a cycle."); - } - - // convert the mappings in the correct class order - for (Map.Entry entry : sortedChanges.entrySet()) { - mappings.renameObfClass(entry.getKey().getName(), entry.getValue().getName()); - } - } - - public interface Doer { - Collection getDroppedEntries(MappingsChecker checker); - - Collection getObfEntries(JarIndex jarIndex); - - Collection> getMappings(ClassMapping destClassMapping); - - Set filterEntries(Collection obfEntries, T obfSourceEntry, ClassMatches classMatches); - - void setUpdateObfMember(ClassMapping classMapping, MemberMapping memberMapping, T newEntry); - - boolean hasObfMember(ClassMapping classMapping, T obfEntry); - - void removeMemberByObf(ClassMapping classMapping, T obfEntry); - } - - public static Doer getFieldDoer() { - return 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; - } - - @Override - public void setUpdateObfMember(ClassMapping classMapping, MemberMapping memberMapping, FieldEntry newField) { - FieldMapping fieldMapping = (FieldMapping) memberMapping; - classMapping.setFieldObfNameAndType(fieldMapping.getObfName(), fieldMapping.getObfType(), newField.getName(), newField.getType()); - } - - @Override - public boolean hasObfMember(ClassMapping classMapping, FieldEntry obfField) { - return classMapping.getFieldByObf(obfField.getName(), obfField.getType()) != null; - } - - @Override - public void removeMemberByObf(ClassMapping classMapping, FieldEntry obfField) { - classMapping.removeFieldMapping(classMapping.getFieldByObf(obfField.getName(), obfField.getType())); - } - }; - } - - public static Doer getMethodDoer() { - return new Doer() { - - @Override - public Collection getDroppedEntries(MappingsChecker checker) { - return checker.getDroppedMethodMappings().keySet(); - } - - @Override - public Collection getObfEntries(JarIndex jarIndex) { - return jarIndex.getObfBehaviorEntries(); - } - - @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) { - // Try to translate the signature - Signature translatedDestSignature = translate(obfDestField.getSignature(), classMatches.getUniqueMatches().inverse()); - if (translatedDestSignature != null && obfSourceField.getSignature() != null && translatedDestSignature.equals(obfSourceField.getSignature())) - out.add(obfDestField); - } - return out; - } - - @Override - public void setUpdateObfMember(ClassMapping classMapping, MemberMapping memberMapping, BehaviorEntry newBehavior) { - MethodMapping methodMapping = (MethodMapping) memberMapping; - classMapping.setMethodObfNameAndSignature(methodMapping.getObfName(), methodMapping.getObfSignature(), newBehavior.getName(), newBehavior.getSignature()); - } - - @Override - public boolean hasObfMember(ClassMapping classMapping, BehaviorEntry obfBehavior) { - return classMapping.getMethodByObf(obfBehavior.getName(), obfBehavior.getSignature()) != null; - } - - @Override - public void removeMemberByObf(ClassMapping classMapping, BehaviorEntry obfBehavior) { - classMapping.removeMethodMapping(classMapping.getMethodByObf(obfBehavior.getName(), obfBehavior.getSignature())); - } - }; - } - - public static int compareMethodByteCode(CodeIterator sourceIt, CodeIterator destIt) - { - int sourcePos = 0; - int destPos = 0; - while (sourceIt.hasNext() && destIt.hasNext()) - { - try - { - sourcePos = sourceIt.next(); - destPos = destIt.next(); - if (sourceIt.byteAt(sourcePos) != destIt.byteAt(destPos)) - return sourcePos; - } catch (BadBytecode badBytecode) - { - // Ignore bad bytecode (it might be a little bit dangerous...) - } - } - if (sourcePos < destPos) - return sourcePos; - else if (destPos < sourcePos) - return destPos; - return sourcePos; - } - - public static BehaviorEntry compareMethods(CtClass destCtClass, CtClass sourceCtClass, BehaviorEntry obfSourceEntry, - Set obfDestEntries) - { - try - { - // Get the source method with Javassist - CtMethod sourceCtClassMethod = sourceCtClass.getMethod(obfSourceEntry.getName(), obfSourceEntry.getSignature().toString()); - CodeAttribute sourceAttribute = sourceCtClassMethod.getMethodInfo().getCodeAttribute(); - - // Empty method body, ignore! - if (sourceAttribute == null) - return null; - for (BehaviorEntry desEntry : obfDestEntries) - { - try - { - CtMethod destCtClassMethod = destCtClass - .getMethod(desEntry.getName(), desEntry.getSignature().toString()); - CodeAttribute destAttribute = destCtClassMethod.getMethodInfo().getCodeAttribute(); - - // Ignore empty body methods - if (destAttribute == null) - continue; - CodeIterator destIterator = destAttribute.iterator(); - int maxPos = compareMethodByteCode(sourceAttribute.iterator(), destIterator); - - // The bytecode is identical to the original method, assuming that the method is correct! - if (sourceAttribute.getCodeLength() == (maxPos + 1) && maxPos > 1) - return desEntry; - } catch (NotFoundException e) - { - e.printStackTrace(); - } - } - } catch (NotFoundException e) - { - e.printStackTrace(); - return null; - } - return null; - } - - public static MemberMatches computeMethodsMatches(Deobfuscator destDeobfuscator, Mappings destMappings, Deobfuscator sourceDeobfuscator, Mappings sourceMappings, ClassMatches classMatches, Doer doer) { - - MemberMatches memberMatches = new MemberMatches<>(); - - // unmatched source fields are easy - MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex()); - checker.dropBrokenMappings(destMappings); - for (BehaviorEntry destObfEntry : doer.getDroppedEntries(checker)) { - BehaviorEntry 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(memberMatches, classMapping, classMatches, doer); - - // get unmatched dest fields - doer.getObfEntries(destDeobfuscator.getJarIndex()).stream() - .filter(destEntry -> !memberMatches.isMatchedDestEntry(destEntry)) - .forEach(memberMatches::addUnmatchedDestEntry); - - // Apply mappings to deobfuscator - - // Create type loader - TranslatingTypeLoader destTypeLoader = destDeobfuscator.createTypeLoader(); - TranslatingTypeLoader sourceTypeLoader = sourceDeobfuscator.createTypeLoader(); - - 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(memberMatches.getSourceClassesWithUnmatchedEntries())) { - for (BehaviorEntry obfSourceEntry : Lists.newArrayList(memberMatches.getUnmatchedSourceEntries(obfSourceClass))) { - - // get the possible dest matches - ClassEntry obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass); - - // filter by type/signature - Set obfDestEntries = doer.filterEntries(memberMatches.getUnmatchedDestEntries(obfDestClass), obfSourceEntry, classMatches); - - if (obfDestEntries.size() == 1) { - // make the easy match - memberMatches.makeMatch(obfSourceEntry, obfDestEntries.iterator().next()); - } else if (obfDestEntries.isEmpty()) { - // no match is possible =( - memberMatches.makeSourceUnmatchable(obfSourceEntry, null); - } else - { - // Multiple matches! Scan methods instructions - CtClass destCtClass = destTypeLoader.loadClass(obfDestClass.getClassName()); - CtClass sourceCtClass = sourceTypeLoader.loadClass(obfSourceClass.getClassName()); - BehaviorEntry match = compareMethods(destCtClass, sourceCtClass, obfSourceEntry, obfDestEntries); - // the method match correctly, match it on the member mapping! - if (match != null) - memberMatches.makeMatch(obfSourceEntry, match); - } - } - } - - System.out.println(String.format("Ended up with %d ambiguous and %d unmatchable source entries", - memberMatches.getUnmatchedSourceEntries().size(), - memberMatches.getUnmatchableSourceEntries().size() - )); - - return memberMatches; - } - - public static MemberMatches computeMemberMatches(Deobfuscator destDeobfuscator, Mappings destMappings, ClassMatches classMatches, Doer doer) { - - MemberMatches memberMatches = new MemberMatches<>(); - - // unmatched source fields are easy - MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex()); - checker.dropBrokenMappings(destMappings); - 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(memberMatches, classMapping, classMatches, doer); - } - - // get unmatched dest fields - for (T destEntry : doer.getObfEntries(destDeobfuscator.getJarIndex())) { - if (!memberMatches.isMatchedDestEntry(destEntry)) { - memberMatches.addUnmatchedDestEntry(destEntry); - } - } - - 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(memberMatches.getSourceClassesWithUnmatchedEntries())) { - for (T obfSourceEntry : Lists.newArrayList(memberMatches.getUnmatchedSourceEntries(obfSourceClass))) { - - // get the possible dest matches - ClassEntry obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass); - - // filter by type/signature - Set obfDestEntries = doer.filterEntries(memberMatches.getUnmatchedDestEntries(obfDestClass), obfSourceEntry, classMatches); - - if (obfDestEntries.size() == 1) { - // make the easy match - memberMatches.makeMatch(obfSourceEntry, obfDestEntries.iterator().next()); - } else if (obfDestEntries.isEmpty()) { - // no match is possible =( - memberMatches.makeSourceUnmatchable(obfSourceEntry, null); - } - } - } - - System.out.println(String.format("Ended up with %d ambiguous and %d unmatchable source entries", - memberMatches.getUnmatchedSourceEntries().size(), - memberMatches.getUnmatchableSourceEntries().size() - )); - - return memberMatches; - } - - private static void collectMatchedFields(MemberMatches memberMatches, ClassMapping destClassMapping, ClassMatches classMatches, Doer doer) { - - // get the fields for this class - 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(memberMatches, destInnerClassMapping, classMatches, doer); - } - } - - @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) { - return new Type(type, inClassName -> - { - ClassEntry outClassEntry = map.get(new ClassEntry(inClassName)); - if (outClassEntry == null) { - return null; - } - return outClassEntry.getName(); - }); - } - - private static Signature translate(Signature signature, final BiMap map) { - if (signature == null) { - return null; - } - return new Signature(signature, inClassName -> - { - ClassEntry outClassEntry = map.get(new ClassEntry(inClassName)); - if (outClassEntry == null) { - return null; - } - return outClassEntry.getName(); - }); - } - - public static void applyMemberMatches(Mappings mappings, ClassMatches classMatches, MemberMatches memberMatches, Doer doer) { - for (ClassMapping classMapping : mappings.classes()) { - applyMemberMatches(classMapping, classMatches, memberMatches, doer); - } - } - - private static void applyMemberMatches(ClassMapping classMapping, ClassMatches classMatches, MemberMatches memberMatches, Doer doer) { - - // get the classes - ClassEntry obfDestClass = new ClassEntry(classMapping.getObfFullName()); - - // make a map of all the renames we need to make - Map renames = Maps.newHashMap(); - for (MemberMapping memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) { - T obfOldDestEntry = memberMapping.getObfEntry(obfDestClass); - T obfSourceEntry = getSourceEntryFromDestMapping(memberMapping, obfDestClass, classMatches); - - // but drop the unmatchable things - if (memberMatches.isUnmatchableSourceEntry(obfSourceEntry)) { - doer.removeMemberByObf(classMapping, obfOldDestEntry); - continue; - } - - T obfNewDestEntry = memberMatches.matches().get(obfSourceEntry); - if (obfNewDestEntry != null && !obfOldDestEntry.getName().equals(obfNewDestEntry.getName())) { - renames.put(obfOldDestEntry, obfNewDestEntry); - } - } - - if (!renames.isEmpty()) { - - // apply to this class (should never need more than n passes) - int numRenamesAppliedThisRound; - do { - numRenamesAppliedThisRound = 0; - - for (MemberMapping memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) { - T obfOldDestEntry = memberMapping.getObfEntry(obfDestClass); - T obfNewDestEntry = renames.get(obfOldDestEntry); - if (obfNewDestEntry != null) { - // make sure this rename won't cause a collision - // otherwise, save it for the next round and try again next time - if (!doer.hasObfMember(classMapping, obfNewDestEntry)) { - doer.setUpdateObfMember(classMapping, memberMapping, obfNewDestEntry); - renames.remove(obfOldDestEntry); - numRenamesAppliedThisRound++; - } - } - } - } while (numRenamesAppliedThisRound > 0); - - if (!renames.isEmpty()) { - System.err.println(String.format("WARNING: Couldn't apply all the renames for class %s. %d renames left.", - classMapping.getObfFullName(), renames.size() - )); - for (Map.Entry entry : renames.entrySet()) { - System.err.println(String.format("\t%s -> %s", entry.getKey().getName(), entry.getValue().getName())); - } - } - } - - // recurse - for (ClassMapping innerClassMapping : classMapping.innerClasses()) { - applyMemberMatches(innerClassMapping, classMatches, memberMatches, doer); - } - } - - private static T getSourceEntryFromDestMapping(MemberMapping destMemberMapping, ClassEntry obfDestClass, ClassMatches classMatches) { - return translate(destMemberMapping.getObfEntry(obfDestClass), classMatches.getUniqueMatches().inverse()); - } + public static ClassMatches computeClassMatches(JarFile sourceJar, JarFile destJar, Mappings mappings) { + + // index jars + System.out.println("Indexing source jar..."); + JarIndex sourceIndex = new JarIndex(); + sourceIndex.indexJar(sourceJar, false); + System.out.println("Indexing dest jar..."); + JarIndex destIndex = new JarIndex(); + destIndex.indexJar(destJar, false); + + // compute the matching + ClassMatching matching = computeMatching(sourceJar, sourceIndex, destJar, destIndex, null); + return new ClassMatches(matching.matches()); + } + + public static ClassMatching computeMatching(JarFile sourceJar, JarIndex sourceIndex, JarFile destJar, JarIndex destIndex, BiMap knownMatches) { + + System.out.println("Iteratively matching classes"); + + ClassMatching lastMatching = null; + int round = 0; + SidedClassNamer sourceNamer = null; + SidedClassNamer destNamer = null; + for (boolean useReferences : Arrays.asList(false, true)) { + + int numUniqueMatchesLastTime = 0; + if (lastMatching != null) { + numUniqueMatchesLastTime = lastMatching.uniqueMatches().size(); + } + + while (true) { + + System.out.println("Round " + (++round) + "..."); + + // init the matching with identity settings + ClassMatching matching = new ClassMatching( + new ClassIdentifier(sourceJar, sourceIndex, sourceNamer, useReferences), + new ClassIdentifier(destJar, destIndex, destNamer, useReferences) + ); + + if (knownMatches != null) { + matching.addKnownMatches(knownMatches); + } + + if (lastMatching == null) { + // search all classes + matching.match(sourceIndex.getObfClassEntries(), destIndex.getObfClassEntries()); + } else { + // we already know about these matches from last time + matching.addKnownMatches(lastMatching.uniqueMatches()); + + // search unmatched and ambiguously-matched classes + matching.match(lastMatching.unmatchedSourceClasses(), lastMatching.unmatchedDestClasses()); + for (ClassMatch match : lastMatching.ambiguousMatches()) { + matching.match(match.sourceClasses, match.destClasses); + } + } + System.out.println(matching); + BiMap uniqueMatches = matching.uniqueMatches(); + + // did we match anything new this time? + if (uniqueMatches.size() > numUniqueMatchesLastTime) { + numUniqueMatchesLastTime = uniqueMatches.size(); + lastMatching = matching; + } else { + break; + } + + // update the namers + ClassNamer namer = new ClassNamer(uniqueMatches); + sourceNamer = namer.getSourceNamer(); + destNamer = namer.getDestNamer(); + } + } + + return lastMatching; + } + + public static Mappings newMappings(ClassMatches matches, Mappings oldMappings, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) + throws MappingConflict { + // sort the unique matches by size of inner class chain + 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); + } + + // build the mappings (in order of small-to-large inner chains) + Mappings newMappings = new Mappings(); + List chainSizes = Lists.newArrayList(matchesByDestChainSize.keySet()); + Collections.sort(chainSizes); + for (int chainSize : chainSizes) { + for (java.util.Map.Entry match : matchesByDestChainSize.get(chainSize)) { + // get class info + ClassEntry obfSourceClassEntry = match.getKey(); + ClassEntry obfDestClassEntry = match.getValue(); + List destClassChain = destDeobfuscator.getJarIndex().getObfClassChain(obfDestClassEntry); + + ClassMapping sourceMapping; + if (obfSourceClassEntry.isInnerClass()) { + List srcClassChain = sourceDeobfuscator.getMappings().getClassMappingChain(obfSourceClassEntry); + sourceMapping = srcClassChain.get(srcClassChain.size() - 1); + } else { + sourceMapping = sourceDeobfuscator.getMappings().getClassByObf(obfSourceClassEntry); + } + + if (sourceMapping == null) { + // if this class was never deobfuscated, don't try to match it + continue; + } + + // find out where to make the dest class mapping + if (destClassChain.size() == 1) { + // not an inner class, add directly to mappings + newMappings.addClassMapping(migrateClassMapping(obfDestClassEntry, sourceMapping, matches, false)); + } else { + // inner class, find the outer class mapping + ClassMapping destMapping = null; + for (int i = 0; i < destClassChain.size() - 1; i++) { + ClassEntry destChainClassEntry = destClassChain.get(i); + if (destMapping == null) { + destMapping = newMappings.getClassByObf(destChainClassEntry); + if (destMapping == null) { + destMapping = new ClassMapping(destChainClassEntry.getName()); + newMappings.addClassMapping(destMapping); + } + } else { + destMapping = destMapping.getInnerClassByObfSimple(destChainClassEntry.getInnermostClassName()); + if (destMapping == null) { + destMapping = new ClassMapping(destChainClassEntry.getName()); + destMapping.addInnerClassMapping(destMapping); + } + } + } + destMapping.addInnerClassMapping(migrateClassMapping(obfDestClassEntry, sourceMapping, matches, true)); + } + } + } + return newMappings; + } + + private static ClassMapping migrateClassMapping(ClassEntry newObfClass, ClassMapping oldClassMapping, final ClassMatches matches, boolean useSimpleName) { + + ClassNameReplacer replacer = className -> + { + ClassEntry newClassEntry = matches.getUniqueMatches().get(new ClassEntry(className)); + if (newClassEntry != null) { + return newClassEntry.getName(); + } + return null; + }; + + ClassMapping newClassMapping; + String deobfName = oldClassMapping.getDeobfName(); + if (deobfName != null) { + if (useSimpleName) { + deobfName = new ClassEntry(deobfName).getSimpleName(); + } + newClassMapping = new ClassMapping(newObfClass.getName(), deobfName); + } else { + newClassMapping = new ClassMapping(newObfClass.getName()); + } + + // migrate fields + for (FieldMapping oldFieldMapping : oldClassMapping.fields()) { + if (canMigrate(oldFieldMapping.getObfType(), matches)) { + newClassMapping.addFieldMapping(new FieldMapping(oldFieldMapping, replacer)); + } else { + System.out.println(String.format("Can't map field, dropping: %s.%s %s", + oldClassMapping.getDeobfName(), + oldFieldMapping.getDeobfName(), + oldFieldMapping.getObfType() + )); + } + } + + // migrate methods + for (MethodMapping oldMethodMapping : oldClassMapping.methods()) { + if (canMigrate(oldMethodMapping.getObfSignature(), matches)) { + newClassMapping.addMethodMapping(new MethodMapping(oldMethodMapping, replacer)); + } else { + System.out.println(String.format("Can't map method, dropping: %s.%s %s", + oldClassMapping.getDeobfName(), + oldMethodMapping.getDeobfName(), + oldMethodMapping.getObfSignature() + )); + } + } + + return newClassMapping; + } + + private static boolean canMigrate(Signature oldObfSignature, ClassMatches classMatches) { + for (Type oldObfType : oldObfSignature.types()) { + if (!canMigrate(oldObfType, classMatches)) { + return false; + } + } + return true; + } + + private static boolean canMigrate(Type oldObfType, ClassMatches classMatches) { + + // non classes can be migrated + if (!oldObfType.hasClass()) { + return true; + } + + // non obfuscated classes can be migrated + ClassEntry classEntry = oldObfType.getClassEntry(); + if (classEntry.getPackageName() != null) { + return true; + } + + // obfuscated classes with mappings can be migrated + return classMatches.getUniqueMatches().containsKey(classEntry); + } + + public static void convertMappings(Mappings mappings, BiMap changes) { + + // sort the changes so classes are renamed in the correct order + // ie. if we have the mappings a->b, b->c, we have to apply b->c before a->b + LinkedHashMap sortedChanges = Maps.newLinkedHashMap(); + int numChangesLeft = changes.size(); + while (!changes.isEmpty()) { + Iterator> iter = changes.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry change = iter.next(); + if (changes.containsKey(change.getValue())) { + sortedChanges.put(change.getKey(), change.getValue()); + iter.remove(); + } + } + + // did we remove any changes? + if (numChangesLeft - changes.size() > 0) { + // keep going + numChangesLeft = changes.size(); + } else { + // can't sort anymore. There must be a loop + break; + } + } + if (!changes.isEmpty()) { + throw new Error("Unable to sort class changes! There must be a cycle."); + } + + // convert the mappings in the correct class order + for (Map.Entry entry : sortedChanges.entrySet()) { + mappings.renameObfClass(entry.getKey().getName(), entry.getValue().getName()); + } + } + + public static Doer getFieldDoer() { + return 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; + } + + @Override + public void setUpdateObfMember(ClassMapping classMapping, MemberMapping memberMapping, FieldEntry newField) { + FieldMapping fieldMapping = (FieldMapping) memberMapping; + classMapping.setFieldObfNameAndType(fieldMapping.getObfName(), fieldMapping.getObfType(), newField.getName(), newField.getType()); + } + + @Override + public boolean hasObfMember(ClassMapping classMapping, FieldEntry obfField) { + return classMapping.getFieldByObf(obfField.getName(), obfField.getType()) != null; + } + + @Override + public void removeMemberByObf(ClassMapping classMapping, FieldEntry obfField) { + classMapping.removeFieldMapping(classMapping.getFieldByObf(obfField.getName(), obfField.getType())); + } + }; + } + + public static Doer getMethodDoer() { + return new Doer() { + + @Override + public Collection getDroppedEntries(MappingsChecker checker) { + return checker.getDroppedMethodMappings().keySet(); + } + + @Override + public Collection getObfEntries(JarIndex jarIndex) { + return jarIndex.getObfBehaviorEntries(); + } + + @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) { + // Try to translate the signature + Signature translatedDestSignature = translate(obfDestField.getSignature(), classMatches.getUniqueMatches().inverse()); + if (translatedDestSignature != null && obfSourceField.getSignature() != null && translatedDestSignature.equals(obfSourceField.getSignature())) + out.add(obfDestField); + } + return out; + } + + @Override + public void setUpdateObfMember(ClassMapping classMapping, MemberMapping memberMapping, BehaviorEntry newBehavior) { + MethodMapping methodMapping = (MethodMapping) memberMapping; + classMapping.setMethodObfNameAndSignature(methodMapping.getObfName(), methodMapping.getObfSignature(), newBehavior.getName(), newBehavior.getSignature()); + } + + @Override + public boolean hasObfMember(ClassMapping classMapping, BehaviorEntry obfBehavior) { + return classMapping.getMethodByObf(obfBehavior.getName(), obfBehavior.getSignature()) != null; + } + + @Override + public void removeMemberByObf(ClassMapping classMapping, BehaviorEntry obfBehavior) { + classMapping.removeMethodMapping(classMapping.getMethodByObf(obfBehavior.getName(), obfBehavior.getSignature())); + } + }; + } + + public static int compareMethodByteCode(CodeIterator sourceIt, CodeIterator destIt) { + int sourcePos = 0; + int destPos = 0; + while (sourceIt.hasNext() && destIt.hasNext()) { + try { + sourcePos = sourceIt.next(); + destPos = destIt.next(); + if (sourceIt.byteAt(sourcePos) != destIt.byteAt(destPos)) + return sourcePos; + } catch (BadBytecode badBytecode) { + // Ignore bad bytecode (it might be a little bit dangerous...) + } + } + if (sourcePos < destPos) + return sourcePos; + else if (destPos < sourcePos) + return destPos; + return sourcePos; + } + + public static BehaviorEntry compareMethods(CtClass destCtClass, CtClass sourceCtClass, BehaviorEntry obfSourceEntry, + Set obfDestEntries) { + try { + // Get the source method with Javassist + CtMethod sourceCtClassMethod = sourceCtClass.getMethod(obfSourceEntry.getName(), obfSourceEntry.getSignature().toString()); + CodeAttribute sourceAttribute = sourceCtClassMethod.getMethodInfo().getCodeAttribute(); + + // Empty method body, ignore! + if (sourceAttribute == null) + return null; + for (BehaviorEntry desEntry : obfDestEntries) { + try { + CtMethod destCtClassMethod = destCtClass + .getMethod(desEntry.getName(), desEntry.getSignature().toString()); + CodeAttribute destAttribute = destCtClassMethod.getMethodInfo().getCodeAttribute(); + + // Ignore empty body methods + if (destAttribute == null) + continue; + CodeIterator destIterator = destAttribute.iterator(); + int maxPos = compareMethodByteCode(sourceAttribute.iterator(), destIterator); + + // The bytecode is identical to the original method, assuming that the method is correct! + if (sourceAttribute.getCodeLength() == (maxPos + 1) && maxPos > 1) + return desEntry; + } catch (NotFoundException e) { + e.printStackTrace(); + } + } + } catch (NotFoundException e) { + e.printStackTrace(); + return null; + } + return null; + } + + public static MemberMatches computeMethodsMatches(Deobfuscator destDeobfuscator, + Mappings destMappings, + Deobfuscator sourceDeobfuscator, + Mappings sourceMappings, + ClassMatches classMatches, + Doer doer) { + + MemberMatches memberMatches = new MemberMatches<>(); + + // unmatched source fields are easy + MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex()); + checker.dropBrokenMappings(destMappings); + for (BehaviorEntry destObfEntry : doer.getDroppedEntries(checker)) { + BehaviorEntry 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(memberMatches, classMapping, classMatches, doer); + + // get unmatched dest fields + doer.getObfEntries(destDeobfuscator.getJarIndex()).stream() + .filter(destEntry -> !memberMatches.isMatchedDestEntry(destEntry)) + .forEach(memberMatches::addUnmatchedDestEntry); + + // Apply mappings to deobfuscator + + // Create type loader + TranslatingTypeLoader destTypeLoader = destDeobfuscator.createTypeLoader(); + TranslatingTypeLoader sourceTypeLoader = sourceDeobfuscator.createTypeLoader(); + + 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(memberMatches.getSourceClassesWithUnmatchedEntries())) { + for (BehaviorEntry obfSourceEntry : Lists.newArrayList(memberMatches.getUnmatchedSourceEntries(obfSourceClass))) { + + // get the possible dest matches + ClassEntry obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass); + + // filter by type/signature + Set obfDestEntries = doer.filterEntries(memberMatches.getUnmatchedDestEntries(obfDestClass), obfSourceEntry, classMatches); + + if (obfDestEntries.size() == 1) { + // make the easy match + memberMatches.makeMatch(obfSourceEntry, obfDestEntries.iterator().next()); + } else if (obfDestEntries.isEmpty()) { + // no match is possible =( + memberMatches.makeSourceUnmatchable(obfSourceEntry, null); + } else { + // Multiple matches! Scan methods instructions + CtClass destCtClass = destTypeLoader.loadClass(obfDestClass.getClassName()); + CtClass sourceCtClass = sourceTypeLoader.loadClass(obfSourceClass.getClassName()); + BehaviorEntry match = compareMethods(destCtClass, sourceCtClass, obfSourceEntry, obfDestEntries); + // the method match correctly, match it on the member mapping! + if (match != null) + memberMatches.makeMatch(obfSourceEntry, match); + } + } + } + + System.out.println(String.format("Ended up with %d ambiguous and %d unmatchable source entries", + memberMatches.getUnmatchedSourceEntries().size(), + memberMatches.getUnmatchableSourceEntries().size() + )); + + return memberMatches; + } + + public static MemberMatches computeMemberMatches(Deobfuscator destDeobfuscator, Mappings destMappings, ClassMatches classMatches, Doer doer) { + + MemberMatches memberMatches = new MemberMatches<>(); + + // unmatched source fields are easy + MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex()); + checker.dropBrokenMappings(destMappings); + 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(memberMatches, classMapping, classMatches, doer); + } + + // get unmatched dest fields + for (T destEntry : doer.getObfEntries(destDeobfuscator.getJarIndex())) { + if (!memberMatches.isMatchedDestEntry(destEntry)) { + memberMatches.addUnmatchedDestEntry(destEntry); + } + } + + 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(memberMatches.getSourceClassesWithUnmatchedEntries())) { + for (T obfSourceEntry : Lists.newArrayList(memberMatches.getUnmatchedSourceEntries(obfSourceClass))) { + + // get the possible dest matches + ClassEntry obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass); + + // filter by type/signature + Set obfDestEntries = doer.filterEntries(memberMatches.getUnmatchedDestEntries(obfDestClass), obfSourceEntry, classMatches); + + if (obfDestEntries.size() == 1) { + // make the easy match + memberMatches.makeMatch(obfSourceEntry, obfDestEntries.iterator().next()); + } else if (obfDestEntries.isEmpty()) { + // no match is possible =( + memberMatches.makeSourceUnmatchable(obfSourceEntry, null); + } + } + } + + System.out.println(String.format("Ended up with %d ambiguous and %d unmatchable source entries", + memberMatches.getUnmatchedSourceEntries().size(), + memberMatches.getUnmatchableSourceEntries().size() + )); + + return memberMatches; + } + + private static void collectMatchedFields(MemberMatches memberMatches, ClassMapping destClassMapping, ClassMatches classMatches, Doer doer) { + + // get the fields for this class + 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(memberMatches, destInnerClassMapping, classMatches, doer); + } + } + + @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) { + return new Type(type, inClassName -> + { + ClassEntry outClassEntry = map.get(new ClassEntry(inClassName)); + if (outClassEntry == null) { + return null; + } + return outClassEntry.getName(); + }); + } + + private static Signature translate(Signature signature, final BiMap map) { + if (signature == null) { + return null; + } + return new Signature(signature, inClassName -> + { + ClassEntry outClassEntry = map.get(new ClassEntry(inClassName)); + if (outClassEntry == null) { + return null; + } + return outClassEntry.getName(); + }); + } + + public static void applyMemberMatches(Mappings mappings, ClassMatches classMatches, MemberMatches memberMatches, Doer doer) { + for (ClassMapping classMapping : mappings.classes()) { + applyMemberMatches(classMapping, classMatches, memberMatches, doer); + } + } + + private static void applyMemberMatches(ClassMapping classMapping, ClassMatches classMatches, MemberMatches memberMatches, Doer doer) { + + // get the classes + ClassEntry obfDestClass = new ClassEntry(classMapping.getObfFullName()); + + // make a map of all the renames we need to make + Map renames = Maps.newHashMap(); + for (MemberMapping memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) { + T obfOldDestEntry = memberMapping.getObfEntry(obfDestClass); + T obfSourceEntry = getSourceEntryFromDestMapping(memberMapping, obfDestClass, classMatches); + + // but drop the unmatchable things + if (memberMatches.isUnmatchableSourceEntry(obfSourceEntry)) { + doer.removeMemberByObf(classMapping, obfOldDestEntry); + continue; + } + + T obfNewDestEntry = memberMatches.matches().get(obfSourceEntry); + if (obfNewDestEntry != null && !obfOldDestEntry.getName().equals(obfNewDestEntry.getName())) { + renames.put(obfOldDestEntry, obfNewDestEntry); + } + } + + if (!renames.isEmpty()) { + + // apply to this class (should never need more than n passes) + int numRenamesAppliedThisRound; + do { + numRenamesAppliedThisRound = 0; + + for (MemberMapping memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) { + T obfOldDestEntry = memberMapping.getObfEntry(obfDestClass); + T obfNewDestEntry = renames.get(obfOldDestEntry); + if (obfNewDestEntry != null) { + // make sure this rename won't cause a collision + // otherwise, save it for the next round and try again next time + if (!doer.hasObfMember(classMapping, obfNewDestEntry)) { + doer.setUpdateObfMember(classMapping, memberMapping, obfNewDestEntry); + renames.remove(obfOldDestEntry); + numRenamesAppliedThisRound++; + } + } + } + } while (numRenamesAppliedThisRound > 0); + + if (!renames.isEmpty()) { + System.err.println(String.format("WARNING: Couldn't apply all the renames for class %s. %d renames left.", + classMapping.getObfFullName(), renames.size() + )); + for (Map.Entry entry : renames.entrySet()) { + System.err.println(String.format("\t%s -> %s", entry.getKey().getName(), entry.getValue().getName())); + } + } + } + + // recurse + for (ClassMapping innerClassMapping : classMapping.innerClasses()) { + applyMemberMatches(innerClassMapping, classMatches, memberMatches, doer); + } + } + + private static T getSourceEntryFromDestMapping(MemberMapping destMemberMapping, ClassEntry obfDestClass, ClassMatches classMatches) { + return translate(destMemberMapping.getObfEntry(obfDestClass), classMatches.getUniqueMatches().inverse()); + } + + public interface Doer { + Collection getDroppedEntries(MappingsChecker checker); + + Collection getObfEntries(JarIndex jarIndex); + + Collection> getMappings(ClassMapping destClassMapping); + + Set filterEntries(Collection obfEntries, T obfSourceEntry, ClassMatches classMatches); + + void setUpdateObfMember(ClassMapping classMapping, MemberMapping memberMapping, T newEntry); + + boolean hasObfMember(ClassMapping classMapping, T obfEntry); + + void removeMemberByObf(ClassMapping classMapping, T obfEntry); + } } diff --git a/src/main/java/cuchaz/enigma/convert/MatchesReader.java b/src/main/java/cuchaz/enigma/convert/MatchesReader.java index d86d6c2a..1cf50fa4 100644 --- a/src/main/java/cuchaz/enigma/convert/MatchesReader.java +++ b/src/main/java/cuchaz/enigma/convert/MatchesReader.java @@ -8,99 +8,98 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.convert; import com.google.common.collect.Lists; +import cuchaz.enigma.mapping.*; import java.io.*; import java.nio.charset.Charset; import java.util.Collection; import java.util.List; -import cuchaz.enigma.mapping.*; - - public class MatchesReader { - public static ClassMatches readClasses(File file) - throws IOException { - try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8")))) { - ClassMatches matches = new ClassMatches(); - String line; - while ((line = in.readLine()) != null) { - matches.add(readClassMatch(line)); - } - return matches; - } - } + public static ClassMatches readClasses(File file) + throws IOException { + try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8")))) { + ClassMatches matches = new ClassMatches(); + String line; + while ((line = in.readLine()) != null) { + matches.add(readClassMatch(line)); + } + return matches; + } + } - private static ClassMatch readClassMatch(String line) { - String[] sides = line.split(":", 2); - return new ClassMatch(readClasses(sides[0]), readClasses(sides[1])); - } + private static ClassMatch readClassMatch(String line) { + String[] sides = line.split(":", 2); + return new ClassMatch(readClasses(sides[0]), readClasses(sides[1])); + } - private static Collection readClasses(String in) { - List entries = Lists.newArrayList(); - for (String className : in.split(",")) { - className = className.trim(); - if (className.length() > 0) { - entries.add(new ClassEntry(className)); - } - } - return entries; - } + private static Collection readClasses(String in) { + List entries = Lists.newArrayList(); + for (String className : in.split(",")) { + className = className.trim(); + if (!className.isEmpty()) { + entries.add(new ClassEntry(className)); + } + } + return entries; + } - public static MemberMatches readMembers(File file) - throws IOException { - try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8")))) { - MemberMatches matches = new MemberMatches<>(); - String line; - while ((line = in.readLine()) != null) { - readMemberMatch(matches, line); - } - return matches; - } - } + public static MemberMatches readMembers(File file) + throws IOException { + try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8")))) { + MemberMatches matches = new MemberMatches<>(); + String line; + while ((line = in.readLine()) != null) { + readMemberMatch(matches, line); + } + return matches; + } + } - private static void readMemberMatch(MemberMatches matches, String line) { - if (line.startsWith("!")) { - T source = readEntry(line.substring(1)); - matches.addUnmatchableSourceEntry(source); - } else { - String[] parts = line.split(":", 2); - T source = readEntry(parts[0]); - T dest = readEntry(parts[1]); - if (source != null && dest != null) { - matches.addMatch(source, dest); - } else if (source != null) { - matches.addUnmatchedSourceEntry(source); - } else if (dest != null) { - matches.addUnmatchedDestEntry(dest); - } - } - } + private static void readMemberMatch(MemberMatches matches, String line) { + if (line.startsWith("!")) { + T source = readEntry(line.substring(1)); + matches.addUnmatchableSourceEntry(source); + } else { + String[] parts = line.split(":", 2); + T source = readEntry(parts[0]); + T dest = readEntry(parts[1]); + if (source != null && dest != null) { + matches.addMatch(source, dest); + } else if (source != null) { + matches.addUnmatchedSourceEntry(source); + } else if (dest != null) { + matches.addUnmatchedDestEntry(dest); + } + } + } - @SuppressWarnings("unchecked") - private static T readEntry(String in) { - if (in.length() <= 0) { - return null; - } - String[] parts = in.split(" "); - 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); - } - } - } + @SuppressWarnings("unchecked") + private static T readEntry(String in) { + if (in.length() <= 0) { + return null; + } + String[] parts = in.split(" "); + 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/main/java/cuchaz/enigma/convert/MatchesWriter.java b/src/main/java/cuchaz/enigma/convert/MatchesWriter.java index dccbf6f1..8fe73265 100644 --- a/src/main/java/cuchaz/enigma/convert/MatchesWriter.java +++ b/src/main/java/cuchaz/enigma/convert/MatchesWriter.java @@ -8,113 +8,116 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ -package cuchaz.enigma.convert; -import java.io.*; -import java.nio.charset.Charset; -import java.util.Map; +package cuchaz.enigma.convert; import cuchaz.enigma.mapping.BehaviorEntry; import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.Entry; import cuchaz.enigma.mapping.FieldEntry; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.nio.charset.Charset; +import java.util.Map; public class MatchesWriter { - public static void writeClasses(ClassMatches matches, File file) - throws IOException { - try (OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(file), Charset.forName("UTF-8"))) { - for (ClassMatch match : matches) { - writeClassMatch(out, match); - } - } - } + public static void writeClasses(ClassMatches matches, File file) + throws IOException { + try (OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(file), Charset.forName("UTF-8"))) { + for (ClassMatch match : matches) { + writeClassMatch(out, match); + } + } + } - private static void writeClassMatch(OutputStreamWriter out, ClassMatch match) - throws IOException { - writeClasses(out, match.sourceClasses); - out.write(":"); - writeClasses(out, match.destClasses); - out.write("\n"); - } + private static void writeClassMatch(OutputStreamWriter out, ClassMatch match) + throws IOException { + writeClasses(out, match.sourceClasses); + out.write(":"); + writeClasses(out, match.destClasses); + out.write("\n"); + } - private static void writeClasses(OutputStreamWriter out, Iterable classes) - throws IOException { - boolean isFirst = true; - for (ClassEntry entry : classes) { - if (isFirst) { - isFirst = false; - } else { - out.write(","); - } - out.write(entry.toString()); - } - } + private static void writeClasses(OutputStreamWriter out, Iterable classes) + throws IOException { + boolean isFirst = true; + for (ClassEntry entry : classes) { + if (isFirst) { + isFirst = false; + } else { + out.write(","); + } + out.write(entry.toString()); + } + } - public static void writeMembers(MemberMatches matches, File file) - throws IOException { - try (OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(file), Charset.forName("UTF-8"))) { - for (Map.Entry match : matches.matches().entrySet()) { - writeMemberMatch(out, match.getKey(), match.getValue()); - } - for (T entry : matches.getUnmatchedSourceEntries()) { - writeMemberMatch(out, entry, null); - } - for (T entry : matches.getUnmatchedDestEntries()) { - writeMemberMatch(out, null, entry); - } - for (T entry : matches.getUnmatchableSourceEntries()) { - writeUnmatchableEntry(out, entry); - } - } - } + public static void writeMembers(MemberMatches matches, File file) + throws IOException { + try (OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(file), Charset.forName("UTF-8"))) { + for (Map.Entry match : matches.matches().entrySet()) { + writeMemberMatch(out, match.getKey(), match.getValue()); + } + for (T entry : matches.getUnmatchedSourceEntries()) { + writeMemberMatch(out, entry, null); + } + for (T entry : matches.getUnmatchedDestEntries()) { + writeMemberMatch(out, null, entry); + } + for (T entry : matches.getUnmatchableSourceEntries()) { + writeUnmatchableEntry(out, entry); + } + } + } - private static void writeMemberMatch(OutputStreamWriter out, T source, T dest) - throws IOException { - if (source != null) { - writeEntry(out, source); - } - out.write(":"); - if (dest != null) { - writeEntry(out, dest); - } - out.write("\n"); - } + private static void writeMemberMatch(OutputStreamWriter out, T source, T dest) + throws IOException { + if (source != null) { + writeEntry(out, source); + } + out.write(":"); + if (dest != null) { + writeEntry(out, dest); + } + out.write("\n"); + } - private static void writeUnmatchableEntry(OutputStreamWriter out, T entry) - throws IOException { - out.write("!"); - writeEntry(out, entry); - out.write("\n"); - } + private static void writeUnmatchableEntry(OutputStreamWriter out, T entry) + throws IOException { + out.write("!"); + writeEntry(out, entry); + out.write("\n"); + } - private static void writeEntry(OutputStreamWriter 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 writeEntry(OutputStreamWriter 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(OutputStreamWriter out, FieldEntry fieldEntry) - throws IOException { - out.write(fieldEntry.getClassName()); - out.write(" "); - out.write(fieldEntry.getName()); - out.write(" "); - out.write(fieldEntry.getType().toString()); - } + private static void writeField(OutputStreamWriter out, FieldEntry fieldEntry) + throws IOException { + out.write(fieldEntry.getClassName()); + out.write(" "); + out.write(fieldEntry.getName()); + out.write(" "); + out.write(fieldEntry.getType().toString()); + } - private static void writeBehavior(OutputStreamWriter 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()); - } - } + private static void writeBehavior(OutputStreamWriter 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/main/java/cuchaz/enigma/convert/MemberMatches.java b/src/main/java/cuchaz/enigma/convert/MemberMatches.java index 51cee859..bd743115 100644 --- a/src/main/java/cuchaz/enigma/convert/MemberMatches.java +++ b/src/main/java/cuchaz/enigma/convert/MemberMatches.java @@ -8,6 +8,7 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.convert; import com.google.common.collect.*; @@ -18,165 +19,161 @@ import cuchaz.enigma.mapping.Entry; import java.util.Collection; import java.util.Set; - public class MemberMatches { - private BiMap matches; - private Multimap matchedSourceEntries; - private Multimap unmatchedSourceEntries; - private Multimap unmatchedDestEntries; - private Multimap unmatchableSourceEntries; - - public MemberMatches() { - matches = HashBiMap.create(); - matchedSourceEntries = HashMultimap.create(); - unmatchedSourceEntries = HashMultimap.create(); - unmatchedDestEntries = HashMultimap.create(); - unmatchableSourceEntries = HashMultimap.create(); - } - - public void addMatch(T srcEntry, T destEntry) { - boolean wasAdded = matches.put(srcEntry, destEntry) == null; - assert (wasAdded); - wasAdded = matchedSourceEntries.put(srcEntry.getClassEntry(), srcEntry); - assert (wasAdded); - } - - public void addUnmatchedSourceEntry(T sourceEntry) { - boolean wasAdded = unmatchedSourceEntries.put(sourceEntry.getClassEntry(), sourceEntry); - assert (wasAdded); - } - - public void addUnmatchedSourceEntries(Iterable sourceEntries) { - for (T sourceEntry : sourceEntries) { - addUnmatchedSourceEntry(sourceEntry); - } - } - - public void addUnmatchedDestEntry(T destEntry) { - if (destEntry.getName().equals("") || destEntry.getName().equals("")) - return; - boolean wasAdded = 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 = unmatchableSourceEntries.put(sourceEntry.getClassEntry(), sourceEntry); - assert (wasAdded); - } - - public Set getSourceClassesWithUnmatchedEntries() { - return unmatchedSourceEntries.keySet(); - } - - public Collection getSourceClassesWithoutUnmatchedEntries() { - Set out = Sets.newHashSet(); - out.addAll(matchedSourceEntries.keySet()); - out.removeAll(unmatchedSourceEntries.keySet()); - return out; - } - - public Collection getUnmatchedSourceEntries() { - return unmatchedSourceEntries.values(); - } - - public Collection getUnmatchedSourceEntries(ClassEntry sourceClass) { - return unmatchedSourceEntries.get(sourceClass); - } - - public Collection getUnmatchedDestEntries() { - return unmatchedDestEntries.values(); - } - - public Collection getUnmatchedDestEntries(ClassEntry destClass) { - return unmatchedDestEntries.get(destClass); - } - - public Collection getUnmatchableSourceEntries() { - return unmatchableSourceEntries.values(); - } - - public boolean hasSource(T sourceEntry) { - return matches.containsKey(sourceEntry) || unmatchedSourceEntries.containsValue(sourceEntry); - } - - public boolean hasDest(T destEntry) { - return matches.containsValue(destEntry) || unmatchedDestEntries.containsValue(destEntry); - } - - public BiMap matches() { - return matches; - } - - public boolean isMatchedSourceEntry(T sourceEntry) { - return matches.containsKey(sourceEntry); - } - - public boolean isMatchedDestEntry(T destEntry) { - return matches.containsValue(destEntry); - } - - public boolean isUnmatchableSourceEntry(T sourceEntry) { - return unmatchableSourceEntries.containsEntry(sourceEntry.getClassEntry(), sourceEntry); - } - public void makeMatch(T sourceEntry, T destEntry) { - makeMatch(sourceEntry, destEntry, null, null); - } - - public void makeMatch(T sourceEntry, T destEntry, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { - if (sourceDeobfuscator != null && destDeobfuscator != null) - { - makeMatch(sourceEntry, destEntry); - sourceEntry = (T) sourceEntry.cloneToNewClass(sourceDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(sourceEntry, true)); - destEntry = (T) destEntry.cloneToNewClass(destDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(destEntry, true)); - } - boolean wasRemoved = unmatchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry); - assert (wasRemoved); - wasRemoved = unmatchedDestEntries.remove(destEntry.getClassEntry(), destEntry); - assert (wasRemoved); - addMatch(sourceEntry, destEntry); - } - - public boolean isMatched(T sourceEntry, T destEntry) { - T match = matches.get(sourceEntry); - return match != null && match.equals(destEntry); - } - - public void unmakeMatch(T sourceEntry, T destEntry, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) - { - if (sourceDeobfuscator != null && destDeobfuscator != null) - { - unmakeMatch(sourceEntry, destEntry, null, null); - sourceEntry = (T) sourceEntry.cloneToNewClass( - sourceDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(sourceEntry, true)); - destEntry = (T) destEntry.cloneToNewClass( - destDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(destEntry, true)); - } - - boolean wasRemoved = matches.remove(sourceEntry) != null; - assert (wasRemoved); - wasRemoved = matchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry); - assert (wasRemoved); - addUnmatchedSourceEntry(sourceEntry); - addUnmatchedDestEntry(destEntry); - } - - public void makeSourceUnmatchable(T sourceEntry, Deobfuscator sourceDeobfuscator) { - if (sourceDeobfuscator != null) - { - makeSourceUnmatchable(sourceEntry, null); - sourceEntry = (T) sourceEntry.cloneToNewClass( - sourceDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(sourceEntry, true)); - } - assert (!isMatchedSourceEntry(sourceEntry)); - boolean wasRemoved = unmatchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry); - assert (wasRemoved); - addUnmatchableSourceEntry(sourceEntry); - } + private BiMap matches; + private Multimap matchedSourceEntries; + private Multimap unmatchedSourceEntries; + private Multimap unmatchedDestEntries; + private Multimap unmatchableSourceEntries; + + public MemberMatches() { + matches = HashBiMap.create(); + matchedSourceEntries = HashMultimap.create(); + unmatchedSourceEntries = HashMultimap.create(); + unmatchedDestEntries = HashMultimap.create(); + unmatchableSourceEntries = HashMultimap.create(); + } + + public void addMatch(T srcEntry, T destEntry) { + boolean wasAdded = matches.put(srcEntry, destEntry) == null; + assert (wasAdded); + wasAdded = matchedSourceEntries.put(srcEntry.getClassEntry(), srcEntry); + assert (wasAdded); + } + + public void addUnmatchedSourceEntry(T sourceEntry) { + boolean wasAdded = unmatchedSourceEntries.put(sourceEntry.getClassEntry(), sourceEntry); + assert (wasAdded); + } + + public void addUnmatchedSourceEntries(Iterable sourceEntries) { + for (T sourceEntry : sourceEntries) { + addUnmatchedSourceEntry(sourceEntry); + } + } + + public void addUnmatchedDestEntry(T destEntry) { + if (destEntry.getName().equals("") || destEntry.getName().equals("")) + return; + boolean wasAdded = 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 = unmatchableSourceEntries.put(sourceEntry.getClassEntry(), sourceEntry); + assert (wasAdded); + } + + public Set getSourceClassesWithUnmatchedEntries() { + return unmatchedSourceEntries.keySet(); + } + + public Collection getSourceClassesWithoutUnmatchedEntries() { + Set out = Sets.newHashSet(); + out.addAll(matchedSourceEntries.keySet()); + out.removeAll(unmatchedSourceEntries.keySet()); + return out; + } + + public Collection getUnmatchedSourceEntries() { + return unmatchedSourceEntries.values(); + } + + public Collection getUnmatchedSourceEntries(ClassEntry sourceClass) { + return unmatchedSourceEntries.get(sourceClass); + } + + public Collection getUnmatchedDestEntries() { + return unmatchedDestEntries.values(); + } + + public Collection getUnmatchedDestEntries(ClassEntry destClass) { + return unmatchedDestEntries.get(destClass); + } + + public Collection getUnmatchableSourceEntries() { + return unmatchableSourceEntries.values(); + } + + public boolean hasSource(T sourceEntry) { + return matches.containsKey(sourceEntry) || unmatchedSourceEntries.containsValue(sourceEntry); + } + + public boolean hasDest(T destEntry) { + return matches.containsValue(destEntry) || unmatchedDestEntries.containsValue(destEntry); + } + + public BiMap matches() { + return matches; + } + + public boolean isMatchedSourceEntry(T sourceEntry) { + return matches.containsKey(sourceEntry); + } + + public boolean isMatchedDestEntry(T destEntry) { + return matches.containsValue(destEntry); + } + + public boolean isUnmatchableSourceEntry(T sourceEntry) { + return unmatchableSourceEntries.containsEntry(sourceEntry.getClassEntry(), sourceEntry); + } + + public void makeMatch(T sourceEntry, T destEntry) { + makeMatch(sourceEntry, destEntry, null, null); + } + + public void makeMatch(T sourceEntry, T destEntry, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { + if (sourceDeobfuscator != null && destDeobfuscator != null) { + makeMatch(sourceEntry, destEntry); + sourceEntry = (T) sourceEntry.cloneToNewClass(sourceDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(sourceEntry, true)); + destEntry = (T) destEntry.cloneToNewClass(destDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(destEntry, true)); + } + boolean wasRemoved = unmatchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry); + assert (wasRemoved); + wasRemoved = unmatchedDestEntries.remove(destEntry.getClassEntry(), destEntry); + assert (wasRemoved); + addMatch(sourceEntry, destEntry); + } + + public boolean isMatched(T sourceEntry, T destEntry) { + T match = matches.get(sourceEntry); + return match != null && match.equals(destEntry); + } + + public void unmakeMatch(T sourceEntry, T destEntry, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { + if (sourceDeobfuscator != null && destDeobfuscator != null) { + unmakeMatch(sourceEntry, destEntry, null, null); + sourceEntry = (T) sourceEntry.cloneToNewClass( + sourceDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(sourceEntry, true)); + destEntry = (T) destEntry.cloneToNewClass( + destDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(destEntry, true)); + } + + boolean wasRemoved = matches.remove(sourceEntry) != null; + assert (wasRemoved); + wasRemoved = matchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry); + assert (wasRemoved); + addUnmatchedSourceEntry(sourceEntry); + addUnmatchedDestEntry(destEntry); + } + + public void makeSourceUnmatchable(T sourceEntry, Deobfuscator sourceDeobfuscator) { + if (sourceDeobfuscator != null) { + makeSourceUnmatchable(sourceEntry, null); + sourceEntry = (T) sourceEntry.cloneToNewClass( + sourceDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(sourceEntry, true)); + } + assert (!isMatchedSourceEntry(sourceEntry)); + boolean wasRemoved = unmatchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry); + assert (wasRemoved); + addUnmatchableSourceEntry(sourceEntry); + } } diff --git a/src/main/java/cuchaz/enigma/gui/BrowserCaret.java b/src/main/java/cuchaz/enigma/gui/BrowserCaret.java index bcdff51e..af105dbd 100644 --- a/src/main/java/cuchaz/enigma/gui/BrowserCaret.java +++ b/src/main/java/cuchaz/enigma/gui/BrowserCaret.java @@ -8,20 +8,21 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.gui; import javax.swing.text.DefaultCaret; public class BrowserCaret extends DefaultCaret { - @Override - public boolean isSelectionVisible() { - return true; - } + @Override + public boolean isSelectionVisible() { + return true; + } - @Override - public boolean isVisible() { - return true; - } + @Override + public boolean isVisible() { + return true; + } } diff --git a/src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java b/src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java index dcbe1c5b..05501f40 100644 --- a/src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java +++ b/src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java @@ -8,6 +8,7 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.gui; import com.google.common.collect.BiMap; @@ -31,508 +32,505 @@ import java.util.Collection; import java.util.List; import java.util.Map; - public class ClassMatchingGui { - private enum SourceType { - Matched { - @Override - public Collection getSourceClasses(ClassMatches matches) { - return matches.getUniqueMatches().keySet(); - } - }, - Unmatched { - @Override - public Collection getSourceClasses(ClassMatches matches) { - return matches.getUnmatchedSourceClasses(); - } - }, - Ambiguous { - @Override - public Collection getSourceClasses(ClassMatches matches) { - return matches.getAmbiguouslyMatchedSourceClasses(); - } - }; - - 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 getSourceClasses(ClassMatches matches); - - public static SourceType getDefault() { - return values()[0]; - } - } - - public interface SaveListener { - void save(ClassMatches matches); - } - - // controls - private JFrame frame; - private ClassSelector sourceClasses; - private ClassSelector destClasses; - private CodeReader sourceReader; - private CodeReader destReader; - private JLabel sourceClassLabel; - private JLabel destClassLabel; - private JButton matchButton; - private Map sourceTypeButtons; - private JCheckBox advanceCheck; - private JCheckBox top10Matches; - - private ClassMatches classMatches; - private Deobfuscator sourceDeobfuscator; - private Deobfuscator destDeobfuscator; - private ClassEntry sourceClass; - private ClassEntry destClass; - private SourceType sourceType; - private SaveListener saveListener; - - public ClassMatchingGui(ClassMatches matches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { - - classMatches = matches; - this.sourceDeobfuscator = sourceDeobfuscator; - this.destDeobfuscator = destDeobfuscator; - - // init frame - frame = new JFrame(Constants.NAME + " - Class Matcher"); - final Container pane = frame.getContentPane(); - pane.setLayout(new BorderLayout()); - - // init source side - JPanel sourcePanel = new JPanel(); - sourcePanel.setLayout(new BoxLayout(sourcePanel, BoxLayout.PAGE_AXIS)); - sourcePanel.setPreferredSize(new Dimension(200, 0)); - pane.add(sourcePanel, BorderLayout.WEST); - sourcePanel.add(new JLabel("Source Classes")); - - // init source type radios - JPanel sourceTypePanel = new JPanel(); - sourcePanel.add(sourceTypePanel); - sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS)); - ActionListener sourceTypeListener = event -> setSourceType(SourceType.valueOf(event.getActionCommand())); - ButtonGroup sourceTypeButtons = new ButtonGroup(); - this.sourceTypeButtons = Maps.newHashMap(); - for (SourceType sourceType : SourceType.values()) { - JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons); - this.sourceTypeButtons.put(sourceType, button); - sourceTypePanel.add(button); - } - - sourceClasses = new ClassSelector(null, ClassSelector.DEOBF_CLASS_COMPARATOR, false); - sourceClasses.setSelectionListener(this::setSourceClass); - JScrollPane sourceScroller = new JScrollPane(sourceClasses); - sourcePanel.add(sourceScroller); - - // init dest side - JPanel destPanel = new JPanel(); - destPanel.setLayout(new BoxLayout(destPanel, BoxLayout.PAGE_AXIS)); - destPanel.setPreferredSize(new Dimension(200, 0)); - pane.add(destPanel, BorderLayout.WEST); - destPanel.add(new JLabel("Destination Classes")); - - top10Matches = new JCheckBox("Show only top 10 matches"); - destPanel.add(top10Matches); - top10Matches.addActionListener(event -> toggleTop10Matches()); - - destClasses = new ClassSelector(null, ClassSelector.DEOBF_CLASS_COMPARATOR, false); - destClasses.setSelectionListener(this::setDestClass); - JScrollPane destScroller = new JScrollPane(destClasses); - destPanel.add(destScroller); - - JButton autoMatchButton = new JButton("AutoMatch"); - autoMatchButton.addActionListener(event -> autoMatch()); - destPanel.add(autoMatchButton); - - // init source panels - DefaultSyntaxKit.initKit(); - sourceReader = new CodeReader(); - destReader = new CodeReader(); - - // init all the splits - JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, sourcePanel, new JScrollPane( - sourceReader)); - splitLeft.setResizeWeight(0); // let the right side take all the slack - JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(destReader), destPanel); - splitRight.setResizeWeight(1); // let the left side take all the slack - JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, splitLeft, splitRight); - splitCenter.setResizeWeight(0.5); // resize 50:50 - pane.add(splitCenter, BorderLayout.CENTER); - splitCenter.resetToPreferredSizes(); - - // init bottom panel - JPanel bottomPanel = new JPanel(); - bottomPanel.setLayout(new FlowLayout()); - - sourceClassLabel = new JLabel(); - sourceClassLabel.setHorizontalAlignment(SwingConstants.RIGHT); - destClassLabel = new JLabel(); - destClassLabel.setHorizontalAlignment(SwingConstants.LEFT); - - matchButton = new JButton(); - - advanceCheck = new JCheckBox("Advance to next likely match"); - advanceCheck.addActionListener(event -> { - if (advanceCheck.isSelected()) { - advance(); - } - }); - - bottomPanel.add(sourceClassLabel); - bottomPanel.add(matchButton); - bottomPanel.add(destClassLabel); - bottomPanel.add(advanceCheck); - pane.add(bottomPanel, BorderLayout.SOUTH); - - // show the frame - pane.doLayout(); - frame.setSize(1024, 576); - frame.setMinimumSize(new Dimension(640, 480)); - frame.setVisible(true); - frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - - // init state - updateDestMappings(); - setSourceType(SourceType.getDefault()); - updateMatchButton(); - saveListener = null; - } - - public void setSaveListener(SaveListener val) { - saveListener = val; - } - - private void updateDestMappings() { - try { - Mappings newMappings = MappingsConverter.newMappings(classMatches, - sourceDeobfuscator.getMappings(), sourceDeobfuscator, destDeobfuscator - ); - - // look for dropped mappings - MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex()); - checker.dropBrokenMappings(newMappings); - - // count them - int numDroppedFields = checker.getDroppedFieldMappings().size(); - int numDroppedMethods = checker.getDroppedMethodMappings().size(); - System.out.println(String.format( - "%d mappings from matched classes don't match the dest jar:\n\t%5d fields\n\t%5d methods", - numDroppedFields + numDroppedMethods, - numDroppedFields, - numDroppedMethods - )); - - destDeobfuscator.setMappings(newMappings); - } catch (MappingConflict ex) { - System.out.println(ex.getMessage()); - ex.printStackTrace(); - return; - } - } - - protected void setSourceType(SourceType val) { - - // show the source classes - sourceType = val; - sourceClasses.setClasses(deobfuscateClasses(sourceType.getSourceClasses(classMatches), sourceDeobfuscator)); - - // update counts - for (SourceType sourceType : SourceType.values()) { - sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)", - sourceType.name(), - sourceType.getSourceClasses(classMatches).size() - )); - } - } - - private Collection deobfuscateClasses(Collection in, Deobfuscator deobfuscator) { - List out = Lists.newArrayList(); - for (ClassEntry entry : in) { - - ClassEntry deobf = deobfuscator.deobfuscateEntry(entry); - - // make sure we preserve any scores - if (entry instanceof ScoredClassEntry) { - deobf = new ScoredClassEntry(deobf, ((ScoredClassEntry) entry).getScore()); - } - - out.add(deobf); - } - return out; - } - - protected void setSourceClass(ClassEntry classEntry) { - - Runnable onGetDestClasses = null; - if (advanceCheck.isSelected()) { - onGetDestClasses = this::pickBestDestClass; - } - - setSourceClass(classEntry, onGetDestClasses); - } - - protected void setSourceClass(ClassEntry classEntry, final Runnable onGetDestClasses) { - - // update the current source class - sourceClass = classEntry; - sourceClassLabel.setText(sourceClass != null ? sourceClass.getName() : ""); - - if (sourceClass != null) { - - // show the dest class(es) - ClassMatch match = classMatches.getMatchBySource(sourceDeobfuscator.obfuscateEntry(sourceClass)); - assert (match != null); - if (match.destClasses.isEmpty()) { - - destClasses.setClasses(null); - - // run in a separate thread to keep ui responsive - new Thread(() -> - { - destClasses.setClasses(deobfuscateClasses(getLikelyMatches(sourceClass), destDeobfuscator)); - destClasses.expandAll(); - - if (onGetDestClasses != null) { - onGetDestClasses.run(); - } - }).start(); - - } else { - - destClasses.setClasses(deobfuscateClasses(match.destClasses, destDeobfuscator)); - destClasses.expandAll(); - - if (onGetDestClasses != null) { - onGetDestClasses.run(); - } - } - } - - setDestClass(null); - sourceReader.decompileClass( - sourceClass, sourceDeobfuscator, () -> sourceReader.navigateToClassDeclaration(sourceClass)); - - updateMatchButton(); - } - - private Collection getLikelyMatches(ClassEntry sourceClass) { - - ClassEntry obfSourceClass = sourceDeobfuscator.obfuscateEntry(sourceClass); - - // set up identifiers - ClassNamer namer = new ClassNamer(classMatches.getUniqueMatches()); - ClassIdentifier sourceIdentifier = new ClassIdentifier( - sourceDeobfuscator.getJar(), sourceDeobfuscator.getJarIndex(), - namer.getSourceNamer(), true - ); - ClassIdentifier destIdentifier = new ClassIdentifier( - destDeobfuscator.getJar(), destDeobfuscator.getJarIndex(), - namer.getDestNamer(), true - ); + // controls + private JFrame frame; + private ClassSelector sourceClasses; + private ClassSelector destClasses; + private CodeReader sourceReader; + private CodeReader destReader; + private JLabel sourceClassLabel; + private JLabel destClassLabel; + private JButton matchButton; + private Map sourceTypeButtons; + private JCheckBox advanceCheck; + private JCheckBox top10Matches; + private ClassMatches classMatches; + private Deobfuscator sourceDeobfuscator; + private Deobfuscator destDeobfuscator; + private ClassEntry sourceClass; + private ClassEntry destClass; + private SourceType sourceType; + private SaveListener saveListener; + public ClassMatchingGui(ClassMatches matches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { + + classMatches = matches; + this.sourceDeobfuscator = sourceDeobfuscator; + this.destDeobfuscator = destDeobfuscator; + + // init frame + frame = new JFrame(Constants.NAME + " - Class Matcher"); + final Container pane = frame.getContentPane(); + pane.setLayout(new BorderLayout()); + + // init source side + JPanel sourcePanel = new JPanel(); + sourcePanel.setLayout(new BoxLayout(sourcePanel, BoxLayout.PAGE_AXIS)); + sourcePanel.setPreferredSize(new Dimension(200, 0)); + pane.add(sourcePanel, BorderLayout.WEST); + sourcePanel.add(new JLabel("Source Classes")); + + // init source type radios + JPanel sourceTypePanel = new JPanel(); + sourcePanel.add(sourceTypePanel); + sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS)); + ActionListener sourceTypeListener = event -> setSourceType(SourceType.valueOf(event.getActionCommand())); + ButtonGroup sourceTypeButtons = new ButtonGroup(); + this.sourceTypeButtons = Maps.newHashMap(); + for (SourceType sourceType : SourceType.values()) { + JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons); + this.sourceTypeButtons.put(sourceType, button); + sourceTypePanel.add(button); + } + + sourceClasses = new ClassSelector(null, ClassSelector.DEOBF_CLASS_COMPARATOR, false); + sourceClasses.setSelectionListener(this::setSourceClass); + JScrollPane sourceScroller = new JScrollPane(sourceClasses); + sourcePanel.add(sourceScroller); + + // init dest side + JPanel destPanel = new JPanel(); + destPanel.setLayout(new BoxLayout(destPanel, BoxLayout.PAGE_AXIS)); + destPanel.setPreferredSize(new Dimension(200, 0)); + pane.add(destPanel, BorderLayout.WEST); + destPanel.add(new JLabel("Destination Classes")); + + top10Matches = new JCheckBox("Show only top 10 matches"); + destPanel.add(top10Matches); + top10Matches.addActionListener(event -> toggleTop10Matches()); + + destClasses = new ClassSelector(null, ClassSelector.DEOBF_CLASS_COMPARATOR, false); + destClasses.setSelectionListener(this::setDestClass); + JScrollPane destScroller = new JScrollPane(destClasses); + destPanel.add(destScroller); + + JButton autoMatchButton = new JButton("AutoMatch"); + autoMatchButton.addActionListener(event -> autoMatch()); + destPanel.add(autoMatchButton); + + // init source panels + DefaultSyntaxKit.initKit(); + sourceReader = new CodeReader(); + destReader = new CodeReader(); + + // init all the splits + JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, sourcePanel, new JScrollPane( + sourceReader)); + splitLeft.setResizeWeight(0); // let the right side take all the slack + JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(destReader), destPanel); + splitRight.setResizeWeight(1); // let the left side take all the slack + JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, splitLeft, splitRight); + splitCenter.setResizeWeight(0.5); // resize 50:50 + pane.add(splitCenter, BorderLayout.CENTER); + splitCenter.resetToPreferredSizes(); + + // init bottom panel + JPanel bottomPanel = new JPanel(); + bottomPanel.setLayout(new FlowLayout()); + + sourceClassLabel = new JLabel(); + sourceClassLabel.setHorizontalAlignment(SwingConstants.RIGHT); + destClassLabel = new JLabel(); + destClassLabel.setHorizontalAlignment(SwingConstants.LEFT); + + matchButton = new JButton(); + + advanceCheck = new JCheckBox("Advance to next likely match"); + advanceCheck.addActionListener(event -> { + if (advanceCheck.isSelected()) { + advance(); + } + }); + + bottomPanel.add(sourceClassLabel); + bottomPanel.add(matchButton); + bottomPanel.add(destClassLabel); + bottomPanel.add(advanceCheck); + pane.add(bottomPanel, BorderLayout.SOUTH); + + // show the frame + pane.doLayout(); + frame.setSize(1024, 576); + frame.setMinimumSize(new Dimension(640, 480)); + frame.setVisible(true); + frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + + // init state + updateDestMappings(); + setSourceType(SourceType.getDefault()); + updateMatchButton(); + saveListener = null; + } + + public void setSaveListener(SaveListener val) { + saveListener = val; + } + + private void updateDestMappings() { + try { + Mappings newMappings = MappingsConverter.newMappings(classMatches, + sourceDeobfuscator.getMappings(), sourceDeobfuscator, destDeobfuscator + ); + + // look for dropped mappings + MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex()); + checker.dropBrokenMappings(newMappings); + + // count them + int numDroppedFields = checker.getDroppedFieldMappings().size(); + int numDroppedMethods = checker.getDroppedMethodMappings().size(); + System.out.println(String.format( + "%d mappings from matched classes don't match the dest jar:\n\t%5d fields\n\t%5d methods", + numDroppedFields + numDroppedMethods, + numDroppedFields, + numDroppedMethods + )); + + destDeobfuscator.setMappings(newMappings); + } catch (MappingConflict ex) { + System.out.println(ex.getMessage()); + ex.printStackTrace(); + return; + } + } + + protected void setSourceType(SourceType val) { + + // show the source classes + sourceType = val; + sourceClasses.setClasses(deobfuscateClasses(sourceType.getSourceClasses(classMatches), sourceDeobfuscator)); + + // update counts + for (SourceType sourceType : SourceType.values()) { + sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)", + sourceType.name(), + sourceType.getSourceClasses(classMatches).size() + )); + } + } + + private Collection deobfuscateClasses(Collection in, Deobfuscator deobfuscator) { + List out = Lists.newArrayList(); + for (ClassEntry entry : in) { + + ClassEntry deobf = deobfuscator.deobfuscateEntry(entry); + + // make sure we preserve any scores + if (entry instanceof ScoredClassEntry) { + deobf = new ScoredClassEntry(deobf, ((ScoredClassEntry) entry).getScore()); + } + + out.add(deobf); + } + return out; + } + + protected void setSourceClass(ClassEntry classEntry) { + + Runnable onGetDestClasses = null; + if (advanceCheck.isSelected()) { + onGetDestClasses = this::pickBestDestClass; + } + + setSourceClass(classEntry, onGetDestClasses); + } + + protected void setSourceClass(ClassEntry classEntry, final Runnable onGetDestClasses) { + + // update the current source class + sourceClass = classEntry; + sourceClassLabel.setText(sourceClass != null ? sourceClass.getName() : ""); + + if (sourceClass != null) { + + // show the dest class(es) + ClassMatch match = classMatches.getMatchBySource(sourceDeobfuscator.obfuscateEntry(sourceClass)); + assert (match != null); + if (match.destClasses.isEmpty()) { + + destClasses.setClasses(null); + + // run in a separate thread to keep ui responsive + new Thread(() -> + { + destClasses.setClasses(deobfuscateClasses(getLikelyMatches(sourceClass), destDeobfuscator)); + destClasses.expandAll(); + + if (onGetDestClasses != null) { + onGetDestClasses.run(); + } + }).start(); + + } else { + + destClasses.setClasses(deobfuscateClasses(match.destClasses, destDeobfuscator)); + destClasses.expandAll(); + + if (onGetDestClasses != null) { + onGetDestClasses.run(); + } + } + } + + setDestClass(null); + sourceReader.decompileClass( + sourceClass, sourceDeobfuscator, () -> sourceReader.navigateToClassDeclaration(sourceClass)); + + updateMatchButton(); + } + + private Collection getLikelyMatches(ClassEntry sourceClass) { + + ClassEntry obfSourceClass = sourceDeobfuscator.obfuscateEntry(sourceClass); + + // set up identifiers + ClassNamer namer = new ClassNamer(classMatches.getUniqueMatches()); + ClassIdentifier sourceIdentifier = new ClassIdentifier( + sourceDeobfuscator.getJar(), sourceDeobfuscator.getJarIndex(), + namer.getSourceNamer(), true + ); + ClassIdentifier destIdentifier = new ClassIdentifier( + destDeobfuscator.getJar(), destDeobfuscator.getJarIndex(), + namer.getDestNamer(), true + ); - try { - - // rank all the unmatched dest classes against the source class - ClassIdentity sourceIdentity = sourceIdentifier.identify(obfSourceClass); - List scoredDestClasses = Lists.newArrayList(); - for (ClassEntry unmatchedDestClass : classMatches.getUnmatchedDestClasses()) { - ClassIdentity destIdentity = destIdentifier.identify(unmatchedDestClass); - float score = 100.0f * (sourceIdentity.getMatchScore(destIdentity) + destIdentity.getMatchScore(sourceIdentity)) - / (sourceIdentity.getMaxMatchScore() + destIdentity.getMaxMatchScore()); - scoredDestClasses.add(new ScoredClassEntry(unmatchedDestClass, score)); - } + try { + + // rank all the unmatched dest classes against the source class + ClassIdentity sourceIdentity = sourceIdentifier.identify(obfSourceClass); + List scoredDestClasses = Lists.newArrayList(); + for (ClassEntry unmatchedDestClass : classMatches.getUnmatchedDestClasses()) { + ClassIdentity destIdentity = destIdentifier.identify(unmatchedDestClass); + float score = 100.0f * (sourceIdentity.getMatchScore(destIdentity) + destIdentity.getMatchScore(sourceIdentity)) + / (sourceIdentity.getMaxMatchScore() + destIdentity.getMaxMatchScore()); + scoredDestClasses.add(new ScoredClassEntry(unmatchedDestClass, score)); + } - if (top10Matches.isSelected() && scoredDestClasses.size() > 10) { - scoredDestClasses.sort((a, b) -> - { - ScoredClassEntry sa = (ScoredClassEntry) a; - ScoredClassEntry sb = (ScoredClassEntry) b; - return -Float.compare(sa.getScore(), sb.getScore()); - }); - scoredDestClasses = scoredDestClasses.subList(0, 10); - } + if (top10Matches.isSelected() && scoredDestClasses.size() > 10) { + scoredDestClasses.sort((a, b) -> + { + ScoredClassEntry sa = (ScoredClassEntry) a; + ScoredClassEntry sb = (ScoredClassEntry) b; + return -Float.compare(sa.getScore(), sb.getScore()); + }); + scoredDestClasses = scoredDestClasses.subList(0, 10); + } - return scoredDestClasses; + return scoredDestClasses; - } catch (ClassNotFoundException ex) { - throw new Error("Unable to find class " + ex.getMessage()); - } - } + } catch (ClassNotFoundException ex) { + throw new Error("Unable to find class " + ex.getMessage()); + } + } - protected void setDestClass(ClassEntry classEntry) { + protected void setDestClass(ClassEntry classEntry) { - // update the current source class - destClass = classEntry; - destClassLabel.setText(destClass != null ? destClass.getName() : ""); - - destReader.decompileClass(destClass, destDeobfuscator, () -> destReader.navigateToClassDeclaration(destClass)); - - updateMatchButton(); - } - - private void updateMatchButton() { - - ClassEntry obfSource = sourceDeobfuscator.obfuscateEntry(sourceClass); - ClassEntry obfDest = destDeobfuscator.obfuscateEntry(destClass); - - BiMap uniqueMatches = classMatches.getUniqueMatches(); - boolean twoSelected = sourceClass != null && destClass != null; - boolean isMatched = uniqueMatches.containsKey(obfSource) && uniqueMatches.containsValue(obfDest); - boolean canMatch = !uniqueMatches.containsKey(obfSource) && !uniqueMatches.containsValue(obfDest); - - GuiTricks.deactivateButton(matchButton); - if (twoSelected) { - if (isMatched) { - GuiTricks.activateButton(matchButton, "Unmatch", event -> onUnmatchClick()); - } else if (canMatch) { - GuiTricks.activateButton(matchButton, "Match", event -> onMatchClick()); - } - } - } - - private void onMatchClick() { - // precondition: source and dest classes are set correctly - - ClassEntry obfSource = sourceDeobfuscator.obfuscateEntry(sourceClass); - ClassEntry obfDest = destDeobfuscator.obfuscateEntry(destClass); - - // remove the classes from their match - classMatches.removeSource(obfSource); - classMatches.removeDest(obfDest); - - // add them as matched classes - classMatches.add(new ClassMatch(obfSource, obfDest)); - - ClassEntry nextClass = null; - if (advanceCheck.isSelected()) { - nextClass = sourceClasses.getNextClass(sourceClass); - } - - save(); - updateMatches(); - - if (nextClass != null) { - advance(nextClass); - } - } - - private void onUnmatchClick() { - // precondition: source and dest classes are set to a unique match - - ClassEntry obfSource = sourceDeobfuscator.obfuscateEntry(sourceClass); - - // remove the source to break the match, then add the source back as unmatched - classMatches.removeSource(obfSource); - classMatches.add(new ClassMatch(obfSource, null)); - - save(); - updateMatches(); - } - - private void updateMatches() { - updateDestMappings(); - setDestClass(null); - destClasses.setClasses(null); - updateMatchButton(); - - // remember where we were in the source tree - String packageName = sourceClasses.getSelectedPackage(); - - setSourceType(sourceType); - - sourceClasses.expandPackage(packageName); - } - - private void save() { - if (saveListener != null) { - saveListener.save(classMatches); - } - } - - private void autoMatch() { - - System.out.println("Automatching..."); - - // compute a new matching - ClassMatching matching = MappingsConverter.computeMatching( - sourceDeobfuscator.getJar(), sourceDeobfuscator.getJarIndex(), - destDeobfuscator.getJar(), destDeobfuscator.getJarIndex(), - classMatches.getUniqueMatches() - ); - ClassMatches newMatches = new ClassMatches(matching.matches()); - System.out.println(String.format("Automatch found %d new matches", - newMatches.getUniqueMatches().size() - classMatches.getUniqueMatches().size() - )); - - // update the current matches - classMatches = newMatches; - save(); - updateMatches(); - } - - private void advance() { - advance(null); - } - - private void advance(ClassEntry sourceClass) { - - // make sure we have a source class - if (sourceClass == null) { - sourceClass = sourceClasses.getSelectedClass(); - if (sourceClass != null) { - sourceClass = sourceClasses.getNextClass(sourceClass); - } else { - sourceClass = sourceClasses.getFirstClass(); - } - } - - // set the source class - setSourceClass(sourceClass, this::pickBestDestClass); - sourceClasses.setSelectionClass(sourceClass); - } - - private void pickBestDestClass() { - - // then, pick the best dest class - ClassEntry firstClass = null; - ScoredClassEntry bestDestClass = null; - for (ClassSelectorPackageNode packageNode : destClasses.packageNodes()) { - for (ClassSelectorClassNode classNode : destClasses.classNodes(packageNode)) { - if (firstClass == null) { - firstClass = classNode.getClassEntry(); - } - if (classNode.getClassEntry() instanceof ScoredClassEntry) { - ScoredClassEntry scoredClass = (ScoredClassEntry) classNode.getClassEntry(); - if (bestDestClass == null || bestDestClass.getScore() < scoredClass.getScore()) { - bestDestClass = scoredClass; - } - } - } - } - - // pick the entry to show - ClassEntry destClass = null; - if (bestDestClass != null) { - destClass = bestDestClass; - } else if (firstClass != null) { - destClass = firstClass; - } - - setDestClass(destClass); - destClasses.setSelectionClass(destClass); - } - - private void toggleTop10Matches() { - if (sourceClass != null) { - destClasses.clearSelection(); - destClasses.setClasses(deobfuscateClasses(getLikelyMatches(sourceClass), destDeobfuscator)); - destClasses.expandAll(); - } - } + // update the current source class + destClass = classEntry; + destClassLabel.setText(destClass != null ? destClass.getName() : ""); + + destReader.decompileClass(destClass, destDeobfuscator, () -> destReader.navigateToClassDeclaration(destClass)); + + updateMatchButton(); + } + + private void updateMatchButton() { + + ClassEntry obfSource = sourceDeobfuscator.obfuscateEntry(sourceClass); + ClassEntry obfDest = destDeobfuscator.obfuscateEntry(destClass); + + BiMap uniqueMatches = classMatches.getUniqueMatches(); + boolean twoSelected = sourceClass != null && destClass != null; + boolean isMatched = uniqueMatches.containsKey(obfSource) && uniqueMatches.containsValue(obfDest); + boolean canMatch = !uniqueMatches.containsKey(obfSource) && !uniqueMatches.containsValue(obfDest); + + GuiTricks.deactivateButton(matchButton); + if (twoSelected) { + if (isMatched) { + GuiTricks.activateButton(matchButton, "Unmatch", event -> onUnmatchClick()); + } else if (canMatch) { + GuiTricks.activateButton(matchButton, "Match", event -> onMatchClick()); + } + } + } + + private void onMatchClick() { + // precondition: source and dest classes are set correctly + + ClassEntry obfSource = sourceDeobfuscator.obfuscateEntry(sourceClass); + ClassEntry obfDest = destDeobfuscator.obfuscateEntry(destClass); + + // remove the classes from their match + classMatches.removeSource(obfSource); + classMatches.removeDest(obfDest); + + // add them as matched classes + classMatches.add(new ClassMatch(obfSource, obfDest)); + + ClassEntry nextClass = null; + if (advanceCheck.isSelected()) { + nextClass = sourceClasses.getNextClass(sourceClass); + } + + save(); + updateMatches(); + + if (nextClass != null) { + advance(nextClass); + } + } + + private void onUnmatchClick() { + // precondition: source and dest classes are set to a unique match + + ClassEntry obfSource = sourceDeobfuscator.obfuscateEntry(sourceClass); + + // remove the source to break the match, then add the source back as unmatched + classMatches.removeSource(obfSource); + classMatches.add(new ClassMatch(obfSource, null)); + + save(); + updateMatches(); + } + + private void updateMatches() { + updateDestMappings(); + setDestClass(null); + destClasses.setClasses(null); + updateMatchButton(); + + // remember where we were in the source tree + String packageName = sourceClasses.getSelectedPackage(); + + setSourceType(sourceType); + + sourceClasses.expandPackage(packageName); + } + + private void save() { + if (saveListener != null) { + saveListener.save(classMatches); + } + } + + private void autoMatch() { + + System.out.println("Automatching..."); + + // compute a new matching + ClassMatching matching = MappingsConverter.computeMatching( + sourceDeobfuscator.getJar(), sourceDeobfuscator.getJarIndex(), + destDeobfuscator.getJar(), destDeobfuscator.getJarIndex(), + classMatches.getUniqueMatches() + ); + ClassMatches newMatches = new ClassMatches(matching.matches()); + System.out.println(String.format("Automatch found %d new matches", + newMatches.getUniqueMatches().size() - classMatches.getUniqueMatches().size() + )); + + // update the current matches + classMatches = newMatches; + save(); + updateMatches(); + } + + private void advance() { + advance(null); + } + + private void advance(ClassEntry sourceClass) { + + // make sure we have a source class + if (sourceClass == null) { + sourceClass = sourceClasses.getSelectedClass(); + if (sourceClass != null) { + sourceClass = sourceClasses.getNextClass(sourceClass); + } else { + sourceClass = sourceClasses.getFirstClass(); + } + } + + // set the source class + setSourceClass(sourceClass, this::pickBestDestClass); + sourceClasses.setSelectionClass(sourceClass); + } + + private void pickBestDestClass() { + + // then, pick the best dest class + ClassEntry firstClass = null; + ScoredClassEntry bestDestClass = null; + for (ClassSelectorPackageNode packageNode : destClasses.packageNodes()) { + for (ClassSelectorClassNode classNode : destClasses.classNodes(packageNode)) { + if (firstClass == null) { + firstClass = classNode.getClassEntry(); + } + if (classNode.getClassEntry() instanceof ScoredClassEntry) { + ScoredClassEntry scoredClass = (ScoredClassEntry) classNode.getClassEntry(); + if (bestDestClass == null || bestDestClass.getScore() < scoredClass.getScore()) { + bestDestClass = scoredClass; + } + } + } + } + + // pick the entry to show + ClassEntry destClass = null; + if (bestDestClass != null) { + destClass = bestDestClass; + } else if (firstClass != null) { + destClass = firstClass; + } + + setDestClass(destClass); + destClasses.setSelectionClass(destClass); + } + + private void toggleTop10Matches() { + if (sourceClass != null) { + destClasses.clearSelection(); + destClasses.setClasses(deobfuscateClasses(getLikelyMatches(sourceClass), destDeobfuscator)); + destClasses.expandAll(); + } + } + + private enum SourceType { + Matched { + @Override + public Collection getSourceClasses(ClassMatches matches) { + return matches.getUniqueMatches().keySet(); + } + }, + Unmatched { + @Override + public Collection getSourceClasses(ClassMatches matches) { + return matches.getUnmatchedSourceClasses(); + } + }, + Ambiguous { + @Override + public Collection getSourceClasses(ClassMatches matches) { + return matches.getAmbiguouslyMatchedSourceClasses(); + } + }; + + public static SourceType getDefault() { + return values()[0]; + } + + 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 getSourceClasses(ClassMatches matches); + } + + public interface SaveListener { + void save(ClassMatches matches); + } } diff --git a/src/main/java/cuchaz/enigma/gui/ClassSelector.java b/src/main/java/cuchaz/enigma/gui/ClassSelector.java index 8ece0a0a..f7d7703f 100644 --- a/src/main/java/cuchaz/enigma/gui/ClassSelector.java +++ b/src/main/java/cuchaz/enigma/gui/ClassSelector.java @@ -8,6 +8,7 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.gui; import com.google.common.collect.ArrayListMultimap; @@ -29,505 +30,470 @@ import java.util.*; public class ClassSelector extends JTree { - public static final Comparator DEOBF_CLASS_COMPARATOR = Comparator.comparing(ClassEntry::getName); - private DefaultMutableTreeNode rootNodes; - - public interface ClassSelectionListener { - void onSelectClass(ClassEntry classEntry); - } - - public interface RenameSelectionListener { - void onSelectionRename(Object prevData, Object data, DefaultMutableTreeNode node); - } - - private ClassSelectionListener selectionListener; - private RenameSelectionListener renameSelectionListener; - private Comparator comparator; - - public ClassSelector(Gui gui, Comparator comparator, boolean isRenamable) { - this.comparator = comparator; - - // configure the tree control - setEditable(gui != null); - setRootVisible(false); - setShowsRootHandles(false); - setModel(null); - - // hook events - addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent event) { - if (selectionListener != null && event.getClickCount() == 2) { - // get the selected node - TreePath path = getSelectionPath(); - if (path != null && path.getLastPathComponent() instanceof ClassSelectorClassNode) { - ClassSelectorClassNode node = (ClassSelectorClassNode) path.getLastPathComponent(); - selectionListener.onSelectClass(node.getClassEntry()); - } - } - } - }); - - if (gui != null) - { - final JTree tree = this; - - final DefaultTreeCellEditor editor = new DefaultTreeCellEditor(tree, - (DefaultTreeCellRenderer) tree.getCellRenderer()) - { - @Override public boolean isCellEditable(EventObject event) - { - return isRenamable && !(event instanceof MouseEvent) && super.isCellEditable(event); - } - }; - this.setCellEditor(editor); - editor.addCellEditorListener(new CellEditorListener() - { - @Override public void editingStopped(ChangeEvent e) - { - String data = editor.getCellEditorValue().toString(); - TreePath path = getSelectionPath(); - - Object realPath = path.getLastPathComponent(); - if (realPath != null && realPath instanceof DefaultMutableTreeNode && data != null) - { - DefaultMutableTreeNode node = (DefaultMutableTreeNode) realPath; - TreeNode parentNode = node.getParent(); - if (parentNode == null) - return; - boolean allowEdit = true; - for (int i = 0; i < parentNode.getChildCount(); i++) - { - TreeNode childNode = parentNode.getChildAt(i); - if (childNode != null && childNode.toString().equals(data) && childNode != node) - { - allowEdit = false; - break; - } - } - if (allowEdit && renameSelectionListener != null) - { - Object prevData = node.getUserObject(); - Object objectData = node.getUserObject() instanceof ClassEntry ? new ClassEntry(((ClassEntry)prevData).getPackageName() + "/" + data) : data; - try - { - renameSelectionListener.onSelectionRename(node.getUserObject(), objectData, node); - node.setUserObject(objectData); // Make sure that it's modified - } catch (IllegalNameException ex) - { - JOptionPane.showOptionDialog(gui.getFrame(), ex.getMessage(), "Enigma - Error", JOptionPane.OK_OPTION, - JOptionPane.ERROR_MESSAGE, null, new String[] {"Ok"}, "OK"); - editor.cancelCellEditing(); - } - } - else - editor.cancelCellEditing(); - } - - } - - @Override public void editingCanceled(ChangeEvent e) - { - // NOP - } - }); - } - // init defaults - this.selectionListener = null; - this.renameSelectionListener = null; - } - - public boolean isDuplicate(Object[] nodes, String data) - { - int count = 0; - - for (Object node : nodes) - { - if (node.toString().equals(data)) - { - count++; - if (count == 2) - return true; - } - } - return false; - } - - public void setSelectionListener(ClassSelectionListener val) { - this.selectionListener = val; - } - - public void setRenameSelectionListener(RenameSelectionListener renameSelectionListener) - { - this.renameSelectionListener = renameSelectionListener; - } - - public void setClasses(Collection classEntries) { - String state = getExpansionState(this, 0); - if (classEntries == null) { - setModel(null); - return; - } - - // build the package names - Map packages = Maps.newHashMap(); - for (ClassEntry classEntry : classEntries) { - packages.put(classEntry.getPackageName(), null); - } - - // sort the packages - List sortedPackageNames = Lists.newArrayList(packages.keySet()); - sortedPackageNames.sort((a, b) -> - { - // I can never keep this rule straight when writing these damn things... - // a < b => -1, a == b => 0, a > b => +1 - - if (b == null || a == null) - { - return 0; - } - - String[] aparts = a.split("/"); - String[] bparts = b.split("/"); - for (int i = 0; true; i++) - { - if (i >= aparts.length) - { - return -1; - } - else if (i >= bparts.length) - { - return 1; - } - - int result = aparts[i].compareTo(bparts[i]); - if (result != 0) - { - return result; - } - } - }); - - // create the rootNodes node and the package nodes - rootNodes = new DefaultMutableTreeNode(); - for (String packageName : sortedPackageNames) { - ClassSelectorPackageNode node = new ClassSelectorPackageNode(packageName); - packages.put(packageName, node); - rootNodes.add(node); - } - - // put the classes into packages - Multimap packagedClassEntries = ArrayListMultimap.create(); - for (ClassEntry classEntry : classEntries) { - packagedClassEntries.put(classEntry.getPackageName(), classEntry); - } - - // build the class nodes - for (String packageName : packagedClassEntries.keySet()) { - // sort the class entries - List classEntriesInPackage = Lists.newArrayList(packagedClassEntries.get(packageName)); - classEntriesInPackage.sort(this.comparator); - - // create the nodes in order - for (ClassEntry classEntry : classEntriesInPackage) { - ClassSelectorPackageNode node = packages.get(packageName); - node.add(new ClassSelectorClassNode(classEntry)); - } - } - - // finally, update the tree control - setModel(new DefaultTreeModel(rootNodes)); - - restoreExpanstionState(this, 0, state); - } - - public ClassEntry getSelectedClass() { - if (!isSelectionEmpty()) { - Object selectedNode = getSelectionPath().getLastPathComponent(); - if (selectedNode instanceof ClassSelectorClassNode) { - ClassSelectorClassNode classNode = (ClassSelectorClassNode)selectedNode; - return classNode.getClassEntry(); - } - } - return null; - } - - public String getSelectedPackage() { - if (!isSelectionEmpty()) { - Object selectedNode = getSelectionPath().getLastPathComponent(); - if (selectedNode instanceof ClassSelectorPackageNode) { - ClassSelectorPackageNode packageNode = (ClassSelectorPackageNode)selectedNode; - return packageNode.getPackageName(); - } else if (selectedNode instanceof ClassSelectorClassNode) { - ClassSelectorClassNode classNode = (ClassSelectorClassNode)selectedNode; - return classNode.getClassEntry().getPackageName(); - } - } - return null; - } - - public boolean isDescendant(TreePath path1, TreePath path2) { - int count1 = path1.getPathCount(); - int count2 = path2.getPathCount(); - if (count1 <= count2) { - return false; - } - while (count1 != count2) { - path1 = path1.getParentPath(); - count1--; - } - return path1.equals(path2); - } - - public String getExpansionState(JTree tree, int row) { - TreePath rowPath = tree.getPathForRow(row); - StringBuilder buf = new StringBuilder(); - int rowCount = tree.getRowCount(); - for (int i = row; i < rowCount; i++) { - TreePath path = tree.getPathForRow(i); - if (i == row || isDescendant(path, rowPath)) { - if (tree.isExpanded(path)) { - buf.append(",").append(String.valueOf(i - row)); - } - } else { - break; - } - } - return buf.toString(); - } - - public void restoreExpanstionState(JTree tree, int row, String expansionState) { - StringTokenizer stok = new StringTokenizer(expansionState, ","); - while (stok.hasMoreTokens()) { - int token = row + Integer.parseInt(stok.nextToken()); - tree.expandRow(token); - } - } - - public List packageNodes() { - List nodes = Lists.newArrayList(); - DefaultMutableTreeNode root = (DefaultMutableTreeNode)getModel().getRoot(); - Enumeration children = root.children(); - while (children.hasMoreElements()) { - ClassSelectorPackageNode packageNode = (ClassSelectorPackageNode)children.nextElement(); - nodes.add(packageNode); - } - return nodes; - } - - public List classNodes(ClassSelectorPackageNode packageNode) { - List nodes = Lists.newArrayList(); - Enumeration children = packageNode.children(); - while (children.hasMoreElements()) { - ClassSelectorClassNode classNode = (ClassSelectorClassNode)children.nextElement(); - nodes.add(classNode); - } - return nodes; - } - - public void expandPackage(String packageName) { - if (packageName == null) { - return; - } - for (ClassSelectorPackageNode packageNode : packageNodes()) { - if (packageNode.getPackageName().equals(packageName)) { - expandPath(new TreePath(new Object[] {getModel().getRoot(), packageNode})); - return; - } - } - } - - public void expandAll() { - for (ClassSelectorPackageNode packageNode : packageNodes()) { - expandPath(new TreePath(new Object[] {getModel().getRoot(), packageNode})); - } - } - - public ClassEntry getFirstClass() { - for (ClassSelectorPackageNode packageNode : packageNodes()) { - for (ClassSelectorClassNode classNode : classNodes(packageNode)) { - return classNode.getClassEntry(); - } - } - return null; - } - - public ClassSelectorPackageNode getPackageNode(ClassEntry entry) { - for (ClassSelectorPackageNode packageNode : packageNodes()) { - if (packageNode.getPackageName().equals(entry.getPackageName())) { - return packageNode; - } - } - return null; - } - - public ClassSelectorPackageNode getPackageNode(ClassSelector selector, ClassEntry entry) - { - ClassSelectorPackageNode packageNode = getPackageNode(entry); - - if (selector != null && packageNode == null && selector.getPackageNode(entry) != null) - return selector.getPackageNode(entry); - return packageNode; - } - - public ClassEntry getNextClass(ClassEntry entry) { - boolean foundIt = false; - for (ClassSelectorPackageNode packageNode : packageNodes()) { - if (!foundIt) { - // skip to the package with our target in it - if (packageNode.getPackageName().equals(entry.getPackageName())) { - for (ClassSelectorClassNode classNode : classNodes(packageNode)) { - if (!foundIt) { - if (classNode.getClassEntry().equals(entry)) { - foundIt = true; - } - } else { - // return the next class - return classNode.getClassEntry(); - } - } - } - } else { - // return the next class - for (ClassSelectorClassNode classNode : classNodes(packageNode)) { - return classNode.getClassEntry(); - } - } - } - return null; - } - - public void setSelectionClass(ClassEntry classEntry) { - expandPackage(classEntry.getPackageName()); - for (ClassSelectorPackageNode packageNode : packageNodes()) { - for (ClassSelectorClassNode classNode : classNodes(packageNode)) { - if (classNode.getClassEntry().equals(classEntry)) { - setSelectionPath(new TreePath(new Object[] {getModel().getRoot(), packageNode, classNode})); - } - } - } - } - - public void removeNode(ClassSelectorPackageNode packageNode, ClassEntry entry) - { - DefaultTreeModel model = (DefaultTreeModel) getModel(); - - if (packageNode == null) - return; - - for (int i = 0; i < packageNode.getChildCount(); i++) - { - DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) packageNode.getChildAt(i); - if (childNode.getUserObject() instanceof ClassEntry && childNode.getUserObject().equals(entry)) - { - model.removeNodeFromParent(childNode); - break; - } - } - } - - public void removeNodeIfEmpty(ClassSelectorPackageNode packageNode) - { - if (packageNode != null && packageNode.getChildCount() == 0) - ((DefaultTreeModel) getModel()).removeNodeFromParent(packageNode); - } - - public void moveClassTree(ClassEntry oldClassEntry, ClassEntry newClassEntry, ClassSelector otherSelector) - { - if (otherSelector == null) - removeNode(getPackageNode(oldClassEntry), oldClassEntry); - insertNode(getOrCreate(newClassEntry), newClassEntry); - } - - public ClassSelectorPackageNode getOrCreate(ClassEntry entry) - { - DefaultTreeModel model = (DefaultTreeModel) getModel(); - ClassSelectorPackageNode newPackageNode = getPackageNode(entry); - if (newPackageNode == null) - { - newPackageNode = new ClassSelectorPackageNode(entry.getPackageName()); - model.insertNodeInto(newPackageNode, (MutableTreeNode) model.getRoot(), getPlacementIndex(newPackageNode)); - } - return newPackageNode; - } - - public void insertNode(ClassSelectorPackageNode packageNode, ClassEntry entry) - { - DefaultTreeModel model = (DefaultTreeModel) getModel(); - ClassSelectorClassNode classNode = new ClassSelectorClassNode(entry); - model.insertNodeInto(classNode, packageNode, getPlacementIndex(packageNode, classNode)); - } - - public void reload() - { - DefaultTreeModel model = (DefaultTreeModel) getModel(); - model.reload(sort(rootNodes)); - } - - private DefaultMutableTreeNode sort(DefaultMutableTreeNode node) { - - for(int i = 0; i < node.getChildCount() - 1; i++) { - DefaultMutableTreeNode child = (DefaultMutableTreeNode) node.getChildAt(i); - if (child == null) - continue; - String nt = child.toString(); - - for(int j = i + 1; j <= node.getChildCount() - 1; j++) { - DefaultMutableTreeNode prevNode = (DefaultMutableTreeNode) node.getChildAt(j); - if (prevNode == null || prevNode.getUserObject() == null) - continue; - String np = prevNode.getUserObject().toString(); - - if(nt.compareToIgnoreCase(np) > 0) { - node.insert(child, j); - node.insert(prevNode, i); - } - } - if(child.getChildCount() > 0) { - sort(child); - } - } - - for(int i = 0; i < node.getChildCount() - 1; i++) { - DefaultMutableTreeNode child = (DefaultMutableTreeNode) node.getChildAt(i); - for(int j = i + 1; j <= node.getChildCount() - 1; j++) { - DefaultMutableTreeNode prevNode = (DefaultMutableTreeNode) node.getChildAt(j); - - if(!prevNode.isLeaf() && child.isLeaf()) { - node.insert(child, j); - node.insert(prevNode, i); - } - } - } - - return node; - } - - private int getPlacementIndex(ClassSelectorPackageNode newPackageNode, ClassSelectorClassNode classNode) - { - List classNodes = classNodes(newPackageNode); - classNodes.add(classNode); - classNodes.sort(Comparator.comparing(ClassSelectorClassNode::toString)); - for (int i = 0; i < classNodes.size(); i++) - if (classNodes.get(i) == classNode) - return i; - - return 0; - } - - private int getPlacementIndex(ClassSelectorPackageNode newPackageNode) - { - List packageNodes = packageNodes(); - if (!packageNodes.contains(newPackageNode)) - { - packageNodes.add(newPackageNode); - packageNodes.sort(Comparator.comparing(ClassSelectorPackageNode::toString)); - } - - for (int i = 0; i < packageNodes.size(); i++) - if (packageNodes.get(i) == newPackageNode) - return i; - - return 0; - } + public static final Comparator DEOBF_CLASS_COMPARATOR = Comparator.comparing(ClassEntry::getName); + private DefaultMutableTreeNode rootNodes; + private ClassSelectionListener selectionListener; + private RenameSelectionListener renameSelectionListener; + private Comparator comparator; + public ClassSelector(Gui gui, Comparator comparator, boolean isRenamable) { + this.comparator = comparator; + + // configure the tree control + setEditable(gui != null); + setRootVisible(false); + setShowsRootHandles(false); + setModel(null); + + // hook events + addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent event) { + if (selectionListener != null && event.getClickCount() == 2) { + // get the selected node + TreePath path = getSelectionPath(); + if (path != null && path.getLastPathComponent() instanceof ClassSelectorClassNode) { + ClassSelectorClassNode node = (ClassSelectorClassNode) path.getLastPathComponent(); + selectionListener.onSelectClass(node.getClassEntry()); + } + } + } + }); + + if (gui != null) { + final JTree tree = this; + + final DefaultTreeCellEditor editor = new DefaultTreeCellEditor(tree, + (DefaultTreeCellRenderer) tree.getCellRenderer()) { + @Override + public boolean isCellEditable(EventObject event) { + return isRenamable && !(event instanceof MouseEvent) && super.isCellEditable(event); + } + }; + this.setCellEditor(editor); + editor.addCellEditorListener(new CellEditorListener() { + @Override + public void editingStopped(ChangeEvent e) { + String data = editor.getCellEditorValue().toString(); + TreePath path = getSelectionPath(); + + Object realPath = path.getLastPathComponent(); + if (realPath != null && realPath instanceof DefaultMutableTreeNode && data != null) { + DefaultMutableTreeNode node = (DefaultMutableTreeNode) realPath; + TreeNode parentNode = node.getParent(); + if (parentNode == null) + return; + boolean allowEdit = true; + for (int i = 0; i < parentNode.getChildCount(); i++) { + TreeNode childNode = parentNode.getChildAt(i); + if (childNode != null && childNode.toString().equals(data) && childNode != node) { + allowEdit = false; + break; + } + } + if (allowEdit && renameSelectionListener != null) { + Object prevData = node.getUserObject(); + Object objectData = node.getUserObject() instanceof ClassEntry ? new ClassEntry(((ClassEntry) prevData).getPackageName() + "/" + data) : data; + try { + renameSelectionListener.onSelectionRename(node.getUserObject(), objectData, node); + node.setUserObject(objectData); // Make sure that it's modified + } catch (IllegalNameException ex) { + JOptionPane.showOptionDialog(gui.getFrame(), ex.getMessage(), "Enigma - Error", JOptionPane.OK_OPTION, + JOptionPane.ERROR_MESSAGE, null, new String[] { "Ok" }, "OK"); + editor.cancelCellEditing(); + } + } else + editor.cancelCellEditing(); + } + + } + + @Override + public void editingCanceled(ChangeEvent e) { + // NOP + } + }); + } + // init defaults + this.selectionListener = null; + this.renameSelectionListener = null; + } + + public boolean isDuplicate(Object[] nodes, String data) { + int count = 0; + + for (Object node : nodes) { + if (node.toString().equals(data)) { + count++; + if (count == 2) + return true; + } + } + return false; + } + + public void setSelectionListener(ClassSelectionListener val) { + this.selectionListener = val; + } + + public void setRenameSelectionListener(RenameSelectionListener renameSelectionListener) { + this.renameSelectionListener = renameSelectionListener; + } + + public void setClasses(Collection classEntries) { + String state = getExpansionState(this, 0); + if (classEntries == null) { + setModel(null); + return; + } + + // build the package names + Map packages = Maps.newHashMap(); + for (ClassEntry classEntry : classEntries) { + packages.put(classEntry.getPackageName(), null); + } + + // sort the packages + List sortedPackageNames = Lists.newArrayList(packages.keySet()); + sortedPackageNames.sort((a, b) -> + { + // I can never keep this rule straight when writing these damn things... + // a < b => -1, a == b => 0, a > b => +1 + + if (b == null || a == null) { + return 0; + } + + String[] aparts = a.split("/"); + String[] bparts = b.split("/"); + for (int i = 0; true; i++) { + if (i >= aparts.length) { + return -1; + } else if (i >= bparts.length) { + return 1; + } + + int result = aparts[i].compareTo(bparts[i]); + if (result != 0) { + return result; + } + } + }); + + // create the rootNodes node and the package nodes + rootNodes = new DefaultMutableTreeNode(); + for (String packageName : sortedPackageNames) { + ClassSelectorPackageNode node = new ClassSelectorPackageNode(packageName); + packages.put(packageName, node); + rootNodes.add(node); + } + + // put the classes into packages + Multimap packagedClassEntries = ArrayListMultimap.create(); + for (ClassEntry classEntry : classEntries) { + packagedClassEntries.put(classEntry.getPackageName(), classEntry); + } + + // build the class nodes + for (String packageName : packagedClassEntries.keySet()) { + // sort the class entries + List classEntriesInPackage = Lists.newArrayList(packagedClassEntries.get(packageName)); + classEntriesInPackage.sort(this.comparator); + + // create the nodes in order + for (ClassEntry classEntry : classEntriesInPackage) { + ClassSelectorPackageNode node = packages.get(packageName); + node.add(new ClassSelectorClassNode(classEntry)); + } + } + + // finally, update the tree control + setModel(new DefaultTreeModel(rootNodes)); + + restoreExpanstionState(this, 0, state); + } + + public ClassEntry getSelectedClass() { + if (!isSelectionEmpty()) { + Object selectedNode = getSelectionPath().getLastPathComponent(); + if (selectedNode instanceof ClassSelectorClassNode) { + ClassSelectorClassNode classNode = (ClassSelectorClassNode) selectedNode; + return classNode.getClassEntry(); + } + } + return null; + } + + public String getSelectedPackage() { + if (!isSelectionEmpty()) { + Object selectedNode = getSelectionPath().getLastPathComponent(); + if (selectedNode instanceof ClassSelectorPackageNode) { + ClassSelectorPackageNode packageNode = (ClassSelectorPackageNode) selectedNode; + return packageNode.getPackageName(); + } else if (selectedNode instanceof ClassSelectorClassNode) { + ClassSelectorClassNode classNode = (ClassSelectorClassNode) selectedNode; + return classNode.getClassEntry().getPackageName(); + } + } + return null; + } + + public boolean isDescendant(TreePath path1, TreePath path2) { + int count1 = path1.getPathCount(); + int count2 = path2.getPathCount(); + if (count1 <= count2) { + return false; + } + while (count1 != count2) { + path1 = path1.getParentPath(); + count1--; + } + return path1.equals(path2); + } + + public String getExpansionState(JTree tree, int row) { + TreePath rowPath = tree.getPathForRow(row); + StringBuilder buf = new StringBuilder(); + int rowCount = tree.getRowCount(); + for (int i = row; i < rowCount; i++) { + TreePath path = tree.getPathForRow(i); + if (i == row || isDescendant(path, rowPath)) { + if (tree.isExpanded(path)) { + buf.append(",").append((i - row)); + } + } else { + break; + } + } + return buf.toString(); + } + + public void restoreExpanstionState(JTree tree, int row, String expansionState) { + StringTokenizer stok = new StringTokenizer(expansionState, ","); + while (stok.hasMoreTokens()) { + int token = row + Integer.parseInt(stok.nextToken()); + tree.expandRow(token); + } + } + + public List packageNodes() { + List nodes = Lists.newArrayList(); + DefaultMutableTreeNode root = (DefaultMutableTreeNode) getModel().getRoot(); + Enumeration children = root.children(); + while (children.hasMoreElements()) { + ClassSelectorPackageNode packageNode = (ClassSelectorPackageNode) children.nextElement(); + nodes.add(packageNode); + } + return nodes; + } + + public List classNodes(ClassSelectorPackageNode packageNode) { + List nodes = Lists.newArrayList(); + Enumeration children = packageNode.children(); + while (children.hasMoreElements()) { + ClassSelectorClassNode classNode = (ClassSelectorClassNode) children.nextElement(); + nodes.add(classNode); + } + return nodes; + } + + public void expandPackage(String packageName) { + if (packageName == null) { + return; + } + for (ClassSelectorPackageNode packageNode : packageNodes()) { + if (packageNode.getPackageName().equals(packageName)) { + expandPath(new TreePath(new Object[] { getModel().getRoot(), packageNode })); + return; + } + } + } + + public void expandAll() { + for (ClassSelectorPackageNode packageNode : packageNodes()) { + expandPath(new TreePath(new Object[] { getModel().getRoot(), packageNode })); + } + } + + public ClassEntry getFirstClass() { + for (ClassSelectorPackageNode packageNode : packageNodes()) { + for (ClassSelectorClassNode classNode : classNodes(packageNode)) { + return classNode.getClassEntry(); + } + } + return null; + } + + public ClassSelectorPackageNode getPackageNode(ClassEntry entry) { + for (ClassSelectorPackageNode packageNode : packageNodes()) { + if (packageNode.getPackageName().equals(entry.getPackageName())) { + return packageNode; + } + } + return null; + } + + public ClassSelectorPackageNode getPackageNode(ClassSelector selector, ClassEntry entry) { + ClassSelectorPackageNode packageNode = getPackageNode(entry); + + if (selector != null && packageNode == null && selector.getPackageNode(entry) != null) + return selector.getPackageNode(entry); + return packageNode; + } + + public ClassEntry getNextClass(ClassEntry entry) { + boolean foundIt = false; + for (ClassSelectorPackageNode packageNode : packageNodes()) { + if (!foundIt) { + // skip to the package with our target in it + if (packageNode.getPackageName().equals(entry.getPackageName())) { + for (ClassSelectorClassNode classNode : classNodes(packageNode)) { + if (!foundIt) { + if (classNode.getClassEntry().equals(entry)) { + foundIt = true; + } + } else { + // return the next class + return classNode.getClassEntry(); + } + } + } + } else { + // return the next class + for (ClassSelectorClassNode classNode : classNodes(packageNode)) { + return classNode.getClassEntry(); + } + } + } + return null; + } + + public void setSelectionClass(ClassEntry classEntry) { + expandPackage(classEntry.getPackageName()); + for (ClassSelectorPackageNode packageNode : packageNodes()) { + for (ClassSelectorClassNode classNode : classNodes(packageNode)) { + if (classNode.getClassEntry().equals(classEntry)) { + setSelectionPath(new TreePath(new Object[] { getModel().getRoot(), packageNode, classNode })); + } + } + } + } + + public void removeNode(ClassSelectorPackageNode packageNode, ClassEntry entry) { + DefaultTreeModel model = (DefaultTreeModel) getModel(); + + if (packageNode == null) + return; + + for (int i = 0; i < packageNode.getChildCount(); i++) { + DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) packageNode.getChildAt(i); + if (childNode.getUserObject() instanceof ClassEntry && childNode.getUserObject().equals(entry)) { + model.removeNodeFromParent(childNode); + break; + } + } + } + + public void removeNodeIfEmpty(ClassSelectorPackageNode packageNode) { + if (packageNode != null && packageNode.getChildCount() == 0) + ((DefaultTreeModel) getModel()).removeNodeFromParent(packageNode); + } + + public void moveClassTree(ClassEntry oldClassEntry, ClassEntry newClassEntry, ClassSelector otherSelector) { + if (otherSelector == null) + removeNode(getPackageNode(oldClassEntry), oldClassEntry); + insertNode(getOrCreate(newClassEntry), newClassEntry); + } + + public ClassSelectorPackageNode getOrCreate(ClassEntry entry) { + DefaultTreeModel model = (DefaultTreeModel) getModel(); + ClassSelectorPackageNode newPackageNode = getPackageNode(entry); + if (newPackageNode == null) { + newPackageNode = new ClassSelectorPackageNode(entry.getPackageName()); + model.insertNodeInto(newPackageNode, (MutableTreeNode) model.getRoot(), getPlacementIndex(newPackageNode)); + } + return newPackageNode; + } + + public void insertNode(ClassSelectorPackageNode packageNode, ClassEntry entry) { + DefaultTreeModel model = (DefaultTreeModel) getModel(); + ClassSelectorClassNode classNode = new ClassSelectorClassNode(entry); + model.insertNodeInto(classNode, packageNode, getPlacementIndex(packageNode, classNode)); + } + + public void reload() { + DefaultTreeModel model = (DefaultTreeModel) getModel(); + model.reload(sort(rootNodes)); + } + + private DefaultMutableTreeNode sort(DefaultMutableTreeNode node) { + + for (int i = 0; i < node.getChildCount() - 1; i++) { + DefaultMutableTreeNode child = (DefaultMutableTreeNode) node.getChildAt(i); + if (child == null) + continue; + String nt = child.toString(); + + for (int j = i + 1; j <= node.getChildCount() - 1; j++) { + DefaultMutableTreeNode prevNode = (DefaultMutableTreeNode) node.getChildAt(j); + if (prevNode == null || prevNode.getUserObject() == null) + continue; + String np = prevNode.getUserObject().toString(); + + if (nt.compareToIgnoreCase(np) > 0) { + node.insert(child, j); + node.insert(prevNode, i); + } + } + if (child.getChildCount() > 0) { + sort(child); + } + } + + for (int i = 0; i < node.getChildCount() - 1; i++) { + DefaultMutableTreeNode child = (DefaultMutableTreeNode) node.getChildAt(i); + for (int j = i + 1; j <= node.getChildCount() - 1; j++) { + DefaultMutableTreeNode prevNode = (DefaultMutableTreeNode) node.getChildAt(j); + + if (!prevNode.isLeaf() && child.isLeaf()) { + node.insert(child, j); + node.insert(prevNode, i); + } + } + } + + return node; + } + + private int getPlacementIndex(ClassSelectorPackageNode newPackageNode, ClassSelectorClassNode classNode) { + List classNodes = classNodes(newPackageNode); + classNodes.add(classNode); + classNodes.sort(Comparator.comparing(ClassSelectorClassNode::toString)); + for (int i = 0; i < classNodes.size(); i++) + if (classNodes.get(i) == classNode) + return i; + + return 0; + } + + private int getPlacementIndex(ClassSelectorPackageNode newPackageNode) { + List packageNodes = packageNodes(); + if (!packageNodes.contains(newPackageNode)) { + packageNodes.add(newPackageNode); + packageNodes.sort(Comparator.comparing(ClassSelectorPackageNode::toString)); + } + + for (int i = 0; i < packageNodes.size(); i++) + if (packageNodes.get(i) == newPackageNode) + return i; + + return 0; + } + + public interface ClassSelectionListener { + void onSelectClass(ClassEntry classEntry); + } + + public interface RenameSelectionListener { + void onSelectionRename(Object prevData, Object data, DefaultMutableTreeNode node); + } } diff --git a/src/main/java/cuchaz/enigma/gui/CodeReader.java b/src/main/java/cuchaz/enigma/gui/CodeReader.java index 8225d8f4..2e235dc6 100644 --- a/src/main/java/cuchaz/enigma/gui/CodeReader.java +++ b/src/main/java/cuchaz/enigma/gui/CodeReader.java @@ -8,20 +8,10 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.gui; import com.strobel.decompiler.languages.java.ast.CompilationUnit; - -import java.awt.Rectangle; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; - -import javax.swing.JEditorPane; -import javax.swing.SwingUtilities; -import javax.swing.Timer; -import javax.swing.text.BadLocationException; -import javax.swing.text.Highlighter.HighlightPainter; - import cuchaz.enigma.Deobfuscator; import cuchaz.enigma.analysis.EntryReference; import cuchaz.enigma.analysis.SourceIndex; @@ -31,180 +21,184 @@ import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.Entry; import de.sciss.syntaxpane.DefaultSyntaxKit; +import javax.swing.*; +import javax.swing.text.BadLocationException; +import javax.swing.text.Highlighter.HighlightPainter; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; public class CodeReader extends JEditorPane { - private static final long serialVersionUID = 3673180950485748810L; - - private static final Object lock = new Object(); - - public interface SelectionListener { - void onSelect(EntryReference reference); - } - - private SelectionHighlightPainter selectionHighlightPainter; - private SourceIndex sourceIndex; - private SelectionListener selectionListener; - - public CodeReader() { - - setEditable(false); - setContentType("text/java"); - - // turn off token highlighting (it's wrong most of the time anyway...) - DefaultSyntaxKit kit = (DefaultSyntaxKit) getEditorKit(); - kit.toggleComponent(this, "de.sciss.syntaxpane.components.TokenMarker"); - - // hook events - addCaretListener(event -> - { - if (selectionListener != null && sourceIndex != null) { - Token token = sourceIndex.getReferenceToken(event.getDot()); - if (token != null) { - selectionListener.onSelect(sourceIndex.getDeobfReference(token)); - } else { - selectionListener.onSelect(null); - } - } - }); - - selectionHighlightPainter = new SelectionHighlightPainter(); - } - - public void setSelectionListener(SelectionListener val) { - selectionListener = val; - } - - public void setCode(String code) { - // sadly, the java lexer is not thread safe, so we have to serialize all these calls - synchronized (lock) { - setText(code); - } - } - - public SourceIndex getSourceIndex() { - return sourceIndex; - } - - public void decompileClass(ClassEntry classEntry, Deobfuscator deobfuscator) { - decompileClass(classEntry, deobfuscator, null); - } - - 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); - return; - } - - setCode("(decompiling...)"); - - // run decompilation in a separate thread to keep ui responsive - new Thread(() -> - { - - // decompile it - CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getOutermostClassName()); - String source = deobfuscator.getSource(sourceTree); - setCode(source); - sourceIndex = deobfuscator.getSourceIndex(sourceTree, source, ignoreBadTokens); - - if (callback != null) { - callback.run(); - } - }).start(); - } - - public void navigateToClassDeclaration(ClassEntry classEntry) { - - // navigate to the class declaration - Token token = sourceIndex.getDeclarationToken(classEntry); - if (token == null) { - // couldn't find the class declaration token, might be an anonymous class - // look for any declaration in that class instead - for (Entry entry : sourceIndex.declarations()) { - if (entry.getClassEntry().equals(classEntry)) { - token = sourceIndex.getDeclarationToken(entry); - break; - } - } - } - - if (token != null) { - navigateToToken(token); - } else { - // couldn't find anything =( - System.out.println("Unable to find declaration in source for " + classEntry); - } - } - - public void navigateToToken(final Token token) { - navigateToToken(this, token, selectionHighlightPainter); - } - - // HACKHACK: someday we can update the main GUI to use this code reader - public static void navigateToToken(final JEditorPane editor, final Token token, final HighlightPainter highlightPainter) { - - // set the caret position to the token - editor.setCaretPosition(token.start); - editor.grabFocus(); - - try { - // make sure the token is visible in the scroll window - Rectangle start = editor.modelToView(token.start); - Rectangle end = editor.modelToView(token.end); - final Rectangle show = start.union(end); - show.grow(start.width * 10, start.height * 6); - SwingUtilities.invokeLater(() -> editor.scrollRectToVisible(show)); - } catch (BadLocationException ex) { - throw new Error(ex); - } - - // highlight the token momentarily - final Timer timer = new Timer(200, new ActionListener() { - private int counter = 0; - private Object highlight = null; - - @Override - public void actionPerformed(ActionEvent event) { - if (counter % 2 == 0) { - try { - highlight = editor.getHighlighter().addHighlight(token.start, token.end, highlightPainter); - } catch (BadLocationException ex) { - // don't care - } - } else if (highlight != null) { - editor.getHighlighter().removeHighlight(highlight); - } - - if (counter++ > 6) { - Timer timer = (Timer) event.getSource(); - timer.stop(); - } - } - }); - timer.start(); - } - - public void setHighlightedTokens(Iterable tokens, HighlightPainter painter) { - for (Token token : tokens) { - setHighlightedToken(token, painter); - } - } - - public void setHighlightedToken(Token token, HighlightPainter painter) { - try { - getHighlighter().addHighlight(token.start, token.end, painter); - } catch (BadLocationException ex) { - throw new IllegalArgumentException(ex); - } - } - - public void clearHighlights() { - getHighlighter().removeAllHighlights(); - } + private static final long serialVersionUID = 3673180950485748810L; + + private static final Object lock = new Object(); + private SelectionHighlightPainter selectionHighlightPainter; + private SourceIndex sourceIndex; + private SelectionListener selectionListener; + public CodeReader() { + + setEditable(false); + setContentType("text/java"); + + // turn off token highlighting (it's wrong most of the time anyway...) + DefaultSyntaxKit kit = (DefaultSyntaxKit) getEditorKit(); + kit.toggleComponent(this, "de.sciss.syntaxpane.components.TokenMarker"); + + // hook events + addCaretListener(event -> + { + if (selectionListener != null && sourceIndex != null) { + Token token = sourceIndex.getReferenceToken(event.getDot()); + if (token != null) { + selectionListener.onSelect(sourceIndex.getDeobfReference(token)); + } else { + selectionListener.onSelect(null); + } + } + }); + + selectionHighlightPainter = new SelectionHighlightPainter(); + } + + // HACKHACK: someday we can update the main GUI to use this code reader + public static void navigateToToken(final JEditorPane editor, final Token token, final HighlightPainter highlightPainter) { + + // set the caret position to the token + editor.setCaretPosition(token.start); + editor.grabFocus(); + + try { + // make sure the token is visible in the scroll window + Rectangle start = editor.modelToView(token.start); + Rectangle end = editor.modelToView(token.end); + final Rectangle show = start.union(end); + show.grow(start.width * 10, start.height * 6); + SwingUtilities.invokeLater(() -> editor.scrollRectToVisible(show)); + } catch (BadLocationException ex) { + throw new Error(ex); + } + + // highlight the token momentarily + final Timer timer = new Timer(200, new ActionListener() { + private int counter = 0; + private Object highlight = null; + + @Override + public void actionPerformed(ActionEvent event) { + if (counter % 2 == 0) { + try { + highlight = editor.getHighlighter().addHighlight(token.start, token.end, highlightPainter); + } catch (BadLocationException ex) { + // don't care + } + } else if (highlight != null) { + editor.getHighlighter().removeHighlight(highlight); + } + + if (counter++ > 6) { + Timer timer = (Timer) event.getSource(); + timer.stop(); + } + } + }); + timer.start(); + } + + public void setSelectionListener(SelectionListener val) { + selectionListener = val; + } + + public void setCode(String code) { + // sadly, the java lexer is not thread safe, so we have to serialize all these calls + synchronized (lock) { + setText(code); + } + } + + public SourceIndex getSourceIndex() { + return sourceIndex; + } + + public void decompileClass(ClassEntry classEntry, Deobfuscator deobfuscator) { + decompileClass(classEntry, deobfuscator, null); + } + + 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); + return; + } + + setCode("(decompiling...)"); + + // run decompilation in a separate thread to keep ui responsive + new Thread(() -> + { + + // decompile it + CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getOutermostClassName()); + String source = deobfuscator.getSource(sourceTree); + setCode(source); + sourceIndex = deobfuscator.getSourceIndex(sourceTree, source, ignoreBadTokens); + + if (callback != null) { + callback.run(); + } + }).start(); + } + + public void navigateToClassDeclaration(ClassEntry classEntry) { + + // navigate to the class declaration + Token token = sourceIndex.getDeclarationToken(classEntry); + if (token == null) { + // couldn't find the class declaration token, might be an anonymous class + // look for any declaration in that class instead + for (Entry entry : sourceIndex.declarations()) { + if (entry.getClassEntry().equals(classEntry)) { + token = sourceIndex.getDeclarationToken(entry); + break; + } + } + } + + if (token != null) { + navigateToToken(token); + } else { + // couldn't find anything =( + System.out.println("Unable to find declaration in source for " + classEntry); + } + } + + public void navigateToToken(final Token token) { + navigateToToken(this, token, selectionHighlightPainter); + } + + public void setHighlightedTokens(Iterable tokens, HighlightPainter painter) { + for (Token token : tokens) { + setHighlightedToken(token, painter); + } + } + + public void setHighlightedToken(Token token, HighlightPainter painter) { + try { + getHighlighter().addHighlight(token.start, token.end, painter); + } catch (BadLocationException ex) { + throw new IllegalArgumentException(ex); + } + } + + public void clearHighlights() { + getHighlighter().removeAllHighlights(); + } + + public interface SelectionListener { + void onSelect(EntryReference reference); + } } diff --git a/src/main/java/cuchaz/enigma/gui/Gui.java b/src/main/java/cuchaz/enigma/gui/Gui.java index 7cb494fa..9f8d6fc4 100644 --- a/src/main/java/cuchaz/enigma/gui/Gui.java +++ b/src/main/java/cuchaz/enigma/gui/Gui.java @@ -8,6 +8,7 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.gui; import com.google.common.collect.Lists; @@ -54,811 +55,790 @@ import java.util.function.Function; public class Gui { - private GuiController controller; - - private final PanelObf obfPanel; - private final PanelDeobf deobfPanel; - - private final MenuBar menuBar; - public final PopupMenuBar popupMenu; - - private JFrame frame; - private PanelEditor editor; - private JPanel classesPanel; - private JSplitPane splitClasses; - private PanelIdentifier infoPanel; - private ObfuscatedHighlightPainter obfuscatedHighlightPainter; - private DeobfuscatedHighlightPainter deobfuscatedHighlightPainter; - private OtherHighlightPainter otherHighlightPainter; - private SelectionHighlightPainter selectionHighlightPainter; - private JTree inheritanceTree; - private JTree implementationsTree; - private JTree callsTree; - private JList tokens; - private JTabbedPane tabs; - - // state - public EntryReference reference; - - public JFileChooser jarFileChooser; - public JFileChooser enigmaMappingsFileChooser; - - public JFileChooser exportSourceFileChooser; - public JFileChooser exportJarFileChooser; - - public Gui() { - - // init frame - this.frame = new JFrame(Constants.NAME); - final Container pane = this.frame.getContentPane(); - pane.setLayout(new BorderLayout()); - - if (Boolean.parseBoolean(System.getProperty("enigma.catchExceptions", "true"))) { - // install a global exception handler to the event thread - CrashDialog.init(this.frame); - Thread.setDefaultUncaughtExceptionHandler((thread, t) -> { - t.printStackTrace(System.err); - if (!ExceptionIgnorer.shouldIgnore(t)) { - CrashDialog.show(t); - } - }); - } - - this.controller = new GuiController(this); - - // init file choosers - this.jarFileChooser = new FileChooserFile(); - - - this.enigmaMappingsFileChooser = new FileChooserAny(); - this.exportSourceFileChooser = new FileChooserFolder(); - this.exportJarFileChooser = new FileChooserFile(); - - this.obfPanel = new PanelObf(this); - this.deobfPanel = new PanelDeobf(this); - - // set up classes panel (don't add the splitter yet) - splitClasses = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, this.obfPanel, this.deobfPanel); - splitClasses.setResizeWeight(0.3); - this.classesPanel = new JPanel(); - this.classesPanel.setLayout(new BorderLayout()); - this.classesPanel.setPreferredSize(new Dimension(250, 0)); - - // init info panel - infoPanel = new PanelIdentifier(this); - infoPanel.clearReference(); - - // init editor - DefaultSyntaxKit.initKit(); - obfuscatedHighlightPainter = new ObfuscatedHighlightPainter(); - deobfuscatedHighlightPainter = new DeobfuscatedHighlightPainter(); - otherHighlightPainter = new OtherHighlightPainter(); - selectionHighlightPainter = new SelectionHighlightPainter(); - this.editor = new PanelEditor(this); - JScrollPane sourceScroller = new JScrollPane(this.editor); - this.editor.setContentType("text/java"); - DefaultSyntaxKit kit = (DefaultSyntaxKit) this.editor.getEditorKit(); - kit.toggleComponent(this.editor, "de.sciss.syntaxpane.components.TokenMarker"); - - // init editor popup menu - this.popupMenu = new PopupMenuBar(this); - this.editor.setComponentPopupMenu(this.popupMenu); - - // init inheritance panel - inheritanceTree = new JTree(); - inheritanceTree.setModel(null); - inheritanceTree.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent event) { - if (event.getClickCount() == 2) { - // get the selected node - TreePath path = inheritanceTree.getSelectionPath(); - if (path == null) { - return; - } - - Object node = path.getLastPathComponent(); - if (node instanceof ClassInheritanceTreeNode) { - ClassInheritanceTreeNode classNode = (ClassInheritanceTreeNode) node; - navigateTo(new ClassEntry(classNode.getObfClassName())); - } else if (node instanceof MethodInheritanceTreeNode) { - MethodInheritanceTreeNode methodNode = (MethodInheritanceTreeNode) node; - if (methodNode.isImplemented()) { - navigateTo(methodNode.getMethodEntry()); - } - } - } - } - }); - JPanel inheritancePanel = new JPanel(); - inheritancePanel.setLayout(new BorderLayout()); - inheritancePanel.add(new JScrollPane(inheritanceTree)); - - // init implementations panel - implementationsTree = new JTree(); - implementationsTree.setModel(null); - implementationsTree.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent event) { - if (event.getClickCount() == 2) { - // get the selected node - TreePath path = implementationsTree.getSelectionPath(); - if (path == null) { - return; - } - - Object node = path.getLastPathComponent(); - if (node instanceof ClassImplementationsTreeNode) { - ClassImplementationsTreeNode classNode = (ClassImplementationsTreeNode) node; - navigateTo(classNode.getClassEntry()); - } else if (node instanceof MethodImplementationsTreeNode) { - MethodImplementationsTreeNode methodNode = (MethodImplementationsTreeNode) node; - navigateTo(methodNode.getMethodEntry()); - } - } - } - }); - JPanel implementationsPanel = new JPanel(); - implementationsPanel.setLayout(new BorderLayout()); - implementationsPanel.add(new JScrollPane(implementationsTree)); - - // init call panel - callsTree = new JTree(); - callsTree.setModel(null); - callsTree.addMouseListener(new MouseAdapter() { - @SuppressWarnings("unchecked") - @Override - public void mouseClicked(MouseEvent event) { - if (event.getClickCount() == 2) { - // get the selected node - TreePath path = callsTree.getSelectionPath(); - if (path == null) { - return; - } - - Object node = path.getLastPathComponent(); - if (node instanceof ReferenceTreeNode) { - ReferenceTreeNode referenceNode = ((ReferenceTreeNode) node); - if (referenceNode.getReference() != null) { - navigateTo(referenceNode.getReference()); - } else { - navigateTo(referenceNode.getEntry()); - } - } - } - } - }); - tokens = new JList<>(); - tokens.setCellRenderer(new TokenListCellRenderer(this.controller)); - tokens.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - tokens.setLayoutOrientation(JList.VERTICAL); - tokens.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent event) { - if (event.getClickCount() == 2) { - Token selected = tokens.getSelectedValue(); - if (selected != null) { - showToken(selected); - } - } - } - }); - tokens.setPreferredSize(new Dimension(0, 200)); - tokens.setMinimumSize(new Dimension(0, 200)); - JSplitPane callPanel = new JSplitPane( - JSplitPane.VERTICAL_SPLIT, - true, - new JScrollPane(callsTree), - new JScrollPane(tokens) - ); - callPanel.setResizeWeight(1); // let the top side take all the slack - callPanel.resetToPreferredSizes(); - - // layout controls - JPanel centerPanel = new JPanel(); - centerPanel.setLayout(new BorderLayout()); - centerPanel.add(infoPanel, BorderLayout.NORTH); - centerPanel.add(sourceScroller, BorderLayout.CENTER); - tabs = new JTabbedPane(); - tabs.setPreferredSize(new Dimension(250, 0)); - tabs.addTab("Inheritance", inheritancePanel); - tabs.addTab("Implementations", implementationsPanel); - tabs.addTab("Call Graph", callPanel); - JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, centerPanel, tabs); - splitRight.setResizeWeight(1); // let the left side take all the slack - splitRight.resetToPreferredSizes(); - JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, this.classesPanel, splitRight); - splitCenter.setResizeWeight(0); // let the right side take all the slack - pane.add(splitCenter, BorderLayout.CENTER); - - // init menus - this.menuBar = new MenuBar(this); - this.frame.setJMenuBar(this.menuBar); - - // init state - onCloseJar(); - - this.frame.addWindowListener(new WindowAdapter() { - @Override - public void windowClosing(WindowEvent event) { - close(); - } - }); - - // show the frame - pane.doLayout(); - this.frame.setSize(1024, 576); - this.frame.setMinimumSize(new Dimension(640, 480)); - this.frame.setVisible(true); - this.frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); - } - - public JFrame getFrame() { - return this.frame; - } - - public GuiController getController() { - return this.controller; - } - - public void onStartOpenJar() { - this.classesPanel.removeAll(); - JPanel panel = new JPanel(); - panel.setLayout(new FlowLayout()); - panel.add(new JLabel("Loading...")); - this.classesPanel.add(panel); - redraw(); - } - - public void onFinishOpenJar(String jarName) { - // update gui - this.frame.setTitle(Constants.NAME + " - " + jarName); - this.classesPanel.removeAll(); - this.classesPanel.add(splitClasses); - setSource(null); - - // update menu - this.menuBar.closeJarMenu.setEnabled(true); - this.menuBar.openEnigmaMappingsMenu.setEnabled(true); - this.menuBar.saveMappingsMenu.setEnabled(false); - this.menuBar.saveMappingEnigmaFileMenu.setEnabled(true); - this.menuBar.saveMappingEnigmaDirectoryMenu.setEnabled(true); - this.menuBar.saveMappingsSrgMenu.setEnabled(true); - this.menuBar.closeMappingsMenu.setEnabled(true); - this.menuBar.exportSourceMenu.setEnabled(true); - this.menuBar.exportJarMenu.setEnabled(true); - - redraw(); - } - - public void onCloseJar() { - // update gui - this.frame.setTitle(Constants.NAME); - setObfClasses(null); - setDeobfClasses(null); - setSource(null); - this.classesPanel.removeAll(); - - // update menu - this.menuBar.closeJarMenu.setEnabled(false); - this.menuBar.openEnigmaMappingsMenu.setEnabled(false); - this.menuBar.saveMappingsMenu.setEnabled(false); - this.menuBar.saveMappingEnigmaFileMenu.setEnabled(false); - this.menuBar.saveMappingEnigmaDirectoryMenu.setEnabled(false); - this.menuBar.saveMappingsSrgMenu.setEnabled(false); - this.menuBar.closeMappingsMenu.setEnabled(false); - this.menuBar.exportSourceMenu.setEnabled(false); - this.menuBar.exportJarMenu.setEnabled(false); - - redraw(); - } - - public void setObfClasses(Collection obfClasses) { - this.obfPanel.obfClasses.setClasses(obfClasses); - } - - public void setDeobfClasses(Collection deobfClasses) { - this.deobfPanel.deobfClasses.setClasses(deobfClasses); - } - - public void setMappingsFile(File file) { - this.enigmaMappingsFileChooser.setSelectedFile(file); - this.menuBar.saveMappingsMenu.setEnabled(file != null); - } - - public void setSource(String source) { - this.editor.getHighlighter().removeAllHighlights(); - this.editor.setText(source); - } - - public void showToken(final Token token) { - if (token == null) { - throw new IllegalArgumentException("Token cannot be null!"); - } - CodeReader.navigateToToken(this.editor, token, selectionHighlightPainter); - redraw(); - } - - public void showTokens(Collection tokens) { - Vector sortedTokens = new Vector<>(tokens); - Collections.sort(sortedTokens); - if (sortedTokens.size() > 1) { - // sort the tokens and update the tokens panel - this.tokens.setListData(sortedTokens); - this.tokens.setSelectedIndex(0); - } else { - this.tokens.setListData(new Vector<>()); - } - - // show the first token - showToken(sortedTokens.get(0)); - } - - public void setHighlightedTokens(Iterable obfuscatedTokens, Iterable deobfuscatedTokens, Iterable otherTokens) { - - // remove any old highlighters - this.editor.getHighlighter().removeAllHighlights(); - - // color things based on the index - if (obfuscatedTokens != null) { - setHighlightedTokens(obfuscatedTokens, obfuscatedHighlightPainter); - } - if (deobfuscatedTokens != null) { - setHighlightedTokens(deobfuscatedTokens, deobfuscatedHighlightPainter); - } - if (otherTokens != null) { - setHighlightedTokens(otherTokens, otherHighlightPainter); - } - - redraw(); - } - - private void setHighlightedTokens(Iterable tokens, Highlighter.HighlightPainter painter) { - for (Token token : tokens) { - try { - this.editor.getHighlighter().addHighlight(token.start, token.end, painter); - } catch (BadLocationException ex) { - throw new IllegalArgumentException(ex); - } - } - } - - private void showReference(EntryReference reference) { - if (reference == null) { - infoPanel.clearReference(); - return; - } - - this.reference = reference; - - infoPanel.removeAll(); - if (reference.entry instanceof ClassEntry) { - showClassEntry((ClassEntry) this.reference.entry); - } else if (this.reference.entry instanceof FieldEntry) { - showFieldEntry((FieldEntry) this.reference.entry); - } else if (this.reference.entry instanceof MethodEntry) { - showMethodEntry((MethodEntry) this.reference.entry); - } else if (this.reference.entry instanceof ConstructorEntry) { - showConstructorEntry((ConstructorEntry) this.reference.entry); - } else if (this.reference.entry instanceof ArgumentEntry) { - showArgumentEntry((ArgumentEntry) this.reference.entry); - } else if (this.reference.entry instanceof LocalVariableEntry) { - showLocalVariableEntry((LocalVariableEntry) this.reference.entry); - } else { - throw new Error("Unknown entry type: " + this.reference.entry.getClass().getName()); - } - - redraw(); - } - - private void showLocalVariableEntry(LocalVariableEntry entry) { - addNameValue(infoPanel, "Variable", entry.getName()); - addNameValue(infoPanel, "Class", entry.getClassEntry().getName()); - addNameValue(infoPanel, "Method", entry.getBehaviorEntry().getName()); - addNameValue(infoPanel, "Index", Integer.toString(entry.getIndex())); - addNameValue(infoPanel, "Type", entry.getType().toString()); - } - - private void showClassEntry(ClassEntry entry) { - addNameValue(infoPanel, "Class", entry.getName()); - addModifierComboBox(infoPanel, "Modifier", entry); - } - - private void showFieldEntry(FieldEntry entry) { - addNameValue(infoPanel, "Field", entry.getName()); - addNameValue(infoPanel, "Class", entry.getClassEntry().getName()); - addNameValue(infoPanel, "Type", entry.getType().toString()); - addModifierComboBox(infoPanel, "Modifier", entry); - } - - private void showMethodEntry(MethodEntry entry) { - addNameValue(infoPanel, "Method", entry.getName()); - addNameValue(infoPanel, "Class", entry.getClassEntry().getName()); - addNameValue(infoPanel, "Signature", entry.getSignature().toString()); - addModifierComboBox(infoPanel, "Modifier", entry); - - } - - private void showConstructorEntry(ConstructorEntry entry) { - addNameValue(infoPanel, "Constructor", entry.getClassEntry().getName()); - if (!entry.isStatic()) { - addNameValue(infoPanel, "Signature", entry.getSignature().toString()); - addModifierComboBox(infoPanel, "Modifier", entry); - } - } - - private void showArgumentEntry(ArgumentEntry entry) { - addNameValue(infoPanel, "Argument", entry.getName()); - addNameValue(infoPanel, "Class", entry.getClassEntry().getName()); - addNameValue(infoPanel, "Method", entry.getBehaviorEntry().getName()); - addNameValue(infoPanel, "Index", Integer.toString(entry.getIndex())); - } - - private void addNameValue(JPanel container, String name, String value) { - JPanel panel = new JPanel(); - panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0)); - container.add(panel); - - JLabel label = new JLabel(name + ":", JLabel.RIGHT); - label.setPreferredSize(new Dimension(100, label.getPreferredSize().height)); - panel.add(label); - - panel.add(Utils.unboldLabel(new JLabel(value, JLabel.LEFT))); - } - - private JComboBox addModifierComboBox(JPanel container, String name, Entry entry) - { - if (!getController().entryIsInJar(entry)) - return null; - JPanel panel = new JPanel(); - panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0)); - container.add(panel); - JLabel label = new JLabel(name + ":", JLabel.RIGHT); - label.setPreferredSize(new Dimension(100, label.getPreferredSize().height)); - panel.add(label); - JComboBox combo = new JComboBox<>(Mappings.EntryModifier.values()); - ((JLabel)combo.getRenderer()).setHorizontalAlignment(JLabel.LEFT); - combo.setPreferredSize(new Dimension(100, label.getPreferredSize().height)); - combo.setSelectedIndex(getController().getDeobfuscator().getModifier(entry).ordinal()); - combo.addItemListener(getController()::modifierChange); - panel.add(combo); - return combo; - } - - public void onCaretMove(int pos) { - - Token token = this.controller.getToken(pos); - boolean isToken = token != null; - - reference = this.controller.getDeobfReference(token); - boolean isClassEntry = isToken && reference.entry instanceof ClassEntry; - boolean isFieldEntry = isToken && reference.entry instanceof FieldEntry; - boolean isMethodEntry = isToken && reference.entry instanceof MethodEntry; - boolean isConstructorEntry = isToken && reference.entry instanceof ConstructorEntry; - boolean isInJar = isToken && this.controller.entryIsInJar(reference.entry); - boolean isRenameable = isToken && this.controller.referenceIsRenameable(reference); - - if (isToken) { - showReference(reference); - } else { - infoPanel.clearReference(); - } - - this.popupMenu.renameMenu.setEnabled(isRenameable); - this.popupMenu.showInheritanceMenu.setEnabled(isClassEntry || isMethodEntry || isConstructorEntry); - this.popupMenu.showImplementationsMenu.setEnabled(isClassEntry || isMethodEntry); - this.popupMenu.showCallsMenu.setEnabled(isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry); - this.popupMenu.openEntryMenu.setEnabled(isInJar && (isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry)); - this.popupMenu.openPreviousMenu.setEnabled(this.controller.hasPreviousLocation()); - this.popupMenu.toggleMappingMenu.setEnabled(isRenameable); - - if (isToken && this.controller.entryHasDeobfuscatedName(reference.entry)) { - this.popupMenu.toggleMappingMenu.setText("Reset to obfuscated"); - } else { - this.popupMenu.toggleMappingMenu.setText("Mark as deobfuscated"); - } - } - - public void navigateTo(Entry entry) { - if (!this.controller.entryIsInJar(entry)) { - // entry is not in the jar. Ignore it - return; - } - if (reference != null) { - this.controller.savePreviousReference(reference); - } - this.controller.openDeclaration(entry); - } - - private void navigateTo(EntryReference reference) { - if (!this.controller.entryIsInJar(reference.getLocationClassEntry())) { - return; - } - if (this.reference != null) { - this.controller.savePreviousReference(this.reference); - } - this.controller.openReference(reference); - } - - public void startRename() { - - // init the text box - final JTextField text = new JTextField(); - text.setText(reference.getNamableName()); - text.setPreferredSize(new Dimension(360, text.getPreferredSize().height)); - text.addKeyListener(new KeyAdapter() { - @Override - public void keyPressed(KeyEvent event) { - switch (event.getKeyCode()) { - case KeyEvent.VK_ENTER: - finishRename(text, true); - break; - - case KeyEvent.VK_ESCAPE: - finishRename(text, false); - break; - default: - break; - } - } - }); - - // find the label with the name and replace it with the text box - JPanel panel = (JPanel) infoPanel.getComponent(0); - panel.remove(panel.getComponentCount() - 1); - panel.add(text); - text.grabFocus(); - - int offset = text.getText().lastIndexOf('/') + 1; - // If it's a class and isn't in the default package, assume that it's deobfuscated. - if (reference.getNameableEntry() instanceof ClassEntry && text.getText().contains("/") && offset != 0) - text.select(offset, text.getText().length()); - else - text.selectAll(); - - redraw(); - } - - private void finishRename(JTextField text, boolean saveName) { - String newName = text.getText(); - if (saveName && newName != null && newName.length() > 0) { - try { - this.controller.rename(reference, newName); - } catch (IllegalNameException ex) { - text.setBorder(BorderFactory.createLineBorder(Color.red, 1)); - text.setToolTipText(ex.getReason()); - Utils.showToolTipNow(text); - } - return; - } - - // abort the rename - JPanel panel = (JPanel) infoPanel.getComponent(0); - panel.remove(panel.getComponentCount() - 1); - panel.add(Utils.unboldLabel(new JLabel(reference.getNamableName(), JLabel.LEFT))); - - this.editor.grabFocus(); - - redraw(); - } - - public void showInheritance() { - - if (reference == null) { - return; - } - - inheritanceTree.setModel(null); - - if (reference.entry instanceof ClassEntry) { - // get the class inheritance - ClassInheritanceTreeNode classNode = this.controller.getClassInheritance((ClassEntry) reference.entry); - - // show the tree at the root - TreePath path = getPathToRoot(classNode); - inheritanceTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0))); - inheritanceTree.expandPath(path); - inheritanceTree.setSelectionRow(inheritanceTree.getRowForPath(path)); - } else if (reference.entry instanceof MethodEntry) { - // get the method inheritance - MethodInheritanceTreeNode classNode = this.controller.getMethodInheritance((MethodEntry) reference.entry); - - // show the tree at the root - TreePath path = getPathToRoot(classNode); - inheritanceTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0))); - inheritanceTree.expandPath(path); - inheritanceTree.setSelectionRow(inheritanceTree.getRowForPath(path)); - } - - tabs.setSelectedIndex(0); - redraw(); - } - - public void showImplementations() { - - if (reference == null) { - return; - } - - implementationsTree.setModel(null); - - DefaultMutableTreeNode node = null; - - // get the class implementations - if (reference.entry instanceof ClassEntry) - node = this.controller.getClassImplementations((ClassEntry) reference.entry); - else // get the method implementations - if (reference.entry instanceof MethodEntry) - node = this.controller.getMethodImplementations((MethodEntry) reference.entry); - - if (node != null) { - // show the tree at the root - TreePath path = getPathToRoot(node); - implementationsTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0))); - implementationsTree.expandPath(path); - implementationsTree.setSelectionRow(implementationsTree.getRowForPath(path)); - } - - tabs.setSelectedIndex(1); - redraw(); - } - - public void showCalls() { - if (reference == null) { - return; - } - - if (reference.entry instanceof ClassEntry) { - // look for calls to the default constructor - // TODO: get a list of all the constructors and find calls to all of them - BehaviorReferenceTreeNode node = this.controller.getMethodReferences(new ConstructorEntry((ClassEntry) reference.entry, new Signature("()V"))); - callsTree.setModel(new DefaultTreeModel(node)); - } else if (reference.entry instanceof FieldEntry) { - FieldReferenceTreeNode node = this.controller.getFieldReferences((FieldEntry) reference.entry); - callsTree.setModel(new DefaultTreeModel(node)); - } else if (reference.entry instanceof MethodEntry) { - BehaviorReferenceTreeNode node = this.controller.getMethodReferences((MethodEntry) reference.entry); - callsTree.setModel(new DefaultTreeModel(node)); - } else if (reference.entry instanceof ConstructorEntry) { - BehaviorReferenceTreeNode node = this.controller.getMethodReferences((ConstructorEntry) reference.entry); - callsTree.setModel(new DefaultTreeModel(node)); - } - - tabs.setSelectedIndex(2); - redraw(); - } - - public void toggleMapping() { - if (this.controller.entryHasDeobfuscatedName(reference.entry)) { - this.controller.removeMapping(reference); - } else { - this.controller.markAsDeobfuscated(reference); - } - } - - private TreePath getPathToRoot(TreeNode node) { - List nodes = Lists.newArrayList(); - TreeNode n = node; - do { - nodes.add(n); - n = n.getParent(); - } while (n != null); - Collections.reverse(nodes); - return new TreePath(nodes.toArray()); - } - - public void showDiscardDiag(Function callback, String... options) - { - int response = JOptionPane.showOptionDialog(this.frame, "Your mappings have not been saved yet. Do you want to save?", "Save your changes?", JOptionPane.YES_NO_CANCEL_OPTION, - JOptionPane.QUESTION_MESSAGE, null, options, options[2]); - callback.apply(response); - } - - public void saveMapping() throws IOException - { - if (this.enigmaMappingsFileChooser.getSelectedFile() != null || this.enigmaMappingsFileChooser.showSaveDialog(this.frame) == JFileChooser.APPROVE_OPTION) - this.controller.saveMappings(this.enigmaMappingsFileChooser.getSelectedFile()); - } - - public void close() { - if (!this.controller.isDirty()) { - // everything is saved, we can exit safely - this.frame.dispose(); - System.exit(0); - } else { - // ask to save before closing - showDiscardDiag((response) -> { - if (response == JOptionPane.YES_OPTION) - { - try { - this.saveMapping(); - this.frame.dispose(); - - } catch (IOException ex) { - throw new Error(ex); - } - } - else if (response == JOptionPane.NO_OPTION) - this.frame.dispose(); - - return null; - }, "Save and exit", "Discard changes", "Cancel"); - } - } - - public void redraw() { - this.frame.validate(); - this.frame.repaint(); - } - - public void onPanelRename(Object prevData, Object data, DefaultMutableTreeNode node) throws IllegalNameException - { - // package rename - if (data instanceof String) - { - for (int i = 0; i < node.getChildCount(); i++) - { - data = Descriptor.toJvmName((String) data); - DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) node.getChildAt(i); - ClassEntry prevDataChild = (ClassEntry) childNode.getUserObject(); - ClassEntry dataChild = new ClassEntry(data + "/" + prevDataChild.getSimpleName()); - this.controller.rename(new EntryReference<>(prevDataChild, prevDataChild.getName()), dataChild.getName(), false, i + 1 == node.getChildCount()); - childNode.setUserObject(dataChild); - } - node.setUserObject(data); - // Ob package will never be modified, just reload deob view - this.deobfPanel.deobfClasses.reload(); - } - // class rename - else if (data instanceof ClassEntry) - this.controller.rename(new EntryReference<>((ClassEntry) prevData, ((ClassEntry) prevData).getName()), ((ClassEntry) data).getName(), false, true); - } - - public void moveClassTree(EntryReference deobfReference, String newName) - { - String oldEntry = deobfReference.entry.getClassEntry().getPackageName(); - String newEntry = new ClassEntry(Descriptor.toJvmName(newName)).getPackageName(); - moveClassTree(deobfReference, newName, oldEntry == null, - newEntry == null); - } - - public void moveClassTree(EntryReference deobfReference, String newName, boolean isOldOb, boolean isNewOb) - { - ClassEntry oldEntry = deobfReference.entry.getClassEntry(); - ClassEntry newEntry = new ClassEntry(Descriptor.toJvmName(newName)); - - // Ob -> deob - if (isOldOb && !isNewOb) - { - this.deobfPanel.deobfClasses.moveClassTree(oldEntry, newEntry, obfPanel.obfClasses); - ClassSelectorPackageNode packageNode = this.obfPanel.obfClasses.getPackageNode(oldEntry); - this.obfPanel.obfClasses.removeNode(packageNode, oldEntry); - this.obfPanel.obfClasses.removeNodeIfEmpty(packageNode); - this.deobfPanel.deobfClasses.reload(); - this.obfPanel.obfClasses.reload(); - } - // Deob -> ob - else if (isNewOb && !isOldOb) - { - this.obfPanel.obfClasses.moveClassTree(oldEntry, newEntry, deobfPanel.deobfClasses); - ClassSelectorPackageNode packageNode = this.deobfPanel.deobfClasses.getPackageNode(oldEntry); - this.deobfPanel.deobfClasses.removeNode(packageNode, oldEntry); - this.deobfPanel.deobfClasses.removeNodeIfEmpty(packageNode); - this.deobfPanel.deobfClasses.reload(); - this.obfPanel.obfClasses.reload(); - } - // Local move - else if (isOldOb) - { - this.obfPanel.obfClasses.moveClassTree(oldEntry, newEntry, null); - this.obfPanel.obfClasses.removeNodeIfEmpty(this.obfPanel.obfClasses.getPackageNode(oldEntry)); - this.obfPanel.obfClasses.reload(); - } - else - { - this.deobfPanel.deobfClasses.moveClassTree(oldEntry, newEntry, null); - this.deobfPanel.deobfClasses.removeNodeIfEmpty(this.deobfPanel.deobfClasses.getPackageNode(oldEntry)); - this.deobfPanel.deobfClasses.reload(); - } - } + public final PopupMenuBar popupMenu; + private final PanelObf obfPanel; + private final PanelDeobf deobfPanel; + + private final MenuBar menuBar; + // state + public EntryReference reference; + public JFileChooser jarFileChooser; + public JFileChooser enigmaMappingsFileChooser; + public JFileChooser exportSourceFileChooser; + public JFileChooser exportJarFileChooser; + private GuiController controller; + private JFrame frame; + private PanelEditor editor; + private JPanel classesPanel; + private JSplitPane splitClasses; + private PanelIdentifier infoPanel; + private ObfuscatedHighlightPainter obfuscatedHighlightPainter; + private DeobfuscatedHighlightPainter deobfuscatedHighlightPainter; + private OtherHighlightPainter otherHighlightPainter; + private SelectionHighlightPainter selectionHighlightPainter; + private JTree inheritanceTree; + private JTree implementationsTree; + private JTree callsTree; + private JList tokens; + private JTabbedPane tabs; + + public Gui() { + + // init frame + this.frame = new JFrame(Constants.NAME); + final Container pane = this.frame.getContentPane(); + pane.setLayout(new BorderLayout()); + + if (Boolean.parseBoolean(System.getProperty("enigma.catchExceptions", "true"))) { + // install a global exception handler to the event thread + CrashDialog.init(this.frame); + Thread.setDefaultUncaughtExceptionHandler((thread, t) -> { + t.printStackTrace(System.err); + if (!ExceptionIgnorer.shouldIgnore(t)) { + CrashDialog.show(t); + } + }); + } + + this.controller = new GuiController(this); + + // init file choosers + this.jarFileChooser = new FileChooserFile(); + + this.enigmaMappingsFileChooser = new FileChooserAny(); + this.exportSourceFileChooser = new FileChooserFolder(); + this.exportJarFileChooser = new FileChooserFile(); + + this.obfPanel = new PanelObf(this); + this.deobfPanel = new PanelDeobf(this); + + // set up classes panel (don't add the splitter yet) + splitClasses = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, this.obfPanel, this.deobfPanel); + splitClasses.setResizeWeight(0.3); + this.classesPanel = new JPanel(); + this.classesPanel.setLayout(new BorderLayout()); + this.classesPanel.setPreferredSize(new Dimension(250, 0)); + + // init info panel + infoPanel = new PanelIdentifier(this); + infoPanel.clearReference(); + + // init editor + DefaultSyntaxKit.initKit(); + obfuscatedHighlightPainter = new ObfuscatedHighlightPainter(); + deobfuscatedHighlightPainter = new DeobfuscatedHighlightPainter(); + otherHighlightPainter = new OtherHighlightPainter(); + selectionHighlightPainter = new SelectionHighlightPainter(); + this.editor = new PanelEditor(this); + JScrollPane sourceScroller = new JScrollPane(this.editor); + this.editor.setContentType("text/java"); + DefaultSyntaxKit kit = (DefaultSyntaxKit) this.editor.getEditorKit(); + kit.toggleComponent(this.editor, "de.sciss.syntaxpane.components.TokenMarker"); + + // init editor popup menu + this.popupMenu = new PopupMenuBar(this); + this.editor.setComponentPopupMenu(this.popupMenu); + + // init inheritance panel + inheritanceTree = new JTree(); + inheritanceTree.setModel(null); + inheritanceTree.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent event) { + if (event.getClickCount() == 2) { + // get the selected node + TreePath path = inheritanceTree.getSelectionPath(); + if (path == null) { + return; + } + + Object node = path.getLastPathComponent(); + if (node instanceof ClassInheritanceTreeNode) { + ClassInheritanceTreeNode classNode = (ClassInheritanceTreeNode) node; + navigateTo(new ClassEntry(classNode.getObfClassName())); + } else if (node instanceof MethodInheritanceTreeNode) { + MethodInheritanceTreeNode methodNode = (MethodInheritanceTreeNode) node; + if (methodNode.isImplemented()) { + navigateTo(methodNode.getMethodEntry()); + } + } + } + } + }); + JPanel inheritancePanel = new JPanel(); + inheritancePanel.setLayout(new BorderLayout()); + inheritancePanel.add(new JScrollPane(inheritanceTree)); + + // init implementations panel + implementationsTree = new JTree(); + implementationsTree.setModel(null); + implementationsTree.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent event) { + if (event.getClickCount() == 2) { + // get the selected node + TreePath path = implementationsTree.getSelectionPath(); + if (path == null) { + return; + } + + Object node = path.getLastPathComponent(); + if (node instanceof ClassImplementationsTreeNode) { + ClassImplementationsTreeNode classNode = (ClassImplementationsTreeNode) node; + navigateTo(classNode.getClassEntry()); + } else if (node instanceof MethodImplementationsTreeNode) { + MethodImplementationsTreeNode methodNode = (MethodImplementationsTreeNode) node; + navigateTo(methodNode.getMethodEntry()); + } + } + } + }); + JPanel implementationsPanel = new JPanel(); + implementationsPanel.setLayout(new BorderLayout()); + implementationsPanel.add(new JScrollPane(implementationsTree)); + + // init call panel + callsTree = new JTree(); + callsTree.setModel(null); + callsTree.addMouseListener(new MouseAdapter() { + @SuppressWarnings("unchecked") + @Override + public void mouseClicked(MouseEvent event) { + if (event.getClickCount() == 2) { + // get the selected node + TreePath path = callsTree.getSelectionPath(); + if (path == null) { + return; + } + + Object node = path.getLastPathComponent(); + if (node instanceof ReferenceTreeNode) { + ReferenceTreeNode referenceNode = ((ReferenceTreeNode) node); + if (referenceNode.getReference() != null) { + navigateTo(referenceNode.getReference()); + } else { + navigateTo(referenceNode.getEntry()); + } + } + } + } + }); + tokens = new JList<>(); + tokens.setCellRenderer(new TokenListCellRenderer(this.controller)); + tokens.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + tokens.setLayoutOrientation(JList.VERTICAL); + tokens.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent event) { + if (event.getClickCount() == 2) { + Token selected = tokens.getSelectedValue(); + if (selected != null) { + showToken(selected); + } + } + } + }); + tokens.setPreferredSize(new Dimension(0, 200)); + tokens.setMinimumSize(new Dimension(0, 200)); + JSplitPane callPanel = new JSplitPane( + JSplitPane.VERTICAL_SPLIT, + true, + new JScrollPane(callsTree), + new JScrollPane(tokens) + ); + callPanel.setResizeWeight(1); // let the top side take all the slack + callPanel.resetToPreferredSizes(); + + // layout controls + JPanel centerPanel = new JPanel(); + centerPanel.setLayout(new BorderLayout()); + centerPanel.add(infoPanel, BorderLayout.NORTH); + centerPanel.add(sourceScroller, BorderLayout.CENTER); + tabs = new JTabbedPane(); + tabs.setPreferredSize(new Dimension(250, 0)); + tabs.addTab("Inheritance", inheritancePanel); + tabs.addTab("Implementations", implementationsPanel); + tabs.addTab("Call Graph", callPanel); + JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, centerPanel, tabs); + splitRight.setResizeWeight(1); // let the left side take all the slack + splitRight.resetToPreferredSizes(); + JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, this.classesPanel, splitRight); + splitCenter.setResizeWeight(0); // let the right side take all the slack + pane.add(splitCenter, BorderLayout.CENTER); + + // init menus + this.menuBar = new MenuBar(this); + this.frame.setJMenuBar(this.menuBar); + + // init state + onCloseJar(); + + this.frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent event) { + close(); + } + }); + + // show the frame + pane.doLayout(); + this.frame.setSize(1024, 576); + this.frame.setMinimumSize(new Dimension(640, 480)); + this.frame.setVisible(true); + this.frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + } + + public JFrame getFrame() { + return this.frame; + } + + public GuiController getController() { + return this.controller; + } + + public void onStartOpenJar() { + this.classesPanel.removeAll(); + JPanel panel = new JPanel(); + panel.setLayout(new FlowLayout()); + panel.add(new JLabel("Loading...")); + this.classesPanel.add(panel); + redraw(); + } + + public void onFinishOpenJar(String jarName) { + // update gui + this.frame.setTitle(Constants.NAME + " - " + jarName); + this.classesPanel.removeAll(); + this.classesPanel.add(splitClasses); + setSource(null); + + // update menu + this.menuBar.closeJarMenu.setEnabled(true); + this.menuBar.openEnigmaMappingsMenu.setEnabled(true); + this.menuBar.saveMappingsMenu.setEnabled(false); + this.menuBar.saveMappingEnigmaFileMenu.setEnabled(true); + this.menuBar.saveMappingEnigmaDirectoryMenu.setEnabled(true); + this.menuBar.saveMappingsSrgMenu.setEnabled(true); + this.menuBar.closeMappingsMenu.setEnabled(true); + this.menuBar.exportSourceMenu.setEnabled(true); + this.menuBar.exportJarMenu.setEnabled(true); + + redraw(); + } + + public void onCloseJar() { + // update gui + this.frame.setTitle(Constants.NAME); + setObfClasses(null); + setDeobfClasses(null); + setSource(null); + this.classesPanel.removeAll(); + + // update menu + this.menuBar.closeJarMenu.setEnabled(false); + this.menuBar.openEnigmaMappingsMenu.setEnabled(false); + this.menuBar.saveMappingsMenu.setEnabled(false); + this.menuBar.saveMappingEnigmaFileMenu.setEnabled(false); + this.menuBar.saveMappingEnigmaDirectoryMenu.setEnabled(false); + this.menuBar.saveMappingsSrgMenu.setEnabled(false); + this.menuBar.closeMappingsMenu.setEnabled(false); + this.menuBar.exportSourceMenu.setEnabled(false); + this.menuBar.exportJarMenu.setEnabled(false); + + redraw(); + } + + public void setObfClasses(Collection obfClasses) { + this.obfPanel.obfClasses.setClasses(obfClasses); + } + + public void setDeobfClasses(Collection deobfClasses) { + this.deobfPanel.deobfClasses.setClasses(deobfClasses); + } + + public void setMappingsFile(File file) { + this.enigmaMappingsFileChooser.setSelectedFile(file); + this.menuBar.saveMappingsMenu.setEnabled(file != null); + } + + public void setSource(String source) { + this.editor.getHighlighter().removeAllHighlights(); + this.editor.setText(source); + } + + public void showToken(final Token token) { + if (token == null) { + throw new IllegalArgumentException("Token cannot be null!"); + } + CodeReader.navigateToToken(this.editor, token, selectionHighlightPainter); + redraw(); + } + + public void showTokens(Collection tokens) { + Vector sortedTokens = new Vector<>(tokens); + Collections.sort(sortedTokens); + if (sortedTokens.size() > 1) { + // sort the tokens and update the tokens panel + this.tokens.setListData(sortedTokens); + this.tokens.setSelectedIndex(0); + } else { + this.tokens.setListData(new Vector<>()); + } + + // show the first token + showToken(sortedTokens.get(0)); + } + + public void setHighlightedTokens(Iterable obfuscatedTokens, Iterable deobfuscatedTokens, Iterable otherTokens) { + + // remove any old highlighters + this.editor.getHighlighter().removeAllHighlights(); + + // color things based on the index + if (obfuscatedTokens != null) { + setHighlightedTokens(obfuscatedTokens, obfuscatedHighlightPainter); + } + if (deobfuscatedTokens != null) { + setHighlightedTokens(deobfuscatedTokens, deobfuscatedHighlightPainter); + } + if (otherTokens != null) { + setHighlightedTokens(otherTokens, otherHighlightPainter); + } + + redraw(); + } + + private void setHighlightedTokens(Iterable tokens, Highlighter.HighlightPainter painter) { + for (Token token : tokens) { + try { + this.editor.getHighlighter().addHighlight(token.start, token.end, painter); + } catch (BadLocationException ex) { + throw new IllegalArgumentException(ex); + } + } + } + + private void showReference(EntryReference reference) { + if (reference == null) { + infoPanel.clearReference(); + return; + } + + this.reference = reference; + + infoPanel.removeAll(); + if (reference.entry instanceof ClassEntry) { + showClassEntry((ClassEntry) this.reference.entry); + } else if (this.reference.entry instanceof FieldEntry) { + showFieldEntry((FieldEntry) this.reference.entry); + } else if (this.reference.entry instanceof MethodEntry) { + showMethodEntry((MethodEntry) this.reference.entry); + } else if (this.reference.entry instanceof ConstructorEntry) { + showConstructorEntry((ConstructorEntry) this.reference.entry); + } else if (this.reference.entry instanceof ArgumentEntry) { + showArgumentEntry((ArgumentEntry) this.reference.entry); + } else if (this.reference.entry instanceof LocalVariableEntry) { + showLocalVariableEntry((LocalVariableEntry) this.reference.entry); + } else { + throw new Error("Unknown entry type: " + this.reference.entry.getClass().getName()); + } + + redraw(); + } + + private void showLocalVariableEntry(LocalVariableEntry entry) { + addNameValue(infoPanel, "Variable", entry.getName()); + addNameValue(infoPanel, "Class", entry.getClassEntry().getName()); + addNameValue(infoPanel, "Method", entry.getBehaviorEntry().getName()); + addNameValue(infoPanel, "Index", Integer.toString(entry.getIndex())); + addNameValue(infoPanel, "Type", entry.getType().toString()); + } + + private void showClassEntry(ClassEntry entry) { + addNameValue(infoPanel, "Class", entry.getName()); + addModifierComboBox(infoPanel, "Modifier", entry); + } + + private void showFieldEntry(FieldEntry entry) { + addNameValue(infoPanel, "Field", entry.getName()); + addNameValue(infoPanel, "Class", entry.getClassEntry().getName()); + addNameValue(infoPanel, "Type", entry.getType().toString()); + addModifierComboBox(infoPanel, "Modifier", entry); + } + + private void showMethodEntry(MethodEntry entry) { + addNameValue(infoPanel, "Method", entry.getName()); + addNameValue(infoPanel, "Class", entry.getClassEntry().getName()); + addNameValue(infoPanel, "Signature", entry.getSignature().toString()); + addModifierComboBox(infoPanel, "Modifier", entry); + + } + + private void showConstructorEntry(ConstructorEntry entry) { + addNameValue(infoPanel, "Constructor", entry.getClassEntry().getName()); + if (!entry.isStatic()) { + addNameValue(infoPanel, "Signature", entry.getSignature().toString()); + addModifierComboBox(infoPanel, "Modifier", entry); + } + } + + private void showArgumentEntry(ArgumentEntry entry) { + addNameValue(infoPanel, "Argument", entry.getName()); + addNameValue(infoPanel, "Class", entry.getClassEntry().getName()); + addNameValue(infoPanel, "Method", entry.getBehaviorEntry().getName()); + addNameValue(infoPanel, "Index", Integer.toString(entry.getIndex())); + } + + private void addNameValue(JPanel container, String name, String value) { + JPanel panel = new JPanel(); + panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0)); + container.add(panel); + + JLabel label = new JLabel(name + ":", JLabel.RIGHT); + label.setPreferredSize(new Dimension(100, label.getPreferredSize().height)); + panel.add(label); + + panel.add(Utils.unboldLabel(new JLabel(value, JLabel.LEFT))); + } + + private JComboBox addModifierComboBox(JPanel container, String name, Entry entry) { + if (!getController().entryIsInJar(entry)) + return null; + JPanel panel = new JPanel(); + panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0)); + container.add(panel); + JLabel label = new JLabel(name + ":", JLabel.RIGHT); + label.setPreferredSize(new Dimension(100, label.getPreferredSize().height)); + panel.add(label); + JComboBox combo = new JComboBox<>(Mappings.EntryModifier.values()); + ((JLabel) combo.getRenderer()).setHorizontalAlignment(JLabel.LEFT); + combo.setPreferredSize(new Dimension(100, label.getPreferredSize().height)); + combo.setSelectedIndex(getController().getDeobfuscator().getModifier(entry).ordinal()); + combo.addItemListener(getController()::modifierChange); + panel.add(combo); + return combo; + } + + public void onCaretMove(int pos) { + + Token token = this.controller.getToken(pos); + boolean isToken = token != null; + + reference = this.controller.getDeobfReference(token); + boolean isClassEntry = isToken && reference.entry instanceof ClassEntry; + boolean isFieldEntry = isToken && reference.entry instanceof FieldEntry; + boolean isMethodEntry = isToken && reference.entry instanceof MethodEntry; + boolean isConstructorEntry = isToken && reference.entry instanceof ConstructorEntry; + boolean isInJar = isToken && this.controller.entryIsInJar(reference.entry); + boolean isRenameable = isToken && this.controller.referenceIsRenameable(reference); + + if (isToken) { + showReference(reference); + } else { + infoPanel.clearReference(); + } + + this.popupMenu.renameMenu.setEnabled(isRenameable); + this.popupMenu.showInheritanceMenu.setEnabled(isClassEntry || isMethodEntry || isConstructorEntry); + this.popupMenu.showImplementationsMenu.setEnabled(isClassEntry || isMethodEntry); + this.popupMenu.showCallsMenu.setEnabled(isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry); + this.popupMenu.openEntryMenu.setEnabled(isInJar && (isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry)); + this.popupMenu.openPreviousMenu.setEnabled(this.controller.hasPreviousLocation()); + this.popupMenu.toggleMappingMenu.setEnabled(isRenameable); + + if (isToken && this.controller.entryHasDeobfuscatedName(reference.entry)) { + this.popupMenu.toggleMappingMenu.setText("Reset to obfuscated"); + } else { + this.popupMenu.toggleMappingMenu.setText("Mark as deobfuscated"); + } + } + + public void navigateTo(Entry entry) { + if (!this.controller.entryIsInJar(entry)) { + // entry is not in the jar. Ignore it + return; + } + if (reference != null) { + this.controller.savePreviousReference(reference); + } + this.controller.openDeclaration(entry); + } + + private void navigateTo(EntryReference reference) { + if (!this.controller.entryIsInJar(reference.getLocationClassEntry())) { + return; + } + if (this.reference != null) { + this.controller.savePreviousReference(this.reference); + } + this.controller.openReference(reference); + } + + public void startRename() { + + // init the text box + final JTextField text = new JTextField(); + text.setText(reference.getNamableName()); + text.setPreferredSize(new Dimension(360, text.getPreferredSize().height)); + text.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent event) { + switch (event.getKeyCode()) { + case KeyEvent.VK_ENTER: + finishRename(text, true); + break; + + case KeyEvent.VK_ESCAPE: + finishRename(text, false); + break; + default: + break; + } + } + }); + + // find the label with the name and replace it with the text box + JPanel panel = (JPanel) infoPanel.getComponent(0); + panel.remove(panel.getComponentCount() - 1); + panel.add(text); + text.grabFocus(); + + int offset = text.getText().lastIndexOf('/') + 1; + // If it's a class and isn't in the default package, assume that it's deobfuscated. + if (reference.getNameableEntry() instanceof ClassEntry && text.getText().contains("/") && offset != 0) + text.select(offset, text.getText().length()); + else + text.selectAll(); + + redraw(); + } + + private void finishRename(JTextField text, boolean saveName) { + String newName = text.getText(); + if (saveName && newName != null && !newName.isEmpty()) { + try { + this.controller.rename(reference, newName); + } catch (IllegalNameException ex) { + text.setBorder(BorderFactory.createLineBorder(Color.red, 1)); + text.setToolTipText(ex.getReason()); + Utils.showToolTipNow(text); + } + return; + } + + // abort the rename + JPanel panel = (JPanel) infoPanel.getComponent(0); + panel.remove(panel.getComponentCount() - 1); + panel.add(Utils.unboldLabel(new JLabel(reference.getNamableName(), JLabel.LEFT))); + + this.editor.grabFocus(); + + redraw(); + } + + public void showInheritance() { + + if (reference == null) { + return; + } + + inheritanceTree.setModel(null); + + if (reference.entry instanceof ClassEntry) { + // get the class inheritance + ClassInheritanceTreeNode classNode = this.controller.getClassInheritance((ClassEntry) reference.entry); + + // show the tree at the root + TreePath path = getPathToRoot(classNode); + inheritanceTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0))); + inheritanceTree.expandPath(path); + inheritanceTree.setSelectionRow(inheritanceTree.getRowForPath(path)); + } else if (reference.entry instanceof MethodEntry) { + // get the method inheritance + MethodInheritanceTreeNode classNode = this.controller.getMethodInheritance((MethodEntry) reference.entry); + + // show the tree at the root + TreePath path = getPathToRoot(classNode); + inheritanceTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0))); + inheritanceTree.expandPath(path); + inheritanceTree.setSelectionRow(inheritanceTree.getRowForPath(path)); + } + + tabs.setSelectedIndex(0); + redraw(); + } + + public void showImplementations() { + + if (reference == null) { + return; + } + + implementationsTree.setModel(null); + + DefaultMutableTreeNode node = null; + + // get the class implementations + if (reference.entry instanceof ClassEntry) + node = this.controller.getClassImplementations((ClassEntry) reference.entry); + else // get the method implementations + if (reference.entry instanceof MethodEntry) + node = this.controller.getMethodImplementations((MethodEntry) reference.entry); + + if (node != null) { + // show the tree at the root + TreePath path = getPathToRoot(node); + implementationsTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0))); + implementationsTree.expandPath(path); + implementationsTree.setSelectionRow(implementationsTree.getRowForPath(path)); + } + + tabs.setSelectedIndex(1); + redraw(); + } + + public void showCalls() { + if (reference == null) { + return; + } + + if (reference.entry instanceof ClassEntry) { + // look for calls to the default constructor + // TODO: get a list of all the constructors and find calls to all of them + BehaviorReferenceTreeNode node = this.controller.getMethodReferences(new ConstructorEntry((ClassEntry) reference.entry, new Signature("()V"))); + callsTree.setModel(new DefaultTreeModel(node)); + } else if (reference.entry instanceof FieldEntry) { + FieldReferenceTreeNode node = this.controller.getFieldReferences((FieldEntry) reference.entry); + callsTree.setModel(new DefaultTreeModel(node)); + } else if (reference.entry instanceof MethodEntry) { + BehaviorReferenceTreeNode node = this.controller.getMethodReferences((MethodEntry) reference.entry); + callsTree.setModel(new DefaultTreeModel(node)); + } else if (reference.entry instanceof ConstructorEntry) { + BehaviorReferenceTreeNode node = this.controller.getMethodReferences((ConstructorEntry) reference.entry); + callsTree.setModel(new DefaultTreeModel(node)); + } + + tabs.setSelectedIndex(2); + redraw(); + } + + public void toggleMapping() { + if (this.controller.entryHasDeobfuscatedName(reference.entry)) { + this.controller.removeMapping(reference); + } else { + this.controller.markAsDeobfuscated(reference); + } + } + + private TreePath getPathToRoot(TreeNode node) { + List nodes = Lists.newArrayList(); + TreeNode n = node; + do { + nodes.add(n); + n = n.getParent(); + } while (n != null); + Collections.reverse(nodes); + return new TreePath(nodes.toArray()); + } + + public void showDiscardDiag(Function callback, String... options) { + int response = JOptionPane.showOptionDialog(this.frame, "Your mappings have not been saved yet. Do you want to save?", "Save your changes?", JOptionPane.YES_NO_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE, null, options, options[2]); + callback.apply(response); + } + + public void saveMapping() throws IOException { + if (this.enigmaMappingsFileChooser.getSelectedFile() != null || this.enigmaMappingsFileChooser.showSaveDialog(this.frame) == JFileChooser.APPROVE_OPTION) + this.controller.saveMappings(this.enigmaMappingsFileChooser.getSelectedFile()); + } + + public void close() { + if (!this.controller.isDirty()) { + // everything is saved, we can exit safely + this.frame.dispose(); + System.exit(0); + } else { + // ask to save before closing + showDiscardDiag((response) -> { + if (response == JOptionPane.YES_OPTION) { + try { + this.saveMapping(); + this.frame.dispose(); + + } catch (IOException ex) { + throw new Error(ex); + } + } else if (response == JOptionPane.NO_OPTION) + this.frame.dispose(); + + return null; + }, "Save and exit", "Discard changes", "Cancel"); + } + } + + public void redraw() { + this.frame.validate(); + this.frame.repaint(); + } + + public void onPanelRename(Object prevData, Object data, DefaultMutableTreeNode node) throws IllegalNameException { + // package rename + if (data instanceof String) { + for (int i = 0; i < node.getChildCount(); i++) { + data = Descriptor.toJvmName((String) data); + DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) node.getChildAt(i); + ClassEntry prevDataChild = (ClassEntry) childNode.getUserObject(); + ClassEntry dataChild = new ClassEntry(data + "/" + prevDataChild.getSimpleName()); + this.controller.rename(new EntryReference<>(prevDataChild, prevDataChild.getName()), dataChild.getName(), false, i + 1 == node.getChildCount()); + childNode.setUserObject(dataChild); + } + node.setUserObject(data); + // Ob package will never be modified, just reload deob view + this.deobfPanel.deobfClasses.reload(); + } + // class rename + else if (data instanceof ClassEntry) + this.controller.rename(new EntryReference<>((ClassEntry) prevData, ((ClassEntry) prevData).getName()), ((ClassEntry) data).getName(), false, true); + } + + public void moveClassTree(EntryReference deobfReference, String newName) { + String oldEntry = deobfReference.entry.getClassEntry().getPackageName(); + String newEntry = new ClassEntry(Descriptor.toJvmName(newName)).getPackageName(); + moveClassTree(deobfReference, newName, oldEntry == null, + newEntry == null); + } + + public void moveClassTree(EntryReference deobfReference, String newName, boolean isOldOb, boolean isNewOb) { + ClassEntry oldEntry = deobfReference.entry.getClassEntry(); + ClassEntry newEntry = new ClassEntry(Descriptor.toJvmName(newName)); + + // Ob -> deob + if (isOldOb && !isNewOb) { + this.deobfPanel.deobfClasses.moveClassTree(oldEntry, newEntry, obfPanel.obfClasses); + ClassSelectorPackageNode packageNode = this.obfPanel.obfClasses.getPackageNode(oldEntry); + this.obfPanel.obfClasses.removeNode(packageNode, oldEntry); + this.obfPanel.obfClasses.removeNodeIfEmpty(packageNode); + this.deobfPanel.deobfClasses.reload(); + this.obfPanel.obfClasses.reload(); + } + // Deob -> ob + else if (isNewOb && !isOldOb) { + this.obfPanel.obfClasses.moveClassTree(oldEntry, newEntry, deobfPanel.deobfClasses); + ClassSelectorPackageNode packageNode = this.deobfPanel.deobfClasses.getPackageNode(oldEntry); + this.deobfPanel.deobfClasses.removeNode(packageNode, oldEntry); + this.deobfPanel.deobfClasses.removeNodeIfEmpty(packageNode); + this.deobfPanel.deobfClasses.reload(); + this.obfPanel.obfClasses.reload(); + } + // Local move + else if (isOldOb) { + this.obfPanel.obfClasses.moveClassTree(oldEntry, newEntry, null); + this.obfPanel.obfClasses.removeNodeIfEmpty(this.obfPanel.obfClasses.getPackageNode(oldEntry)); + this.obfPanel.obfClasses.reload(); + } else { + this.deobfPanel.deobfClasses.moveClassTree(oldEntry, newEntry, null); + this.deobfPanel.deobfClasses.removeNodeIfEmpty(this.deobfPanel.deobfClasses.getPackageNode(oldEntry)); + this.deobfPanel.deobfClasses.reload(); + } + } } diff --git a/src/main/java/cuchaz/enigma/gui/GuiController.java b/src/main/java/cuchaz/enigma/gui/GuiController.java index 68fd4843..1b461da7 100644 --- a/src/main/java/cuchaz/enigma/gui/GuiController.java +++ b/src/main/java/cuchaz/enigma/gui/GuiController.java @@ -8,6 +8,7 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.gui; import com.google.common.collect.Lists; @@ -30,326 +31,321 @@ import java.util.jar.JarFile; public class GuiController { - private Deobfuscator deobfuscator; - private Gui gui; - private SourceIndex index; - private ClassEntry currentObfClass; - private boolean isDirty; - private Deque> referenceStack; - - public GuiController(Gui gui) { - this.gui = gui; - this.deobfuscator = null; - this.index = null; - this.currentObfClass = null; - this.isDirty = false; - this.referenceStack = Queues.newArrayDeque(); - } - - public boolean isDirty() { - return this.isDirty; - } - - public void openJar(final JarFile jar) { - this.gui.onStartOpenJar(); - this.deobfuscator = new Deobfuscator(jar); - this.gui.onFinishOpenJar(this.deobfuscator.getJarName()); - refreshClasses(); - } - - public void closeJar() { - this.deobfuscator = null; - this.gui.onCloseJar(); - } - - public void openEnigmaMappings(File file) throws IOException, MappingParseException { - this.deobfuscator.setMappings(new MappingsEnigmaReader().read(file)); - this.isDirty = false; - this.gui.setMappingsFile(file); - refreshClasses(); - refreshCurrentClass(); - } - - public void saveMappings(File file) throws IOException { - Mappings mappings = this.deobfuscator.getMappings(); - switch (mappings.getOriginMappingFormat()) - { - case SRG_FILE: - saveSRGMappings(file); - break; - default: - saveEnigmaMappings(file, Mappings.FormatType.ENIGMA_FILE != mappings.getOriginMappingFormat()); - break; - } - - } - - public void saveEnigmaMappings(File file, boolean isDirectoryFormat) throws IOException { - this.deobfuscator.getMappings().saveEnigmaMappings(file, isDirectoryFormat); - this.isDirty = false; - } - - public void saveSRGMappings(File file) throws IOException { - this.deobfuscator.getMappings().saveSRGMappings(file); - this.isDirty = false; - } - - public void closeMappings() { - this.deobfuscator.setMappings(null); - this.gui.setMappingsFile(null); - refreshClasses(); - refreshCurrentClass(); - } - - public void rebuildMethodNames() { - ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.rebuildMethodNames(progress)); - } - - public void exportSource(final File dirOut) { - ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.writeSources(dirOut, progress)); - } - - public void exportJar(final File fileOut) { - ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.writeJar(fileOut, progress)); - } - - public Token getToken(int pos) { - if (this.index == null) { - return null; - } - return this.index.getReferenceToken(pos); - } - - public EntryReference getDeobfReference(Token token) { - if (this.index == null) { - return null; - } - return this.index.getDeobfReference(token); - } - - public ReadableToken getReadableToken(Token token) { - if (this.index == null) { - return null; - } - return new ReadableToken( - this.index.getLineNumber(token.start), - this.index.getColumnNumber(token.start), - this.index.getColumnNumber(token.end) - ); - } - - public boolean entryHasDeobfuscatedName(Entry deobfEntry) { - return this.deobfuscator.hasDeobfuscatedName(this.deobfuscator.obfuscateEntry(deobfEntry)); - } - - public boolean entryIsInJar(Entry deobfEntry) { - return this.deobfuscator.isObfuscatedIdentifier(this.deobfuscator.obfuscateEntry(deobfEntry)); - } - - public boolean referenceIsRenameable(EntryReference deobfReference) { - return this.deobfuscator.isRenameable(this.deobfuscator.obfuscateReference(deobfReference), true); - } - - public ClassInheritanceTreeNode getClassInheritance(ClassEntry deobfClassEntry) { - ClassEntry obfClassEntry = this.deobfuscator.obfuscateEntry(deobfClassEntry); - ClassInheritanceTreeNode rootNode = this.deobfuscator.getJarIndex().getClassInheritance(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfClassEntry); - return ClassInheritanceTreeNode.findNode(rootNode, obfClassEntry); - } - - public ClassImplementationsTreeNode getClassImplementations(ClassEntry deobfClassEntry) { - ClassEntry obfClassEntry = this.deobfuscator.obfuscateEntry(deobfClassEntry); - return this.deobfuscator.getJarIndex().getClassImplementations(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfClassEntry); - } - - public MethodInheritanceTreeNode getMethodInheritance(MethodEntry deobfMethodEntry) { - MethodEntry obfMethodEntry = this.deobfuscator.obfuscateEntry(deobfMethodEntry); - MethodInheritanceTreeNode rootNode = this.deobfuscator.getJarIndex().getMethodInheritance(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfMethodEntry); - return MethodInheritanceTreeNode.findNode(rootNode, obfMethodEntry); - } - - public MethodImplementationsTreeNode getMethodImplementations(MethodEntry deobfMethodEntry) { - MethodEntry obfMethodEntry = this.deobfuscator.obfuscateEntry(deobfMethodEntry); - List rootNodes = this.deobfuscator.getJarIndex().getMethodImplementations(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfMethodEntry); - if (rootNodes.isEmpty()) { - return null; - } - if (rootNodes.size() > 1) { - System.err.println("WARNING: Method " + deobfMethodEntry + " implements multiple interfaces. Only showing first one."); - } - return MethodImplementationsTreeNode.findNode(rootNodes.get(0), obfMethodEntry); - } - - public FieldReferenceTreeNode getFieldReferences(FieldEntry deobfFieldEntry) { - FieldEntry obfFieldEntry = this.deobfuscator.obfuscateEntry(deobfFieldEntry); - FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfFieldEntry); - rootNode.load(this.deobfuscator.getJarIndex(), true); - return rootNode; - } - - public BehaviorReferenceTreeNode getMethodReferences(BehaviorEntry deobfBehaviorEntry) { - BehaviorEntry obfBehaviorEntry = this.deobfuscator.obfuscateEntry(deobfBehaviorEntry); - BehaviorReferenceTreeNode rootNode = new BehaviorReferenceTreeNode(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfBehaviorEntry); - rootNode.load(this.deobfuscator.getJarIndex(), true); - return rootNode; - } - - public void rename(EntryReference deobfReference, String newName) { - rename(deobfReference, newName, true, true); - } - - public void rename(EntryReference deobfReference, String newName, boolean refreshClassTree, boolean clearTranslationCache) - { - EntryReference obfReference = this.deobfuscator.obfuscateReference(deobfReference); - this.deobfuscator.rename(obfReference.getNameableEntry(), newName, clearTranslationCache); - this.isDirty = true; - - if (refreshClassTree && deobfReference.entry instanceof ClassEntry && !((ClassEntry) deobfReference.entry).isInnerClass()) - this.gui.moveClassTree(deobfReference, newName); - refreshCurrentClass(obfReference); - - } - - public void removeMapping(EntryReference deobfReference) { - EntryReference obfReference = this.deobfuscator.obfuscateReference(deobfReference); - this.deobfuscator.removeMapping(obfReference.getNameableEntry()); - this.isDirty = true; - if (deobfReference.entry instanceof ClassEntry) - this.gui.moveClassTree(deobfReference, obfReference.entry.getName(), false, true); - refreshCurrentClass(obfReference); - } - - public void markAsDeobfuscated(EntryReference deobfReference) { - EntryReference obfReference = this.deobfuscator.obfuscateReference(deobfReference); - this.deobfuscator.markAsDeobfuscated(obfReference.getNameableEntry()); - this.isDirty = true; - if (deobfReference.entry instanceof ClassEntry && !((ClassEntry) deobfReference.entry).isInnerClass()) - this.gui.moveClassTree(deobfReference, obfReference.entry.getName(), true, false); - refreshCurrentClass(obfReference); - } - - public void openDeclaration(Entry deobfEntry) { - if (deobfEntry == null) { - throw new IllegalArgumentException("Entry cannot be null!"); - } - openReference(new EntryReference<>(deobfEntry, deobfEntry.getName())); - } - - public void openReference(EntryReference deobfReference) { - if (deobfReference == null) { - throw new IllegalArgumentException("Reference cannot be null!"); - } - - // get the reference target class - EntryReference obfReference = this.deobfuscator.obfuscateReference(deobfReference); - ClassEntry obfClassEntry = obfReference.getLocationClassEntry().getOutermostClassEntry(); - if (!this.deobfuscator.isObfuscatedIdentifier(obfClassEntry)) { - throw new IllegalArgumentException("Obfuscated class " + obfClassEntry + " was not found in the jar!"); - } - if (this.currentObfClass == null || !this.currentObfClass.equals(obfClassEntry)) { - // deobfuscate the class, then navigate to the reference - this.currentObfClass = obfClassEntry; - deobfuscate(this.currentObfClass, obfReference); - } else { - showReference(obfReference); - } - } - - private void showReference(EntryReference obfReference) { - EntryReference deobfReference = this.deobfuscator.deobfuscateReference(obfReference); - Collection tokens = this.index.getReferenceTokens(deobfReference); - if (tokens.isEmpty()) { - // DEBUG - System.err.println(String.format("WARNING: no tokens found for %s in %s", deobfReference, this.currentObfClass)); - } else { - this.gui.showTokens(tokens); - } - } - - public void savePreviousReference(EntryReference deobfReference) { - this.referenceStack.push(this.deobfuscator.obfuscateReference(deobfReference)); - } - - public void openPreviousReference() { - if (hasPreviousLocation()) { - openReference(this.deobfuscator.deobfuscateReference(this.referenceStack.pop())); - } - } - - public boolean hasPreviousLocation() { - return !this.referenceStack.isEmpty(); - } - - private void refreshClasses() { - List obfClasses = Lists.newArrayList(); - List deobfClasses = Lists.newArrayList(); - this.deobfuscator.getSeparatedClasses(obfClasses, deobfClasses); - this.gui.setObfClasses(obfClasses); - this.gui.setDeobfClasses(deobfClasses); - } - - public void refreshCurrentClass() { - refreshCurrentClass(null); - } - - private void refreshCurrentClass(EntryReference obfReference) { - if (this.currentObfClass != null) { - deobfuscate(this.currentObfClass, obfReference); - } - } - - private void deobfuscate(final ClassEntry classEntry, final EntryReference obfReference) { - - this.gui.setSource("(deobfuscating...)"); - - // run the deobfuscator in a separate thread so we don't block the GUI event queue - new Thread(() -> - { - // decompile,deobfuscate the bytecode - CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getClassName()); - if (sourceTree == null) { - // decompilation of this class is not supported - gui.setSource("Unable to find class: " + classEntry); - return; - } - String source = deobfuscator.getSource(sourceTree); - index = deobfuscator.getSourceIndex(sourceTree, source); - gui.setSource(index.getSource()); - if (obfReference != null) { - showReference(obfReference); - } - - // set the highlighted tokens - List obfuscatedTokens = Lists.newArrayList(); - List deobfuscatedTokens = Lists.newArrayList(); - List otherTokens = Lists.newArrayList(); - for (Token token : index.referenceTokens()) { - EntryReference reference = index.getDeobfReference(token); - if (referenceIsRenameable(reference)) { - if (entryHasDeobfuscatedName(reference.getNameableEntry())) { - deobfuscatedTokens.add(token); - } else { - obfuscatedTokens.add(token); - } - } else { - otherTokens.add(token); - } - } - gui.setHighlightedTokens(obfuscatedTokens, deobfuscatedTokens, otherTokens); - }).start(); - } - - public Deobfuscator getDeobfuscator() - { - return deobfuscator; - } - - public void modifierChange(ItemEvent event) - { - if (event.getStateChange() == ItemEvent.SELECTED) - { - deobfuscator.changeModifier(gui.reference.entry, (Mappings.EntryModifier) event.getItem()); - this.isDirty = true; - refreshCurrentClass(); - } - } + private Deobfuscator deobfuscator; + private Gui gui; + private SourceIndex index; + private ClassEntry currentObfClass; + private boolean isDirty; + private Deque> referenceStack; + + public GuiController(Gui gui) { + this.gui = gui; + this.deobfuscator = null; + this.index = null; + this.currentObfClass = null; + this.isDirty = false; + this.referenceStack = Queues.newArrayDeque(); + } + + public boolean isDirty() { + return this.isDirty; + } + + public void openJar(final JarFile jar) { + this.gui.onStartOpenJar(); + this.deobfuscator = new Deobfuscator(jar); + this.gui.onFinishOpenJar(this.deobfuscator.getJarName()); + refreshClasses(); + } + + public void closeJar() { + this.deobfuscator = null; + this.gui.onCloseJar(); + } + + public void openEnigmaMappings(File file) throws IOException, MappingParseException { + this.deobfuscator.setMappings(new MappingsEnigmaReader().read(file)); + this.isDirty = false; + this.gui.setMappingsFile(file); + refreshClasses(); + refreshCurrentClass(); + } + + public void saveMappings(File file) throws IOException { + Mappings mappings = this.deobfuscator.getMappings(); + switch (mappings.getOriginMappingFormat()) { + case SRG_FILE: + saveSRGMappings(file); + break; + default: + saveEnigmaMappings(file, Mappings.FormatType.ENIGMA_FILE != mappings.getOriginMappingFormat()); + break; + } + + } + + public void saveEnigmaMappings(File file, boolean isDirectoryFormat) throws IOException { + this.deobfuscator.getMappings().saveEnigmaMappings(file, isDirectoryFormat); + this.isDirty = false; + } + + public void saveSRGMappings(File file) throws IOException { + this.deobfuscator.getMappings().saveSRGMappings(file); + this.isDirty = false; + } + + public void closeMappings() { + this.deobfuscator.setMappings(null); + this.gui.setMappingsFile(null); + refreshClasses(); + refreshCurrentClass(); + } + + public void rebuildMethodNames() { + ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.rebuildMethodNames(progress)); + } + + public void exportSource(final File dirOut) { + ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.writeSources(dirOut, progress)); + } + + public void exportJar(final File fileOut) { + ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.writeJar(fileOut, progress)); + } + + public Token getToken(int pos) { + if (this.index == null) { + return null; + } + return this.index.getReferenceToken(pos); + } + + public EntryReference getDeobfReference(Token token) { + if (this.index == null) { + return null; + } + return this.index.getDeobfReference(token); + } + + public ReadableToken getReadableToken(Token token) { + if (this.index == null) { + return null; + } + return new ReadableToken( + this.index.getLineNumber(token.start), + this.index.getColumnNumber(token.start), + this.index.getColumnNumber(token.end) + ); + } + + public boolean entryHasDeobfuscatedName(Entry deobfEntry) { + return this.deobfuscator.hasDeobfuscatedName(this.deobfuscator.obfuscateEntry(deobfEntry)); + } + + public boolean entryIsInJar(Entry deobfEntry) { + return this.deobfuscator.isObfuscatedIdentifier(this.deobfuscator.obfuscateEntry(deobfEntry)); + } + + public boolean referenceIsRenameable(EntryReference deobfReference) { + return this.deobfuscator.isRenameable(this.deobfuscator.obfuscateReference(deobfReference), true); + } + + public ClassInheritanceTreeNode getClassInheritance(ClassEntry deobfClassEntry) { + ClassEntry obfClassEntry = this.deobfuscator.obfuscateEntry(deobfClassEntry); + ClassInheritanceTreeNode rootNode = this.deobfuscator.getJarIndex().getClassInheritance(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfClassEntry); + return ClassInheritanceTreeNode.findNode(rootNode, obfClassEntry); + } + + public ClassImplementationsTreeNode getClassImplementations(ClassEntry deobfClassEntry) { + ClassEntry obfClassEntry = this.deobfuscator.obfuscateEntry(deobfClassEntry); + return this.deobfuscator.getJarIndex().getClassImplementations(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfClassEntry); + } + + public MethodInheritanceTreeNode getMethodInheritance(MethodEntry deobfMethodEntry) { + MethodEntry obfMethodEntry = this.deobfuscator.obfuscateEntry(deobfMethodEntry); + MethodInheritanceTreeNode rootNode = this.deobfuscator.getJarIndex().getMethodInheritance(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfMethodEntry); + return MethodInheritanceTreeNode.findNode(rootNode, obfMethodEntry); + } + + public MethodImplementationsTreeNode getMethodImplementations(MethodEntry deobfMethodEntry) { + MethodEntry obfMethodEntry = this.deobfuscator.obfuscateEntry(deobfMethodEntry); + List rootNodes = this.deobfuscator.getJarIndex().getMethodImplementations(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfMethodEntry); + if (rootNodes.isEmpty()) { + return null; + } + if (rootNodes.size() > 1) { + System.err.println("WARNING: Method " + deobfMethodEntry + " implements multiple interfaces. Only showing first one."); + } + return MethodImplementationsTreeNode.findNode(rootNodes.get(0), obfMethodEntry); + } + + public FieldReferenceTreeNode getFieldReferences(FieldEntry deobfFieldEntry) { + FieldEntry obfFieldEntry = this.deobfuscator.obfuscateEntry(deobfFieldEntry); + FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfFieldEntry); + rootNode.load(this.deobfuscator.getJarIndex(), true); + return rootNode; + } + + public BehaviorReferenceTreeNode getMethodReferences(BehaviorEntry deobfBehaviorEntry) { + BehaviorEntry obfBehaviorEntry = this.deobfuscator.obfuscateEntry(deobfBehaviorEntry); + BehaviorReferenceTreeNode rootNode = new BehaviorReferenceTreeNode(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfBehaviorEntry); + rootNode.load(this.deobfuscator.getJarIndex(), true); + return rootNode; + } + + public void rename(EntryReference deobfReference, String newName) { + rename(deobfReference, newName, true, true); + } + + public void rename(EntryReference deobfReference, String newName, boolean refreshClassTree, boolean clearTranslationCache) { + EntryReference obfReference = this.deobfuscator.obfuscateReference(deobfReference); + this.deobfuscator.rename(obfReference.getNameableEntry(), newName, clearTranslationCache); + this.isDirty = true; + + if (refreshClassTree && deobfReference.entry instanceof ClassEntry && !((ClassEntry) deobfReference.entry).isInnerClass()) + this.gui.moveClassTree(deobfReference, newName); + refreshCurrentClass(obfReference); + + } + + public void removeMapping(EntryReference deobfReference) { + EntryReference obfReference = this.deobfuscator.obfuscateReference(deobfReference); + this.deobfuscator.removeMapping(obfReference.getNameableEntry()); + this.isDirty = true; + if (deobfReference.entry instanceof ClassEntry) + this.gui.moveClassTree(deobfReference, obfReference.entry.getName(), false, true); + refreshCurrentClass(obfReference); + } + + public void markAsDeobfuscated(EntryReference deobfReference) { + EntryReference obfReference = this.deobfuscator.obfuscateReference(deobfReference); + this.deobfuscator.markAsDeobfuscated(obfReference.getNameableEntry()); + this.isDirty = true; + if (deobfReference.entry instanceof ClassEntry && !((ClassEntry) deobfReference.entry).isInnerClass()) + this.gui.moveClassTree(deobfReference, obfReference.entry.getName(), true, false); + refreshCurrentClass(obfReference); + } + + public void openDeclaration(Entry deobfEntry) { + if (deobfEntry == null) { + throw new IllegalArgumentException("Entry cannot be null!"); + } + openReference(new EntryReference<>(deobfEntry, deobfEntry.getName())); + } + + public void openReference(EntryReference deobfReference) { + if (deobfReference == null) { + throw new IllegalArgumentException("Reference cannot be null!"); + } + + // get the reference target class + EntryReference obfReference = this.deobfuscator.obfuscateReference(deobfReference); + ClassEntry obfClassEntry = obfReference.getLocationClassEntry().getOutermostClassEntry(); + if (!this.deobfuscator.isObfuscatedIdentifier(obfClassEntry)) { + throw new IllegalArgumentException("Obfuscated class " + obfClassEntry + " was not found in the jar!"); + } + if (this.currentObfClass == null || !this.currentObfClass.equals(obfClassEntry)) { + // deobfuscate the class, then navigate to the reference + this.currentObfClass = obfClassEntry; + deobfuscate(this.currentObfClass, obfReference); + } else { + showReference(obfReference); + } + } + + private void showReference(EntryReference obfReference) { + EntryReference deobfReference = this.deobfuscator.deobfuscateReference(obfReference); + Collection tokens = this.index.getReferenceTokens(deobfReference); + if (tokens.isEmpty()) { + // DEBUG + System.err.println(String.format("WARNING: no tokens found for %s in %s", deobfReference, this.currentObfClass)); + } else { + this.gui.showTokens(tokens); + } + } + + public void savePreviousReference(EntryReference deobfReference) { + this.referenceStack.push(this.deobfuscator.obfuscateReference(deobfReference)); + } + + public void openPreviousReference() { + if (hasPreviousLocation()) { + openReference(this.deobfuscator.deobfuscateReference(this.referenceStack.pop())); + } + } + + public boolean hasPreviousLocation() { + return !this.referenceStack.isEmpty(); + } + + private void refreshClasses() { + List obfClasses = Lists.newArrayList(); + List deobfClasses = Lists.newArrayList(); + this.deobfuscator.getSeparatedClasses(obfClasses, deobfClasses); + this.gui.setObfClasses(obfClasses); + this.gui.setDeobfClasses(deobfClasses); + } + + public void refreshCurrentClass() { + refreshCurrentClass(null); + } + + private void refreshCurrentClass(EntryReference obfReference) { + if (this.currentObfClass != null) { + deobfuscate(this.currentObfClass, obfReference); + } + } + + private void deobfuscate(final ClassEntry classEntry, final EntryReference obfReference) { + + this.gui.setSource("(deobfuscating...)"); + + // run the deobfuscator in a separate thread so we don't block the GUI event queue + new Thread(() -> + { + // decompile,deobfuscate the bytecode + CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getClassName()); + if (sourceTree == null) { + // decompilation of this class is not supported + gui.setSource("Unable to find class: " + classEntry); + return; + } + String source = deobfuscator.getSource(sourceTree); + index = deobfuscator.getSourceIndex(sourceTree, source); + gui.setSource(index.getSource()); + if (obfReference != null) { + showReference(obfReference); + } + + // set the highlighted tokens + List obfuscatedTokens = Lists.newArrayList(); + List deobfuscatedTokens = Lists.newArrayList(); + List otherTokens = Lists.newArrayList(); + for (Token token : index.referenceTokens()) { + EntryReference reference = index.getDeobfReference(token); + if (referenceIsRenameable(reference)) { + if (entryHasDeobfuscatedName(reference.getNameableEntry())) { + deobfuscatedTokens.add(token); + } else { + obfuscatedTokens.add(token); + } + } else { + otherTokens.add(token); + } + } + gui.setHighlightedTokens(obfuscatedTokens, deobfuscatedTokens, otherTokens); + }).start(); + } + + public Deobfuscator getDeobfuscator() { + return deobfuscator; + } + + public void modifierChange(ItemEvent event) { + if (event.getStateChange() == ItemEvent.SELECTED) { + deobfuscator.changeModifier(gui.reference.entry, (Mappings.EntryModifier) event.getItem()); + this.isDirty = true; + refreshCurrentClass(); + } + } } diff --git a/src/main/java/cuchaz/enigma/gui/GuiTricks.java b/src/main/java/cuchaz/enigma/gui/GuiTricks.java index 85b65b0a..8bf57d38 100644 --- a/src/main/java/cuchaz/enigma/gui/GuiTricks.java +++ b/src/main/java/cuchaz/enigma/gui/GuiTricks.java @@ -8,6 +8,7 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.gui; import javax.swing.*; @@ -17,26 +18,26 @@ import java.util.Arrays; public class GuiTricks { - public static JLabel unboldLabel(JLabel label) { - Font font = label.getFont(); - label.setFont(font.deriveFont(font.getStyle() & ~Font.BOLD)); - return label; - } + public static JLabel unboldLabel(JLabel label) { + Font font = label.getFont(); + label.setFont(font.deriveFont(font.getStyle() & ~Font.BOLD)); + return label; + } - public static void deactivateButton(JButton button) { - button.setEnabled(false); - button.setText(""); - for (ActionListener listener : Arrays.asList(button.getActionListeners())) { - button.removeActionListener(listener); - } - } + 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); - } + 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); + } } diff --git a/src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java b/src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java index 671f85fe..4f5231f1 100644 --- a/src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java +++ b/src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java @@ -8,25 +8,11 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.gui; import com.google.common.collect.Lists; import com.google.common.collect.Maps; - -import java.awt.BorderLayout; -import java.awt.Container; -import java.awt.Dimension; -import java.awt.FlowLayout; -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.*; -import javax.swing.text.Highlighter.HighlightPainter; - import cuchaz.enigma.Constants; import cuchaz.enigma.Deobfuscator; import cuchaz.enigma.analysis.SourceIndex; @@ -39,403 +25,410 @@ import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.Entry; import de.sciss.syntaxpane.DefaultSyntaxKit; +import javax.swing.*; +import javax.swing.text.Highlighter.HighlightPainter; +import java.awt.*; +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; public class MemberMatchingGui { - private 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 interface SaveListener { - void save(MemberMatches matches); - } - - // controls - private JFrame frame; - private Map sourceTypeButtons; - private ClassSelector sourceClasses; - private CodeReader sourceReader; - private CodeReader destReader; - private JButton matchButton; - private JButton unmatchableButton; - private JLabel sourceLabel; - private JLabel destLabel; - private HighlightPainter unmatchedHighlightPainter; - private HighlightPainter matchedHighlightPainter; - private ClassMatches classMatches; - private MemberMatches memberMatches; - private Deobfuscator sourceDeobfuscator; - private Deobfuscator destDeobfuscator; - private SaveListener saveListener; - private SourceType sourceType; - private ClassEntry obfSourceClass; - private ClassEntry obfDestClass; - private T obfSourceEntry; - private T obfDestEntry; - - public MemberMatchingGui(ClassMatches classMatches, MemberMatches fieldMatches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { - - this.classMatches = classMatches; - memberMatches = fieldMatches; - this.sourceDeobfuscator = sourceDeobfuscator; - this.destDeobfuscator = destDeobfuscator; - - // init frame - frame = new JFrame(Constants.NAME + " - Member Matcher"); - final Container pane = 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 = event -> setSourceType(SourceType.valueOf(event.getActionCommand())); - ButtonGroup sourceTypeButtons = new ButtonGroup(); - this.sourceTypeButtons = Maps.newHashMap(); - for (SourceType sourceType : SourceType.values()) { - JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons); - this.sourceTypeButtons.put(sourceType, button); - sourceTypePanel.add(button); - } - - sourceClasses = new ClassSelector(null, ClassSelector.DEOBF_CLASS_COMPARATOR, false); - sourceClasses.setSelectionListener(this::setSourceClass); - JScrollPane sourceScroller = new JScrollPane(sourceClasses); - classesPanel.add(sourceScroller); - - // init readers - DefaultSyntaxKit.initKit(); - sourceReader = new CodeReader(); - sourceReader.setSelectionListener(reference -> - { - if (reference != null) { - onSelectSource(reference.entry); - } else { - onSelectSource(null); - } - }); - destReader = new CodeReader(); - destReader.setSelectionListener(reference -> - { - if (reference != null) { - onSelectDest(reference.entry); - } else { - onSelectDest(null); - } - }); - - // add key bindings - KeyAdapter keyListener = new KeyAdapter() { - @Override - public void keyPressed(KeyEvent event) { - if (event.getKeyCode() == KeyEvent.VK_M) - matchButton.doClick(); - } - }; - sourceReader.addKeyListener(keyListener); - destReader.addKeyListener(keyListener); - - // init all the splits - JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(sourceReader), new JScrollPane( - 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); - - matchButton = new JButton(); - unmatchableButton = new JButton(); - - sourceLabel = new JLabel(); - bottomPanel.add(sourceLabel); - bottomPanel.add(matchButton); - bottomPanel.add(unmatchableButton); - destLabel = new JLabel(); - bottomPanel.add(destLabel); - - // show the frame - pane.doLayout(); - frame.setSize(1024, 576); - frame.setMinimumSize(new Dimension(640, 480)); - frame.setVisible(true); - frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - - unmatchedHighlightPainter = new ObfuscatedHighlightPainter(); - matchedHighlightPainter = new DeobfuscatedHighlightPainter(); - - // init state - saveListener = null; - obfSourceClass = null; - obfDestClass = null; - obfSourceEntry = null; - obfDestEntry = null; - setSourceType(SourceType.getDefault()); - updateButtons(); - } - - protected void setSourceType(SourceType val) { - sourceType = val; - updateSourceClasses(); - } - - public void setSaveListener(SaveListener val) { - saveListener = val; - } - - private void updateSourceClasses() { - - String selectedPackage = sourceClasses.getSelectedPackage(); - - List deobfClassEntries = Lists.newArrayList(); - for (ClassEntry entry : sourceType.getObfSourceClasses(memberMatches)) { - deobfClassEntries.add(sourceDeobfuscator.deobfuscateEntry(entry)); - } - sourceClasses.setClasses(deobfClassEntries); - - if (selectedPackage != null) { - sourceClasses.expandPackage(selectedPackage); - } - - for (SourceType sourceType : SourceType.values()) { - sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)", - sourceType.name(), sourceType.getObfSourceClasses(memberMatches).size() - )); - } - } - - protected void setSourceClass(ClassEntry sourceClass) { - - obfSourceClass = sourceDeobfuscator.obfuscateEntry(sourceClass); - obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass); - if (obfDestClass == null) { - throw new Error("No matching dest class for source class: " + obfSourceClass); - } - - sourceReader.decompileClass(obfSourceClass, sourceDeobfuscator, false, this::updateSourceHighlights); - destReader.decompileClass(obfDestClass, destDeobfuscator, false, this::updateDestHighlights); - } - - protected void updateSourceHighlights() { - highlightEntries(sourceReader, sourceDeobfuscator, memberMatches.matches().keySet(), memberMatches.getUnmatchedSourceEntries()); - } - - protected void updateDestHighlights() { - highlightEntries(destReader, destDeobfuscator, memberMatches.matches().values(), memberMatches.getUnmatchedDestEntries()); - } - - private void highlightEntries(CodeReader reader, Deobfuscator deobfuscator, Collection obfMatchedEntries, Collection obfUnmatchedEntries) { - reader.clearHighlights(); - // matched fields - updateHighlighted(obfMatchedEntries, deobfuscator, reader, matchedHighlightPainter); - // unmatched fields - updateHighlighted(obfUnmatchedEntries, deobfuscator, reader, unmatchedHighlightPainter); - } - - private void updateHighlighted(Collection entries, Deobfuscator deobfuscator, CodeReader reader, HighlightPainter painter) - { - SourceIndex index = reader.getSourceIndex(); - for (T obfT : entries) { - T deobfT = deobfuscator.deobfuscateEntry(obfT); - Token token = index.getDeclarationToken(deobfT); - if (token != null) { - reader.setHighlightedToken(token, painter); - } - } - } - - private boolean isSelectionMatched() { - return obfSourceEntry != null && obfDestEntry != null - && memberMatches.isMatched(obfSourceEntry, 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 = sourceDeobfuscator.obfuscateEntry(sourceEntry); - if (memberMatches.hasSource(obfSourceEntry)) { - setSource(obfSourceEntry); - - // look for a matched dest too - T obfDestEntry = 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 = destDeobfuscator.obfuscateEntry(destEntry); - if (memberMatches.hasDest(obfDestEntry)) { - setDest(obfDestEntry); - - // look for a matched source too - T obfSourceEntry = memberMatches.matches().inverse().get(obfDestEntry); - if (obfSourceEntry != null) { - setSource(obfSourceEntry); - } - } - } - - updateButtons(); - } - - private void setSource(T obfEntry) { - if (obfEntry == null) { - obfSourceEntry = null; - sourceLabel.setText(""); - } else { - obfSourceEntry = obfEntry; - sourceLabel.setText(getEntryLabel(obfEntry, sourceDeobfuscator)); - } - } - - private void setDest(T obfEntry) { - if (obfEntry == null) { - obfDestEntry = null; - destLabel.setText(""); - } else { - obfDestEntry = obfEntry; - destLabel.setText(getEntryLabel(obfEntry, destDeobfuscator)); - } - } - - private String getEntryLabel(T obfEntry, Deobfuscator deobfuscator) { - // show obfuscated and deobfuscated names, but no types/signatures - T deobfEntry = deobfuscator.deobfuscateEntry(obfEntry); - return String.format("%s (%s)", deobfEntry.getName(), obfEntry.getName()); - } - - private void updateButtons() { - - GuiTricks.deactivateButton(matchButton); - GuiTricks.deactivateButton(unmatchableButton); - - if (obfSourceEntry != null && obfDestEntry != null) { - if (memberMatches.isMatched(obfSourceEntry, obfDestEntry)) - GuiTricks.activateButton(matchButton, "Unmatch", event -> unmatch()); - else if (!memberMatches.isMatchedSourceEntry(obfSourceEntry) && !memberMatches.isMatchedDestEntry( - obfDestEntry)) - GuiTricks.activateButton(matchButton, "Match", event -> match()); - } else if (obfSourceEntry != null) - GuiTricks.activateButton(unmatchableButton, "Set Unmatchable", event -> unmatchable()); - } - - protected void match() { - - // update the field matches - memberMatches.makeMatch(obfSourceEntry, obfDestEntry, sourceDeobfuscator, destDeobfuscator); - save(); - - // update the ui - onSelectSource(null); - onSelectDest(null); - updateSourceHighlights(); - updateDestHighlights(); - updateSourceClasses(); - } - - protected void unmatch() { - - // update the field matches - memberMatches.unmakeMatch(obfSourceEntry, obfDestEntry, sourceDeobfuscator, destDeobfuscator); - save(); - - // update the ui - onSelectSource(null); - onSelectDest(null); - updateSourceHighlights(); - updateDestHighlights(); - updateSourceClasses(); - } - - protected void unmatchable() { - - // update the field matches - memberMatches.makeSourceUnmatchable(obfSourceEntry, sourceDeobfuscator); - save(); - - // update the ui - onSelectSource(null); - onSelectDest(null); - updateSourceHighlights(); - updateDestHighlights(); - updateSourceClasses(); - } - - private void save() { - if (saveListener != null) { - saveListener.save(memberMatches); - } - } + // controls + private JFrame frame; + private Map sourceTypeButtons; + private ClassSelector sourceClasses; + private CodeReader sourceReader; + private CodeReader destReader; + private JButton matchButton; + private JButton unmatchableButton; + private JLabel sourceLabel; + private JLabel destLabel; + private HighlightPainter unmatchedHighlightPainter; + private HighlightPainter matchedHighlightPainter; + private ClassMatches classMatches; + private MemberMatches memberMatches; + private Deobfuscator sourceDeobfuscator; + private Deobfuscator destDeobfuscator; + private SaveListener saveListener; + private SourceType sourceType; + private ClassEntry obfSourceClass; + private ClassEntry obfDestClass; + private T obfSourceEntry; + private T obfDestEntry; + public MemberMatchingGui(ClassMatches classMatches, MemberMatches fieldMatches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { + + this.classMatches = classMatches; + memberMatches = fieldMatches; + this.sourceDeobfuscator = sourceDeobfuscator; + this.destDeobfuscator = destDeobfuscator; + + // init frame + frame = new JFrame(Constants.NAME + " - Member Matcher"); + final Container pane = 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 = event -> setSourceType(SourceType.valueOf(event.getActionCommand())); + ButtonGroup sourceTypeButtons = new ButtonGroup(); + this.sourceTypeButtons = Maps.newHashMap(); + for (SourceType sourceType : SourceType.values()) { + JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons); + this.sourceTypeButtons.put(sourceType, button); + sourceTypePanel.add(button); + } + + sourceClasses = new ClassSelector(null, ClassSelector.DEOBF_CLASS_COMPARATOR, false); + sourceClasses.setSelectionListener(this::setSourceClass); + JScrollPane sourceScroller = new JScrollPane(sourceClasses); + classesPanel.add(sourceScroller); + + // init readers + DefaultSyntaxKit.initKit(); + sourceReader = new CodeReader(); + sourceReader.setSelectionListener(reference -> + { + if (reference != null) { + onSelectSource(reference.entry); + } else { + onSelectSource(null); + } + }); + destReader = new CodeReader(); + destReader.setSelectionListener(reference -> + { + if (reference != null) { + onSelectDest(reference.entry); + } else { + onSelectDest(null); + } + }); + + // add key bindings + KeyAdapter keyListener = new KeyAdapter() { + @Override + public void keyPressed(KeyEvent event) { + if (event.getKeyCode() == KeyEvent.VK_M) + matchButton.doClick(); + } + }; + sourceReader.addKeyListener(keyListener); + destReader.addKeyListener(keyListener); + + // init all the splits + JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(sourceReader), new JScrollPane( + 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); + + matchButton = new JButton(); + unmatchableButton = new JButton(); + + sourceLabel = new JLabel(); + bottomPanel.add(sourceLabel); + bottomPanel.add(matchButton); + bottomPanel.add(unmatchableButton); + destLabel = new JLabel(); + bottomPanel.add(destLabel); + + // show the frame + pane.doLayout(); + frame.setSize(1024, 576); + frame.setMinimumSize(new Dimension(640, 480)); + frame.setVisible(true); + frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + + unmatchedHighlightPainter = new ObfuscatedHighlightPainter(); + matchedHighlightPainter = new DeobfuscatedHighlightPainter(); + + // init state + saveListener = null; + obfSourceClass = null; + obfDestClass = null; + obfSourceEntry = null; + obfDestEntry = null; + setSourceType(SourceType.getDefault()); + updateButtons(); + } + + protected void setSourceType(SourceType val) { + sourceType = val; + updateSourceClasses(); + } + + public void setSaveListener(SaveListener val) { + saveListener = val; + } + + private void updateSourceClasses() { + + String selectedPackage = sourceClasses.getSelectedPackage(); + + List deobfClassEntries = Lists.newArrayList(); + for (ClassEntry entry : sourceType.getObfSourceClasses(memberMatches)) { + deobfClassEntries.add(sourceDeobfuscator.deobfuscateEntry(entry)); + } + sourceClasses.setClasses(deobfClassEntries); + + if (selectedPackage != null) { + sourceClasses.expandPackage(selectedPackage); + } + + for (SourceType sourceType : SourceType.values()) { + sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)", + sourceType.name(), sourceType.getObfSourceClasses(memberMatches).size() + )); + } + } + + protected void setSourceClass(ClassEntry sourceClass) { + + obfSourceClass = sourceDeobfuscator.obfuscateEntry(sourceClass); + obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass); + if (obfDestClass == null) { + throw new Error("No matching dest class for source class: " + obfSourceClass); + } + + sourceReader.decompileClass(obfSourceClass, sourceDeobfuscator, false, this::updateSourceHighlights); + destReader.decompileClass(obfDestClass, destDeobfuscator, false, this::updateDestHighlights); + } + + protected void updateSourceHighlights() { + highlightEntries(sourceReader, sourceDeobfuscator, memberMatches.matches().keySet(), memberMatches.getUnmatchedSourceEntries()); + } + + protected void updateDestHighlights() { + highlightEntries(destReader, destDeobfuscator, memberMatches.matches().values(), memberMatches.getUnmatchedDestEntries()); + } + + private void highlightEntries(CodeReader reader, Deobfuscator deobfuscator, Collection obfMatchedEntries, Collection obfUnmatchedEntries) { + reader.clearHighlights(); + // matched fields + updateHighlighted(obfMatchedEntries, deobfuscator, reader, matchedHighlightPainter); + // unmatched fields + updateHighlighted(obfUnmatchedEntries, deobfuscator, reader, unmatchedHighlightPainter); + } + + private void updateHighlighted(Collection entries, Deobfuscator deobfuscator, CodeReader reader, HighlightPainter painter) { + SourceIndex index = reader.getSourceIndex(); + for (T obfT : entries) { + T deobfT = deobfuscator.deobfuscateEntry(obfT); + Token token = index.getDeclarationToken(deobfT); + if (token != null) { + reader.setHighlightedToken(token, painter); + } + } + } + + private boolean isSelectionMatched() { + return obfSourceEntry != null && obfDestEntry != null + && memberMatches.isMatched(obfSourceEntry, 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 = sourceDeobfuscator.obfuscateEntry(sourceEntry); + if (memberMatches.hasSource(obfSourceEntry)) { + setSource(obfSourceEntry); + + // look for a matched dest too + T obfDestEntry = 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 = destDeobfuscator.obfuscateEntry(destEntry); + if (memberMatches.hasDest(obfDestEntry)) { + setDest(obfDestEntry); + + // look for a matched source too + T obfSourceEntry = memberMatches.matches().inverse().get(obfDestEntry); + if (obfSourceEntry != null) { + setSource(obfSourceEntry); + } + } + } + + updateButtons(); + } + + private void setSource(T obfEntry) { + if (obfEntry == null) { + obfSourceEntry = null; + sourceLabel.setText(""); + } else { + obfSourceEntry = obfEntry; + sourceLabel.setText(getEntryLabel(obfEntry, sourceDeobfuscator)); + } + } + + private void setDest(T obfEntry) { + if (obfEntry == null) { + obfDestEntry = null; + destLabel.setText(""); + } else { + obfDestEntry = obfEntry; + destLabel.setText(getEntryLabel(obfEntry, destDeobfuscator)); + } + } + + private String getEntryLabel(T obfEntry, Deobfuscator deobfuscator) { + // show obfuscated and deobfuscated names, but no types/signatures + T deobfEntry = deobfuscator.deobfuscateEntry(obfEntry); + return String.format("%s (%s)", deobfEntry.getName(), obfEntry.getName()); + } + + private void updateButtons() { + + GuiTricks.deactivateButton(matchButton); + GuiTricks.deactivateButton(unmatchableButton); + + if (obfSourceEntry != null && obfDestEntry != null) { + if (memberMatches.isMatched(obfSourceEntry, obfDestEntry)) + GuiTricks.activateButton(matchButton, "Unmatch", event -> unmatch()); + else if (!memberMatches.isMatchedSourceEntry(obfSourceEntry) && !memberMatches.isMatchedDestEntry( + obfDestEntry)) + GuiTricks.activateButton(matchButton, "Match", event -> match()); + } else if (obfSourceEntry != null) + GuiTricks.activateButton(unmatchableButton, "Set Unmatchable", event -> unmatchable()); + } + + protected void match() { + + // update the field matches + memberMatches.makeMatch(obfSourceEntry, obfDestEntry, sourceDeobfuscator, destDeobfuscator); + save(); + + // update the ui + onSelectSource(null); + onSelectDest(null); + updateSourceHighlights(); + updateDestHighlights(); + updateSourceClasses(); + } + + protected void unmatch() { + + // update the field matches + memberMatches.unmakeMatch(obfSourceEntry, obfDestEntry, sourceDeobfuscator, destDeobfuscator); + save(); + + // update the ui + onSelectSource(null); + onSelectDest(null); + updateSourceHighlights(); + updateDestHighlights(); + updateSourceClasses(); + } + + protected void unmatchable() { + + // update the field matches + memberMatches.makeSourceUnmatchable(obfSourceEntry, sourceDeobfuscator); + save(); + + // update the ui + onSelectSource(null); + onSelectDest(null); + updateSourceHighlights(); + updateDestHighlights(); + updateSourceClasses(); + } + + private void save() { + if (saveListener != null) { + saveListener.save(memberMatches); + } + } + + private enum SourceType { + Matched { + @Override + public Collection getObfSourceClasses(MemberMatches matches) { + return matches.getSourceClassesWithoutUnmatchedEntries(); + } + }, + Unmatched { + @Override + public Collection getObfSourceClasses(MemberMatches matches) { + return matches.getSourceClassesWithUnmatchedEntries(); + } + }; + + public static SourceType getDefault() { + return values()[0]; + } + + 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 interface SaveListener { + void save(MemberMatches matches); + } } diff --git a/src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java b/src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java index bd9fe3d4..1fd2fa85 100644 --- a/src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java +++ b/src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java @@ -8,37 +8,37 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.gui; import cuchaz.enigma.mapping.ClassEntry; - public class ScoredClassEntry extends ClassEntry { - private static final long serialVersionUID = -8798725308554217105L; + private static final long serialVersionUID = -8798725308554217105L; - private float score; + private float score; - public ScoredClassEntry(ClassEntry other, float score) { - super(other); - this.score = score; - } + public ScoredClassEntry(ClassEntry other, float score) { + super(other); + this.score = score; + } - public float getScore() { - return score; - } + public float getScore() { + return score; + } - @Override - public int hashCode() { - return Float.hashCode(score) + super.hashCode(); - } + @Override + public int hashCode() { + return Float.hashCode(score) + super.hashCode(); + } - @Override - public boolean equals(Object other) { - return super.equals(other) && other instanceof ScoredClassEntry && equals((ScoredClassEntry) other); - } + @Override + public boolean equals(Object other) { + return super.equals(other) && other instanceof ScoredClassEntry && equals((ScoredClassEntry) other); + } - public boolean equals(ScoredClassEntry other) { - return other != null && score == other.score; - } + public boolean equals(ScoredClassEntry other) { + return other != null && Float.compare(score, other.score) == 0; + } } diff --git a/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java b/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java index 518055fd..7375111e 100644 --- a/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java +++ b/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java @@ -8,31 +8,28 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ -package cuchaz.enigma.gui; - -import java.awt.Component; -import javax.swing.DefaultListCellRenderer; -import javax.swing.JLabel; -import javax.swing.JList; -import javax.swing.ListCellRenderer; +package cuchaz.enigma.gui; import cuchaz.enigma.analysis.Token; +import javax.swing.*; +import java.awt.*; + public class TokenListCellRenderer implements ListCellRenderer { - private GuiController controller; - private DefaultListCellRenderer defaultRenderer; + private GuiController controller; + private DefaultListCellRenderer defaultRenderer; - public TokenListCellRenderer(GuiController controller) { - this.controller = controller; - this.defaultRenderer = new DefaultListCellRenderer(); - } + public TokenListCellRenderer(GuiController controller) { + this.controller = controller; + this.defaultRenderer = new DefaultListCellRenderer(); + } - @Override - public Component getListCellRendererComponent(JList list, Token token, int index, boolean isSelected, boolean hasFocus) { - JLabel label = (JLabel) this.defaultRenderer.getListCellRendererComponent(list, token, index, isSelected, hasFocus); - label.setText(this.controller.getReadableToken(token).toString()); - return label; - } + @Override + public Component getListCellRendererComponent(JList list, Token token, int index, boolean isSelected, boolean hasFocus) { + JLabel label = (JLabel) this.defaultRenderer.getListCellRendererComponent(list, token, index, isSelected, hasFocus); + label.setText(this.controller.getReadableToken(token).toString()); + return label; + } } diff --git a/src/main/java/cuchaz/enigma/gui/dialog/AboutDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/AboutDialog.java index f690b159..7b3234d8 100644 --- a/src/main/java/cuchaz/enigma/gui/dialog/AboutDialog.java +++ b/src/main/java/cuchaz/enigma/gui/dialog/AboutDialog.java @@ -8,63 +8,60 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ -package cuchaz.enigma.gui.dialog; - -import java.awt.Color; -import java.awt.Container; -import java.awt.Cursor; -import java.awt.FlowLayout; -import java.io.IOException; -import javax.swing.*; +package cuchaz.enigma.gui.dialog; import cuchaz.enigma.Constants; import cuchaz.enigma.utils.Utils; +import javax.swing.*; +import java.awt.*; +import java.io.IOException; + public class AboutDialog { - public static void show(JFrame parent) { - // init frame - final JFrame frame = new JFrame(Constants.NAME + " - About"); - final Container pane = frame.getContentPane(); - pane.setLayout(new FlowLayout()); + public static void show(JFrame parent) { + // init frame + final JFrame frame = new JFrame(Constants.NAME + " - About"); + final Container pane = frame.getContentPane(); + pane.setLayout(new FlowLayout()); - // load the content - try { - String html = Utils.readResourceToString("/about.html"); - html = String.format(html, Constants.NAME, Constants.VERSION); - JLabel label = new JLabel(html); - label.setHorizontalAlignment(JLabel.CENTER); - pane.add(label); - } catch (IOException ex) { - throw new Error(ex); - } + // load the content + try { + String html = Utils.readResourceToString("/about.html"); + html = String.format(html, Constants.NAME, Constants.VERSION); + JLabel label = new JLabel(html); + label.setHorizontalAlignment(JLabel.CENTER); + pane.add(label); + } catch (IOException ex) { + throw new Error(ex); + } - // show the link - String html = "%s"; - html = String.format(html, Constants.URL, Constants.URL); - JButton link = new JButton(html); - link.addActionListener(event -> Utils.openUrl(Constants.URL)); - link.setBorderPainted(false); - link.setOpaque(false); - link.setBackground(Color.WHITE); - link.setCursor(new Cursor(Cursor.HAND_CURSOR)); - link.setFocusable(false); - JPanel linkPanel = new JPanel(); - linkPanel.add(link); - pane.add(linkPanel); + // show the link + String html = "%s"; + html = String.format(html, Constants.URL, Constants.URL); + JButton link = new JButton(html); + link.addActionListener(event -> Utils.openUrl(Constants.URL)); + link.setBorderPainted(false); + link.setOpaque(false); + link.setBackground(Color.WHITE); + link.setCursor(new Cursor(Cursor.HAND_CURSOR)); + link.setFocusable(false); + JPanel linkPanel = new JPanel(); + linkPanel.add(link); + pane.add(linkPanel); - // show ok button - JButton okButton = new JButton("Ok"); - pane.add(okButton); - okButton.addActionListener(arg0 -> frame.dispose()); + // show ok button + JButton okButton = new JButton("Ok"); + pane.add(okButton); + okButton.addActionListener(arg0 -> frame.dispose()); - // show the frame - pane.doLayout(); - frame.setSize(400, 220); - frame.setResizable(false); - frame.setLocationRelativeTo(parent); - frame.setVisible(true); - frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - } + // show the frame + pane.doLayout(); + frame.setSize(400, 220); + frame.setResizable(false); + frame.setLocationRelativeTo(parent); + frame.setVisible(true); + frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + } } diff --git a/src/main/java/cuchaz/enigma/gui/dialog/CrashDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/CrashDialog.java index 5c1d9ff8..04dd5d7b 100644 --- a/src/main/java/cuchaz/enigma/gui/dialog/CrashDialog.java +++ b/src/main/java/cuchaz/enigma/gui/dialog/CrashDialog.java @@ -8,80 +8,78 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ -package cuchaz.enigma.gui.dialog; -import java.awt.BorderLayout; -import java.awt.Container; -import java.awt.FlowLayout; -import java.io.PrintWriter; -import java.io.StringWriter; - -import javax.swing.*; +package cuchaz.enigma.gui.dialog; import cuchaz.enigma.Constants; import cuchaz.enigma.utils.Utils; +import javax.swing.*; +import java.awt.*; +import java.io.PrintWriter; +import java.io.StringWriter; + public class CrashDialog { - private static CrashDialog instance = null; + private static CrashDialog instance = null; - private JFrame frame; - private JTextArea text; + private JFrame frame; + private JTextArea text; - private CrashDialog(JFrame parent) { - // init frame - frame = new JFrame(Constants.NAME + " - Crash Report"); - final Container pane = frame.getContentPane(); - pane.setLayout(new BorderLayout()); + private CrashDialog(JFrame parent) { + // init frame + frame = new JFrame(Constants.NAME + " - Crash Report"); + final Container pane = frame.getContentPane(); + pane.setLayout(new BorderLayout()); - JLabel label = new JLabel(Constants.NAME + " has crashed! =("); - label.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); - pane.add(label, BorderLayout.NORTH); + JLabel label = new JLabel(Constants.NAME + " has crashed! =("); + label.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + pane.add(label, BorderLayout.NORTH); - // report panel - text = new JTextArea(); - text.setTabSize(2); - pane.add(new JScrollPane(text), BorderLayout.CENTER); + // report panel + text = new JTextArea(); + text.setTabSize(2); + pane.add(new JScrollPane(text), BorderLayout.CENTER); - // buttons panel - JPanel buttonsPanel = new JPanel(); - FlowLayout buttonsLayout = new FlowLayout(); - buttonsLayout.setAlignment(FlowLayout.RIGHT); - buttonsPanel.setLayout(buttonsLayout); - buttonsPanel.add(Utils.unboldLabel(new JLabel("If you choose exit, you will lose any unsaved work."))); - JButton ignoreButton = new JButton("Ignore"); - ignoreButton.addActionListener(event -> { - // close (hide) the dialog - frame.setVisible(false); - }); - buttonsPanel.add(ignoreButton); - JButton exitButton = new JButton("Exit"); - exitButton.addActionListener(event -> { - // exit enigma - System.exit(1); - }); - buttonsPanel.add(exitButton); - pane.add(buttonsPanel, BorderLayout.SOUTH); + // buttons panel + JPanel buttonsPanel = new JPanel(); + FlowLayout buttonsLayout = new FlowLayout(); + buttonsLayout.setAlignment(FlowLayout.RIGHT); + buttonsPanel.setLayout(buttonsLayout); + buttonsPanel.add(Utils.unboldLabel(new JLabel("If you choose exit, you will lose any unsaved work."))); + JButton ignoreButton = new JButton("Ignore"); + ignoreButton.addActionListener(event -> { + // close (hide) the dialog + frame.setVisible(false); + }); + buttonsPanel.add(ignoreButton); + JButton exitButton = new JButton("Exit"); + exitButton.addActionListener(event -> { + // exit enigma + System.exit(1); + }); + buttonsPanel.add(exitButton); + pane.add(buttonsPanel, BorderLayout.SOUTH); - // show the frame - frame.setSize(600, 400); - frame.setLocationRelativeTo(parent); - frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); - } + // show the frame + frame.setSize(600, 400); + frame.setLocationRelativeTo(parent); + frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + } - public static void init(JFrame parent) { - instance = new CrashDialog(parent); - } + public static void init(JFrame parent) { + instance = new CrashDialog(parent); + } - public static void show(Throwable ex) { - // get the error report - StringWriter buf = new StringWriter(); - ex.printStackTrace(new PrintWriter(buf)); - String report = buf.toString(); + public static void show(Throwable ex) { + // get the error report + StringWriter buf = new StringWriter(); + ex.printStackTrace(new PrintWriter(buf)); + String report = buf.toString(); - // show it! - instance.text.setText(report); - instance.frame.doLayout(); - instance.frame.setVisible(true); - } + // show it! + instance.text.setText(report); + instance.frame.doLayout(); + instance.frame.setVisible(true); + } } diff --git a/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java index 8df22a7f..5f048331 100644 --- a/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java +++ b/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java @@ -8,92 +8,89 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ -package cuchaz.enigma.gui.dialog; - -import java.awt.BorderLayout; -import java.awt.Container; -import java.awt.Dimension; -import java.awt.FlowLayout; -import javax.swing.*; +package cuchaz.enigma.gui.dialog; import cuchaz.enigma.Constants; import cuchaz.enigma.Deobfuscator.ProgressListener; import cuchaz.enigma.utils.Utils; +import javax.swing.*; +import java.awt.*; + public class ProgressDialog implements ProgressListener, AutoCloseable { - private JFrame frame; - private JLabel labelTitle; - private JLabel labelText; - private JProgressBar progress; - - public ProgressDialog(JFrame parent) { - - // init frame - this.frame = new JFrame(Constants.NAME + " - Operation in progress"); - final Container pane = this.frame.getContentPane(); - FlowLayout layout = new FlowLayout(); - layout.setAlignment(FlowLayout.LEFT); - pane.setLayout(layout); - - this.labelTitle = new JLabel(); - pane.add(this.labelTitle); - - // set up the progress bar - JPanel panel = new JPanel(); - pane.add(panel); - panel.setLayout(new BorderLayout()); - this.labelText = Utils.unboldLabel(new JLabel()); - this.progress = new JProgressBar(); - this.labelText.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0)); - panel.add(this.labelText, BorderLayout.NORTH); - panel.add(this.progress, BorderLayout.CENTER); - panel.setPreferredSize(new Dimension(360, 50)); - - // show the frame - pane.doLayout(); - this.frame.setSize(400, 120); - this.frame.setResizable(false); - this.frame.setLocationRelativeTo(parent); - this.frame.setVisible(true); - this.frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); - } - - public void close() { - this.frame.dispose(); - } - - @Override - public void init(int totalWork, String title) { - this.labelTitle.setText(title); - this.progress.setMinimum(0); - this.progress.setMaximum(totalWork); - this.progress.setValue(0); - } - - @Override - public void onProgress(int numDone, String message) { - this.labelText.setText(message); - this.progress.setValue(numDone); - - // update the frame - this.frame.validate(); - this.frame.repaint(); - } - - public interface ProgressRunnable { - void run(ProgressListener listener) throws Exception; - } - - public static void runInThread(final JFrame parent, final ProgressRunnable runnable) { - new Thread(() -> - { - try (ProgressDialog progress = new ProgressDialog(parent)) { - runnable.run(progress); - } catch (Exception ex) { - throw new Error(ex); - } - }).start(); - } + private JFrame frame; + private JLabel labelTitle; + private JLabel labelText; + private JProgressBar progress; + + public ProgressDialog(JFrame parent) { + + // init frame + this.frame = new JFrame(Constants.NAME + " - Operation in progress"); + final Container pane = this.frame.getContentPane(); + FlowLayout layout = new FlowLayout(); + layout.setAlignment(FlowLayout.LEFT); + pane.setLayout(layout); + + this.labelTitle = new JLabel(); + pane.add(this.labelTitle); + + // set up the progress bar + JPanel panel = new JPanel(); + pane.add(panel); + panel.setLayout(new BorderLayout()); + this.labelText = Utils.unboldLabel(new JLabel()); + this.progress = new JProgressBar(); + this.labelText.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0)); + panel.add(this.labelText, BorderLayout.NORTH); + panel.add(this.progress, BorderLayout.CENTER); + panel.setPreferredSize(new Dimension(360, 50)); + + // show the frame + pane.doLayout(); + this.frame.setSize(400, 120); + this.frame.setResizable(false); + this.frame.setLocationRelativeTo(parent); + this.frame.setVisible(true); + this.frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + } + + public static void runInThread(final JFrame parent, final ProgressRunnable runnable) { + new Thread(() -> + { + try (ProgressDialog progress = new ProgressDialog(parent)) { + runnable.run(progress); + } catch (Exception ex) { + throw new Error(ex); + } + }).start(); + } + + public void close() { + this.frame.dispose(); + } + + @Override + public void init(int totalWork, String title) { + this.labelTitle.setText(title); + this.progress.setMinimum(0); + this.progress.setMaximum(totalWork); + this.progress.setValue(0); + } + + @Override + public void onProgress(int numDone, String message) { + this.labelText.setText(message); + this.progress.setValue(numDone); + + // update the frame + this.frame.validate(); + this.frame.repaint(); + } + + public interface ProgressRunnable { + void run(ProgressListener listener) throws Exception; + } } diff --git a/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java b/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java index 0ccd5372..cd11acaf 100644 --- a/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java +++ b/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java @@ -1,219 +1,207 @@ package cuchaz.enigma.gui.elements; +import cuchaz.enigma.gui.Gui; +import cuchaz.enigma.gui.dialog.AboutDialog; +import cuchaz.enigma.throwables.MappingParseException; + +import javax.swing.*; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.io.IOException; import java.util.jar.JarFile; -import javax.swing.*; - -import cuchaz.enigma.gui.Gui; -import cuchaz.enigma.gui.dialog.AboutDialog; -import cuchaz.enigma.throwables.MappingParseException; - public class MenuBar extends JMenuBar { - private final Gui gui; - - public final JMenuItem closeJarMenu; - - public final JMenuItem openEnigmaMappingsMenu; - - public final JMenuItem saveMappingsMenu; - public final JMenuItem saveMappingEnigmaFileMenu; - public final JMenuItem saveMappingEnigmaDirectoryMenu; - public final JMenuItem saveMappingsSrgMenu; - public final JMenuItem closeMappingsMenu; - - public final JMenuItem rebuildMethodNamesMenu; - - public final JMenuItem exportSourceMenu; - public final JMenuItem exportJarMenu; + public final JMenuItem closeJarMenu; + public final JMenuItem openEnigmaMappingsMenu; + public final JMenuItem saveMappingsMenu; + public final JMenuItem saveMappingEnigmaFileMenu; + public final JMenuItem saveMappingEnigmaDirectoryMenu; + public final JMenuItem saveMappingsSrgMenu; + public final JMenuItem closeMappingsMenu; + public final JMenuItem rebuildMethodNamesMenu; + public final JMenuItem exportSourceMenu; + public final JMenuItem exportJarMenu; + private final Gui gui; - public MenuBar(Gui gui) { - this.gui = gui; + public MenuBar(Gui gui) { + this.gui = gui; - { - JMenu menu = new JMenu("File"); - this.add(menu); - { - JMenuItem item = new JMenuItem("Open Jar..."); - menu.add(item); - item.addActionListener(event -> { - if (this.gui.jarFileChooser.showOpenDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { - // load the jar in a separate thread - new Thread(() -> - { - try { - gui.getController().openJar(new JarFile(gui.jarFileChooser.getSelectedFile())); - } catch (IOException ex) { - throw new Error(ex); - } - }).start(); - } - }); - } - { - JMenuItem item = new JMenuItem("Close Jar"); - menu.add(item); - item.addActionListener(event -> this.gui.getController().closeJar()); - this.closeJarMenu = item; - } - menu.addSeparator(); - JMenu openMenu = new JMenu("Open Mappings..."); - menu.add(openMenu); - { - JMenuItem item = new JMenuItem("Enigma"); - openMenu.add(item); - item.addActionListener(event -> { - if (this.gui.enigmaMappingsFileChooser.showOpenDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { - try { - this.gui.getController().openEnigmaMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile()); - } catch (IOException ex) { - throw new Error(ex); - } catch (MappingParseException ex) { - JOptionPane.showMessageDialog(this.gui.getFrame(), ex.getMessage()); - } - } - }); - this.openEnigmaMappingsMenu = item; - } - { - JMenuItem item = new JMenuItem("Save Mappings"); - menu.add(item); - item.addActionListener(event -> { - try { - this.gui.getController().saveMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile()); - } catch (IOException ex) { - throw new Error(ex); - } - }); - item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK)); - this.saveMappingsMenu = item; - } - JMenu saveMenu = new JMenu("Save Mappings As..."); - menu.add(saveMenu); - { - JMenuItem item = new JMenuItem("Enigma (single file)"); - saveMenu.add(item); - item.addActionListener(event -> { - // TODO: Use a specific file chooser for it - if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { - try { - this.gui.getController().saveEnigmaMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile(), false); - this.saveMappingsMenu.setEnabled(true); - } catch (IOException ex) { - throw new Error(ex); - } - } - }); - this.saveMappingEnigmaFileMenu = item; - } - { - JMenuItem item = new JMenuItem("Enigma (directory)"); - saveMenu.add(item); - item.addActionListener(event -> { - // TODO: Use a specific file chooser for it - if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { - try { - this.gui.getController().saveEnigmaMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile(), true); - this.saveMappingsMenu.setEnabled(true); - } catch (IOException ex) { - throw new Error(ex); - } - } - }); - item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK)); - this.saveMappingEnigmaDirectoryMenu = item; - } - { - JMenuItem item = new JMenuItem("SRG (single file)"); - saveMenu.add(item); - item.addActionListener(event -> { - // TODO: Use a specific file chooser for it - if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { - try { - this.gui.getController().saveSRGMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile()); - this.saveMappingsMenu.setEnabled(true); - } catch (IOException ex) { - throw new Error(ex); - } - } - }); - this.saveMappingsSrgMenu = item; - } - { - JMenuItem item = new JMenuItem("Close Mappings"); - menu.add(item); - item.addActionListener(event -> { - if (this.gui.getController().isDirty()) - { - this.gui.showDiscardDiag((response -> { - if (response == JOptionPane.YES_OPTION) - { - try - { - gui.saveMapping(); - this.gui.getController().closeMappings(); - } catch (IOException e) - { - throw new Error(e); - } - } - else if (response == JOptionPane.NO_OPTION) - this.gui.getController().closeMappings(); - return null; - }), "Save and close", "Discard changes", "Cancel"); - } - else - this.gui.getController().closeMappings(); + { + JMenu menu = new JMenu("File"); + this.add(menu); + { + JMenuItem item = new JMenuItem("Open Jar..."); + menu.add(item); + item.addActionListener(event -> { + if (this.gui.jarFileChooser.showOpenDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { + // load the jar in a separate thread + new Thread(() -> + { + try { + gui.getController().openJar(new JarFile(gui.jarFileChooser.getSelectedFile())); + } catch (IOException ex) { + throw new Error(ex); + } + }).start(); + } + }); + } + { + JMenuItem item = new JMenuItem("Close Jar"); + menu.add(item); + item.addActionListener(event -> this.gui.getController().closeJar()); + this.closeJarMenu = item; + } + menu.addSeparator(); + JMenu openMenu = new JMenu("Open Mappings..."); + menu.add(openMenu); + { + JMenuItem item = new JMenuItem("Enigma"); + openMenu.add(item); + item.addActionListener(event -> { + if (this.gui.enigmaMappingsFileChooser.showOpenDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { + try { + this.gui.getController().openEnigmaMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile()); + } catch (IOException ex) { + throw new Error(ex); + } catch (MappingParseException ex) { + JOptionPane.showMessageDialog(this.gui.getFrame(), ex.getMessage()); + } + } + }); + this.openEnigmaMappingsMenu = item; + } + { + JMenuItem item = new JMenuItem("Save Mappings"); + menu.add(item); + item.addActionListener(event -> { + try { + this.gui.getController().saveMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile()); + } catch (IOException ex) { + throw new Error(ex); + } + }); + item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK)); + this.saveMappingsMenu = item; + } + JMenu saveMenu = new JMenu("Save Mappings As..."); + menu.add(saveMenu); + { + JMenuItem item = new JMenuItem("Enigma (single file)"); + saveMenu.add(item); + item.addActionListener(event -> { + // TODO: Use a specific file chooser for it + if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { + try { + this.gui.getController().saveEnigmaMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile(), false); + this.saveMappingsMenu.setEnabled(true); + } catch (IOException ex) { + throw new Error(ex); + } + } + }); + this.saveMappingEnigmaFileMenu = item; + } + { + JMenuItem item = new JMenuItem("Enigma (directory)"); + saveMenu.add(item); + item.addActionListener(event -> { + // TODO: Use a specific file chooser for it + if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { + try { + this.gui.getController().saveEnigmaMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile(), true); + this.saveMappingsMenu.setEnabled(true); + } catch (IOException ex) { + throw new Error(ex); + } + } + }); + item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK)); + this.saveMappingEnigmaDirectoryMenu = item; + } + { + JMenuItem item = new JMenuItem("SRG (single file)"); + saveMenu.add(item); + item.addActionListener(event -> { + // TODO: Use a specific file chooser for it + if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { + try { + this.gui.getController().saveSRGMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile()); + this.saveMappingsMenu.setEnabled(true); + } catch (IOException ex) { + throw new Error(ex); + } + } + }); + this.saveMappingsSrgMenu = item; + } + { + JMenuItem item = new JMenuItem("Close Mappings"); + menu.add(item); + item.addActionListener(event -> { + if (this.gui.getController().isDirty()) { + this.gui.showDiscardDiag((response -> { + if (response == JOptionPane.YES_OPTION) { + try { + gui.saveMapping(); + this.gui.getController().closeMappings(); + } catch (IOException e) { + throw new Error(e); + } + } else if (response == JOptionPane.NO_OPTION) + this.gui.getController().closeMappings(); + return null; + }), "Save and close", "Discard changes", "Cancel"); + } else + this.gui.getController().closeMappings(); - }); - this.closeMappingsMenu = item; - } - menu.addSeparator(); - { - JMenuItem item = new JMenuItem("Rebuild Method Names"); - menu.add(item); - item.addActionListener(event -> this.gui.getController().rebuildMethodNames()); - this.rebuildMethodNamesMenu = item; - } - menu.addSeparator(); - { - JMenuItem item = new JMenuItem("Export Source..."); - menu.add(item); - item.addActionListener(event -> { - if (this.gui.exportSourceFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { - this.gui.getController().exportSource(this.gui.exportSourceFileChooser.getSelectedFile()); - } - }); - this.exportSourceMenu = item; - } - { - JMenuItem item = new JMenuItem("Export Jar..."); - menu.add(item); - item.addActionListener(event -> { - if (this.gui.exportJarFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { - this.gui.getController().exportJar(this.gui.exportJarFileChooser.getSelectedFile()); - } - }); - this.exportJarMenu = item; - } - menu.addSeparator(); - { - JMenuItem item = new JMenuItem("Exit"); - menu.add(item); - item.addActionListener(event -> this.gui.close()); - } - } - { - JMenu menu = new JMenu("Help"); - this.add(menu); - { - JMenuItem item = new JMenuItem("About"); - menu.add(item); - item.addActionListener(event -> AboutDialog.show(this.gui.getFrame())); - } - } - } + }); + this.closeMappingsMenu = item; + } + menu.addSeparator(); + { + JMenuItem item = new JMenuItem("Rebuild Method Names"); + menu.add(item); + item.addActionListener(event -> this.gui.getController().rebuildMethodNames()); + this.rebuildMethodNamesMenu = item; + } + menu.addSeparator(); + { + JMenuItem item = new JMenuItem("Export Source..."); + menu.add(item); + item.addActionListener(event -> { + if (this.gui.exportSourceFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { + this.gui.getController().exportSource(this.gui.exportSourceFileChooser.getSelectedFile()); + } + }); + this.exportSourceMenu = item; + } + { + JMenuItem item = new JMenuItem("Export Jar..."); + menu.add(item); + item.addActionListener(event -> { + if (this.gui.exportJarFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { + this.gui.getController().exportJar(this.gui.exportJarFileChooser.getSelectedFile()); + } + }); + this.exportJarMenu = item; + } + menu.addSeparator(); + { + JMenuItem item = new JMenuItem("Exit"); + menu.add(item); + item.addActionListener(event -> this.gui.close()); + } + } + { + JMenu menu = new JMenu("Help"); + this.add(menu); + { + JMenuItem item = new JMenuItem("About"); + menu.add(item); + item.addActionListener(event -> AboutDialog.show(this.gui.getFrame())); + } + } + } } diff --git a/src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java b/src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java index e387ed31..2f6d96c4 100644 --- a/src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java +++ b/src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java @@ -1,79 +1,76 @@ package cuchaz.enigma.gui.elements; -import java.awt.event.KeyEvent; - -import javax.swing.JMenuItem; -import javax.swing.JPopupMenu; -import javax.swing.KeyStroke; - import cuchaz.enigma.gui.Gui; +import javax.swing.*; +import java.awt.event.KeyEvent; + public class PopupMenuBar extends JPopupMenu { - public final JMenuItem renameMenu; - public final JMenuItem showInheritanceMenu; - public final JMenuItem showImplementationsMenu; - public final JMenuItem showCallsMenu; - public final JMenuItem openEntryMenu; - public final JMenuItem openPreviousMenu; - public final JMenuItem toggleMappingMenu; + public final JMenuItem renameMenu; + public final JMenuItem showInheritanceMenu; + public final JMenuItem showImplementationsMenu; + public final JMenuItem showCallsMenu; + public final JMenuItem openEntryMenu; + public final JMenuItem openPreviousMenu; + public final JMenuItem toggleMappingMenu; - public PopupMenuBar(Gui gui) { - { - JMenuItem menu = new JMenuItem("Rename"); - menu.addActionListener(event -> gui.startRename()); - menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, 0)); - menu.setEnabled(false); - this.add(menu); - this.renameMenu = menu; - } - { - JMenuItem menu = new JMenuItem("Show Inheritance"); - menu.addActionListener(event -> gui.showInheritance()); - menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, 0)); - menu.setEnabled(false); - this.add(menu); - this.showInheritanceMenu = menu; - } - { - JMenuItem menu = new JMenuItem("Show Implementations"); - menu.addActionListener(event -> gui.showImplementations()); - menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, 0)); - menu.setEnabled(false); - this.add(menu); - this.showImplementationsMenu = menu; - } - { - JMenuItem menu = new JMenuItem("Show Calls"); - menu.addActionListener(event -> gui.showCalls()); - menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, 0)); - menu.setEnabled(false); - this.add(menu); - this.showCallsMenu = menu; - } - { - JMenuItem menu = new JMenuItem("Go to Declaration"); - menu.addActionListener(event -> gui.navigateTo(gui.reference.entry)); - menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, 0)); - menu.setEnabled(false); - this.add(menu); - this.openEntryMenu = menu; - } - { - JMenuItem menu = new JMenuItem("Go to previous"); - menu.addActionListener(event -> gui.getController().openPreviousReference()); - menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, 0)); - menu.setEnabled(false); - this.add(menu); - this.openPreviousMenu = menu; - } - { - JMenuItem menu = new JMenuItem("Mark as deobfuscated"); - menu.addActionListener(event -> gui.toggleMapping()); - menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, 0)); - menu.setEnabled(false); - this.add(menu); - this.toggleMappingMenu = menu; - } - } + public PopupMenuBar(Gui gui) { + { + JMenuItem menu = new JMenuItem("Rename"); + menu.addActionListener(event -> gui.startRename()); + menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, 0)); + menu.setEnabled(false); + this.add(menu); + this.renameMenu = menu; + } + { + JMenuItem menu = new JMenuItem("Show Inheritance"); + menu.addActionListener(event -> gui.showInheritance()); + menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, 0)); + menu.setEnabled(false); + this.add(menu); + this.showInheritanceMenu = menu; + } + { + JMenuItem menu = new JMenuItem("Show Implementations"); + menu.addActionListener(event -> gui.showImplementations()); + menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, 0)); + menu.setEnabled(false); + this.add(menu); + this.showImplementationsMenu = menu; + } + { + JMenuItem menu = new JMenuItem("Show Calls"); + menu.addActionListener(event -> gui.showCalls()); + menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, 0)); + menu.setEnabled(false); + this.add(menu); + this.showCallsMenu = menu; + } + { + JMenuItem menu = new JMenuItem("Go to Declaration"); + menu.addActionListener(event -> gui.navigateTo(gui.reference.entry)); + menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, 0)); + menu.setEnabled(false); + this.add(menu); + this.openEntryMenu = menu; + } + { + JMenuItem menu = new JMenuItem("Go to previous"); + menu.addActionListener(event -> gui.getController().openPreviousReference()); + menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, 0)); + menu.setEnabled(false); + this.add(menu); + this.openPreviousMenu = menu; + } + { + JMenuItem menu = new JMenuItem("Mark as deobfuscated"); + menu.addActionListener(event -> gui.toggleMapping()); + menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, 0)); + menu.setEnabled(false); + this.add(menu); + this.toggleMappingMenu = menu; + } + } } diff --git a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserAny.java b/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserAny.java index 23393347..f5f66287 100644 --- a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserAny.java +++ b/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserAny.java @@ -2,10 +2,9 @@ package cuchaz.enigma.gui.filechooser; import javax.swing.*; -public class FileChooserAny extends JFileChooser -{ - public FileChooserAny() { - this.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); - this.setAcceptAllFileFilterUsed(false); - } +public class FileChooserAny extends JFileChooser { + public FileChooserAny() { + this.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); + this.setAcceptAllFileFilterUsed(false); + } } \ No newline at end of file diff --git a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFile.java b/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFile.java index 62a0f200..cea11a68 100644 --- a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFile.java +++ b/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFile.java @@ -1,8 +1,8 @@ package cuchaz.enigma.gui.filechooser; -import javax.swing.JFileChooser; +import javax.swing.*; public class FileChooserFile extends JFileChooser { - public FileChooserFile() { - } + public FileChooserFile() { + } } diff --git a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFolder.java b/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFolder.java index 93ca5d68..c16e0afc 100644 --- a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFolder.java +++ b/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFolder.java @@ -1,11 +1,11 @@ package cuchaz.enigma.gui.filechooser; -import javax.swing.JFileChooser; +import javax.swing.*; public class FileChooserFolder extends JFileChooser { - public FileChooserFolder() { - this.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); - this.setAcceptAllFileFilterUsed(false); - } + public FileChooserFolder() { + this.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + this.setAcceptAllFileFilterUsed(false); + } } diff --git a/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java b/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java index 0a730880..0f649278 100644 --- a/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java +++ b/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java @@ -8,57 +8,54 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ -package cuchaz.enigma.gui.highlight; -import java.awt.Color; -import java.awt.Graphics; -import java.awt.Rectangle; -import java.awt.Shape; +package cuchaz.enigma.gui.highlight; import javax.swing.text.BadLocationException; import javax.swing.text.Highlighter; import javax.swing.text.JTextComponent; +import java.awt.*; public abstract class BoxHighlightPainter implements Highlighter.HighlightPainter { - private Color fillColor; - private Color borderColor; - - protected BoxHighlightPainter(Color fillColor, Color borderColor) { - this.fillColor = fillColor; - this.borderColor = borderColor; - } - - @Override - public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) { - Rectangle bounds = getBounds(text, start, end); - - // fill the area - if (this.fillColor != null) { - g.setColor(this.fillColor); - g.fillRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4); - } - - // draw a box around the area - g.setColor(this.borderColor); - g.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4); - } - - public static Rectangle getBounds(JTextComponent text, int start, int end) { - try { - // determine the bounds of the text - Rectangle bounds = text.modelToView(start).union(text.modelToView(end)); - - // adjust the box so it looks nice - bounds.x -= 2; - bounds.width += 2; - bounds.y += 1; - bounds.height -= 2; - - return bounds; - } catch (BadLocationException ex) { - // don't care... just return something - return new Rectangle(0, 0, 0, 0); - } - } + private Color fillColor; + private Color borderColor; + + protected BoxHighlightPainter(Color fillColor, Color borderColor) { + this.fillColor = fillColor; + this.borderColor = borderColor; + } + + public static Rectangle getBounds(JTextComponent text, int start, int end) { + try { + // determine the bounds of the text + Rectangle bounds = text.modelToView(start).union(text.modelToView(end)); + + // adjust the box so it looks nice + bounds.x -= 2; + bounds.width += 2; + bounds.y += 1; + bounds.height -= 2; + + return bounds; + } catch (BadLocationException ex) { + // don't care... just return something + return new Rectangle(0, 0, 0, 0); + } + } + + @Override + public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) { + Rectangle bounds = getBounds(text, start, end); + + // fill the area + if (this.fillColor != null) { + g.setColor(this.fillColor); + g.fillRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4); + } + + // draw a box around the area + g.setColor(this.borderColor); + g.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4); + } } diff --git a/src/main/java/cuchaz/enigma/gui/highlight/DeobfuscatedHighlightPainter.java b/src/main/java/cuchaz/enigma/gui/highlight/DeobfuscatedHighlightPainter.java index 5d572030..a2d28844 100644 --- a/src/main/java/cuchaz/enigma/gui/highlight/DeobfuscatedHighlightPainter.java +++ b/src/main/java/cuchaz/enigma/gui/highlight/DeobfuscatedHighlightPainter.java @@ -8,13 +8,14 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.gui.highlight; -import java.awt.Color; +import java.awt.*; public class DeobfuscatedHighlightPainter extends BoxHighlightPainter { - public DeobfuscatedHighlightPainter() { - super(new Color(220, 255, 220), new Color(80, 160, 80)); - } + public DeobfuscatedHighlightPainter() { + super(new Color(220, 255, 220), new Color(80, 160, 80)); + } } diff --git a/src/main/java/cuchaz/enigma/gui/highlight/ObfuscatedHighlightPainter.java b/src/main/java/cuchaz/enigma/gui/highlight/ObfuscatedHighlightPainter.java index ee64d86a..0947d4b7 100644 --- a/src/main/java/cuchaz/enigma/gui/highlight/ObfuscatedHighlightPainter.java +++ b/src/main/java/cuchaz/enigma/gui/highlight/ObfuscatedHighlightPainter.java @@ -8,13 +8,14 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.gui.highlight; -import java.awt.Color; +import java.awt.*; public class ObfuscatedHighlightPainter extends BoxHighlightPainter { - public ObfuscatedHighlightPainter() { - super(new Color(255, 220, 220), new Color(160, 80, 80)); - } + public ObfuscatedHighlightPainter() { + super(new Color(255, 220, 220), new Color(160, 80, 80)); + } } diff --git a/src/main/java/cuchaz/enigma/gui/highlight/OtherHighlightPainter.java b/src/main/java/cuchaz/enigma/gui/highlight/OtherHighlightPainter.java index 43d8352e..74e7273d 100644 --- a/src/main/java/cuchaz/enigma/gui/highlight/OtherHighlightPainter.java +++ b/src/main/java/cuchaz/enigma/gui/highlight/OtherHighlightPainter.java @@ -8,13 +8,14 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.gui.highlight; -import java.awt.Color; +import java.awt.*; public class OtherHighlightPainter extends BoxHighlightPainter { - public OtherHighlightPainter() { - super(null, new Color(180, 180, 180)); - } + public OtherHighlightPainter() { + super(null, new Color(180, 180, 180)); + } } diff --git a/src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java b/src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java index f772284f..5cbeabc0 100644 --- a/src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java +++ b/src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java @@ -8,22 +8,22 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ -package cuchaz.enigma.gui.highlight; -import java.awt.*; +package cuchaz.enigma.gui.highlight; import javax.swing.text.Highlighter; import javax.swing.text.JTextComponent; +import java.awt.*; public class SelectionHighlightPainter implements Highlighter.HighlightPainter { - @Override - public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) { - // draw a thick border - Graphics2D g2d = (Graphics2D) g; - Rectangle bounds = BoxHighlightPainter.getBounds(text, start, end); - g2d.setColor(Color.black); - g2d.setStroke(new BasicStroke(2.0f)); - g2d.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4); - } + @Override + public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) { + // draw a thick border + Graphics2D g2d = (Graphics2D) g; + Rectangle bounds = BoxHighlightPainter.getBounds(text, start, end); + g2d.setColor(Color.black); + g2d.setStroke(new BasicStroke(2.0f)); + g2d.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4); + } } diff --git a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java index 9f391f2e..dc933cd5 100644 --- a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java +++ b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java @@ -8,58 +8,59 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ -package cuchaz.enigma.gui.node; -import javax.swing.tree.DefaultMutableTreeNode; +package cuchaz.enigma.gui.node; import cuchaz.enigma.mapping.ClassEntry; +import javax.swing.tree.DefaultMutableTreeNode; + public class ClassSelectorClassNode extends DefaultMutableTreeNode { - private ClassEntry classEntry; + private ClassEntry classEntry; - public ClassSelectorClassNode(ClassEntry classEntry) { - this.classEntry = classEntry; - this.setUserObject(classEntry); - } + public ClassSelectorClassNode(ClassEntry classEntry) { + this.classEntry = classEntry; + this.setUserObject(classEntry); + } - public ClassEntry getClassEntry() { - return this.classEntry; - } + public ClassEntry getClassEntry() { + return this.classEntry; + } - @Override - public String toString() { - return this.classEntry.getSimpleName(); - } + @Override + public String toString() { + return this.classEntry.getSimpleName(); + } - @Override - public boolean equals(Object other) { - return other instanceof ClassSelectorClassNode && equals((ClassSelectorClassNode) other); - } + @Override + public boolean equals(Object other) { + return other instanceof ClassSelectorClassNode && equals((ClassSelectorClassNode) other); + } - @Override public int hashCode() - { - return 17 + (classEntry != null ? classEntry.hashCode() : 0); - } + @Override + public int hashCode() { + return 17 + (classEntry != null ? classEntry.hashCode() : 0); + } - @Override public void setUserObject(Object userObject) - { - String packageName = ""; - if (classEntry.getPackageName() != null) - packageName = classEntry.getPackageName() + "/"; - if (userObject instanceof String) - this.classEntry = new ClassEntry(packageName + userObject); - else if (userObject instanceof ClassEntry) - this.classEntry = (ClassEntry) userObject; - super.setUserObject(classEntry); - } + @Override + public Object getUserObject() { + return classEntry; + } - @Override public Object getUserObject() - { - return classEntry; - } + @Override + public void setUserObject(Object userObject) { + String packageName = ""; + if (classEntry.getPackageName() != null) + packageName = classEntry.getPackageName() + "/"; + if (userObject instanceof String) + this.classEntry = new ClassEntry(packageName + userObject); + else if (userObject instanceof ClassEntry) + this.classEntry = (ClassEntry) userObject; + super.setUserObject(classEntry); + } - public boolean equals(ClassSelectorClassNode other) { - return this.classEntry.equals(other.classEntry); - } + public boolean equals(ClassSelectorClassNode other) { + return this.classEntry.equals(other.classEntry); + } } diff --git a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java index b3eb90ef..f80abba6 100644 --- a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java +++ b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java @@ -8,6 +8,7 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.gui.node; import javassist.bytecode.Descriptor; @@ -16,44 +17,44 @@ import javax.swing.tree.DefaultMutableTreeNode; public class ClassSelectorPackageNode extends DefaultMutableTreeNode { - private String packageName; - - public ClassSelectorPackageNode(String packageName) { - this.packageName = packageName != null ? packageName : "(none)"; - } - - public String getPackageName() { - return packageName; - } - - @Override public void setUserObject(Object userObject) - { - if (userObject instanceof String) - this.packageName = (String) userObject; - super.setUserObject(userObject); - } - - @Override public Object getUserObject() - { - return packageName; - } - - @Override - public String toString() { - return !packageName.equals("(none)") ? Descriptor.toJavaName(this.packageName) : "(none)"; - } - - @Override - public boolean equals(Object other) { - return other instanceof ClassSelectorPackageNode && equals((ClassSelectorPackageNode) other); - } - - @Override public int hashCode() - { - return packageName.hashCode(); - } - - public boolean equals(ClassSelectorPackageNode other) { - return other != null && this.packageName.equals(other.packageName); - } + private String packageName; + + public ClassSelectorPackageNode(String packageName) { + this.packageName = packageName != null ? packageName : "(none)"; + } + + public String getPackageName() { + return packageName; + } + + @Override + public Object getUserObject() { + return packageName; + } + + @Override + public void setUserObject(Object userObject) { + if (userObject instanceof String) + this.packageName = (String) userObject; + super.setUserObject(userObject); + } + + @Override + public String toString() { + return !packageName.equals("(none)") ? Descriptor.toJavaName(this.packageName) : "(none)"; + } + + @Override + public boolean equals(Object other) { + return other instanceof ClassSelectorPackageNode && equals((ClassSelectorPackageNode) other); + } + + @Override + public int hashCode() { + return packageName.hashCode(); + } + + public boolean equals(ClassSelectorPackageNode other) { + return other != null && this.packageName.equals(other.packageName); + } } diff --git a/src/main/java/cuchaz/enigma/gui/panels/PanelDeobf.java b/src/main/java/cuchaz/enigma/gui/panels/PanelDeobf.java index 4f551756..68cc8e14 100644 --- a/src/main/java/cuchaz/enigma/gui/panels/PanelDeobf.java +++ b/src/main/java/cuchaz/enigma/gui/panels/PanelDeobf.java @@ -1,28 +1,25 @@ package cuchaz.enigma.gui.panels; -import java.awt.BorderLayout; - -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JScrollPane; - import cuchaz.enigma.gui.ClassSelector; import cuchaz.enigma.gui.Gui; +import javax.swing.*; +import java.awt.*; + public class PanelDeobf extends JPanel { - public final ClassSelector deobfClasses; - private final Gui gui; + public final ClassSelector deobfClasses; + private final Gui gui; - public PanelDeobf(Gui gui) { - this.gui = gui; + public PanelDeobf(Gui gui) { + this.gui = gui; - this.deobfClasses = new ClassSelector(gui, ClassSelector.DEOBF_CLASS_COMPARATOR, true); - this.deobfClasses.setSelectionListener(gui::navigateTo); - this.deobfClasses.setRenameSelectionListener(gui::onPanelRename); + this.deobfClasses = new ClassSelector(gui, ClassSelector.DEOBF_CLASS_COMPARATOR, true); + this.deobfClasses.setSelectionListener(gui::navigateTo); + this.deobfClasses.setRenameSelectionListener(gui::onPanelRename); - this.setLayout(new BorderLayout()); - this.add(new JLabel("De-obfuscated Classes"), BorderLayout.NORTH); - this.add(new JScrollPane(this.deobfClasses), BorderLayout.CENTER); - } + this.setLayout(new BorderLayout()); + this.add(new JLabel("De-obfuscated Classes"), BorderLayout.NORTH); + this.add(new JScrollPane(this.deobfClasses), BorderLayout.CENTER); + } } diff --git a/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java b/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java index 914952b9..fd30e389 100644 --- a/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java +++ b/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java @@ -1,73 +1,71 @@ package cuchaz.enigma.gui.panels; +import cuchaz.enigma.gui.BrowserCaret; +import cuchaz.enigma.gui.Gui; + +import javax.swing.*; import java.awt.*; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; -import javax.swing.JEditorPane; - -import cuchaz.enigma.gui.BrowserCaret; -import cuchaz.enigma.gui.Gui; - public class PanelEditor extends JEditorPane { - private final Gui gui; + private final Gui gui; - public PanelEditor(Gui gui) { - this.gui = gui; + public PanelEditor(Gui gui) { + this.gui = gui; - this.setEditable(false); - this.setSelectionColor(new Color(31, 46, 90)); - this.setCaret(new BrowserCaret()); - this.addCaretListener(event -> gui.onCaretMove(event.getDot())); - final PanelEditor self = this; - this.addMouseListener(new MouseAdapter() - { - @Override public void mouseReleased(MouseEvent e) - { - if (e.getButton() == MouseEvent.BUTTON3) - self.setCaretPosition(self.viewToModel(e.getPoint())); - } - }); - this.addKeyListener(new KeyAdapter() { - @Override - public void keyPressed(KeyEvent event) { - switch (event.getKeyCode()) { - case KeyEvent.VK_R: - gui.popupMenu.renameMenu.doClick(); - break; + this.setEditable(false); + this.setSelectionColor(new Color(31, 46, 90)); + this.setCaret(new BrowserCaret()); + this.addCaretListener(event -> gui.onCaretMove(event.getDot())); + final PanelEditor self = this; + this.addMouseListener(new MouseAdapter() { + @Override + public void mouseReleased(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON3) + self.setCaretPosition(self.viewToModel(e.getPoint())); + } + }); + this.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent event) { + switch (event.getKeyCode()) { + case KeyEvent.VK_R: + gui.popupMenu.renameMenu.doClick(); + break; - case KeyEvent.VK_I: - gui.popupMenu.showInheritanceMenu.doClick(); - break; + case KeyEvent.VK_I: + gui.popupMenu.showInheritanceMenu.doClick(); + break; - case KeyEvent.VK_M: - gui.popupMenu.showImplementationsMenu.doClick(); - break; + case KeyEvent.VK_M: + gui.popupMenu.showImplementationsMenu.doClick(); + break; - case KeyEvent.VK_N: - gui.popupMenu.openEntryMenu.doClick(); - break; + case KeyEvent.VK_N: + gui.popupMenu.openEntryMenu.doClick(); + break; - case KeyEvent.VK_P: - gui.popupMenu.openPreviousMenu.doClick(); - break; + case KeyEvent.VK_P: + gui.popupMenu.openPreviousMenu.doClick(); + break; - case KeyEvent.VK_C: - gui.popupMenu.showCallsMenu.doClick(); - break; + case KeyEvent.VK_C: + gui.popupMenu.showCallsMenu.doClick(); + break; - case KeyEvent.VK_T: - gui.popupMenu.toggleMappingMenu.doClick(); - break; - case KeyEvent.VK_F5: - gui.getController().refreshCurrentClass(); - break; - default: - break; - } - } - }); - } + case KeyEvent.VK_T: + gui.popupMenu.toggleMappingMenu.doClick(); + break; + case KeyEvent.VK_F5: + gui.getController().refreshCurrentClass(); + break; + default: + break; + } + } + }); + } } diff --git a/src/main/java/cuchaz/enigma/gui/panels/PanelIdentifier.java b/src/main/java/cuchaz/enigma/gui/panels/PanelIdentifier.java index faa023bd..1bf68879 100644 --- a/src/main/java/cuchaz/enigma/gui/panels/PanelIdentifier.java +++ b/src/main/java/cuchaz/enigma/gui/panels/PanelIdentifier.java @@ -1,34 +1,30 @@ package cuchaz.enigma.gui.panels; -import java.awt.Dimension; -import java.awt.GridLayout; - -import javax.swing.BorderFactory; -import javax.swing.JLabel; -import javax.swing.JPanel; - import cuchaz.enigma.gui.Gui; import cuchaz.enigma.utils.Utils; +import javax.swing.*; +import java.awt.*; + public class PanelIdentifier extends JPanel { - private final Gui gui; + private final Gui gui; - public PanelIdentifier(Gui gui) { - this.gui = gui; + public PanelIdentifier(Gui gui) { + this.gui = gui; - this.setLayout(new GridLayout(4, 1, 0, 0)); - this.setPreferredSize(new Dimension(0, 100)); - this.setBorder(BorderFactory.createTitledBorder("Identifier Info")); - } + this.setLayout(new GridLayout(4, 1, 0, 0)); + this.setPreferredSize(new Dimension(0, 100)); + this.setBorder(BorderFactory.createTitledBorder("Identifier Info")); + } - public void clearReference() { - this.removeAll(); - JLabel label = new JLabel("No identifier selected"); - Utils.unboldLabel(label); - label.setHorizontalAlignment(JLabel.CENTER); - this.add(label); + public void clearReference() { + this.removeAll(); + JLabel label = new JLabel("No identifier selected"); + Utils.unboldLabel(label); + label.setHorizontalAlignment(JLabel.CENTER); + this.add(label); - gui.redraw(); - } + gui.redraw(); + } } diff --git a/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java b/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java index 27bb70b4..4bbd32bd 100644 --- a/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java +++ b/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java @@ -1,39 +1,36 @@ package cuchaz.enigma.gui.panels; -import java.awt.BorderLayout; -import java.util.Comparator; - -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JScrollPane; - import cuchaz.enigma.gui.ClassSelector; import cuchaz.enigma.gui.Gui; import cuchaz.enigma.mapping.ClassEntry; +import javax.swing.*; +import java.awt.*; +import java.util.Comparator; + public class PanelObf extends JPanel { - private final Gui gui; - public final ClassSelector obfClasses; - - public PanelObf(Gui gui) { - this.gui = gui; - - Comparator obfClassComparator = (a, b) -> { - String aname = a.getName(); - String bname = b.getName(); - if (aname.length() != bname.length()) { - return aname.length() - bname.length(); - } - return aname.compareTo(bname); - }; - - this.obfClasses = new ClassSelector(gui, obfClassComparator, false); - this.obfClasses.setSelectionListener(gui::navigateTo); - this.obfClasses.setRenameSelectionListener(gui::onPanelRename); - - this.setLayout(new BorderLayout()); - this.add(new JLabel("Obfuscated Classes"), BorderLayout.NORTH); - this.add(new JScrollPane(this.obfClasses), BorderLayout.CENTER); - } + public final ClassSelector obfClasses; + private final Gui gui; + + public PanelObf(Gui gui) { + this.gui = gui; + + Comparator obfClassComparator = (a, b) -> { + String aname = a.getName(); + String bname = b.getName(); + if (aname.length() != bname.length()) { + return aname.length() - bname.length(); + } + return aname.compareTo(bname); + }; + + this.obfClasses = new ClassSelector(gui, obfClassComparator, false); + this.obfClasses.setSelectionListener(gui::navigateTo); + this.obfClasses.setRenameSelectionListener(gui::onPanelRename); + + this.setLayout(new BorderLayout()); + this.add(new JLabel("Obfuscated Classes"), BorderLayout.NORTH); + this.add(new JScrollPane(this.obfClasses), BorderLayout.CENTER); + } } diff --git a/src/main/java/cuchaz/enigma/mapping/ArgumentEntry.java b/src/main/java/cuchaz/enigma/mapping/ArgumentEntry.java index 662516da..9154cc22 100644 --- a/src/main/java/cuchaz/enigma/mapping/ArgumentEntry.java +++ b/src/main/java/cuchaz/enigma/mapping/ArgumentEntry.java @@ -8,102 +8,103 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.mapping; import cuchaz.enigma.utils.Utils; public class ArgumentEntry implements Entry { - private BehaviorEntry behaviorEntry; - private int index; - private String name; - - public ArgumentEntry(BehaviorEntry behaviorEntry, int index, String name) { - if (behaviorEntry == null) { - throw new IllegalArgumentException("Behavior cannot be null!"); - } - if (index < 0) { - throw new IllegalArgumentException("Index must be non-negative!"); - } - if (name == null) { - throw new IllegalArgumentException("Argument name cannot be null!"); - } - - this.behaviorEntry = behaviorEntry; - this.index = index; - this.name = name; - } - - public ArgumentEntry(ArgumentEntry other) { - this.behaviorEntry = other.getBehaviorEntry(); - this.index = other.index; - this.name = other.name; - } - - public ArgumentEntry(ArgumentEntry other, String newClassName) { - this.behaviorEntry = (BehaviorEntry) other.behaviorEntry.cloneToNewClass(new ClassEntry(newClassName)); - this.index = other.index; - this.name = other.name; - } - - public ArgumentEntry(ArgumentEntry other, BehaviorEntry entry) { - this.behaviorEntry = entry; - this.index = other.index; - this.name = other.name; - } - - public BehaviorEntry getBehaviorEntry() { - return this.behaviorEntry; - } - - public int getIndex() { - return this.index; - } - - @Override - public String getName() { - return this.name; - } - - @Override - public ClassEntry getClassEntry() { - return this.behaviorEntry.getClassEntry(); - } - - @Override - public String getClassName() { - return this.behaviorEntry.getClassName(); - } - - @Override - public ArgumentEntry cloneToNewClass(ClassEntry classEntry) { - return new ArgumentEntry(this, classEntry.getName()); - } - - public String getMethodName() { - return this.behaviorEntry.getName(); - } - - public Signature getMethodSignature() { - return this.behaviorEntry.getSignature(); - } - - @Override - public int hashCode() { - return Utils.combineHashesOrdered(this.behaviorEntry, Integer.valueOf(this.index).hashCode(), this.name.hashCode()); - } - - @Override - public boolean equals(Object other) { - return other instanceof ArgumentEntry && equals((ArgumentEntry) other); - } - - public boolean equals(ArgumentEntry other) { - return this.behaviorEntry.equals(other.behaviorEntry) && this.index == other.index && this.name.equals(other.name); - } - - @Override - public String toString() { - return this.behaviorEntry.toString() + "(" + this.index + ":" + this.name + ")"; - } + private BehaviorEntry behaviorEntry; + private int index; + private String name; + + public ArgumentEntry(BehaviorEntry behaviorEntry, int index, String name) { + if (behaviorEntry == null) { + throw new IllegalArgumentException("Behavior cannot be null!"); + } + if (index < 0) { + throw new IllegalArgumentException("Index must be non-negative!"); + } + if (name == null) { + throw new IllegalArgumentException("Argument name cannot be null!"); + } + + this.behaviorEntry = behaviorEntry; + this.index = index; + this.name = name; + } + + public ArgumentEntry(ArgumentEntry other) { + this.behaviorEntry = other.getBehaviorEntry(); + this.index = other.index; + this.name = other.name; + } + + public ArgumentEntry(ArgumentEntry other, String newClassName) { + this.behaviorEntry = (BehaviorEntry) other.behaviorEntry.cloneToNewClass(new ClassEntry(newClassName)); + this.index = other.index; + this.name = other.name; + } + + public ArgumentEntry(ArgumentEntry other, BehaviorEntry entry) { + this.behaviorEntry = entry; + this.index = other.index; + this.name = other.name; + } + + public BehaviorEntry getBehaviorEntry() { + return this.behaviorEntry; + } + + public int getIndex() { + return this.index; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public ClassEntry getClassEntry() { + return this.behaviorEntry.getClassEntry(); + } + + @Override + public String getClassName() { + return this.behaviorEntry.getClassName(); + } + + @Override + public ArgumentEntry cloneToNewClass(ClassEntry classEntry) { + return new ArgumentEntry(this, classEntry.getName()); + } + + public String getMethodName() { + return this.behaviorEntry.getName(); + } + + public Signature getMethodSignature() { + return this.behaviorEntry.getSignature(); + } + + @Override + public int hashCode() { + return Utils.combineHashesOrdered(this.behaviorEntry, Integer.valueOf(this.index).hashCode(), this.name.hashCode()); + } + + @Override + public boolean equals(Object other) { + return other instanceof ArgumentEntry && equals((ArgumentEntry) other); + } + + public boolean equals(ArgumentEntry other) { + return this.behaviorEntry.equals(other.behaviorEntry) && this.index == other.index && this.name.equals(other.name); + } + + @Override + public String toString() { + return this.behaviorEntry + "(" + this.index + ":" + this.name + ")"; + } } diff --git a/src/main/java/cuchaz/enigma/mapping/ArgumentMapping.java b/src/main/java/cuchaz/enigma/mapping/ArgumentMapping.java index e3f89272..91ecd106 100644 --- a/src/main/java/cuchaz/enigma/mapping/ArgumentMapping.java +++ b/src/main/java/cuchaz/enigma/mapping/ArgumentMapping.java @@ -8,42 +8,43 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.mapping; public class ArgumentMapping implements Comparable { - private int index; - private String name; + private int index; + private String name; - // NOTE: this argument order is important for the MethodReader/MethodWriter - public ArgumentMapping(int index, String name) { - this.index = index; - this.name = NameValidator.validateArgumentName(name); - } + // NOTE: this argument order is important for the MethodReader/MethodWriter + public ArgumentMapping(int index, String name) { + this.index = index; + this.name = NameValidator.validateArgumentName(name); + } - public ArgumentMapping(ArgumentMapping other) { - this.index = other.index; - this.name = other.name; - } + public ArgumentMapping(ArgumentMapping other) { + this.index = other.index; + this.name = other.name; + } - public int getIndex() { - return this.index; - } + public int getIndex() { + return this.index; + } - public String getName() { - return this.name; - } + public String getName() { + return this.name; + } - public void setName(String val) { - this.name = NameValidator.validateArgumentName(val); - } + public void setName(String val) { + this.name = NameValidator.validateArgumentName(val); + } - public ArgumentEntry getObfEntry(BehaviorEntry behaviorEntry) { - return new ArgumentEntry(behaviorEntry, index, name); - } + public ArgumentEntry getObfEntry(BehaviorEntry behaviorEntry) { + return new ArgumentEntry(behaviorEntry, index, name); + } - @Override - public int compareTo(ArgumentMapping other) { - return Integer.compare(this.index, other.index); - } + @Override + public int compareTo(ArgumentMapping other) { + return Integer.compare(this.index, other.index); + } } diff --git a/src/main/java/cuchaz/enigma/mapping/BehaviorEntry.java b/src/main/java/cuchaz/enigma/mapping/BehaviorEntry.java index f5c6c051..04b4ebc7 100644 --- a/src/main/java/cuchaz/enigma/mapping/BehaviorEntry.java +++ b/src/main/java/cuchaz/enigma/mapping/BehaviorEntry.java @@ -8,8 +8,9 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.mapping; public interface BehaviorEntry extends Entry { - Signature getSignature(); + Signature getSignature(); } diff --git a/src/main/java/cuchaz/enigma/mapping/ClassEntry.java b/src/main/java/cuchaz/enigma/mapping/ClassEntry.java index 398b1355..788811ff 100644 --- a/src/main/java/cuchaz/enigma/mapping/ClassEntry.java +++ b/src/main/java/cuchaz/enigma/mapping/ClassEntry.java @@ -8,6 +8,7 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.mapping; import com.google.common.collect.Lists; @@ -16,151 +17,151 @@ import java.util.List; public class ClassEntry implements Entry { - private String name; - - public ClassEntry(String className) { - if (className == null) { - throw new IllegalArgumentException("Class name cannot be null!"); - } - if (className.indexOf('.') >= 0) { - throw new IllegalArgumentException("Class name must be in JVM format. ie, path/to/package/class$inner : " + className); - } - - this.name = className; - - if (isInnerClass() && getInnermostClassName().indexOf('/') >= 0) { - throw new IllegalArgumentException("Inner class must not have a package: " + className); - } - } - - public ClassEntry(ClassEntry other) { - this.name = other.name; - } - - @Override - public String getName() { - return this.name; - } - - @Override - public String getClassName() { - return this.name; - } - - @Override - public ClassEntry getClassEntry() { - return this; - } - - @Override - public ClassEntry cloneToNewClass(ClassEntry classEntry) { - return classEntry; - } - - @Override - public int hashCode() { - return this.name.hashCode(); - } - - @Override - public boolean equals(Object other) { - return other instanceof ClassEntry && equals((ClassEntry) other); - } - - public boolean equals(ClassEntry other) { - return other != null && this.name.equals(other.name); - } - - @Override - public String toString() { - return this.name; - } - - public boolean isInnerClass() { - return this.name.lastIndexOf('$') >= 0; - } - - public List getClassChainNames() { - return Lists.newArrayList(this.name.split("\\$")); - } - - public List getClassChain() { - List entries = Lists.newArrayList(); - StringBuilder buf = new StringBuilder(); - for (String name : getClassChainNames()) { - if (buf.length() > 0) { - buf.append("$"); - } - buf.append(name); - entries.add(new ClassEntry(buf.toString())); - } - return entries; - } - - public String getOutermostClassName() { - if (isInnerClass()) { - return this.name.substring(0, this.name.indexOf('$')); - } - return this.name; - } - - public ClassEntry getOutermostClassEntry() { - return new ClassEntry(getOutermostClassName()); - } - - public String getOuterClassName() { - if (!isInnerClass()) { - throw new Error("This is not an inner class!"); - } - return this.name.substring(0, this.name.lastIndexOf('$')); - } - - public ClassEntry getOuterClassEntry() { - return new ClassEntry(getOuterClassName()); - } - - public String getInnermostClassName() { - if (!isInnerClass()) { - throw new Error("This is not an inner class!"); - } - return this.name.substring(this.name.lastIndexOf('$') + 1); - } - - public boolean isInDefaultPackage() { - return this.name.indexOf('/') < 0; - } - - public String getPackageName() { - int pos = this.name.lastIndexOf('/'); - if (pos > 0) { - return this.name.substring(0, pos); - } - return null; - } - - public String getSimpleName() { - int pos = this.name.lastIndexOf('/'); - if (pos > 0) { - return this.name.substring(pos + 1); - } - return this.name; - } - - public ClassEntry buildClassEntry(List classChain) { - assert (classChain.contains(this)); - StringBuilder buf = new StringBuilder(); - for (ClassEntry chainEntry : classChain) { - if (buf.length() == 0) { - buf.append(chainEntry.getName()); - } else { - buf.append("$"); - buf.append(chainEntry.isInnerClass() ? chainEntry.getInnermostClassName() : chainEntry.getSimpleName()); - } - - if (chainEntry == this) { - break; - } - } - return new ClassEntry(buf.toString()); - } + private String name; + + public ClassEntry(String className) { + if (className == null) { + throw new IllegalArgumentException("Class name cannot be null!"); + } + if (className.indexOf('.') >= 0) { + throw new IllegalArgumentException("Class name must be in JVM format. ie, path/to/package/class$inner : " + className); + } + + this.name = className; + + if (isInnerClass() && getInnermostClassName().indexOf('/') >= 0) { + throw new IllegalArgumentException("Inner class must not have a package: " + className); + } + } + + public ClassEntry(ClassEntry other) { + this.name = other.name; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public String getClassName() { + return this.name; + } + + @Override + public ClassEntry getClassEntry() { + return this; + } + + @Override + public ClassEntry cloneToNewClass(ClassEntry classEntry) { + return classEntry; + } + + @Override + public int hashCode() { + return this.name.hashCode(); + } + + @Override + public boolean equals(Object other) { + return other instanceof ClassEntry && equals((ClassEntry) other); + } + + public boolean equals(ClassEntry other) { + return other != null && this.name.equals(other.name); + } + + @Override + public String toString() { + return this.name; + } + + public boolean isInnerClass() { + return this.name.lastIndexOf('$') >= 0; + } + + public List getClassChainNames() { + return Lists.newArrayList(this.name.split("\\$")); + } + + public List getClassChain() { + List entries = Lists.newArrayList(); + StringBuilder buf = new StringBuilder(); + for (String name : getClassChainNames()) { + if (buf.length() > 0) { + buf.append("$"); + } + buf.append(name); + entries.add(new ClassEntry(buf.toString())); + } + return entries; + } + + public String getOutermostClassName() { + if (isInnerClass()) { + return this.name.substring(0, this.name.indexOf('$')); + } + return this.name; + } + + public ClassEntry getOutermostClassEntry() { + return new ClassEntry(getOutermostClassName()); + } + + public String getOuterClassName() { + if (!isInnerClass()) { + throw new Error("This is not an inner class!"); + } + return this.name.substring(0, this.name.lastIndexOf('$')); + } + + public ClassEntry getOuterClassEntry() { + return new ClassEntry(getOuterClassName()); + } + + public String getInnermostClassName() { + if (!isInnerClass()) { + throw new Error("This is not an inner class!"); + } + return this.name.substring(this.name.lastIndexOf('$') + 1); + } + + public boolean isInDefaultPackage() { + return this.name.indexOf('/') < 0; + } + + public String getPackageName() { + int pos = this.name.lastIndexOf('/'); + if (pos > 0) { + return this.name.substring(0, pos); + } + return null; + } + + public String getSimpleName() { + int pos = this.name.lastIndexOf('/'); + if (pos > 0) { + return this.name.substring(pos + 1); + } + return this.name; + } + + public ClassEntry buildClassEntry(List classChain) { + assert (classChain.contains(this)); + StringBuilder buf = new StringBuilder(); + for (ClassEntry chainEntry : classChain) { + if (buf.length() == 0) { + buf.append(chainEntry.getName()); + } else { + buf.append("$"); + buf.append(chainEntry.isInnerClass() ? chainEntry.getInnermostClassName() : chainEntry.getSimpleName()); + } + + if (chainEntry == this) { + break; + } + } + return new ClassEntry(buf.toString()); + } } diff --git a/src/main/java/cuchaz/enigma/mapping/ClassMapping.java b/src/main/java/cuchaz/enigma/mapping/ClassMapping.java index a261c912..178dd3c6 100644 --- a/src/main/java/cuchaz/enigma/mapping/ClassMapping.java +++ b/src/main/java/cuchaz/enigma/mapping/ClassMapping.java @@ -8,540 +8,530 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.mapping; import com.google.common.collect.Maps; +import cuchaz.enigma.throwables.MappingConflict; import java.util.ArrayList; import java.util.Map; -import cuchaz.enigma.throwables.MappingConflict; - // FIXME: Enigma doesn't support inner classes of inner class????! public class ClassMapping implements Comparable { - private String obfFullName; - private String obfSimpleName; - private String deobfName; - private String previousDeobfName; - private Map innerClassesByObfSimple; - private Map innerClassesByObfFull; - private Map innerClassesByDeobf; - private Map fieldsByObf; - private Map fieldsByDeobf; - private Map methodsByObf; - private Map methodsByDeobf; - private boolean isDirty; - private Mappings.EntryModifier modifier; - - public ClassMapping(String obfFullName) - { - this(obfFullName, null, Mappings.EntryModifier.UNCHANGED); - } - - public ClassMapping(String obfFullName, String deobfName) - { - this(obfFullName, deobfName, Mappings.EntryModifier.UNCHANGED); - } - - public ClassMapping(String obfFullName, String deobfName, Mappings.EntryModifier modifier) - { - this.obfFullName = obfFullName; - ClassEntry classEntry = new ClassEntry(obfFullName); - obfSimpleName = classEntry.isInnerClass() ? classEntry.getInnermostClassName() : classEntry.getSimpleName(); - previousDeobfName = null; - this.deobfName = NameValidator.validateClassName(deobfName, false); - innerClassesByObfSimple = Maps.newHashMap(); - innerClassesByObfFull = Maps.newHashMap(); - innerClassesByDeobf = Maps.newHashMap(); - fieldsByObf = Maps.newHashMap(); - fieldsByDeobf = Maps.newHashMap(); - methodsByObf = Maps.newHashMap(); - methodsByDeobf = Maps.newHashMap(); - isDirty = true; - this.modifier = modifier; - } - - public String getObfFullName() { - return obfFullName; - } - - public String getObfSimpleName() { - return obfSimpleName; - } - - public String getPreviousDeobfName() { - return previousDeobfName; - } - - public String getDeobfName() { - return deobfName; - } - - public void setDeobfName(String val) { - previousDeobfName = deobfName; - deobfName = NameValidator.validateClassName(val, false); - this.isDirty = true; - } - - //// INNER CLASSES //////// - - public Iterable innerClasses() { - assert (innerClassesByObfSimple.size() >= innerClassesByDeobf.size()); - return innerClassesByObfSimple.values(); - } - - public void addInnerClassMapping(ClassMapping classMapping) throws MappingConflict { - // FIXME: dirty hack, that can get into issues, but it's a temp fix! - if (this.innerClassesByObfFull.containsKey(classMapping.getObfSimpleName())) { - throw new MappingConflict("classes", classMapping.getObfSimpleName(), this.innerClassesByObfSimple.get(classMapping.getObfSimpleName()).getObfSimpleName()); - } - innerClassesByObfFull.put(classMapping.getObfFullName(), classMapping); - innerClassesByObfSimple.put(classMapping.getObfSimpleName(), classMapping); - - if (classMapping.getDeobfName() != null) { - if (this.innerClassesByDeobf.containsKey(classMapping.getDeobfName())) { - throw new MappingConflict("classes", classMapping.getDeobfName(), this.innerClassesByDeobf.get(classMapping.getDeobfName()).getDeobfName()); - } - innerClassesByDeobf.put(classMapping.getDeobfName(), classMapping); - } - this.isDirty = true; - } - - public void removeInnerClassMapping(ClassMapping classMapping) { - innerClassesByObfFull.remove(classMapping.getObfFullName()); - boolean obfWasRemoved = innerClassesByObfSimple.remove(classMapping.getObfSimpleName()) != null; - assert (obfWasRemoved); - if (classMapping.getDeobfName() != null) { - boolean deobfWasRemoved = innerClassesByDeobf.remove(classMapping.getDeobfName()) != null; - assert (deobfWasRemoved); - } - this.isDirty = true; - } - - public ClassMapping getOrCreateInnerClass(ClassEntry obfInnerClass) { - ClassMapping classMapping = innerClassesByObfSimple.get(obfInnerClass.getInnermostClassName()); - if (classMapping == null) { - classMapping = new ClassMapping(obfInnerClass.getName()); - innerClassesByObfFull.put(classMapping.getObfFullName(), classMapping); - boolean wasAdded = innerClassesByObfSimple.put(classMapping.getObfSimpleName(), classMapping) == null; - assert (wasAdded); - this.isDirty = true; - } - return classMapping; - } - - public ClassMapping getInnerClassByObfSimple(String obfSimpleName) { - assert (isSimpleClassName(obfSimpleName)); - return innerClassesByObfSimple.get(obfSimpleName); - } - - public ClassMapping getInnerClassByDeobf(String deobfName) { - assert (isSimpleClassName(deobfName)); - return innerClassesByDeobf.get(deobfName); - } - - public ClassMapping getInnerClassByDeobfThenObfSimple(String name) { - ClassMapping classMapping = getInnerClassByDeobf(name); - if (classMapping == null) { - classMapping = getInnerClassByObfSimple(name); - } - return classMapping; - } - - public String getDeobfInnerClassName(String obfSimpleName) { - assert (isSimpleClassName(obfSimpleName)); - ClassMapping classMapping = innerClassesByObfSimple.get(obfSimpleName); - if (classMapping != null) { - return classMapping.getDeobfName(); - } - return null; - } - - public void setInnerClassName(ClassEntry obfInnerClass, String deobfName) { - ClassMapping classMapping = getOrCreateInnerClass(obfInnerClass); - if (classMapping.getDeobfName() != null) { - boolean wasRemoved = innerClassesByDeobf.remove(classMapping.getDeobfName()) != null; - assert (wasRemoved); - } - classMapping.setDeobfName(deobfName); - if (deobfName != null) { - assert (isSimpleClassName(deobfName)); - boolean wasAdded = innerClassesByDeobf.put(deobfName, classMapping) == null; - assert (wasAdded); - } - this.isDirty = true; - } - - public boolean hasInnerClassByObfSimple(String obfSimpleName) { - return innerClassesByObfSimple.containsKey(obfSimpleName); - } - - public boolean hasInnerClassByDeobf(String deobfName) { - return innerClassesByDeobf.containsKey(deobfName); - } - - - //// FIELDS //////// - - public Iterable fields() { - assert (fieldsByObf.size() == fieldsByDeobf.size()); - return fieldsByObf.values(); - } - - public boolean containsObfField(String obfName, Type obfType) { - return fieldsByObf.containsKey(getFieldKey(obfName, obfType)); - } - - public boolean containsDeobfField(String deobfName, Type deobfType) { - return fieldsByDeobf.containsKey(getFieldKey(deobfName, deobfType)); - } - - public void addFieldMapping(FieldMapping fieldMapping) { - String obfKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType()); - if (fieldsByObf.containsKey(obfKey)) { - throw new Error("Already have mapping for " + obfFullName + "." + obfKey); - } - if (fieldMapping.getDeobfName() != null) { - String deobfKey = getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfType()); - if (fieldsByDeobf.containsKey(deobfKey)) { - throw new Error("Already have mapping for " + deobfName + "." + deobfKey); - } - boolean deobfWasAdded = fieldsByDeobf.put(deobfKey, fieldMapping) == null; - assert (deobfWasAdded); - } - boolean obfWasAdded = fieldsByObf.put(obfKey, fieldMapping) == null; - assert (obfWasAdded); - this.isDirty = true; - } - - public void removeFieldMapping(FieldMapping fieldMapping) { - boolean obfWasRemoved = fieldsByObf.remove(getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType())) != null; - assert (obfWasRemoved); - if (fieldMapping.getDeobfName() != null) { - boolean deobfWasRemoved = fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfType())) != null; - assert (deobfWasRemoved); - } - this.isDirty = true; - } - - public FieldMapping getFieldByObf(String obfName, Type obfType) { - return fieldsByObf.get(getFieldKey(obfName, obfType)); - } - - public FieldMapping getFieldByDeobf(String deobfName, Type obfType) { - return fieldsByDeobf.get(getFieldKey(deobfName, obfType)); - } - - public String getObfFieldName(String deobfName, Type obfType) { - FieldMapping fieldMapping = fieldsByDeobf.get(getFieldKey(deobfName, obfType)); - if (fieldMapping != null) { - return fieldMapping.getObfName(); - } - return null; - } - - public String getDeobfFieldName(String obfName, Type obfType) { - FieldMapping fieldMapping = fieldsByObf.get(getFieldKey(obfName, obfType)); - if (fieldMapping != null) { - return fieldMapping.getDeobfName(); - } - return null; - } - - private String getFieldKey(String name, Type type) { - if (name == null) { - throw new IllegalArgumentException("name cannot be null!"); - } - if (type == null) { - throw new IllegalArgumentException("type cannot be null!"); - } - return name + ":" + type; - } - - public void setFieldName(String obfName, Type obfType, String deobfName) { - assert (deobfName != null); - FieldMapping fieldMapping = fieldsByObf.get(getFieldKey(obfName, obfType)); - if (fieldMapping == null) { - fieldMapping = new FieldMapping(obfName, obfType, deobfName, Mappings.EntryModifier.UNCHANGED); - boolean obfWasAdded = fieldsByObf.put(getFieldKey(obfName, obfType), fieldMapping) == null; - assert (obfWasAdded); - } else { - boolean wasRemoved = fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), obfType)) != null; - assert (wasRemoved); - } - fieldMapping.setDeobfName(deobfName); - if (deobfName != null) { - boolean wasAdded = fieldsByDeobf.put(getFieldKey(deobfName, obfType), fieldMapping) == null; - assert (wasAdded); - } - this.isDirty = true; - } - - public void setFieldObfNameAndType(String oldObfName, Type obfType, String newObfName, Type newObfType) { - assert(newObfName != null); - FieldMapping fieldMapping = fieldsByObf.remove(getFieldKey(oldObfName, obfType)); - assert(fieldMapping != null); - fieldMapping.setObfName(newObfName); - fieldMapping.setObfType(newObfType); - boolean obfWasAdded = fieldsByObf.put(getFieldKey(newObfName, newObfType), fieldMapping) == null; - assert(obfWasAdded); - this.isDirty = true; - } - - //// METHODS //////// - - public Iterable methods() { - assert (methodsByObf.size() >= methodsByDeobf.size()); - return methodsByObf.values(); - } - - public boolean containsObfMethod(String obfName, Signature obfSignature) { - return methodsByObf.containsKey(getMethodKey(obfName, obfSignature)); - } - - public boolean containsDeobfMethod(String deobfName, Signature obfSignature) { - return methodsByDeobf.containsKey(getMethodKey(deobfName, obfSignature)); - } - - public void addMethodMapping(MethodMapping methodMapping) { - String obfKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()); - if (methodsByObf.containsKey(obfKey)) { - throw new Error("Already have mapping for " + obfFullName + "." + obfKey); - } - boolean wasAdded = methodsByObf.put(obfKey, methodMapping) == null; - assert (wasAdded); - if (methodMapping.getDeobfName() != null) { - String deobfKey = getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature()); - if (methodsByDeobf.containsKey(deobfKey)) { - throw new Error("Already have mapping for " + deobfName + "." + deobfKey); - } - boolean deobfWasAdded = methodsByDeobf.put(deobfKey, methodMapping) == null; - assert (deobfWasAdded); - } - this.isDirty = true; - assert (methodsByObf.size() >= methodsByDeobf.size()); - } - - public void removeMethodMapping(MethodMapping methodMapping) { - boolean obfWasRemoved = methodsByObf.remove(getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature())) != null; - assert (obfWasRemoved); - if (methodMapping.getDeobfName() != null) { - boolean deobfWasRemoved = methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature())) != null; - assert (deobfWasRemoved); - } - this.isDirty = true; - } - - public MethodMapping getMethodByObf(String obfName, Signature obfSignature) { - return methodsByObf.get(getMethodKey(obfName, obfSignature)); - } - - public MethodMapping getMethodByDeobf(String deobfName, Signature obfSignature) { - return methodsByDeobf.get(getMethodKey(deobfName, obfSignature)); - } - - private String getMethodKey(String name, Signature signature) { - if (name == null) { - throw new IllegalArgumentException("name cannot be null!"); - } - if (signature == null) { - throw new IllegalArgumentException("signature cannot be null!"); - } - return name + signature; - } - - public void setMethodName(String obfName, Signature obfSignature, String deobfName) { - MethodMapping methodMapping = methodsByObf.get(getMethodKey(obfName, obfSignature)); - if (methodMapping == null) { - methodMapping = createMethodMapping(obfName, obfSignature); - } else if (methodMapping.getDeobfName() != null) { - boolean wasRemoved = methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature())) != null; - assert (wasRemoved); - } - methodMapping.setDeobfName(deobfName); - if (deobfName != null) { - boolean wasAdded = methodsByDeobf.put(getMethodKey(deobfName, obfSignature), methodMapping) == null; - assert (wasAdded); - } - this.isDirty = true; - } - - public void setMethodObfNameAndSignature(String oldObfName, Signature obfSignature, String newObfName, Signature newObfSignature) { - assert(newObfName != null); - MethodMapping methodMapping = methodsByObf.remove(getMethodKey(oldObfName, obfSignature)); - assert(methodMapping != null); - methodMapping.setObfName(newObfName); - methodMapping.setObfSignature(newObfSignature); - boolean obfWasAdded = methodsByObf.put(getMethodKey(newObfName, newObfSignature), methodMapping) == null; - assert(obfWasAdded); - this.isDirty = true; - } - - //// ARGUMENTS //////// - - public void setArgumentName(String obfMethodName, Signature obfMethodSignature, int argumentIndex, String argumentName) { - assert (argumentName != null); - MethodMapping methodMapping = methodsByObf.get(getMethodKey(obfMethodName, obfMethodSignature)); - if (methodMapping == null) { - methodMapping = createMethodMapping(obfMethodName, obfMethodSignature); - } - methodMapping.setArgumentName(argumentIndex, argumentName); - this.isDirty = true; - } - - public void removeArgumentName(String obfMethodName, Signature obfMethodSignature, int argumentIndex) { - methodsByObf.get(getMethodKey(obfMethodName, obfMethodSignature)).removeArgumentName(argumentIndex); - this.isDirty = true; - } - - private MethodMapping createMethodMapping(String obfName, Signature obfSignature) { - MethodMapping methodMapping = new MethodMapping(obfName, obfSignature); - boolean wasAdded = methodsByObf.put(getMethodKey(obfName, obfSignature), methodMapping) == null; - assert (wasAdded); - this.isDirty = true; - return methodMapping; - } - - @Override - public String toString() { - StringBuilder buf = new StringBuilder(); - buf.append(obfFullName); - buf.append(" <-> "); - buf.append(deobfName); - buf.append("\n"); - buf.append("Fields:\n"); - for (FieldMapping fieldMapping : fields()) { - buf.append("\t"); - buf.append(fieldMapping.getObfName()); - buf.append(" <-> "); - buf.append(fieldMapping.getDeobfName()); - buf.append("\n"); - } - buf.append("Methods:\n"); - for (MethodMapping methodMapping : methodsByObf.values()) { - buf.append(methodMapping.toString()); - buf.append("\n"); - } - buf.append("Inner Classes:\n"); - for (ClassMapping classMapping : innerClassesByObfSimple.values()) { - buf.append("\t"); - buf.append(classMapping.getObfSimpleName()); - buf.append(" <-> "); - buf.append(classMapping.getDeobfName()); - buf.append("\n"); - } - return buf.toString(); - } - - @Override - public int compareTo(ClassMapping other) { - // sort by a, b, c, ... aa, ab, etc - if (obfFullName.length() != other.obfFullName.length()) { - return obfFullName.length() - other.obfFullName.length(); - } - return obfFullName.compareTo(other.obfFullName); - } - - public boolean renameObfClass(String oldObfClassName, String newObfClassName) { - - // rename inner classes - for (ClassMapping innerClassMapping : new ArrayList<>(innerClassesByObfSimple.values())) { - if (innerClassMapping.renameObfClass(oldObfClassName, newObfClassName)) { - boolean wasRemoved = innerClassesByObfSimple.remove(oldObfClassName) != null; - assert (wasRemoved); - boolean wasAdded = innerClassesByObfSimple.put(newObfClassName, innerClassMapping) == null; - assert (wasAdded); - } - } - - // rename field types - for (FieldMapping fieldMapping : new ArrayList<>(fieldsByObf.values())) { - String oldFieldKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType()); - if (fieldMapping.renameObfClass(oldObfClassName, newObfClassName)) { - boolean wasRemoved = fieldsByObf.remove(oldFieldKey) != null; - assert (wasRemoved); - boolean wasAdded = fieldsByObf - .put(getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType()), fieldMapping) == null; - assert (wasAdded); - } - } - - // rename method signatures - for (MethodMapping methodMapping : new ArrayList<>(methodsByObf.values())) { - String oldMethodKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()); - if (methodMapping.renameObfClass(oldObfClassName, newObfClassName)) { - boolean wasRemoved = methodsByObf.remove(oldMethodKey) != null; - assert (wasRemoved); - boolean wasAdded = methodsByObf - .put(getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()), methodMapping) == null; - assert (wasAdded); - } - } - - if (obfFullName.equals(oldObfClassName)) { - // rename this class - obfFullName = newObfClassName; - return true; - } - this.isDirty = true; - return false; - } - - public boolean containsArgument(BehaviorEntry obfBehaviorEntry, String name) { - MethodMapping methodMapping = methodsByObf.get(getMethodKey(obfBehaviorEntry.getName(), obfBehaviorEntry.getSignature())); - return methodMapping != null && methodMapping.containsArgument(name); - } - - public static boolean isSimpleClassName(String name) { - return name.indexOf('/') < 0 && name.indexOf('$') < 0; - } - - public ClassEntry getObfEntry() { - return new ClassEntry(obfFullName); - } - - public boolean isDirty() - { - return isDirty; - } - - public void resetDirty() - { - this.isDirty = false; - } - - public void setModifier(Mappings.EntryModifier modifier) - { - if (this.modifier != modifier) - this.isDirty = true; - this.modifier = modifier; - } - - public Mappings.EntryModifier getModifier() - { - return modifier; - } - - public void setFieldModifier(String obfName, Type obfType, Mappings.EntryModifier modifier) { - FieldMapping fieldMapping = fieldsByObf.computeIfAbsent(getFieldKey(obfName, obfType), - k -> new FieldMapping(obfName, obfType, null, Mappings.EntryModifier.UNCHANGED)); - - if (fieldMapping.getModifier() != modifier) - { - fieldMapping.setModifier(modifier); - this.isDirty = true; - } - } - - public void setMethodModifier(String obfName, Signature sig, Mappings.EntryModifier modifier) { - MethodMapping methodMapping = methodsByObf.computeIfAbsent(getMethodKey(obfName, sig), - k -> new MethodMapping(obfName, sig, null, Mappings.EntryModifier.UNCHANGED)); - - if (methodMapping.getModifier() != modifier) - { - methodMapping.setModifier(modifier); - this.isDirty = true; - } - } + private String obfFullName; + private String obfSimpleName; + private String deobfName; + private String previousDeobfName; + private Map innerClassesByObfSimple; + private Map innerClassesByObfFull; + private Map innerClassesByDeobf; + private Map fieldsByObf; + private Map fieldsByDeobf; + private Map methodsByObf; + private Map methodsByDeobf; + private boolean isDirty; + private Mappings.EntryModifier modifier; + + public ClassMapping(String obfFullName) { + this(obfFullName, null, Mappings.EntryModifier.UNCHANGED); + } + + public ClassMapping(String obfFullName, String deobfName) { + this(obfFullName, deobfName, Mappings.EntryModifier.UNCHANGED); + } + + public ClassMapping(String obfFullName, String deobfName, Mappings.EntryModifier modifier) { + this.obfFullName = obfFullName; + ClassEntry classEntry = new ClassEntry(obfFullName); + obfSimpleName = classEntry.isInnerClass() ? classEntry.getInnermostClassName() : classEntry.getSimpleName(); + previousDeobfName = null; + this.deobfName = NameValidator.validateClassName(deobfName, false); + innerClassesByObfSimple = Maps.newHashMap(); + innerClassesByObfFull = Maps.newHashMap(); + innerClassesByDeobf = Maps.newHashMap(); + fieldsByObf = Maps.newHashMap(); + fieldsByDeobf = Maps.newHashMap(); + methodsByObf = Maps.newHashMap(); + methodsByDeobf = Maps.newHashMap(); + isDirty = true; + this.modifier = modifier; + } + + public static boolean isSimpleClassName(String name) { + return name.indexOf('/') < 0 && name.indexOf('$') < 0; + } + + public String getObfFullName() { + return obfFullName; + } + + public String getObfSimpleName() { + return obfSimpleName; + } + + public String getPreviousDeobfName() { + return previousDeobfName; + } + + public String getDeobfName() { + return deobfName; + } + + //// INNER CLASSES //////// + + public void setDeobfName(String val) { + previousDeobfName = deobfName; + deobfName = NameValidator.validateClassName(val, false); + this.isDirty = true; + } + + public Iterable innerClasses() { + assert (innerClassesByObfSimple.size() >= innerClassesByDeobf.size()); + return innerClassesByObfSimple.values(); + } + + public void addInnerClassMapping(ClassMapping classMapping) throws MappingConflict { + // FIXME: dirty hack, that can get into issues, but it's a temp fix! + if (this.innerClassesByObfFull.containsKey(classMapping.getObfSimpleName())) { + throw new MappingConflict("classes", classMapping.getObfSimpleName(), this.innerClassesByObfSimple.get(classMapping.getObfSimpleName()).getObfSimpleName()); + } + innerClassesByObfFull.put(classMapping.getObfFullName(), classMapping); + innerClassesByObfSimple.put(classMapping.getObfSimpleName(), classMapping); + + if (classMapping.getDeobfName() != null) { + if (this.innerClassesByDeobf.containsKey(classMapping.getDeobfName())) { + throw new MappingConflict("classes", classMapping.getDeobfName(), this.innerClassesByDeobf.get(classMapping.getDeobfName()).getDeobfName()); + } + innerClassesByDeobf.put(classMapping.getDeobfName(), classMapping); + } + this.isDirty = true; + } + + public void removeInnerClassMapping(ClassMapping classMapping) { + innerClassesByObfFull.remove(classMapping.getObfFullName()); + boolean obfWasRemoved = innerClassesByObfSimple.remove(classMapping.getObfSimpleName()) != null; + assert (obfWasRemoved); + if (classMapping.getDeobfName() != null) { + boolean deobfWasRemoved = innerClassesByDeobf.remove(classMapping.getDeobfName()) != null; + assert (deobfWasRemoved); + } + this.isDirty = true; + } + + public ClassMapping getOrCreateInnerClass(ClassEntry obfInnerClass) { + ClassMapping classMapping = innerClassesByObfSimple.get(obfInnerClass.getInnermostClassName()); + if (classMapping == null) { + classMapping = new ClassMapping(obfInnerClass.getName()); + innerClassesByObfFull.put(classMapping.getObfFullName(), classMapping); + boolean wasAdded = innerClassesByObfSimple.put(classMapping.getObfSimpleName(), classMapping) == null; + assert (wasAdded); + this.isDirty = true; + } + return classMapping; + } + + public ClassMapping getInnerClassByObfSimple(String obfSimpleName) { + assert (isSimpleClassName(obfSimpleName)); + return innerClassesByObfSimple.get(obfSimpleName); + } + + public ClassMapping getInnerClassByDeobf(String deobfName) { + assert (isSimpleClassName(deobfName)); + return innerClassesByDeobf.get(deobfName); + } + + public ClassMapping getInnerClassByDeobfThenObfSimple(String name) { + ClassMapping classMapping = getInnerClassByDeobf(name); + if (classMapping == null) { + classMapping = getInnerClassByObfSimple(name); + } + return classMapping; + } + + public String getDeobfInnerClassName(String obfSimpleName) { + assert (isSimpleClassName(obfSimpleName)); + ClassMapping classMapping = innerClassesByObfSimple.get(obfSimpleName); + if (classMapping != null) { + return classMapping.getDeobfName(); + } + return null; + } + + public void setInnerClassName(ClassEntry obfInnerClass, String deobfName) { + ClassMapping classMapping = getOrCreateInnerClass(obfInnerClass); + if (classMapping.getDeobfName() != null) { + boolean wasRemoved = innerClassesByDeobf.remove(classMapping.getDeobfName()) != null; + assert (wasRemoved); + } + classMapping.setDeobfName(deobfName); + if (deobfName != null) { + assert (isSimpleClassName(deobfName)); + boolean wasAdded = innerClassesByDeobf.put(deobfName, classMapping) == null; + assert (wasAdded); + } + this.isDirty = true; + } + + public boolean hasInnerClassByObfSimple(String obfSimpleName) { + return innerClassesByObfSimple.containsKey(obfSimpleName); + } + + //// FIELDS //////// + + public boolean hasInnerClassByDeobf(String deobfName) { + return innerClassesByDeobf.containsKey(deobfName); + } + + public Iterable fields() { + assert (fieldsByObf.size() == fieldsByDeobf.size()); + return fieldsByObf.values(); + } + + public boolean containsObfField(String obfName, Type obfType) { + return fieldsByObf.containsKey(getFieldKey(obfName, obfType)); + } + + public boolean containsDeobfField(String deobfName, Type deobfType) { + return fieldsByDeobf.containsKey(getFieldKey(deobfName, deobfType)); + } + + public void addFieldMapping(FieldMapping fieldMapping) { + String obfKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType()); + if (fieldsByObf.containsKey(obfKey)) { + throw new Error("Already have mapping for " + obfFullName + "." + obfKey); + } + if (fieldMapping.getDeobfName() != null) { + String deobfKey = getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfType()); + if (fieldsByDeobf.containsKey(deobfKey)) { + throw new Error("Already have mapping for " + deobfName + "." + deobfKey); + } + boolean deobfWasAdded = fieldsByDeobf.put(deobfKey, fieldMapping) == null; + assert (deobfWasAdded); + } + boolean obfWasAdded = fieldsByObf.put(obfKey, fieldMapping) == null; + assert (obfWasAdded); + this.isDirty = true; + } + + public void removeFieldMapping(FieldMapping fieldMapping) { + boolean obfWasRemoved = fieldsByObf.remove(getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType())) != null; + assert (obfWasRemoved); + if (fieldMapping.getDeobfName() != null) { + boolean deobfWasRemoved = fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfType())) != null; + assert (deobfWasRemoved); + } + this.isDirty = true; + } + + public FieldMapping getFieldByObf(String obfName, Type obfType) { + return fieldsByObf.get(getFieldKey(obfName, obfType)); + } + + public FieldMapping getFieldByDeobf(String deobfName, Type obfType) { + return fieldsByDeobf.get(getFieldKey(deobfName, obfType)); + } + + public String getObfFieldName(String deobfName, Type obfType) { + FieldMapping fieldMapping = fieldsByDeobf.get(getFieldKey(deobfName, obfType)); + if (fieldMapping != null) { + return fieldMapping.getObfName(); + } + return null; + } + + public String getDeobfFieldName(String obfName, Type obfType) { + FieldMapping fieldMapping = fieldsByObf.get(getFieldKey(obfName, obfType)); + if (fieldMapping != null) { + return fieldMapping.getDeobfName(); + } + return null; + } + + private String getFieldKey(String name, Type type) { + if (name == null) { + throw new IllegalArgumentException("name cannot be null!"); + } + if (type == null) { + throw new IllegalArgumentException("type cannot be null!"); + } + return name + ":" + type; + } + + public void setFieldName(String obfName, Type obfType, String deobfName) { + assert (deobfName != null); + FieldMapping fieldMapping = fieldsByObf.get(getFieldKey(obfName, obfType)); + if (fieldMapping == null) { + fieldMapping = new FieldMapping(obfName, obfType, deobfName, Mappings.EntryModifier.UNCHANGED); + boolean obfWasAdded = fieldsByObf.put(getFieldKey(obfName, obfType), fieldMapping) == null; + assert (obfWasAdded); + } else { + boolean wasRemoved = fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), obfType)) != null; + assert (wasRemoved); + } + fieldMapping.setDeobfName(deobfName); + if (deobfName != null) { + boolean wasAdded = fieldsByDeobf.put(getFieldKey(deobfName, obfType), fieldMapping) == null; + assert (wasAdded); + } + this.isDirty = true; + } + + //// METHODS //////// + + public void setFieldObfNameAndType(String oldObfName, Type obfType, String newObfName, Type newObfType) { + assert (newObfName != null); + FieldMapping fieldMapping = fieldsByObf.remove(getFieldKey(oldObfName, obfType)); + assert (fieldMapping != null); + fieldMapping.setObfName(newObfName); + fieldMapping.setObfType(newObfType); + boolean obfWasAdded = fieldsByObf.put(getFieldKey(newObfName, newObfType), fieldMapping) == null; + assert (obfWasAdded); + this.isDirty = true; + } + + public Iterable methods() { + assert (methodsByObf.size() >= methodsByDeobf.size()); + return methodsByObf.values(); + } + + public boolean containsObfMethod(String obfName, Signature obfSignature) { + return methodsByObf.containsKey(getMethodKey(obfName, obfSignature)); + } + + public boolean containsDeobfMethod(String deobfName, Signature obfSignature) { + return methodsByDeobf.containsKey(getMethodKey(deobfName, obfSignature)); + } + + public void addMethodMapping(MethodMapping methodMapping) { + String obfKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()); + if (methodsByObf.containsKey(obfKey)) { + throw new Error("Already have mapping for " + obfFullName + "." + obfKey); + } + boolean wasAdded = methodsByObf.put(obfKey, methodMapping) == null; + assert (wasAdded); + if (methodMapping.getDeobfName() != null) { + String deobfKey = getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature()); + if (methodsByDeobf.containsKey(deobfKey)) { + throw new Error("Already have mapping for " + deobfName + "." + deobfKey); + } + boolean deobfWasAdded = methodsByDeobf.put(deobfKey, methodMapping) == null; + assert (deobfWasAdded); + } + this.isDirty = true; + assert (methodsByObf.size() >= methodsByDeobf.size()); + } + + public void removeMethodMapping(MethodMapping methodMapping) { + boolean obfWasRemoved = methodsByObf.remove(getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature())) != null; + assert (obfWasRemoved); + if (methodMapping.getDeobfName() != null) { + boolean deobfWasRemoved = methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature())) != null; + assert (deobfWasRemoved); + } + this.isDirty = true; + } + + public MethodMapping getMethodByObf(String obfName, Signature obfSignature) { + return methodsByObf.get(getMethodKey(obfName, obfSignature)); + } + + public MethodMapping getMethodByDeobf(String deobfName, Signature obfSignature) { + return methodsByDeobf.get(getMethodKey(deobfName, obfSignature)); + } + + private String getMethodKey(String name, Signature signature) { + if (name == null) { + throw new IllegalArgumentException("name cannot be null!"); + } + if (signature == null) { + throw new IllegalArgumentException("signature cannot be null!"); + } + return name + signature; + } + + public void setMethodName(String obfName, Signature obfSignature, String deobfName) { + MethodMapping methodMapping = methodsByObf.get(getMethodKey(obfName, obfSignature)); + if (methodMapping == null) { + methodMapping = createMethodMapping(obfName, obfSignature); + } else if (methodMapping.getDeobfName() != null) { + boolean wasRemoved = methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature())) != null; + assert (wasRemoved); + } + methodMapping.setDeobfName(deobfName); + if (deobfName != null) { + boolean wasAdded = methodsByDeobf.put(getMethodKey(deobfName, obfSignature), methodMapping) == null; + assert (wasAdded); + } + this.isDirty = true; + } + + //// ARGUMENTS //////// + + public void setMethodObfNameAndSignature(String oldObfName, Signature obfSignature, String newObfName, Signature newObfSignature) { + assert (newObfName != null); + MethodMapping methodMapping = methodsByObf.remove(getMethodKey(oldObfName, obfSignature)); + assert (methodMapping != null); + methodMapping.setObfName(newObfName); + methodMapping.setObfSignature(newObfSignature); + boolean obfWasAdded = methodsByObf.put(getMethodKey(newObfName, newObfSignature), methodMapping) == null; + assert (obfWasAdded); + this.isDirty = true; + } + + public void setArgumentName(String obfMethodName, Signature obfMethodSignature, int argumentIndex, String argumentName) { + assert (argumentName != null); + MethodMapping methodMapping = methodsByObf.get(getMethodKey(obfMethodName, obfMethodSignature)); + if (methodMapping == null) { + methodMapping = createMethodMapping(obfMethodName, obfMethodSignature); + } + methodMapping.setArgumentName(argumentIndex, argumentName); + this.isDirty = true; + } + + public void removeArgumentName(String obfMethodName, Signature obfMethodSignature, int argumentIndex) { + methodsByObf.get(getMethodKey(obfMethodName, obfMethodSignature)).removeArgumentName(argumentIndex); + this.isDirty = true; + } + + private MethodMapping createMethodMapping(String obfName, Signature obfSignature) { + MethodMapping methodMapping = new MethodMapping(obfName, obfSignature); + boolean wasAdded = methodsByObf.put(getMethodKey(obfName, obfSignature), methodMapping) == null; + assert (wasAdded); + this.isDirty = true; + return methodMapping; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append(obfFullName); + buf.append(" <-> "); + buf.append(deobfName); + buf.append("\n"); + buf.append("Fields:\n"); + for (FieldMapping fieldMapping : fields()) { + buf.append("\t"); + buf.append(fieldMapping.getObfName()); + buf.append(" <-> "); + buf.append(fieldMapping.getDeobfName()); + buf.append("\n"); + } + buf.append("Methods:\n"); + for (MethodMapping methodMapping : methodsByObf.values()) { + buf.append(methodMapping); + buf.append("\n"); + } + buf.append("Inner Classes:\n"); + for (ClassMapping classMapping : innerClassesByObfSimple.values()) { + buf.append("\t"); + buf.append(classMapping.getObfSimpleName()); + buf.append(" <-> "); + buf.append(classMapping.getDeobfName()); + buf.append("\n"); + } + return buf.toString(); + } + + @Override + public int compareTo(ClassMapping other) { + // sort by a, b, c, ... aa, ab, etc + if (obfFullName.length() != other.obfFullName.length()) { + return obfFullName.length() - other.obfFullName.length(); + } + return obfFullName.compareTo(other.obfFullName); + } + + public boolean renameObfClass(String oldObfClassName, String newObfClassName) { + + // rename inner classes + for (ClassMapping innerClassMapping : new ArrayList<>(innerClassesByObfSimple.values())) { + if (innerClassMapping.renameObfClass(oldObfClassName, newObfClassName)) { + boolean wasRemoved = innerClassesByObfSimple.remove(oldObfClassName) != null; + assert (wasRemoved); + boolean wasAdded = innerClassesByObfSimple.put(newObfClassName, innerClassMapping) == null; + assert (wasAdded); + } + } + + // rename field types + for (FieldMapping fieldMapping : new ArrayList<>(fieldsByObf.values())) { + String oldFieldKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType()); + if (fieldMapping.renameObfClass(oldObfClassName, newObfClassName)) { + boolean wasRemoved = fieldsByObf.remove(oldFieldKey) != null; + assert (wasRemoved); + boolean wasAdded = fieldsByObf + .put(getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType()), fieldMapping) == null; + assert (wasAdded); + } + } + + // rename method signatures + for (MethodMapping methodMapping : new ArrayList<>(methodsByObf.values())) { + String oldMethodKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()); + if (methodMapping.renameObfClass(oldObfClassName, newObfClassName)) { + boolean wasRemoved = methodsByObf.remove(oldMethodKey) != null; + assert (wasRemoved); + boolean wasAdded = methodsByObf + .put(getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()), methodMapping) == null; + assert (wasAdded); + } + } + + if (obfFullName.equals(oldObfClassName)) { + // rename this class + obfFullName = newObfClassName; + return true; + } + this.isDirty = true; + return false; + } + + public boolean containsArgument(BehaviorEntry obfBehaviorEntry, String name) { + MethodMapping methodMapping = methodsByObf.get(getMethodKey(obfBehaviorEntry.getName(), obfBehaviorEntry.getSignature())); + return methodMapping != null && methodMapping.containsArgument(name); + } + + public ClassEntry getObfEntry() { + return new ClassEntry(obfFullName); + } + + public boolean isDirty() { + return isDirty; + } + + public void resetDirty() { + this.isDirty = false; + } + + public Mappings.EntryModifier getModifier() { + return modifier; + } + + public void setModifier(Mappings.EntryModifier modifier) { + if (this.modifier != modifier) + this.isDirty = true; + this.modifier = modifier; + } + + public void setFieldModifier(String obfName, Type obfType, Mappings.EntryModifier modifier) { + FieldMapping fieldMapping = fieldsByObf.computeIfAbsent(getFieldKey(obfName, obfType), + k -> new FieldMapping(obfName, obfType, null, Mappings.EntryModifier.UNCHANGED)); + + if (fieldMapping.getModifier() != modifier) { + fieldMapping.setModifier(modifier); + this.isDirty = true; + } + } + + public void setMethodModifier(String obfName, Signature sig, Mappings.EntryModifier modifier) { + MethodMapping methodMapping = methodsByObf.computeIfAbsent(getMethodKey(obfName, sig), + k -> new MethodMapping(obfName, sig, null, Mappings.EntryModifier.UNCHANGED)); + + if (methodMapping.getModifier() != modifier) { + methodMapping.setModifier(modifier); + this.isDirty = true; + } + } } diff --git a/src/main/java/cuchaz/enigma/mapping/ClassNameReplacer.java b/src/main/java/cuchaz/enigma/mapping/ClassNameReplacer.java index dc833bb8..801c4104 100644 --- a/src/main/java/cuchaz/enigma/mapping/ClassNameReplacer.java +++ b/src/main/java/cuchaz/enigma/mapping/ClassNameReplacer.java @@ -8,8 +8,9 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.mapping; public interface ClassNameReplacer { - String replace(String className); + String replace(String className); } diff --git a/src/main/java/cuchaz/enigma/mapping/ConstructorEntry.java b/src/main/java/cuchaz/enigma/mapping/ConstructorEntry.java index 4c798204..20e51138 100644 --- a/src/main/java/cuchaz/enigma/mapping/ConstructorEntry.java +++ b/src/main/java/cuchaz/enigma/mapping/ConstructorEntry.java @@ -8,97 +8,98 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.mapping; import cuchaz.enigma.utils.Utils; public class ConstructorEntry implements BehaviorEntry { - private ClassEntry classEntry; - private Signature signature; - - public ConstructorEntry(ClassEntry classEntry) { - this(classEntry, null); - } - - public ConstructorEntry(ClassEntry classEntry, Signature signature) { - if (classEntry == null) { - throw new IllegalArgumentException("Class cannot be null!"); - } - - this.classEntry = classEntry; - this.signature = signature; - } - - public ConstructorEntry(ConstructorEntry other, String newClassName) { - this.classEntry = new ClassEntry(newClassName); - this.signature = other.signature; - } - - @Override - public ClassEntry getClassEntry() { - return this.classEntry; - } - - @Override - public String getName() { - if (isStatic()) { - return ""; - } - return ""; - } - - public boolean isStatic() { - return this.signature == null; - } - - @Override - public Signature getSignature() { - return this.signature; - } - - @Override - public String getClassName() { - return this.classEntry.getName(); - } - - @Override - public ConstructorEntry cloneToNewClass(ClassEntry classEntry) { - return new ConstructorEntry(this, classEntry.getName()); - } - - @Override - public int hashCode() { - if (isStatic()) { - return Utils.combineHashesOrdered(this.classEntry); - } else { - return Utils.combineHashesOrdered(this.classEntry, this.signature); - } - } - - @Override - public boolean equals(Object other) { - return other instanceof ConstructorEntry && equals((ConstructorEntry) other); - } - - public boolean equals(ConstructorEntry other) { - if (isStatic() != other.isStatic()) { - return false; - } - - if (isStatic()) { - return this.classEntry.equals(other.classEntry); - } else { - return this.classEntry.equals(other.classEntry) && this.signature.equals(other.signature); - } - } - - @Override - public String toString() { - if (isStatic()) { - return this.classEntry.getName() + "." + getName(); - } else { - return this.classEntry.getName() + "." + getName() + this.signature; - } - } + private ClassEntry classEntry; + private Signature signature; + + public ConstructorEntry(ClassEntry classEntry) { + this(classEntry, null); + } + + public ConstructorEntry(ClassEntry classEntry, Signature signature) { + if (classEntry == null) { + throw new IllegalArgumentException("Class cannot be null!"); + } + + this.classEntry = classEntry; + this.signature = signature; + } + + public ConstructorEntry(ConstructorEntry other, String newClassName) { + this.classEntry = new ClassEntry(newClassName); + this.signature = other.signature; + } + + @Override + public ClassEntry getClassEntry() { + return this.classEntry; + } + + @Override + public String getName() { + if (isStatic()) { + return ""; + } + return ""; + } + + public boolean isStatic() { + return this.signature == null; + } + + @Override + public Signature getSignature() { + return this.signature; + } + + @Override + public String getClassName() { + return this.classEntry.getName(); + } + + @Override + public ConstructorEntry cloneToNewClass(ClassEntry classEntry) { + return new ConstructorEntry(this, classEntry.getName()); + } + + @Override + public int hashCode() { + if (isStatic()) { + return Utils.combineHashesOrdered(this.classEntry); + } else { + return Utils.combineHashesOrdered(this.classEntry, this.signature); + } + } + + @Override + public boolean equals(Object other) { + return other instanceof ConstructorEntry && equals((ConstructorEntry) other); + } + + public boolean equals(ConstructorEntry other) { + if (isStatic() != other.isStatic()) { + return false; + } + + if (isStatic()) { + return this.classEntry.equals(other.classEntry); + } else { + return this.classEntry.equals(other.classEntry) && this.signature.equals(other.signature); + } + } + + @Override + public String toString() { + if (isStatic()) { + return this.classEntry.getName() + "." + getName(); + } else { + return this.classEntry.getName() + "." + getName() + this.signature; + } + } } diff --git a/src/main/java/cuchaz/enigma/mapping/Entry.java b/src/main/java/cuchaz/enigma/mapping/Entry.java index 95747d55..c79510b9 100644 --- a/src/main/java/cuchaz/enigma/mapping/Entry.java +++ b/src/main/java/cuchaz/enigma/mapping/Entry.java @@ -8,14 +8,15 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.mapping; public interface Entry { - String getName(); + String getName(); - String getClassName(); + String getClassName(); - ClassEntry getClassEntry(); + ClassEntry getClassEntry(); - Entry cloneToNewClass(ClassEntry classEntry); + Entry cloneToNewClass(ClassEntry classEntry); } diff --git a/src/main/java/cuchaz/enigma/mapping/EntryFactory.java b/src/main/java/cuchaz/enigma/mapping/EntryFactory.java index ce4b948a..993bb64b 100644 --- a/src/main/java/cuchaz/enigma/mapping/EntryFactory.java +++ b/src/main/java/cuchaz/enigma/mapping/EntryFactory.java @@ -8,6 +8,7 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.mapping; import cuchaz.enigma.analysis.JarIndex; @@ -20,112 +21,112 @@ import javassist.expr.NewExpr; public class EntryFactory { - public static ClassEntry getClassEntry(CtClass c) { - return new ClassEntry(Descriptor.toJvmName(c.getName())); - } - - public static ClassEntry getObfClassEntry(JarIndex jarIndex, ClassMapping classMapping) { - ClassEntry obfClassEntry = new ClassEntry(classMapping.getObfFullName()); - return obfClassEntry.buildClassEntry(jarIndex.getObfClassChain(obfClassEntry)); - } - - private static ClassEntry getObfClassEntry(ClassMapping classMapping) { - return new ClassEntry(classMapping.getObfFullName()); - } - - public static ClassEntry getDeobfClassEntry(ClassMapping classMapping) { - return new ClassEntry(classMapping.getDeobfName()); - } - - public static ClassEntry getSuperclassEntry(CtClass c) { - return new ClassEntry(Descriptor.toJvmName(c.getClassFile().getSuperclass())); - } - - public static FieldEntry getFieldEntry(CtField field) { - return new FieldEntry(getClassEntry(field.getDeclaringClass()), field.getName(), new Type(field.getFieldInfo().getDescriptor())); - } - - public static FieldEntry getFieldEntry(FieldAccess call) { - return new FieldEntry(new ClassEntry(Descriptor.toJvmName(call.getClassName())), call.getFieldName(), new Type(call.getSignature())); - } - - public static FieldEntry getFieldEntry(String className, String name, String type) { - return new FieldEntry(new ClassEntry(className), name, new Type(type)); - } - - public static FieldEntry getObfFieldEntry(ClassMapping classMapping, FieldMapping fieldMapping) { - return new FieldEntry(getObfClassEntry(classMapping), fieldMapping.getObfName(), fieldMapping.getObfType()); - } - - public static MethodEntry getMethodEntry(CtMethod method) { - return new MethodEntry(getClassEntry(method.getDeclaringClass()), method.getName(), new Signature(method.getMethodInfo().getDescriptor())); - } - - public static MethodEntry getMethodEntry(MethodCall call) { - return new MethodEntry(new ClassEntry(Descriptor.toJvmName(call.getClassName())), call.getMethodName(), new Signature(call.getSignature())); - } - - public static ConstructorEntry getConstructorEntry(CtConstructor constructor) { - if (constructor.isClassInitializer()) { - return new ConstructorEntry(getClassEntry(constructor.getDeclaringClass())); - } else { - return new ConstructorEntry(getClassEntry(constructor.getDeclaringClass()), new Signature(constructor.getMethodInfo().getDescriptor())); - } - } - - public static ConstructorEntry getConstructorEntry(ConstructorCall call) { - return new ConstructorEntry(new ClassEntry(Descriptor.toJvmName(call.getClassName())), new Signature(call.getSignature())); - } - - public static ConstructorEntry getConstructorEntry(NewExpr call) { - return new ConstructorEntry(new ClassEntry(Descriptor.toJvmName(call.getClassName())), new Signature(call.getSignature())); - } - - public static BehaviorEntry getBehaviorEntry(CtBehavior behavior) { - if (behavior instanceof CtMethod) { - return getMethodEntry((CtMethod) behavior); - } else if (behavior instanceof CtConstructor) { - return getConstructorEntry((CtConstructor) behavior); - } - throw new Error("behavior is neither Method nor Constructor!"); - } - - 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(String className) { - return new ConstructorEntry(new ClassEntry(className)); - } - - public static BehaviorEntry getBehaviorEntry(ClassEntry classEntry, String behaviorName, Signature behaviorSignature) { - switch (behaviorName) { - case "": - return new ConstructorEntry(classEntry, behaviorSignature); - case "": - return new ConstructorEntry(classEntry); - default: - return new MethodEntry(classEntry, behaviorName, behaviorSignature); - } - } - - 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 getObfBehaviorEntry(ClassEntry classEntry, MethodMapping methodMapping) { - return getBehaviorEntry(classEntry, methodMapping.getObfName(), methodMapping.getObfSignature()); - } - - public static BehaviorEntry getObfBehaviorEntry(ClassMapping classMapping, MethodMapping methodMapping) { - return getObfBehaviorEntry(getObfClassEntry(classMapping), methodMapping); - } + public static ClassEntry getClassEntry(CtClass c) { + return new ClassEntry(Descriptor.toJvmName(c.getName())); + } + + public static ClassEntry getObfClassEntry(JarIndex jarIndex, ClassMapping classMapping) { + ClassEntry obfClassEntry = new ClassEntry(classMapping.getObfFullName()); + return obfClassEntry.buildClassEntry(jarIndex.getObfClassChain(obfClassEntry)); + } + + private static ClassEntry getObfClassEntry(ClassMapping classMapping) { + return new ClassEntry(classMapping.getObfFullName()); + } + + public static ClassEntry getDeobfClassEntry(ClassMapping classMapping) { + return new ClassEntry(classMapping.getDeobfName()); + } + + public static ClassEntry getSuperclassEntry(CtClass c) { + return new ClassEntry(Descriptor.toJvmName(c.getClassFile().getSuperclass())); + } + + public static FieldEntry getFieldEntry(CtField field) { + return new FieldEntry(getClassEntry(field.getDeclaringClass()), field.getName(), new Type(field.getFieldInfo().getDescriptor())); + } + + public static FieldEntry getFieldEntry(FieldAccess call) { + return new FieldEntry(new ClassEntry(Descriptor.toJvmName(call.getClassName())), call.getFieldName(), new Type(call.getSignature())); + } + + public static FieldEntry getFieldEntry(String className, String name, String type) { + return new FieldEntry(new ClassEntry(className), name, new Type(type)); + } + + public static FieldEntry getObfFieldEntry(ClassMapping classMapping, FieldMapping fieldMapping) { + return new FieldEntry(getObfClassEntry(classMapping), fieldMapping.getObfName(), fieldMapping.getObfType()); + } + + public static MethodEntry getMethodEntry(CtMethod method) { + return new MethodEntry(getClassEntry(method.getDeclaringClass()), method.getName(), new Signature(method.getMethodInfo().getDescriptor())); + } + + public static MethodEntry getMethodEntry(MethodCall call) { + return new MethodEntry(new ClassEntry(Descriptor.toJvmName(call.getClassName())), call.getMethodName(), new Signature(call.getSignature())); + } + + public static ConstructorEntry getConstructorEntry(CtConstructor constructor) { + if (constructor.isClassInitializer()) { + return new ConstructorEntry(getClassEntry(constructor.getDeclaringClass())); + } else { + return new ConstructorEntry(getClassEntry(constructor.getDeclaringClass()), new Signature(constructor.getMethodInfo().getDescriptor())); + } + } + + public static ConstructorEntry getConstructorEntry(ConstructorCall call) { + return new ConstructorEntry(new ClassEntry(Descriptor.toJvmName(call.getClassName())), new Signature(call.getSignature())); + } + + public static ConstructorEntry getConstructorEntry(NewExpr call) { + return new ConstructorEntry(new ClassEntry(Descriptor.toJvmName(call.getClassName())), new Signature(call.getSignature())); + } + + public static BehaviorEntry getBehaviorEntry(CtBehavior behavior) { + if (behavior instanceof CtMethod) { + return getMethodEntry((CtMethod) behavior); + } else if (behavior instanceof CtConstructor) { + return getConstructorEntry((CtConstructor) behavior); + } + throw new Error("behavior is neither Method nor Constructor!"); + } + + 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(String className) { + return new ConstructorEntry(new ClassEntry(className)); + } + + public static BehaviorEntry getBehaviorEntry(ClassEntry classEntry, String behaviorName, Signature behaviorSignature) { + switch (behaviorName) { + case "": + return new ConstructorEntry(classEntry, behaviorSignature); + case "": + return new ConstructorEntry(classEntry); + default: + return new MethodEntry(classEntry, behaviorName, behaviorSignature); + } + } + + 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 getObfBehaviorEntry(ClassEntry classEntry, MethodMapping methodMapping) { + return getBehaviorEntry(classEntry, methodMapping.getObfName(), methodMapping.getObfSignature()); + } + + public static BehaviorEntry getObfBehaviorEntry(ClassMapping classMapping, MethodMapping methodMapping) { + return getObfBehaviorEntry(getObfClassEntry(classMapping), methodMapping); + } } diff --git a/src/main/java/cuchaz/enigma/mapping/FieldEntry.java b/src/main/java/cuchaz/enigma/mapping/FieldEntry.java index 9980e8e6..0f1f5065 100644 --- a/src/main/java/cuchaz/enigma/mapping/FieldEntry.java +++ b/src/main/java/cuchaz/enigma/mapping/FieldEntry.java @@ -8,79 +8,80 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.mapping; import cuchaz.enigma.utils.Utils; public class FieldEntry implements Entry { - private ClassEntry classEntry; - private String name; - private Type type; - - // NOTE: this argument order is important for the MethodReader/MethodWriter - public FieldEntry(ClassEntry classEntry, String name, Type type) { - if (classEntry == null) { - throw new IllegalArgumentException("Class cannot be null!"); - } - if (name == null) { - throw new IllegalArgumentException("Field name cannot be null!"); - } - if (type == null) { - throw new IllegalArgumentException("Field type cannot be null!"); - } - - this.classEntry = classEntry; - this.name = name; - this.type = type; - } - - public FieldEntry(FieldEntry other, ClassEntry newClassEntry) { - this.classEntry = newClassEntry; - this.name = other.name; - this.type = other.type; - } - - @Override - public ClassEntry getClassEntry() { - return this.classEntry; - } - - @Override - public String getName() { - return this.name; - } - - @Override - public String getClassName() { - return this.classEntry.getName(); - } - - public Type getType() { - return this.type; - } - - @Override - public FieldEntry cloneToNewClass(ClassEntry classEntry) { - return new FieldEntry(this, classEntry); - } - - @Override - public int hashCode() { - return Utils.combineHashesOrdered(this.classEntry, this.name, this.type); - } - - @Override - public boolean equals(Object other) { - return other instanceof FieldEntry && equals((FieldEntry) other); - } - - public boolean equals(FieldEntry other) { - return this.classEntry.equals(other.classEntry) && this.name.equals(other.name) && this.type.equals(other.type); - } - - @Override - public String toString() { - return this.classEntry.getName() + "." + this.name + ":" + this.type; - } + private ClassEntry classEntry; + private String name; + private Type type; + + // NOTE: this argument order is important for the MethodReader/MethodWriter + public FieldEntry(ClassEntry classEntry, String name, Type type) { + if (classEntry == null) { + throw new IllegalArgumentException("Class cannot be null!"); + } + if (name == null) { + throw new IllegalArgumentException("Field name cannot be null!"); + } + if (type == null) { + throw new IllegalArgumentException("Field type cannot be null!"); + } + + this.classEntry = classEntry; + this.name = name; + this.type = type; + } + + public FieldEntry(FieldEntry other, ClassEntry newClassEntry) { + this.classEntry = newClassEntry; + this.name = other.name; + this.type = other.type; + } + + @Override + public ClassEntry getClassEntry() { + return this.classEntry; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public String getClassName() { + return this.classEntry.getName(); + } + + public Type getType() { + return this.type; + } + + @Override + public FieldEntry cloneToNewClass(ClassEntry classEntry) { + return new FieldEntry(this, classEntry); + } + + @Override + public int hashCode() { + return Utils.combineHashesOrdered(this.classEntry, this.name, this.type); + } + + @Override + public boolean equals(Object other) { + return other instanceof FieldEntry && equals((FieldEntry) other); + } + + public boolean equals(FieldEntry other) { + return this.classEntry.equals(other.classEntry) && this.name.equals(other.name) && this.type.equals(other.type); + } + + @Override + public String toString() { + return this.classEntry.getName() + "." + this.name + ":" + this.type; + } } diff --git a/src/main/java/cuchaz/enigma/mapping/FieldMapping.java b/src/main/java/cuchaz/enigma/mapping/FieldMapping.java index 22ba307e..cd761b47 100644 --- a/src/main/java/cuchaz/enigma/mapping/FieldMapping.java +++ b/src/main/java/cuchaz/enigma/mapping/FieldMapping.java @@ -8,103 +8,99 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.mapping; import cuchaz.enigma.throwables.IllegalNameException; public class FieldMapping implements Comparable, MemberMapping { - private String obfName; - private String deobfName; - private Type obfType; - private Mappings.EntryModifier modifier; - - public FieldMapping(String obfName, Type obfType, String deobfName, Mappings.EntryModifier modifier) { - this.obfName = obfName; - this.deobfName = NameValidator.validateFieldName(deobfName); - this.obfType = obfType; - this.modifier = modifier; - } - - public FieldMapping(FieldMapping other, ClassNameReplacer obfClassNameReplacer) { - this.obfName = other.obfName; - this.deobfName = other.deobfName; - this.modifier = other.modifier; - this.obfType = new Type(other.obfType, obfClassNameReplacer); - } - - @Override - public FieldEntry getObfEntry(ClassEntry classEntry) { - return new FieldEntry(classEntry, this.obfName, this.obfType); - } - - @Override - public String getObfName() { - return this.obfName; - } - - public String getDeobfName() { - return this.deobfName; - } - - public void setDeobfName(String val) { - this.deobfName = NameValidator.validateFieldName(val); - } - - public void setObfName(String name) { - try - { - NameValidator.validateMethodName(name); - } catch (IllegalNameException ex) - { - // Invalid name, damn obfuscation! Map to a deob name with another name to avoid issues - if (this.deobfName == null) - { - System.err.println("WARNING: " + name + " is conflicting, auto deobfuscate to " + (name + "_auto_deob")); - setDeobfName(name + "_auto_deob"); - } - } - this.obfName = name; - } - - public Type getObfType() { - return this.obfType; - } - - public void setObfType(Type val) { - this.obfType = val; - } - - public void setModifier(Mappings.EntryModifier modifier) - { - this.modifier = modifier; - } - - public Mappings.EntryModifier getModifier() - { - return modifier; - } - - @Override - public int compareTo(FieldMapping other) { - return (this.obfName + this.obfType).compareTo(other.obfName + other.obfType); - } - - public boolean renameObfClass(final String oldObfClassName, final String newObfClassName) { - // rename obf classes in the type - Type newType = new Type(this.obfType, className -> - { - if (className.equals(oldObfClassName)) { - return newObfClassName; - } - return null; - }); - - if (!newType.equals(this.obfType)) { - this.obfType = newType; - return true; - } - return false; - } + private String obfName; + private String deobfName; + private Type obfType; + private Mappings.EntryModifier modifier; + + public FieldMapping(String obfName, Type obfType, String deobfName, Mappings.EntryModifier modifier) { + this.obfName = obfName; + this.deobfName = NameValidator.validateFieldName(deobfName); + this.obfType = obfType; + this.modifier = modifier; + } + + public FieldMapping(FieldMapping other, ClassNameReplacer obfClassNameReplacer) { + this.obfName = other.obfName; + this.deobfName = other.deobfName; + this.modifier = other.modifier; + this.obfType = new Type(other.obfType, obfClassNameReplacer); + } + + @Override + public FieldEntry getObfEntry(ClassEntry classEntry) { + return new FieldEntry(classEntry, this.obfName, this.obfType); + } + + @Override + public String getObfName() { + return this.obfName; + } + + public void setObfName(String name) { + try { + NameValidator.validateMethodName(name); + } catch (IllegalNameException ex) { + // Invalid name, damn obfuscation! Map to a deob name with another name to avoid issues + if (this.deobfName == null) { + System.err.println("WARNING: " + name + " is conflicting, auto deobfuscate to " + (name + "_auto_deob")); + setDeobfName(name + "_auto_deob"); + } + } + this.obfName = name; + } + + public String getDeobfName() { + return this.deobfName; + } + + public void setDeobfName(String val) { + this.deobfName = NameValidator.validateFieldName(val); + } + + public Type getObfType() { + return this.obfType; + } + + public void setObfType(Type val) { + this.obfType = val; + } + + public Mappings.EntryModifier getModifier() { + return modifier; + } + + public void setModifier(Mappings.EntryModifier modifier) { + this.modifier = modifier; + } + + @Override + public int compareTo(FieldMapping other) { + return (this.obfName + this.obfType).compareTo(other.obfName + other.obfType); + } + + public boolean renameObfClass(final String oldObfClassName, final String newObfClassName) { + // rename obf classes in the type + Type newType = new Type(this.obfType, className -> + { + if (className.equals(oldObfClassName)) { + return newObfClassName; + } + return null; + }); + + if (!newType.equals(this.obfType)) { + this.obfType = newType; + return true; + } + return false; + } } diff --git a/src/main/java/cuchaz/enigma/mapping/LocalVariableEntry.java b/src/main/java/cuchaz/enigma/mapping/LocalVariableEntry.java index 8bbaaaf6..2bb5e3f7 100644 --- a/src/main/java/cuchaz/enigma/mapping/LocalVariableEntry.java +++ b/src/main/java/cuchaz/enigma/mapping/LocalVariableEntry.java @@ -7,98 +7,96 @@ import cuchaz.enigma.utils.Utils; * Created by Thog * 19/10/2016 */ -public class LocalVariableEntry implements Entry -{ - - protected final BehaviorEntry behaviorEntry; - protected final String name; - protected final Type type; - protected final int index; - - public LocalVariableEntry(BehaviorEntry behaviorEntry, int index, String name, Type type) { - if (behaviorEntry == null) { - throw new IllegalArgumentException("Behavior cannot be null!"); - } - if (index < 0) { - throw new IllegalArgumentException("Index must be non-negative!"); - } - if (name == null) { - throw new IllegalArgumentException("Variable name cannot be null!"); - } - if (type == null) { - throw new IllegalArgumentException("Variable type cannot be null!"); - } - - this.behaviorEntry = behaviorEntry; - this.name = name; - this.type = type; - this.index = index; - } - - - public LocalVariableEntry(LocalVariableEntry other, ClassEntry newClassEntry) { - this.behaviorEntry = (BehaviorEntry) other.behaviorEntry.cloneToNewClass(newClassEntry); - this.name = other.name; - this.type = other.type; - this.index = other.index; - } - - public BehaviorEntry getBehaviorEntry() { - return this.behaviorEntry; - } - - public Type getType() { - return type; - } - - public int getIndex() { - return index; - } - - @Override - public String getName() { - return this.name; - } - - @Override - public ClassEntry getClassEntry() { - return this.behaviorEntry.getClassEntry(); - } - - @Override - public String getClassName() { - return this.behaviorEntry.getClassName(); - } - - @Override - public LocalVariableEntry cloneToNewClass(ClassEntry classEntry) { - return new LocalVariableEntry(this, classEntry); - } - - public String getMethodName() { - return this.behaviorEntry.getName(); - } - - public Signature getMethodSignature() { - return this.behaviorEntry.getSignature(); - } - - @Override - public int hashCode() { - return Utils.combineHashesOrdered(this.behaviorEntry, this.type.hashCode(), this.name.hashCode(), Integer.hashCode(this.index)); - } - - @Override - public boolean equals(Object other) { - return other instanceof LocalVariableEntry && equals((LocalVariableEntry) other); - } - - public boolean equals(LocalVariableEntry other) { - return this.behaviorEntry.equals(other.behaviorEntry) && this.type.equals(other.type) && this.name.equals(other.name) && this.index == other.index; - } - - @Override - public String toString() { - return this.behaviorEntry.toString() + "(" + this.index + ":" + this.name + ":" + this.type + ")"; - } +public class LocalVariableEntry implements Entry { + + protected final BehaviorEntry behaviorEntry; + protected final String name; + protected final Type type; + protected final int index; + + public LocalVariableEntry(BehaviorEntry behaviorEntry, int index, String name, Type type) { + if (behaviorEntry == null) { + throw new IllegalArgumentException("Behavior cannot be null!"); + } + if (index < 0) { + throw new IllegalArgumentException("Index must be non-negative!"); + } + if (name == null) { + throw new IllegalArgumentException("Variable name cannot be null!"); + } + if (type == null) { + throw new IllegalArgumentException("Variable type cannot be null!"); + } + + this.behaviorEntry = behaviorEntry; + this.name = name; + this.type = type; + this.index = index; + } + + public LocalVariableEntry(LocalVariableEntry other, ClassEntry newClassEntry) { + this.behaviorEntry = (BehaviorEntry) other.behaviorEntry.cloneToNewClass(newClassEntry); + this.name = other.name; + this.type = other.type; + this.index = other.index; + } + + public BehaviorEntry getBehaviorEntry() { + return this.behaviorEntry; + } + + public Type getType() { + return type; + } + + public int getIndex() { + return index; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public ClassEntry getClassEntry() { + return this.behaviorEntry.getClassEntry(); + } + + @Override + public String getClassName() { + return this.behaviorEntry.getClassName(); + } + + @Override + public LocalVariableEntry cloneToNewClass(ClassEntry classEntry) { + return new LocalVariableEntry(this, classEntry); + } + + public String getMethodName() { + return this.behaviorEntry.getName(); + } + + public Signature getMethodSignature() { + return this.behaviorEntry.getSignature(); + } + + @Override + public int hashCode() { + return Utils.combineHashesOrdered(this.behaviorEntry, this.type.hashCode(), this.name.hashCode(), Integer.hashCode(this.index)); + } + + @Override + public boolean equals(Object other) { + return other instanceof LocalVariableEntry && equals((LocalVariableEntry) other); + } + + public boolean equals(LocalVariableEntry other) { + return this.behaviorEntry.equals(other.behaviorEntry) && this.type.equals(other.type) && this.name.equals(other.name) && this.index == other.index; + } + + @Override + public String toString() { + return this.behaviorEntry + "(" + this.index + ":" + this.name + ":" + this.type + ")"; + } } diff --git a/src/main/java/cuchaz/enigma/mapping/Mappings.java b/src/main/java/cuchaz/enigma/mapping/Mappings.java index d493dcfa..33dd3c54 100644 --- a/src/main/java/cuchaz/enigma/mapping/Mappings.java +++ b/src/main/java/cuchaz/enigma/mapping/Mappings.java @@ -8,246 +8,237 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.mapping; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import cuchaz.enigma.analysis.TranslationIndex; +import cuchaz.enigma.throwables.MappingConflict; import java.io.File; import java.io.IOException; import java.util.*; -import com.google.common.collect.Sets; -import cuchaz.enigma.analysis.TranslationIndex; -import cuchaz.enigma.throwables.MappingConflict; - public class Mappings { - protected Map classesByObf; - protected Map classesByDeobf; - private final FormatType originMapping; - private Mappings previousState; - - public Mappings() - { - this(FormatType.ENIGMA_DIRECTORY); - } - - public Mappings(FormatType originMapping) { - this.originMapping = originMapping; - this.classesByObf = Maps.newHashMap(); - this.classesByDeobf = Maps.newHashMap(); - this.previousState = null; - } - - public Collection classes() { - assert (this.classesByObf.size() >= this.classesByDeobf.size()); - return this.classesByObf.values(); - } - - public void addClassMapping(ClassMapping classMapping) throws MappingConflict { - if (this.classesByObf.containsKey(classMapping.getObfFullName())) { - throw new MappingConflict("class", classMapping.getObfFullName(), this.classesByObf.get(classMapping.getObfFullName()).getObfFullName()); - } - this.classesByObf.put(classMapping.getObfFullName(), classMapping); - - if (classMapping.getDeobfName() != null) { - if (this.classesByDeobf.containsKey(classMapping.getDeobfName())) { - throw new MappingConflict("class", classMapping.getDeobfName(), this.classesByDeobf.get(classMapping.getDeobfName()).getDeobfName()); - } - this.classesByDeobf.put(classMapping.getDeobfName(), classMapping); - } - } - - public void removeClassMapping(ClassMapping classMapping) { - boolean obfWasRemoved = this.classesByObf.remove(classMapping.getObfFullName()) != null; - assert (obfWasRemoved); - if (classMapping.getDeobfName() != null) { - boolean deobfWasRemoved = this.classesByDeobf.remove(classMapping.getDeobfName()) != null; - assert (deobfWasRemoved); - } - } - - - public ClassMapping getClassByObf(ClassEntry entry) { - return getClassByObf(entry.getName()); - } - - public ClassMapping getClassByObf(String obfName) { - return this.classesByObf.get(obfName); - } - - public ClassMapping getClassByDeobf(ClassEntry entry) { - return getClassByDeobf(entry.getName()); - } - - public ClassMapping getClassByDeobf(String deobfName) { - return this.classesByDeobf.get(deobfName); - } - - public void setClassDeobfName(ClassMapping classMapping, String deobfName) { - if (classMapping.getDeobfName() != null) { - boolean wasRemoved = this.classesByDeobf.remove(classMapping.getDeobfName()) != null; - assert (wasRemoved); - } - classMapping.setDeobfName(deobfName); - if (deobfName != null) { - boolean wasAdded = this.classesByDeobf.put(deobfName, classMapping) == null; - assert (wasAdded); - } - } - - public Translator getTranslator(TranslationDirection direction, TranslationIndex index) { - switch (direction) { - case Deobfuscating: - - return new Translator(direction, this.classesByObf, index); - - case Obfuscating: - - // fill in the missing deobf class entries with obf entries - Map classes = Maps.newHashMap(); - for (ClassMapping classMapping : classes()) { - if (classMapping.getDeobfName() != null) { - classes.put(classMapping.getDeobfName(), classMapping); - } else { - classes.put(classMapping.getObfFullName(), classMapping); - } - } - - // translate the translation index - // NOTE: this isn't actually recursive - TranslationIndex deobfIndex = new TranslationIndex(index, getTranslator(TranslationDirection.Deobfuscating, index)); - - return new Translator(direction, classes, deobfIndex); - - default: - throw new Error("Invalid translation direction!"); - } - } - - @Override - public String toString() { - StringBuilder buf = new StringBuilder(); - for (ClassMapping classMapping : this.classesByObf.values()) { - buf.append(classMapping.toString()); - buf.append("\n"); - } - return buf.toString(); - } - - public void renameObfClass(String oldObfName, String newObfName) { - new ArrayList<>(classes()).stream().filter(classMapping -> classMapping.renameObfClass(oldObfName, newObfName)).forEach(classMapping -> { - boolean wasRemoved = this.classesByObf.remove(oldObfName) != null; - assert (wasRemoved); - boolean wasAdded = this.classesByObf.put(newObfName, classMapping) == null; - assert (wasAdded); - }); - } - - public Set getAllObfClassNames() { - final Set classNames = Sets.newHashSet(); - for (ClassMapping classMapping : classes()) { - - // add the class name - classNames.add(classMapping.getObfFullName()); - - // add classes from method signatures - for (MethodMapping methodMapping : classMapping.methods()) { - for (Type type : methodMapping.getObfSignature().types()) { - if (type.hasClass()) { - classNames.add(type.getClassEntry().getClassName()); - } - } - } - } - return classNames; - } - - public boolean containsDeobfClass(String deobfName) { - return this.classesByDeobf.containsKey(deobfName); - } - - public boolean containsDeobfField(ClassEntry obfClassEntry, String deobfName, Type obfType) { - ClassMapping classMapping = this.classesByObf.get(obfClassEntry.getName()); - return classMapping != null && classMapping.containsDeobfField(deobfName, obfType); - } - - public boolean containsDeobfField(ClassEntry obfClassEntry, String deobfName) { - ClassMapping classMapping = this.classesByObf.get(obfClassEntry.getName()); - if (classMapping != null) - for (FieldMapping fieldMapping : classMapping.fields()) - if (deobfName.equals(fieldMapping.getDeobfName()) || deobfName.equals(fieldMapping.getObfName())) - return true; - - return false; - } - - public boolean containsDeobfMethod(ClassEntry obfClassEntry, String deobfName, Signature obfSignature) { - ClassMapping classMapping = this.classesByObf.get(obfClassEntry.getName()); - return classMapping != null && classMapping.containsDeobfMethod(deobfName, obfSignature); - } - - public boolean containsArgument(BehaviorEntry obfBehaviorEntry, String name) { - ClassMapping classMapping = this.classesByObf.get(obfBehaviorEntry.getClassName()); - return classMapping != null && classMapping.containsArgument(obfBehaviorEntry, name); - } - - public List getClassMappingChain(ClassEntry obfClass) { - List mappingChain = Lists.newArrayList(); - ClassMapping classMapping = null; - for (ClassEntry obfClassEntry : obfClass.getClassChain()) { - if (mappingChain.isEmpty()) { - classMapping = this.classesByObf.get(obfClassEntry.getName()); - } else if (classMapping != null) { - classMapping = classMapping.getInnerClassByObfSimple(obfClassEntry.getInnermostClassName()); - } - mappingChain.add(classMapping); - } - return mappingChain; - } - - public FormatType getOriginMappingFormat() - { - return originMapping; - } - - public void savePreviousState() - { - this.previousState = new Mappings(this.originMapping); - this.previousState.classesByDeobf = Maps.newHashMap(this.classesByDeobf); - this.previousState.classesByObf = Maps.newHashMap(this.classesByObf); - classesByDeobf.values().forEach(ClassMapping::resetDirty); - classesByObf.values().forEach(ClassMapping::resetDirty); - } - - public void saveEnigmaMappings(File file, boolean isDirectoryFormat) throws IOException - { - new MappingsEnigmaWriter().write(file, this, isDirectoryFormat); - this.savePreviousState(); - } - - public void saveSRGMappings(File file) throws IOException - { - new MappingsSRGWriter().write(file, this); - } - - public Mappings getPreviousState() { - return previousState; - } - - public enum FormatType - { - ENIGMA_FILE, ENIGMA_DIRECTORY, SRG_FILE - } - - public enum EntryModifier - { - UNCHANGED, PUBLIC, PROTECTED, PRIVATE; - - public String getFormattedName() - { - return " ACC:" + super.toString(); - } - } + private final FormatType originMapping; + protected Map classesByObf; + protected Map classesByDeobf; + private Mappings previousState; + + public Mappings() { + this(FormatType.ENIGMA_DIRECTORY); + } + + public Mappings(FormatType originMapping) { + this.originMapping = originMapping; + this.classesByObf = Maps.newHashMap(); + this.classesByDeobf = Maps.newHashMap(); + this.previousState = null; + } + + public Collection classes() { + assert (this.classesByObf.size() >= this.classesByDeobf.size()); + return this.classesByObf.values(); + } + + public void addClassMapping(ClassMapping classMapping) throws MappingConflict { + if (this.classesByObf.containsKey(classMapping.getObfFullName())) { + throw new MappingConflict("class", classMapping.getObfFullName(), this.classesByObf.get(classMapping.getObfFullName()).getObfFullName()); + } + this.classesByObf.put(classMapping.getObfFullName(), classMapping); + + if (classMapping.getDeobfName() != null) { + if (this.classesByDeobf.containsKey(classMapping.getDeobfName())) { + throw new MappingConflict("class", classMapping.getDeobfName(), this.classesByDeobf.get(classMapping.getDeobfName()).getDeobfName()); + } + this.classesByDeobf.put(classMapping.getDeobfName(), classMapping); + } + } + + public void removeClassMapping(ClassMapping classMapping) { + boolean obfWasRemoved = this.classesByObf.remove(classMapping.getObfFullName()) != null; + assert (obfWasRemoved); + if (classMapping.getDeobfName() != null) { + boolean deobfWasRemoved = this.classesByDeobf.remove(classMapping.getDeobfName()) != null; + assert (deobfWasRemoved); + } + } + + public ClassMapping getClassByObf(ClassEntry entry) { + return getClassByObf(entry.getName()); + } + + public ClassMapping getClassByObf(String obfName) { + return this.classesByObf.get(obfName); + } + + public ClassMapping getClassByDeobf(ClassEntry entry) { + return getClassByDeobf(entry.getName()); + } + + public ClassMapping getClassByDeobf(String deobfName) { + return this.classesByDeobf.get(deobfName); + } + + public void setClassDeobfName(ClassMapping classMapping, String deobfName) { + if (classMapping.getDeobfName() != null) { + boolean wasRemoved = this.classesByDeobf.remove(classMapping.getDeobfName()) != null; + assert (wasRemoved); + } + classMapping.setDeobfName(deobfName); + if (deobfName != null) { + boolean wasAdded = this.classesByDeobf.put(deobfName, classMapping) == null; + assert (wasAdded); + } + } + + public Translator getTranslator(TranslationDirection direction, TranslationIndex index) { + switch (direction) { + case Deobfuscating: + + return new Translator(direction, this.classesByObf, index); + + case Obfuscating: + + // fill in the missing deobf class entries with obf entries + Map classes = Maps.newHashMap(); + for (ClassMapping classMapping : classes()) { + if (classMapping.getDeobfName() != null) { + classes.put(classMapping.getDeobfName(), classMapping); + } else { + classes.put(classMapping.getObfFullName(), classMapping); + } + } + + // translate the translation index + // NOTE: this isn't actually recursive + TranslationIndex deobfIndex = new TranslationIndex(index, getTranslator(TranslationDirection.Deobfuscating, index)); + + return new Translator(direction, classes, deobfIndex); + + default: + throw new Error("Invalid translation direction!"); + } + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + for (ClassMapping classMapping : this.classesByObf.values()) { + buf.append(classMapping); + buf.append("\n"); + } + return buf.toString(); + } + + public void renameObfClass(String oldObfName, String newObfName) { + new ArrayList<>(classes()).stream().filter(classMapping -> classMapping.renameObfClass(oldObfName, newObfName)).forEach(classMapping -> { + boolean wasRemoved = this.classesByObf.remove(oldObfName) != null; + assert (wasRemoved); + boolean wasAdded = this.classesByObf.put(newObfName, classMapping) == null; + assert (wasAdded); + }); + } + + public Set getAllObfClassNames() { + final Set classNames = Sets.newHashSet(); + for (ClassMapping classMapping : classes()) { + + // add the class name + classNames.add(classMapping.getObfFullName()); + + // add classes from method signatures + for (MethodMapping methodMapping : classMapping.methods()) { + for (Type type : methodMapping.getObfSignature().types()) { + if (type.hasClass()) { + classNames.add(type.getClassEntry().getClassName()); + } + } + } + } + return classNames; + } + + public boolean containsDeobfClass(String deobfName) { + return this.classesByDeobf.containsKey(deobfName); + } + + public boolean containsDeobfField(ClassEntry obfClassEntry, String deobfName, Type obfType) { + ClassMapping classMapping = this.classesByObf.get(obfClassEntry.getName()); + return classMapping != null && classMapping.containsDeobfField(deobfName, obfType); + } + + public boolean containsDeobfField(ClassEntry obfClassEntry, String deobfName) { + ClassMapping classMapping = this.classesByObf.get(obfClassEntry.getName()); + if (classMapping != null) + for (FieldMapping fieldMapping : classMapping.fields()) + if (deobfName.equals(fieldMapping.getDeobfName()) || deobfName.equals(fieldMapping.getObfName())) + return true; + + return false; + } + + public boolean containsDeobfMethod(ClassEntry obfClassEntry, String deobfName, Signature obfSignature) { + ClassMapping classMapping = this.classesByObf.get(obfClassEntry.getName()); + return classMapping != null && classMapping.containsDeobfMethod(deobfName, obfSignature); + } + + public boolean containsArgument(BehaviorEntry obfBehaviorEntry, String name) { + ClassMapping classMapping = this.classesByObf.get(obfBehaviorEntry.getClassName()); + return classMapping != null && classMapping.containsArgument(obfBehaviorEntry, name); + } + + public List getClassMappingChain(ClassEntry obfClass) { + List mappingChain = Lists.newArrayList(); + ClassMapping classMapping = null; + for (ClassEntry obfClassEntry : obfClass.getClassChain()) { + if (mappingChain.isEmpty()) { + classMapping = this.classesByObf.get(obfClassEntry.getName()); + } else if (classMapping != null) { + classMapping = classMapping.getInnerClassByObfSimple(obfClassEntry.getInnermostClassName()); + } + mappingChain.add(classMapping); + } + return mappingChain; + } + + public FormatType getOriginMappingFormat() { + return originMapping; + } + + public void savePreviousState() { + this.previousState = new Mappings(this.originMapping); + this.previousState.classesByDeobf = Maps.newHashMap(this.classesByDeobf); + this.previousState.classesByObf = Maps.newHashMap(this.classesByObf); + classesByDeobf.values().forEach(ClassMapping::resetDirty); + classesByObf.values().forEach(ClassMapping::resetDirty); + } + + public void saveEnigmaMappings(File file, boolean isDirectoryFormat) throws IOException { + new MappingsEnigmaWriter().write(file, this, isDirectoryFormat); + this.savePreviousState(); + } + + public void saveSRGMappings(File file) throws IOException { + new MappingsSRGWriter().write(file, this); + } + + public Mappings getPreviousState() { + return previousState; + } + + public enum FormatType { + ENIGMA_FILE, ENIGMA_DIRECTORY, SRG_FILE + } + + public enum EntryModifier { + UNCHANGED, PUBLIC, PROTECTED, PRIVATE; + + public String getFormattedName() { + return " ACC:" + super.toString(); + } + } } diff --git a/src/main/java/cuchaz/enigma/mapping/MappingsChecker.java b/src/main/java/cuchaz/enigma/mapping/MappingsChecker.java index 6cf279d1..172641bd 100644 --- a/src/main/java/cuchaz/enigma/mapping/MappingsChecker.java +++ b/src/main/java/cuchaz/enigma/mapping/MappingsChecker.java @@ -8,91 +8,90 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.mapping; import com.google.common.collect.Lists; import com.google.common.collect.Maps; - -import java.util.Map; - import cuchaz.enigma.analysis.JarIndex; +import java.util.Map; public class MappingsChecker { - private JarIndex index; - private Map droppedClassMappings; - private Map droppedInnerClassMappings; - private Map droppedFieldMappings; - private Map droppedMethodMappings; - - public MappingsChecker(JarIndex index) { - this.index = index; - this.droppedClassMappings = Maps.newHashMap(); - this.droppedInnerClassMappings = Maps.newHashMap(); - this.droppedFieldMappings = Maps.newHashMap(); - this.droppedMethodMappings = Maps.newHashMap(); - } - - public Map getDroppedClassMappings() { - return this.droppedClassMappings; - } - - public Map getDroppedInnerClassMappings() { - return this.droppedInnerClassMappings; - } - - public Map getDroppedFieldMappings() { - return this.droppedFieldMappings; - } - - public Map getDroppedMethodMappings() { - return this.droppedMethodMappings; - } - - public void dropBrokenMappings(Mappings mappings) { - for (ClassMapping classMapping : Lists.newArrayList(mappings.classes())) { - if (!checkClassMapping(classMapping)) { - mappings.removeClassMapping(classMapping); - this.droppedClassMappings.put(EntryFactory.getObfClassEntry(this.index, classMapping), classMapping); - } - } - } - - private boolean checkClassMapping(ClassMapping classMapping) { - - // check the class - ClassEntry classEntry = EntryFactory.getObfClassEntry(this.index, classMapping); - if (!this.index.getObfClassEntries().contains(classEntry)) { - return false; - } - - // check the fields - for (FieldMapping fieldMapping : Lists.newArrayList(classMapping.fields())) { - FieldEntry obfFieldEntry = EntryFactory.getObfFieldEntry(classMapping, fieldMapping); - if (!this.index.containsObfField(obfFieldEntry)) { - classMapping.removeFieldMapping(fieldMapping); - this.droppedFieldMappings.put(obfFieldEntry, fieldMapping); - } - } - - // check methods - for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) { - BehaviorEntry obfBehaviorEntry = EntryFactory.getObfBehaviorEntry(classEntry, methodMapping); - if (!this.index.containsObfBehavior(obfBehaviorEntry)) { - classMapping.removeMethodMapping(methodMapping); - this.droppedMethodMappings.put(obfBehaviorEntry, methodMapping); - } - } - - // check inner classes - for (ClassMapping innerClassMapping : Lists.newArrayList(classMapping.innerClasses())) { - if (!checkClassMapping(innerClassMapping)) { - classMapping.removeInnerClassMapping(innerClassMapping); - this.droppedInnerClassMappings.put(EntryFactory.getObfClassEntry(this.index, innerClassMapping), innerClassMapping); - } - } - - return true; - } + private JarIndex index; + private Map droppedClassMappings; + private Map droppedInnerClassMappings; + private Map droppedFieldMappings; + private Map droppedMethodMappings; + + public MappingsChecker(JarIndex index) { + this.index = index; + this.droppedClassMappings = Maps.newHashMap(); + this.droppedInnerClassMappings = Maps.newHashMap(); + this.droppedFieldMappings = Maps.newHashMap(); + this.droppedMethodMappings = Maps.newHashMap(); + } + + public Map getDroppedClassMappings() { + return this.droppedClassMappings; + } + + public Map getDroppedInnerClassMappings() { + return this.droppedInnerClassMappings; + } + + public Map getDroppedFieldMappings() { + return this.droppedFieldMappings; + } + + public Map getDroppedMethodMappings() { + return this.droppedMethodMappings; + } + + public void dropBrokenMappings(Mappings mappings) { + for (ClassMapping classMapping : Lists.newArrayList(mappings.classes())) { + if (!checkClassMapping(classMapping)) { + mappings.removeClassMapping(classMapping); + this.droppedClassMappings.put(EntryFactory.getObfClassEntry(this.index, classMapping), classMapping); + } + } + } + + private boolean checkClassMapping(ClassMapping classMapping) { + + // check the class + ClassEntry classEntry = EntryFactory.getObfClassEntry(this.index, classMapping); + if (!this.index.getObfClassEntries().contains(classEntry)) { + return false; + } + + // check the fields + for (FieldMapping fieldMapping : Lists.newArrayList(classMapping.fields())) { + FieldEntry obfFieldEntry = EntryFactory.getObfFieldEntry(classMapping, fieldMapping); + if (!this.index.containsObfField(obfFieldEntry)) { + classMapping.removeFieldMapping(fieldMapping); + this.droppedFieldMappings.put(obfFieldEntry, fieldMapping); + } + } + + // check methods + for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) { + BehaviorEntry obfBehaviorEntry = EntryFactory.getObfBehaviorEntry(classEntry, methodMapping); + if (!this.index.containsObfBehavior(obfBehaviorEntry)) { + classMapping.removeMethodMapping(methodMapping); + this.droppedMethodMappings.put(obfBehaviorEntry, methodMapping); + } + } + + // check inner classes + for (ClassMapping innerClassMapping : Lists.newArrayList(classMapping.innerClasses())) { + if (!checkClassMapping(innerClassMapping)) { + classMapping.removeInnerClassMapping(innerClassMapping); + this.droppedInnerClassMappings.put(EntryFactory.getObfClassEntry(this.index, innerClassMapping), innerClassMapping); + } + } + + return true; + } } diff --git a/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaReader.java b/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaReader.java index cdfed726..a0d43133 100644 --- a/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaReader.java +++ b/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaReader.java @@ -8,178 +8,170 @@ import cuchaz.enigma.throwables.MappingParseException; import java.io.*; import java.util.Deque; -public class MappingsEnigmaReader -{ - - public Mappings read(File file) throws IOException, MappingParseException { - Mappings mappings; - - // Multiple file - if (file.isDirectory()) - { - mappings = new Mappings(Mappings.FormatType.ENIGMA_DIRECTORY); - readDirectory(mappings, file); - } - else - { - mappings = new Mappings(); - readFile(mappings, file); - } - return mappings; - } - - public void readDirectory(Mappings mappings, File directory) throws IOException, MappingParseException { - File[] files = directory.listFiles(); - if (files != null) { - for (File file : files) { - if (file.isFile() && !file.getName().startsWith(".") && file.getName().endsWith(".mapping")) - readFile(mappings, file); - else if (file.isDirectory()) - readDirectory(mappings, file.getAbsoluteFile()); - } - mappings.savePreviousState(); - } - else - throw new IOException("Cannot access directory" + directory.getAbsolutePath()); - } - - public Mappings readFile(Mappings mappings, File file) throws IOException, MappingParseException { - - BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), Charsets.UTF_8)); - Deque mappingStack = Queues.newArrayDeque(); - - int lineNumber = 0; - String line; - while ((line = in.readLine()) != null) { - lineNumber++; - - // strip comments - int commentPos = line.indexOf('#'); - if (commentPos >= 0) { - line = line.substring(0, commentPos); - } - - // skip blank lines - if (line.trim().length() <= 0) { - continue; - } - - // get the indent of this line - int indent = 0; - for (int i = 0; i < line.length(); i++) { - if (line.charAt(i) != '\t') { - break; - } - indent++; - } - - // handle stack pops - while (indent < mappingStack.size()) { - mappingStack.pop(); - } - - String[] parts = line.trim().split("\\s"); - try { - // read the first token - String token = parts[0]; - - if (token.equalsIgnoreCase("CLASS")) { - ClassMapping classMapping; - if (indent <= 0) { - // outer class - classMapping = readClass(parts, false); - mappings.addClassMapping(classMapping); - } else { - - // inner class - if (!(mappingStack.peek() instanceof ClassMapping)) { - throw new MappingParseException(file, lineNumber, "Unexpected CLASS entry here!"); - } - - classMapping = readClass(parts, true); - ((ClassMapping) mappingStack.peek()).addInnerClassMapping(classMapping); - } - mappingStack.push(classMapping); - } else if (token.equalsIgnoreCase("FIELD")) { - if (mappingStack.isEmpty() || !(mappingStack.peek() instanceof ClassMapping)) { - throw new MappingParseException(file, lineNumber, "Unexpected FIELD entry here!"); - } - ((ClassMapping) mappingStack.peek()).addFieldMapping(readField(parts)); - } else if (token.equalsIgnoreCase("METHOD")) { - if (mappingStack.isEmpty() || !(mappingStack.peek() instanceof ClassMapping)) { - throw new MappingParseException(file, lineNumber, "Unexpected METHOD entry here!"); - } - MethodMapping methodMapping = readMethod(parts); - ((ClassMapping) mappingStack.peek()).addMethodMapping(methodMapping); - mappingStack.push(methodMapping); - } else if (token.equalsIgnoreCase("ARG")) { - if (mappingStack.isEmpty() || !(mappingStack.peek() instanceof MethodMapping)) { - throw new MappingParseException(file, lineNumber, "Unexpected ARG entry here!"); - } - ((MethodMapping) mappingStack.peek()).addArgumentMapping(readArgument(parts)); - } - } catch (ArrayIndexOutOfBoundsException | IllegalArgumentException ex) { - throw new MappingParseException(file, lineNumber, "Malformed line:\n" + line); - } catch (MappingConflict e) { - throw new MappingParseException(file, lineNumber, e.getMessage()); - } - } - in.close(); - return mappings; - } - - private ArgumentMapping readArgument(String[] parts) { - return new ArgumentMapping(Integer.parseInt(parts[1]), parts[2]); - } - - private ClassMapping readClass(String[] parts, boolean makeSimple) { - if (parts.length == 2) { - return new ClassMapping(parts[1]); - } else if (parts.length == 3) { - boolean access = parts[2].startsWith("ACC:"); - ClassMapping mapping; - if (access) - mapping = new ClassMapping(parts[1], null, Mappings.EntryModifier.valueOf(parts[2].substring(4))); - else - mapping = new ClassMapping(parts[1], parts[2]); - - return mapping; - } else if (parts.length == 4) - return new ClassMapping(parts[1], parts[2], Mappings.EntryModifier.valueOf(parts[3].substring(4))); - return null; - } - - /* TEMP */ - protected FieldMapping readField(String[] parts) { - FieldMapping mapping = null; - if (parts.length == 4) - { - boolean access = parts[3].startsWith("ACC:"); - if (access) - mapping = new FieldMapping(parts[1], new Type(parts[2]), null, - Mappings.EntryModifier.valueOf(parts[3].substring(4))); - else - mapping = new FieldMapping(parts[1], new Type(parts[3]), parts[2], Mappings.EntryModifier.UNCHANGED); - } - else if (parts.length == 5) - mapping = new FieldMapping(parts[1], new Type(parts[3]), parts[2], Mappings.EntryModifier.valueOf(parts[4].substring(4))); - return mapping; - } - - private MethodMapping readMethod(String[] parts) { - MethodMapping mapping = null; - if (parts.length == 3) - mapping = new MethodMapping(parts[1], new Signature(parts[2])); - else if (parts.length == 4){ - boolean access = parts[3].startsWith("ACC:"); - if (access) - mapping = new MethodMapping(parts[1], new Signature(parts[2]), null, Mappings.EntryModifier.valueOf(parts[3].substring(4))); - else - mapping = new MethodMapping(parts[1], new Signature(parts[3]), parts[2]); - } - else if (parts.length == 5) - mapping = new MethodMapping(parts[1], new Signature(parts[3]), parts[2], - Mappings.EntryModifier.valueOf(parts[4].substring(4))); - return mapping; - } +public class MappingsEnigmaReader { + + public Mappings read(File file) throws IOException, MappingParseException { + Mappings mappings; + + // Multiple file + if (file.isDirectory()) { + mappings = new Mappings(Mappings.FormatType.ENIGMA_DIRECTORY); + readDirectory(mappings, file); + } else { + mappings = new Mappings(); + readFile(mappings, file); + } + return mappings; + } + + public void readDirectory(Mappings mappings, File directory) throws IOException, MappingParseException { + File[] files = directory.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isFile() && !file.getName().startsWith(".") && file.getName().endsWith(".mapping")) + readFile(mappings, file); + else if (file.isDirectory()) + readDirectory(mappings, file.getAbsoluteFile()); + } + mappings.savePreviousState(); + } else + throw new IOException("Cannot access directory" + directory.getAbsolutePath()); + } + + public Mappings readFile(Mappings mappings, File file) throws IOException, MappingParseException { + + BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), Charsets.UTF_8)); + Deque mappingStack = Queues.newArrayDeque(); + + int lineNumber = 0; + String line; + while ((line = in.readLine()) != null) { + lineNumber++; + + // strip comments + int commentPos = line.indexOf('#'); + if (commentPos >= 0) { + line = line.substring(0, commentPos); + } + + // skip blank lines + if (line.trim().length() <= 0) { + continue; + } + + // get the indent of this line + int indent = 0; + for (int i = 0; i < line.length(); i++) { + if (line.charAt(i) != '\t') { + break; + } + indent++; + } + + // handle stack pops + while (indent < mappingStack.size()) { + mappingStack.pop(); + } + + String[] parts = line.trim().split("\\s"); + try { + // read the first token + String token = parts[0]; + + if (token.equalsIgnoreCase("CLASS")) { + ClassMapping classMapping; + if (indent <= 0) { + // outer class + classMapping = readClass(parts, false); + mappings.addClassMapping(classMapping); + } else { + + // inner class + if (!(mappingStack.peek() instanceof ClassMapping)) { + throw new MappingParseException(file, lineNumber, "Unexpected CLASS entry here!"); + } + + classMapping = readClass(parts, true); + ((ClassMapping) mappingStack.peek()).addInnerClassMapping(classMapping); + } + mappingStack.push(classMapping); + } else if (token.equalsIgnoreCase("FIELD")) { + if (mappingStack.isEmpty() || !(mappingStack.peek() instanceof ClassMapping)) { + throw new MappingParseException(file, lineNumber, "Unexpected FIELD entry here!"); + } + ((ClassMapping) mappingStack.peek()).addFieldMapping(readField(parts)); + } else if (token.equalsIgnoreCase("METHOD")) { + if (mappingStack.isEmpty() || !(mappingStack.peek() instanceof ClassMapping)) { + throw new MappingParseException(file, lineNumber, "Unexpected METHOD entry here!"); + } + MethodMapping methodMapping = readMethod(parts); + ((ClassMapping) mappingStack.peek()).addMethodMapping(methodMapping); + mappingStack.push(methodMapping); + } else if (token.equalsIgnoreCase("ARG")) { + if (mappingStack.isEmpty() || !(mappingStack.peek() instanceof MethodMapping)) { + throw new MappingParseException(file, lineNumber, "Unexpected ARG entry here!"); + } + ((MethodMapping) mappingStack.peek()).addArgumentMapping(readArgument(parts)); + } + } catch (ArrayIndexOutOfBoundsException | IllegalArgumentException ex) { + throw new MappingParseException(file, lineNumber, "Malformed line:\n" + line); + } catch (MappingConflict e) { + throw new MappingParseException(file, lineNumber, e.getMessage()); + } + } + in.close(); + return mappings; + } + + private ArgumentMapping readArgument(String[] parts) { + return new ArgumentMapping(Integer.parseInt(parts[1]), parts[2]); + } + + private ClassMapping readClass(String[] parts, boolean makeSimple) { + if (parts.length == 2) { + return new ClassMapping(parts[1]); + } else if (parts.length == 3) { + boolean access = parts[2].startsWith("ACC:"); + ClassMapping mapping; + if (access) + mapping = new ClassMapping(parts[1], null, Mappings.EntryModifier.valueOf(parts[2].substring(4))); + else + mapping = new ClassMapping(parts[1], parts[2]); + + return mapping; + } else if (parts.length == 4) + return new ClassMapping(parts[1], parts[2], Mappings.EntryModifier.valueOf(parts[3].substring(4))); + return null; + } + + /* TEMP */ + protected FieldMapping readField(String[] parts) { + FieldMapping mapping = null; + if (parts.length == 4) { + boolean access = parts[3].startsWith("ACC:"); + if (access) + mapping = new FieldMapping(parts[1], new Type(parts[2]), null, + Mappings.EntryModifier.valueOf(parts[3].substring(4))); + else + mapping = new FieldMapping(parts[1], new Type(parts[3]), parts[2], Mappings.EntryModifier.UNCHANGED); + } else if (parts.length == 5) + mapping = new FieldMapping(parts[1], new Type(parts[3]), parts[2], Mappings.EntryModifier.valueOf(parts[4].substring(4))); + return mapping; + } + + private MethodMapping readMethod(String[] parts) { + MethodMapping mapping = null; + if (parts.length == 3) + mapping = new MethodMapping(parts[1], new Signature(parts[2])); + else if (parts.length == 4) { + boolean access = parts[3].startsWith("ACC:"); + if (access) + mapping = new MethodMapping(parts[1], new Signature(parts[2]), null, Mappings.EntryModifier.valueOf(parts[3].substring(4))); + else + mapping = new MethodMapping(parts[1], new Signature(parts[3]), parts[2]); + } else if (parts.length == 5) + mapping = new MethodMapping(parts[1], new Signature(parts[3]), parts[2], + Mappings.EntryModifier.valueOf(parts[4].substring(4))); + return mapping; + } } diff --git a/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaWriter.java b/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaWriter.java index 6c57200f..ba1b258b 100644 --- a/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaWriter.java +++ b/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaWriter.java @@ -4,10 +4,11 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.mapping; import com.google.common.base.Charsets; @@ -18,15 +19,13 @@ import java.util.Collections; import java.util.List; public class MappingsEnigmaWriter { - + public void write(File out, Mappings mappings, boolean isDirectoryFormat) throws IOException { - if (!isDirectoryFormat) - { + if (!isDirectoryFormat) { PrintWriter outputWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(out), Charsets.UTF_8)); write(outputWriter, mappings); outputWriter.close(); - } - else + } else writeAsDirectory(out, mappings); } @@ -42,8 +41,7 @@ public class MappingsEnigmaWriter { File result; if (classMapping.getDeobfName() == null) result = obFile; - else - { + else { // Make sure that old version of the file doesn't exist if (obFile.exists()) obFile.delete(); @@ -59,19 +57,16 @@ public class MappingsEnigmaWriter { } // Remove dropped mappings - if (mappings.getPreviousState() != null) - { + if (mappings.getPreviousState() != null) { List droppedClassMappings = new ArrayList<>(mappings.getPreviousState().classes()); List classMappings = new ArrayList<>(mappings.classes()); droppedClassMappings.removeAll(classMappings); - for (ClassMapping classMapping : droppedClassMappings) - { + for (ClassMapping classMapping : droppedClassMappings) { File obFile = new File(target, classMapping.getObfFullName() + ".mapping"); File result; if (classMapping.getDeobfName() == null) result = obFile; - else - { + else { // Make sure that old version of the file doesn't exist if (obFile.exists()) obFile.delete(); @@ -86,18 +81,15 @@ public class MappingsEnigmaWriter { private void deletePreviousClassMapping(File target, ClassMapping classMapping) { File prevFile = null; // Deob rename - if (classMapping.getDeobfName() != null && classMapping.getPreviousDeobfName() != null && !classMapping.getPreviousDeobfName().equals(classMapping.getDeobfName())) - { + if (classMapping.getDeobfName() != null && classMapping.getPreviousDeobfName() != null && !classMapping.getPreviousDeobfName().equals(classMapping.getDeobfName())) { prevFile = new File(target, classMapping.getPreviousDeobfName() + ".mapping"); } // Deob to ob rename - else if (classMapping.getDeobfName() == null && classMapping.getPreviousDeobfName() != null) - { + else if (classMapping.getDeobfName() == null && classMapping.getPreviousDeobfName() != null) { prevFile = new File(target, classMapping.getPreviousDeobfName() + ".mapping"); } // Ob to Deob rename - else if (classMapping.getDeobfName() != null && classMapping.getPreviousDeobfName() == null) - { + else if (classMapping.getDeobfName() != null && classMapping.getPreviousDeobfName() == null) { prevFile = new File(target, classMapping.getObfFullName() + ".mapping"); } @@ -110,50 +102,56 @@ public class MappingsEnigmaWriter { write(out, classMapping, 0); } } - + private void write(PrintWriter out, ClassMapping classMapping, int depth) throws IOException { if (classMapping.getDeobfName() == null) { - out.format("%sCLASS %s%s\n", getIndent(depth), classMapping.getObfFullName(), classMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : classMapping.getModifier().getFormattedName()); + out.format("%sCLASS %s%s\n", getIndent(depth), classMapping.getObfFullName(), + classMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : classMapping.getModifier().getFormattedName()); } else { - out.format("%sCLASS %s %s%s\n", getIndent(depth), classMapping.getObfFullName(), classMapping.getDeobfName(), classMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : classMapping.getModifier().getFormattedName()); + out.format("%sCLASS %s %s%s\n", getIndent(depth), classMapping.getObfFullName(), classMapping.getDeobfName(), + classMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : classMapping.getModifier().getFormattedName()); } - + for (ClassMapping innerClassMapping : sorted(classMapping.innerClasses())) { write(out, innerClassMapping, depth + 1); } - + for (FieldMapping fieldMapping : sorted(classMapping.fields())) { write(out, fieldMapping, depth + 1); } - + for (MethodMapping methodMapping : sorted(classMapping.methods())) { write(out, methodMapping, depth + 1); } } - + private void write(PrintWriter out, FieldMapping fieldMapping, int depth) { if (fieldMapping.getDeobfName() == null) - out.format("%sFIELD %s %s%s\n", getIndent(depth), fieldMapping.getObfName(), fieldMapping.getObfType().toString(), fieldMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : fieldMapping.getModifier().getFormattedName()); + out.format("%sFIELD %s %s%s\n", getIndent(depth), fieldMapping.getObfName(), fieldMapping.getObfType().toString(), + fieldMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : fieldMapping.getModifier().getFormattedName()); else - out.format("%sFIELD %s %s %s%s\n", getIndent(depth), fieldMapping.getObfName(), fieldMapping.getDeobfName(), fieldMapping.getObfType().toString(), fieldMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : fieldMapping.getModifier().getFormattedName()); + out.format("%sFIELD %s %s %s%s\n", getIndent(depth), fieldMapping.getObfName(), fieldMapping.getDeobfName(), fieldMapping.getObfType().toString(), + fieldMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : fieldMapping.getModifier().getFormattedName()); } - + private void write(PrintWriter out, MethodMapping methodMapping, int depth) throws IOException { if (methodMapping.getDeobfName() == null) { - out.format("%sMETHOD %s %s%s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getObfSignature(), methodMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" :methodMapping.getModifier().getFormattedName()); + out.format("%sMETHOD %s %s%s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getObfSignature(), + methodMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : methodMapping.getModifier().getFormattedName()); } else { - out.format("%sMETHOD %s %s %s%s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getDeobfName(), methodMapping.getObfSignature(), methodMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : methodMapping.getModifier().getFormattedName()); + out.format("%sMETHOD %s %s %s%s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getDeobfName(), methodMapping.getObfSignature(), + methodMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : methodMapping.getModifier().getFormattedName()); } - + for (ArgumentMapping argumentMapping : sorted(methodMapping.arguments())) { write(out, argumentMapping, depth + 1); } } - + private void write(PrintWriter out, ArgumentMapping argumentMapping, int depth) { out.format("%sARG %d %s\n", getIndent(depth), argumentMapping.getIndex(), argumentMapping.getName()); } - + private > List sorted(Iterable classes) { List out = new ArrayList<>(); for (T t : classes) { @@ -162,7 +160,7 @@ public class MappingsEnigmaWriter { Collections.sort(out); return out; } - + private String getIndent(int depth) { StringBuilder buf = new StringBuilder(); for (int i = 0; i < depth; i++) { diff --git a/src/main/java/cuchaz/enigma/mapping/MappingsRenamer.java b/src/main/java/cuchaz/enigma/mapping/MappingsRenamer.java index e1428ea0..7126d2b6 100644 --- a/src/main/java/cuchaz/enigma/mapping/MappingsRenamer.java +++ b/src/main/java/cuchaz/enigma/mapping/MappingsRenamer.java @@ -8,8 +8,14 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.mapping; +import com.google.common.collect.Lists; +import cuchaz.enigma.analysis.JarIndex; +import cuchaz.enigma.throwables.IllegalNameException; +import cuchaz.enigma.throwables.MappingConflict; + import java.io.IOException; import java.io.ObjectOutputStream; import java.io.OutputStream; @@ -17,324 +23,315 @@ import java.util.List; import java.util.Set; import java.util.zip.GZIPOutputStream; -import com.google.common.collect.Lists; -import cuchaz.enigma.analysis.JarIndex; -import cuchaz.enigma.throwables.IllegalNameException; -import cuchaz.enigma.throwables.MappingConflict; - public class MappingsRenamer { - private JarIndex index; - private Mappings mappings; - - public MappingsRenamer(JarIndex index, Mappings mappings) { - this.index = index; - this.mappings = mappings; - } - - public void setMappings(Mappings mappings) - { - this.mappings = mappings; - } - - public void setClassName(ClassEntry obf, String deobfName) { - - deobfName = NameValidator.validateClassName(deobfName, !obf.isInnerClass()); - - List mappingChain = getOrCreateClassMappingChain(obf); - if (mappingChain.size() == 1) { - - if (deobfName != null) { - // make sure we don't rename to an existing obf or deobf class - if (mappings.containsDeobfClass(deobfName) || index.containsObfClass(new ClassEntry(deobfName))) { - throw new IllegalNameException(deobfName, "There is already a class with that name"); - } - } - - ClassMapping classMapping = mappingChain.get(0); - mappings.setClassDeobfName(classMapping, deobfName); - - } else { - - ClassMapping outerClassMapping = mappingChain.get(mappingChain.size() - 2); - - if (deobfName != null) { - // make sure we don't rename to an existing obf or deobf inner class - if (outerClassMapping.hasInnerClassByDeobf(deobfName) || outerClassMapping.hasInnerClassByObfSimple(deobfName)) { - throw new IllegalNameException(deobfName, "There is already a class with that name"); - } - } - - outerClassMapping.setInnerClassName(obf, deobfName); - } - } - - public void removeClassMapping(ClassEntry obf) { - setClassName(obf, null); - } - - public void markClassAsDeobfuscated(ClassEntry obf) { - String deobfName = obf.isInnerClass() ? obf.getInnermostClassName() : obf.getName(); - List mappingChain = getOrCreateClassMappingChain(obf); - if (mappingChain.size() == 1) { - ClassMapping classMapping = mappingChain.get(0); - mappings.setClassDeobfName(classMapping, deobfName); - } else { - ClassMapping outerClassMapping = mappingChain.get(mappingChain.size() - 2); - outerClassMapping.setInnerClassName(obf, deobfName); - } - } - - public void setFieldName(FieldEntry obf, String deobfName) { - deobfName = NameValidator.validateFieldName(deobfName); - FieldEntry targetEntry = new FieldEntry(obf.getClassEntry(), deobfName, obf.getType()); - ClassEntry definedClass = null; - if (mappings.containsDeobfField(obf.getClassEntry(), deobfName) || index.containsEntryWithSameName(targetEntry)) - definedClass = obf.getClassEntry(); - else { - for (ClassEntry ancestorEntry : this.index.getTranslationIndex().getAncestry(obf.getClassEntry())) { - if (mappings.containsDeobfField(ancestorEntry, deobfName) || index.containsEntryWithSameName(targetEntry.cloneToNewClass(ancestorEntry))) { - definedClass = ancestorEntry; - break; - } - } - } - - if (definedClass != null) { - String className = mappings.getTranslator(TranslationDirection.Deobfuscating, index.getTranslationIndex()).translateClass(definedClass.getClassName()); - if (className == null) - className = definedClass.getClassName(); - throw new IllegalNameException(deobfName, "There is already a field with that name in " + className); - } - - ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); - classMapping.setFieldName(obf.getName(), obf.getType(), deobfName); - } - - public void removeFieldMapping(FieldEntry obf) { - ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); - classMapping.removeFieldMapping(classMapping.getFieldByObf(obf.getName(), obf.getType())); - } - - public void markFieldAsDeobfuscated(FieldEntry obf) { - ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); - classMapping.setFieldName(obf.getName(), obf.getType(), obf.getName()); - } - - private void validateMethodTreeName(MethodEntry entry, String deobfName) { - MethodEntry targetEntry = new MethodEntry(entry.getClassEntry(), deobfName, entry.getSignature()); - - // TODO: Verify if I don't break things - ClassMapping classMapping = mappings.getClassByObf(entry.getClassEntry()); - if ((classMapping != null && classMapping.containsDeobfMethod(deobfName, entry.getSignature()) && classMapping.getMethodByObf(entry.getName(), entry.getSignature()) != classMapping.getMethodByDeobf(deobfName, entry.getSignature())) - || index.containsObfBehavior(targetEntry)) { - String deobfClassName = mappings.getTranslator(TranslationDirection.Deobfuscating, index.getTranslationIndex()).translateClass(entry.getClassName()); - if (deobfClassName == null) { - deobfClassName = entry.getClassName(); - } - throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName); - } - - for (ClassEntry child : index.getTranslationIndex().getSubclass(entry.getClassEntry())) { - validateMethodTreeName(entry.cloneToNewClass(child), deobfName); - } - } - - public void setMethodTreeName(MethodEntry obf, String deobfName) { - Set implementations = index.getRelatedMethodImplementations(obf); - - deobfName = NameValidator.validateMethodName(deobfName); - for (MethodEntry entry : implementations) { - validateMethodTreeName(entry, deobfName); - } - - for (MethodEntry entry : implementations) { - setMethodName(entry, deobfName); - } - } - - public void setMethodName(MethodEntry obf, String deobfName) { - deobfName = NameValidator.validateMethodName(deobfName); - MethodEntry targetEntry = new MethodEntry(obf.getClassEntry(), deobfName, obf.getSignature()); - ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); - - // TODO: Verify if I don't break things - if ((mappings.containsDeobfMethod(obf.getClassEntry(), deobfName, obf.getSignature()) && classMapping.getMethodByObf(obf.getName(), obf.getSignature()) != classMapping.getMethodByDeobf(deobfName, obf.getSignature())) - || index.containsObfBehavior(targetEntry)) { - String deobfClassName = mappings.getTranslator(TranslationDirection.Deobfuscating, index.getTranslationIndex()).translateClass(obf.getClassName()); - if (deobfClassName == null) { - deobfClassName = obf.getClassName(); - } - throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName); - } - - classMapping.setMethodName(obf.getName(), obf.getSignature(), deobfName); - } - - public void removeMethodTreeMapping(MethodEntry obf) { - index.getRelatedMethodImplementations(obf).forEach(this::removeMethodMapping); - } - - public void removeMethodMapping(MethodEntry obf) { - ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); - classMapping.setMethodName(obf.getName(), obf.getSignature(), null); - } - - public void markMethodTreeAsDeobfuscated(MethodEntry obf) { - index.getRelatedMethodImplementations(obf).forEach(this::markMethodAsDeobfuscated); - } - - public void markMethodAsDeobfuscated(MethodEntry obf) { - ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); - classMapping.setMethodName(obf.getName(), obf.getSignature(), obf.getName()); - } - - public void setArgumentTreeName(ArgumentEntry obf, String deobfName) { - if (!(obf.getBehaviorEntry() instanceof MethodEntry)) { - setArgumentName(obf, deobfName); - return; - } - - MethodEntry obfMethod = (MethodEntry) obf.getBehaviorEntry(); - - Set implementations = index.getRelatedMethodImplementations(obfMethod); - for (MethodEntry entry : implementations) { - ClassMapping classMapping = mappings.getClassByObf(entry.getClassEntry()); - if (classMapping != null) { - MethodMapping mapping = classMapping.getMethodByObf(entry.getName(), entry.getSignature()); - // NOTE: don't need to check arguments for name collisions with names determined by Procyon - // TODO: Verify if I don't break things - if (mapping != null) { - for (ArgumentMapping argumentMapping : Lists.newArrayList(mapping.arguments())) { - if (argumentMapping.getIndex() != obf.getIndex()) { - if (mapping.getDeobfArgumentName(argumentMapping.getIndex()).equals(deobfName) - || argumentMapping.getName().equals(deobfName)) { - throw new IllegalNameException(deobfName, "There is already an argument with that name"); - } - } - } - } - } - } - - for (MethodEntry entry : implementations) { - setArgumentName(new ArgumentEntry(obf, entry), deobfName); - } - } - - public void setArgumentName(ArgumentEntry obf, String deobfName) { - deobfName = NameValidator.validateArgumentName(deobfName); - ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); - MethodMapping mapping = classMapping.getMethodByObf(obf.getMethodName(), obf.getMethodSignature()); - // NOTE: don't need to check arguments for name collisions with names determined by Procyon - // TODO: Verify if I don't break things - if (mapping != null) { - for (ArgumentMapping argumentMapping : Lists.newArrayList(mapping.arguments())) { - if (argumentMapping.getIndex() != obf.getIndex()) { - if (mapping.getDeobfArgumentName(argumentMapping.getIndex()).equals(deobfName) - || argumentMapping.getName().equals(deobfName)) { - throw new IllegalNameException(deobfName, "There is already an argument with that name"); - } - } - } - } - - classMapping.setArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex(), deobfName); - } - - public void removeArgumentMapping(ArgumentEntry obf) { - ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); - classMapping.removeArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex()); - } - - public void markArgumentAsDeobfuscated(ArgumentEntry obf) { - ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); - classMapping.setArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex(), obf.getName()); - } - - public boolean moveFieldToObfClass(ClassMapping classMapping, FieldMapping fieldMapping, ClassEntry obfClass) { - classMapping.removeFieldMapping(fieldMapping); - ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass); - if (!targetClassMapping.containsObfField(fieldMapping.getObfName(), fieldMapping.getObfType())) { - if (!targetClassMapping.containsDeobfField(fieldMapping.getDeobfName(), fieldMapping.getObfType())) { - targetClassMapping.addFieldMapping(fieldMapping); - return true; - } else { - System.err.println("WARNING: deobf field was already there: " + obfClass + "." + fieldMapping.getDeobfName()); - } - } - return false; - } - - public boolean moveMethodToObfClass(ClassMapping classMapping, MethodMapping methodMapping, ClassEntry obfClass) { - classMapping.removeMethodMapping(methodMapping); - ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass); - if (!targetClassMapping.containsObfMethod(methodMapping.getObfName(), methodMapping.getObfSignature())) { - if (!targetClassMapping.containsDeobfMethod(methodMapping.getDeobfName(), methodMapping.getObfSignature())) { - targetClassMapping.addMethodMapping(methodMapping); - return true; - } else { - System.err.println("WARNING: deobf method was already there: " + obfClass + "." + methodMapping.getDeobfName() + methodMapping.getObfSignature()); - } - } - return false; - } - - public void write(OutputStream out) throws IOException { - // TEMP: just use the object output for now. We can find a more efficient storage format later - GZIPOutputStream gzipout = new GZIPOutputStream(out); - ObjectOutputStream oout = new ObjectOutputStream(gzipout); - oout.writeObject(this); - gzipout.finish(); - } - - private ClassMapping getOrCreateClassMapping(ClassEntry obfClassEntry) { - List mappingChain = getOrCreateClassMappingChain(obfClassEntry); - return mappingChain.get(mappingChain.size() - 1); - } - - private List getOrCreateClassMappingChain(ClassEntry obfClassEntry) { - List classChain = obfClassEntry.getClassChain(); - List mappingChain = mappings.getClassMappingChain(obfClassEntry); - for (int i = 0; i < classChain.size(); i++) { - ClassEntry classEntry = classChain.get(i); - ClassMapping classMapping = mappingChain.get(i); - if (classMapping == null) { - - // create it - classMapping = new ClassMapping(classEntry.getName()); - mappingChain.set(i, classMapping); - - // add it to the right parent - try { - if (i == 0) { - mappings.addClassMapping(classMapping); - } else { - mappingChain.get(i - 1).addInnerClassMapping(classMapping); - } - } catch (MappingConflict mappingConflict) { - mappingConflict.printStackTrace(); - } - } - } - return mappingChain; - } - - public void setClassModifier(ClassEntry obEntry, Mappings.EntryModifier modifier) - { - ClassMapping classMapping = getOrCreateClassMapping(obEntry); - classMapping.setModifier(modifier); - } - - public void setFieldModifier(FieldEntry obEntry, Mappings.EntryModifier modifier) - { - ClassMapping classMapping = getOrCreateClassMapping(obEntry.getClassEntry()); - classMapping.setFieldModifier(obEntry.getName(), obEntry.getType(), modifier); - } - - public void setMethodModifier(BehaviorEntry obEntry, Mappings.EntryModifier modifier) - { - ClassMapping classMapping = getOrCreateClassMapping(obEntry.getClassEntry()); - classMapping.setMethodModifier(obEntry.getName(), obEntry.getSignature(), modifier); - } + private JarIndex index; + private Mappings mappings; + + public MappingsRenamer(JarIndex index, Mappings mappings) { + this.index = index; + this.mappings = mappings; + } + + public void setMappings(Mappings mappings) { + this.mappings = mappings; + } + + public void setClassName(ClassEntry obf, String deobfName) { + + deobfName = NameValidator.validateClassName(deobfName, !obf.isInnerClass()); + + List mappingChain = getOrCreateClassMappingChain(obf); + if (mappingChain.size() == 1) { + + if (deobfName != null) { + // make sure we don't rename to an existing obf or deobf class + if (mappings.containsDeobfClass(deobfName) || index.containsObfClass(new ClassEntry(deobfName))) { + throw new IllegalNameException(deobfName, "There is already a class with that name"); + } + } + + ClassMapping classMapping = mappingChain.get(0); + mappings.setClassDeobfName(classMapping, deobfName); + + } else { + + ClassMapping outerClassMapping = mappingChain.get(mappingChain.size() - 2); + + if (deobfName != null) { + // make sure we don't rename to an existing obf or deobf inner class + if (outerClassMapping.hasInnerClassByDeobf(deobfName) || outerClassMapping.hasInnerClassByObfSimple(deobfName)) { + throw new IllegalNameException(deobfName, "There is already a class with that name"); + } + } + + outerClassMapping.setInnerClassName(obf, deobfName); + } + } + + public void removeClassMapping(ClassEntry obf) { + setClassName(obf, null); + } + + public void markClassAsDeobfuscated(ClassEntry obf) { + String deobfName = obf.isInnerClass() ? obf.getInnermostClassName() : obf.getName(); + List mappingChain = getOrCreateClassMappingChain(obf); + if (mappingChain.size() == 1) { + ClassMapping classMapping = mappingChain.get(0); + mappings.setClassDeobfName(classMapping, deobfName); + } else { + ClassMapping outerClassMapping = mappingChain.get(mappingChain.size() - 2); + outerClassMapping.setInnerClassName(obf, deobfName); + } + } + + public void setFieldName(FieldEntry obf, String deobfName) { + deobfName = NameValidator.validateFieldName(deobfName); + FieldEntry targetEntry = new FieldEntry(obf.getClassEntry(), deobfName, obf.getType()); + ClassEntry definedClass = null; + if (mappings.containsDeobfField(obf.getClassEntry(), deobfName) || index.containsEntryWithSameName(targetEntry)) + definedClass = obf.getClassEntry(); + else { + for (ClassEntry ancestorEntry : this.index.getTranslationIndex().getAncestry(obf.getClassEntry())) { + if (mappings.containsDeobfField(ancestorEntry, deobfName) || index.containsEntryWithSameName(targetEntry.cloneToNewClass(ancestorEntry))) { + definedClass = ancestorEntry; + break; + } + } + } + + if (definedClass != null) { + String className = mappings.getTranslator(TranslationDirection.Deobfuscating, index.getTranslationIndex()).translateClass(definedClass.getClassName()); + if (className == null) + className = definedClass.getClassName(); + throw new IllegalNameException(deobfName, "There is already a field with that name in " + className); + } + + ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); + classMapping.setFieldName(obf.getName(), obf.getType(), deobfName); + } + + public void removeFieldMapping(FieldEntry obf) { + ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); + classMapping.removeFieldMapping(classMapping.getFieldByObf(obf.getName(), obf.getType())); + } + + public void markFieldAsDeobfuscated(FieldEntry obf) { + ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); + classMapping.setFieldName(obf.getName(), obf.getType(), obf.getName()); + } + + private void validateMethodTreeName(MethodEntry entry, String deobfName) { + MethodEntry targetEntry = new MethodEntry(entry.getClassEntry(), deobfName, entry.getSignature()); + + // TODO: Verify if I don't break things + ClassMapping classMapping = mappings.getClassByObf(entry.getClassEntry()); + if ((classMapping != null && classMapping.containsDeobfMethod(deobfName, entry.getSignature()) && classMapping.getMethodByObf(entry.getName(), entry.getSignature()) != classMapping.getMethodByDeobf(deobfName, entry.getSignature())) + || index.containsObfBehavior(targetEntry)) { + String deobfClassName = mappings.getTranslator(TranslationDirection.Deobfuscating, index.getTranslationIndex()).translateClass(entry.getClassName()); + if (deobfClassName == null) { + deobfClassName = entry.getClassName(); + } + throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName); + } + + for (ClassEntry child : index.getTranslationIndex().getSubclass(entry.getClassEntry())) { + validateMethodTreeName(entry.cloneToNewClass(child), deobfName); + } + } + + public void setMethodTreeName(MethodEntry obf, String deobfName) { + Set implementations = index.getRelatedMethodImplementations(obf); + + deobfName = NameValidator.validateMethodName(deobfName); + for (MethodEntry entry : implementations) { + validateMethodTreeName(entry, deobfName); + } + + for (MethodEntry entry : implementations) { + setMethodName(entry, deobfName); + } + } + + public void setMethodName(MethodEntry obf, String deobfName) { + deobfName = NameValidator.validateMethodName(deobfName); + MethodEntry targetEntry = new MethodEntry(obf.getClassEntry(), deobfName, obf.getSignature()); + ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); + + // TODO: Verify if I don't break things + if ((mappings.containsDeobfMethod(obf.getClassEntry(), deobfName, obf.getSignature()) && classMapping.getMethodByObf(obf.getName(), obf.getSignature()) != classMapping.getMethodByDeobf(deobfName, obf.getSignature())) + || index.containsObfBehavior(targetEntry)) { + String deobfClassName = mappings.getTranslator(TranslationDirection.Deobfuscating, index.getTranslationIndex()).translateClass(obf.getClassName()); + if (deobfClassName == null) { + deobfClassName = obf.getClassName(); + } + throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName); + } + + classMapping.setMethodName(obf.getName(), obf.getSignature(), deobfName); + } + + public void removeMethodTreeMapping(MethodEntry obf) { + index.getRelatedMethodImplementations(obf).forEach(this::removeMethodMapping); + } + + public void removeMethodMapping(MethodEntry obf) { + ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); + classMapping.setMethodName(obf.getName(), obf.getSignature(), null); + } + + public void markMethodTreeAsDeobfuscated(MethodEntry obf) { + index.getRelatedMethodImplementations(obf).forEach(this::markMethodAsDeobfuscated); + } + + public void markMethodAsDeobfuscated(MethodEntry obf) { + ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); + classMapping.setMethodName(obf.getName(), obf.getSignature(), obf.getName()); + } + + public void setArgumentTreeName(ArgumentEntry obf, String deobfName) { + if (!(obf.getBehaviorEntry() instanceof MethodEntry)) { + setArgumentName(obf, deobfName); + return; + } + + MethodEntry obfMethod = (MethodEntry) obf.getBehaviorEntry(); + + Set implementations = index.getRelatedMethodImplementations(obfMethod); + for (MethodEntry entry : implementations) { + ClassMapping classMapping = mappings.getClassByObf(entry.getClassEntry()); + if (classMapping != null) { + MethodMapping mapping = classMapping.getMethodByObf(entry.getName(), entry.getSignature()); + // NOTE: don't need to check arguments for name collisions with names determined by Procyon + // TODO: Verify if I don't break things + if (mapping != null) { + for (ArgumentMapping argumentMapping : Lists.newArrayList(mapping.arguments())) { + if (argumentMapping.getIndex() != obf.getIndex()) { + if (mapping.getDeobfArgumentName(argumentMapping.getIndex()).equals(deobfName) + || argumentMapping.getName().equals(deobfName)) { + throw new IllegalNameException(deobfName, "There is already an argument with that name"); + } + } + } + } + } + } + + for (MethodEntry entry : implementations) { + setArgumentName(new ArgumentEntry(obf, entry), deobfName); + } + } + + public void setArgumentName(ArgumentEntry obf, String deobfName) { + deobfName = NameValidator.validateArgumentName(deobfName); + ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); + MethodMapping mapping = classMapping.getMethodByObf(obf.getMethodName(), obf.getMethodSignature()); + // NOTE: don't need to check arguments for name collisions with names determined by Procyon + // TODO: Verify if I don't break things + if (mapping != null) { + for (ArgumentMapping argumentMapping : Lists.newArrayList(mapping.arguments())) { + if (argumentMapping.getIndex() != obf.getIndex()) { + if (mapping.getDeobfArgumentName(argumentMapping.getIndex()).equals(deobfName) + || argumentMapping.getName().equals(deobfName)) { + throw new IllegalNameException(deobfName, "There is already an argument with that name"); + } + } + } + } + + classMapping.setArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex(), deobfName); + } + + public void removeArgumentMapping(ArgumentEntry obf) { + ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); + classMapping.removeArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex()); + } + + public void markArgumentAsDeobfuscated(ArgumentEntry obf) { + ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); + classMapping.setArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex(), obf.getName()); + } + + public boolean moveFieldToObfClass(ClassMapping classMapping, FieldMapping fieldMapping, ClassEntry obfClass) { + classMapping.removeFieldMapping(fieldMapping); + ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass); + if (!targetClassMapping.containsObfField(fieldMapping.getObfName(), fieldMapping.getObfType())) { + if (!targetClassMapping.containsDeobfField(fieldMapping.getDeobfName(), fieldMapping.getObfType())) { + targetClassMapping.addFieldMapping(fieldMapping); + return true; + } else { + System.err.println("WARNING: deobf field was already there: " + obfClass + "." + fieldMapping.getDeobfName()); + } + } + return false; + } + + public boolean moveMethodToObfClass(ClassMapping classMapping, MethodMapping methodMapping, ClassEntry obfClass) { + classMapping.removeMethodMapping(methodMapping); + ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass); + if (!targetClassMapping.containsObfMethod(methodMapping.getObfName(), methodMapping.getObfSignature())) { + if (!targetClassMapping.containsDeobfMethod(methodMapping.getDeobfName(), methodMapping.getObfSignature())) { + targetClassMapping.addMethodMapping(methodMapping); + return true; + } else { + System.err.println("WARNING: deobf method was already there: " + obfClass + "." + methodMapping.getDeobfName() + methodMapping.getObfSignature()); + } + } + return false; + } + + public void write(OutputStream out) throws IOException { + // TEMP: just use the object output for now. We can find a more efficient storage format later + GZIPOutputStream gzipout = new GZIPOutputStream(out); + ObjectOutputStream oout = new ObjectOutputStream(gzipout); + oout.writeObject(this); + gzipout.finish(); + } + + private ClassMapping getOrCreateClassMapping(ClassEntry obfClassEntry) { + List mappingChain = getOrCreateClassMappingChain(obfClassEntry); + return mappingChain.get(mappingChain.size() - 1); + } + + private List getOrCreateClassMappingChain(ClassEntry obfClassEntry) { + List classChain = obfClassEntry.getClassChain(); + List mappingChain = mappings.getClassMappingChain(obfClassEntry); + for (int i = 0; i < classChain.size(); i++) { + ClassEntry classEntry = classChain.get(i); + ClassMapping classMapping = mappingChain.get(i); + if (classMapping == null) { + + // create it + classMapping = new ClassMapping(classEntry.getName()); + mappingChain.set(i, classMapping); + + // add it to the right parent + try { + if (i == 0) { + mappings.addClassMapping(classMapping); + } else { + mappingChain.get(i - 1).addInnerClassMapping(classMapping); + } + } catch (MappingConflict mappingConflict) { + mappingConflict.printStackTrace(); + } + } + } + return mappingChain; + } + + public void setClassModifier(ClassEntry obEntry, Mappings.EntryModifier modifier) { + ClassMapping classMapping = getOrCreateClassMapping(obEntry); + classMapping.setModifier(modifier); + } + + public void setFieldModifier(FieldEntry obEntry, Mappings.EntryModifier modifier) { + ClassMapping classMapping = getOrCreateClassMapping(obEntry.getClassEntry()); + classMapping.setFieldModifier(obEntry.getName(), obEntry.getType(), modifier); + } + + public void setMethodModifier(BehaviorEntry obEntry, Mappings.EntryModifier modifier) { + ClassMapping classMapping = getOrCreateClassMapping(obEntry.getClassEntry()); + classMapping.setMethodModifier(obEntry.getName(), obEntry.getSignature(), modifier); + } } diff --git a/src/main/java/cuchaz/enigma/mapping/MappingsSRGWriter.java b/src/main/java/cuchaz/enigma/mapping/MappingsSRGWriter.java index a3f0cc87..b0eb826e 100644 --- a/src/main/java/cuchaz/enigma/mapping/MappingsSRGWriter.java +++ b/src/main/java/cuchaz/enigma/mapping/MappingsSRGWriter.java @@ -13,69 +13,67 @@ import java.util.List; */ public class MappingsSRGWriter { - public void write(File file, Mappings mappings) throws IOException { - if(file.exists()){ - file.delete(); - } - file.createNewFile(); + public void write(File file, Mappings mappings) throws IOException { + if (file.exists()) { + file.delete(); + } + file.createNewFile(); - TranslationIndex index = new TranslationIndex(); + TranslationIndex index = new TranslationIndex(); - PrintWriter writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(file), Charsets.UTF_8)); - List fieldMappings = new ArrayList<>(); - List methodMappings = new ArrayList<>(); - for (ClassMapping classMapping : sorted(mappings.classes())) { - if(classMapping.getDeobfName() == null || classMapping.getObfSimpleName() == null || classMapping.getDeobfName() == null){ - continue; - } - writer.write("CL: " + classMapping.getObfSimpleName() + " " + classMapping.getDeobfName()); - writer.write(System.lineSeparator()); - for (ClassMapping innerClassMapping : sorted(classMapping.innerClasses())) { - if(innerClassMapping.getDeobfName() == null || innerClassMapping.getObfSimpleName() == null || innerClassMapping.getDeobfName() == null){ - continue; - } - String innerClassName = classMapping.getObfSimpleName() + "$" + innerClassMapping.getObfSimpleName(); - String innerDeobfClassName = classMapping.getDeobfName() + "$" + innerClassMapping.getDeobfName(); - writer.write("CL: " + innerClassName + " " + classMapping.getDeobfName() + "$" + innerClassMapping.getDeobfName()); - writer.write(System.lineSeparator()); - for (FieldMapping fieldMapping : sorted(innerClassMapping.fields())) { - fieldMappings.add("FD: " + innerClassName + "/" + fieldMapping.getObfName() + " " + innerDeobfClassName + "/" + fieldMapping.getDeobfName()); - } + PrintWriter writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(file), Charsets.UTF_8)); + List fieldMappings = new ArrayList<>(); + List methodMappings = new ArrayList<>(); + for (ClassMapping classMapping : sorted(mappings.classes())) { + if (classMapping.getDeobfName() == null || classMapping.getObfSimpleName() == null || classMapping.getDeobfName() == null) { + continue; + } + writer.write("CL: " + classMapping.getObfSimpleName() + " " + classMapping.getDeobfName()); + writer.write(System.lineSeparator()); + for (ClassMapping innerClassMapping : sorted(classMapping.innerClasses())) { + if (innerClassMapping.getDeobfName() == null || innerClassMapping.getObfSimpleName() == null || innerClassMapping.getDeobfName() == null) { + continue; + } + String innerClassName = classMapping.getObfSimpleName() + "$" + innerClassMapping.getObfSimpleName(); + String innerDeobfClassName = classMapping.getDeobfName() + "$" + innerClassMapping.getDeobfName(); + writer.write("CL: " + innerClassName + " " + classMapping.getDeobfName() + "$" + innerClassMapping.getDeobfName()); + writer.write(System.lineSeparator()); + for (FieldMapping fieldMapping : sorted(innerClassMapping.fields())) { + fieldMappings.add("FD: " + innerClassName + "/" + fieldMapping.getObfName() + " " + innerDeobfClassName + "/" + fieldMapping.getDeobfName()); + } - for (MethodMapping methodMapping : sorted(innerClassMapping.methods())) { - methodMappings.add("MD: " + innerClassName + "/" + methodMapping.getObfName() + " " + methodMapping.getObfSignature().toString() + " " + innerDeobfClassName + "/" + methodMapping.getDeobfName() + " " + mappings.getTranslator(TranslationDirection.Deobfuscating, index).translateSignature(methodMapping.getObfSignature())); - } - } + for (MethodMapping methodMapping : sorted(innerClassMapping.methods())) { + methodMappings.add("MD: " + innerClassName + "/" + methodMapping.getObfName() + " " + methodMapping.getObfSignature() + " " + innerDeobfClassName + "/" + methodMapping.getDeobfName() + " " + mappings.getTranslator(TranslationDirection.Deobfuscating, index).translateSignature(methodMapping.getObfSignature())); + } + } - for (FieldMapping fieldMapping : sorted(classMapping.fields())) { - fieldMappings.add("FD: " + classMapping.getObfFullName() + "/" + fieldMapping.getObfName() + " " + classMapping.getDeobfName() + "/" + fieldMapping.getDeobfName()); - } + for (FieldMapping fieldMapping : sorted(classMapping.fields())) { + fieldMappings.add("FD: " + classMapping.getObfFullName() + "/" + fieldMapping.getObfName() + " " + classMapping.getDeobfName() + "/" + fieldMapping.getDeobfName()); + } - for (MethodMapping methodMapping : sorted(classMapping.methods())) { - methodMappings.add("MD: " + classMapping.getObfFullName() + "/" + methodMapping.getObfName() + " " + methodMapping.getObfSignature().toString() + " " + classMapping.getDeobfName() + "/" + methodMapping.getDeobfName() + " " + mappings.getTranslator(TranslationDirection.Deobfuscating, index).translateSignature(methodMapping.getObfSignature())); - } - } - for(String fd : fieldMappings){ - writer.write(fd); - writer.write(System.lineSeparator()); - } + for (MethodMapping methodMapping : sorted(classMapping.methods())) { + methodMappings.add("MD: " + classMapping.getObfFullName() + "/" + methodMapping.getObfName() + " " + methodMapping.getObfSignature() + " " + classMapping.getDeobfName() + "/" + methodMapping.getDeobfName() + " " + mappings.getTranslator(TranslationDirection.Deobfuscating, index).translateSignature(methodMapping.getObfSignature())); + } + } + for (String fd : fieldMappings) { + writer.write(fd); + writer.write(System.lineSeparator()); + } - for(String md : methodMappings){ - writer.write(md); - writer.write(System.lineSeparator()); - } + for (String md : methodMappings) { + writer.write(md); + writer.write(System.lineSeparator()); + } + writer.close(); + } - writer.close(); - } - - - private > List sorted(Iterable classes) { - List out = new ArrayList<>(); - for (T t : classes) { - out.add(t); - } - Collections.sort(out); - return out; - } + private > List sorted(Iterable classes) { + List out = new ArrayList<>(); + for (T t : classes) { + out.add(t); + } + Collections.sort(out); + return out; + } } diff --git a/src/main/java/cuchaz/enigma/mapping/MemberMapping.java b/src/main/java/cuchaz/enigma/mapping/MemberMapping.java index 90c096fa..d4514d42 100644 --- a/src/main/java/cuchaz/enigma/mapping/MemberMapping.java +++ b/src/main/java/cuchaz/enigma/mapping/MemberMapping.java @@ -8,11 +8,11 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ -package cuchaz.enigma.mapping; +package cuchaz.enigma.mapping; public interface MemberMapping { - T getObfEntry(ClassEntry classEntry); + T getObfEntry(ClassEntry classEntry); - String getObfName(); + String getObfName(); } diff --git a/src/main/java/cuchaz/enigma/mapping/MethodEntry.java b/src/main/java/cuchaz/enigma/mapping/MethodEntry.java index 4d7ed8f0..9c3058c4 100644 --- a/src/main/java/cuchaz/enigma/mapping/MethodEntry.java +++ b/src/main/java/cuchaz/enigma/mapping/MethodEntry.java @@ -8,82 +8,83 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.mapping; import cuchaz.enigma.utils.Utils; public class MethodEntry implements BehaviorEntry { - private ClassEntry classEntry; - private String name; - private Signature signature; - - public MethodEntry(ClassEntry classEntry, String name, Signature signature) { - if (classEntry == null) { - throw new IllegalArgumentException("Class cannot be null!"); - } - if (name == null) { - throw new IllegalArgumentException("Method name cannot be null!"); - } - if (signature == null) { - throw new IllegalArgumentException("Method signature cannot be null!"); - } - if (name.startsWith("<")) { - throw new IllegalArgumentException("Don't use MethodEntry for a constructor!"); - } - - this.classEntry = classEntry; - this.name = name; - this.signature = signature; - } - - public MethodEntry(MethodEntry other, String newClassName) { - this.classEntry = new ClassEntry(newClassName); - this.name = other.name; - this.signature = other.signature; - } - - @Override - public ClassEntry getClassEntry() { - return this.classEntry; - } - - @Override - public String getName() { - return this.name; - } - - @Override - public Signature getSignature() { - return this.signature; - } - - @Override - public String getClassName() { - return this.classEntry.getName(); - } - - @Override - public MethodEntry cloneToNewClass(ClassEntry classEntry) { - return new MethodEntry(this, classEntry.getName()); - } - - @Override - public int hashCode() { - return Utils.combineHashesOrdered(this.classEntry, this.name, this.signature); - } - - @Override - public boolean equals(Object other) { - return other instanceof MethodEntry && equals((MethodEntry) other); - } - - public boolean equals(MethodEntry other) { - return this.classEntry.equals(other.classEntry) && this.name.equals(other.name) && this.signature.equals(other.signature); - } - - @Override - public String toString() { - return this.classEntry.getName() + "." + this.name + this.signature; - } + private ClassEntry classEntry; + private String name; + private Signature signature; + + public MethodEntry(ClassEntry classEntry, String name, Signature signature) { + if (classEntry == null) { + throw new IllegalArgumentException("Class cannot be null!"); + } + if (name == null) { + throw new IllegalArgumentException("Method name cannot be null!"); + } + if (signature == null) { + throw new IllegalArgumentException("Method signature cannot be null!"); + } + if (name.startsWith("<")) { + throw new IllegalArgumentException("Don't use MethodEntry for a constructor!"); + } + + this.classEntry = classEntry; + this.name = name; + this.signature = signature; + } + + public MethodEntry(MethodEntry other, String newClassName) { + this.classEntry = new ClassEntry(newClassName); + this.name = other.name; + this.signature = other.signature; + } + + @Override + public ClassEntry getClassEntry() { + return this.classEntry; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public Signature getSignature() { + return this.signature; + } + + @Override + public String getClassName() { + return this.classEntry.getName(); + } + + @Override + public MethodEntry cloneToNewClass(ClassEntry classEntry) { + return new MethodEntry(this, classEntry.getName()); + } + + @Override + public int hashCode() { + return Utils.combineHashesOrdered(this.classEntry, this.name, this.signature); + } + + @Override + public boolean equals(Object other) { + return other instanceof MethodEntry && equals((MethodEntry) other); + } + + public boolean equals(MethodEntry other) { + return this.classEntry.equals(other.classEntry) && this.name.equals(other.name) && this.signature.equals(other.signature); + } + + @Override + public String toString() { + return this.classEntry.getName() + "." + this.name + this.signature; + } } diff --git a/src/main/java/cuchaz/enigma/mapping/MethodMapping.java b/src/main/java/cuchaz/enigma/mapping/MethodMapping.java index e0aeea2d..1524ce63 100644 --- a/src/main/java/cuchaz/enigma/mapping/MethodMapping.java +++ b/src/main/java/cuchaz/enigma/mapping/MethodMapping.java @@ -8,211 +8,206 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.mapping; import com.google.common.collect.Maps; - -import java.util.Map; - import cuchaz.enigma.throwables.IllegalNameException; import cuchaz.enigma.throwables.MappingConflict; +import java.util.Map; + public class MethodMapping implements Comparable, MemberMapping { - private String obfName; - private String deobfName; - private Signature obfSignature; - private Map arguments; - private Mappings.EntryModifier modifier; - - public MethodMapping(String obfName, Signature obfSignature) { - this(obfName, obfSignature, null,Mappings.EntryModifier.UNCHANGED); - } - - public MethodMapping(String obfName, Signature obfSignature, String deobfName) { - this(obfName, obfSignature, deobfName, Mappings.EntryModifier.UNCHANGED); - } - - public MethodMapping(String obfName, Signature obfSignature, String deobfName, Mappings.EntryModifier modifier) { - if (obfName == null) { - throw new IllegalArgumentException("obf name cannot be null!"); - } - if (obfSignature == null) { - throw new IllegalArgumentException("obf signature cannot be null!"); - } - this.obfName = obfName; - this.deobfName = NameValidator.validateMethodName(deobfName); - this.obfSignature = obfSignature; - this.arguments = Maps.newTreeMap(); - this.modifier = modifier; - } - - public MethodMapping(MethodMapping other, ClassNameReplacer obfClassNameReplacer) { - this.obfName = other.obfName; - this.deobfName = other.deobfName; - this.modifier = other.modifier; - this.obfSignature = new Signature(other.obfSignature, obfClassNameReplacer); - this.arguments = Maps.newTreeMap(); - for (Map.Entry entry : other.arguments.entrySet()) { - this.arguments.put(entry.getKey(), new ArgumentMapping(entry.getValue())); - } - } - - @Override - public String getObfName() { - return this.obfName; - } - - public String getDeobfName() { - return this.deobfName; - } - - public void setDeobfName(String val) { - this.deobfName = NameValidator.validateMethodName(val); - } - - public Signature getObfSignature() { - return this.obfSignature; - } - - public void setObfName(String name) { - try - { - NameValidator.validateMethodName(name); - } catch (IllegalNameException ex) - { - // Invalid name, damn obfuscation! Map to a deob name with another name to avoid issues - if (this.deobfName == null) - { - System.err.println("WARNING: " + name + " is conflicting, auto deobfuscate to " + (name + "_auto_deob")); - setDeobfName(name + "_auto_deob"); - } - } - this.obfName = name; - } - - public void setObfSignature(Signature val) { - this.obfSignature = val; - } - - public Iterable arguments() { - return this.arguments.values(); - } - - public void addArgumentMapping(ArgumentMapping argumentMapping) throws MappingConflict { - if (this.arguments.containsKey(argumentMapping.getIndex())) { - throw new MappingConflict("argument", argumentMapping.getName(), this.arguments.get(argumentMapping.getIndex()).getName()); - } - this.arguments.put(argumentMapping.getIndex(), argumentMapping); - } - - public String getObfArgumentName(int index) { - ArgumentMapping argumentMapping = this.arguments.get(index); - if (argumentMapping != null) { - return argumentMapping.getName(); - } - - return null; - } - - public String getDeobfArgumentName(int index) { - ArgumentMapping argumentMapping = this.arguments.get(index); - if (argumentMapping != null) { - return argumentMapping.getName(); - } - - return null; - } - - public void setArgumentName(int index, String name) { - ArgumentMapping argumentMapping = this.arguments.get(index); - if (argumentMapping == null) { - argumentMapping = new ArgumentMapping(index, name); - boolean wasAdded = this.arguments.put(index, argumentMapping) == null; - assert (wasAdded); - } else { - argumentMapping.setName(name); - } - } - - public void removeArgumentName(int index) { - boolean wasRemoved = this.arguments.remove(index) != null; - assert (wasRemoved); - } - - @Override - public String toString() { - StringBuilder buf = new StringBuilder(); - buf.append("\t"); - buf.append(this.obfName); - buf.append(" <-> "); - buf.append(this.deobfName); - buf.append("\n"); - buf.append("\t"); - buf.append(this.obfSignature); - buf.append("\n"); - buf.append("\tArguments:\n"); - for (ArgumentMapping argumentMapping : this.arguments.values()) { - buf.append("\t\t"); - buf.append(argumentMapping.getIndex()); - buf.append(" -> "); - buf.append(argumentMapping.getName()); - buf.append("\n"); - } - return buf.toString(); - } - - @Override - public int compareTo(MethodMapping other) { - return (this.obfName + this.obfSignature).compareTo(other.obfName + other.obfSignature); - } - - public boolean containsArgument(String name) { - for (ArgumentMapping argumentMapping : this.arguments.values()) { - if (argumentMapping.getName().equals(name)) { - return true; - } - } - return false; - } - - public boolean renameObfClass(final String oldObfClassName, final String newObfClassName) { - // rename obf classes in the signature - Signature newSignature = new Signature(this.obfSignature, className -> - { - if (className.equals(oldObfClassName)) { - return newObfClassName; - } - return null; - }); - - if (!newSignature.equals(this.obfSignature)) { - this.obfSignature = newSignature; - return true; - } - return false; - } - - public boolean isConstructor() { - return this.obfName.startsWith("<"); - } - - @Override - public BehaviorEntry getObfEntry(ClassEntry classEntry) { - if (isConstructor()) { - return new ConstructorEntry(classEntry, this.obfSignature); - } else { - return new MethodEntry(classEntry, this.obfName, this.obfSignature); - } - } - - public Mappings.EntryModifier getModifier() - { - return modifier; - } - - public void setModifier(Mappings.EntryModifier modifier) - { - this.modifier = modifier; - } + private String obfName; + private String deobfName; + private Signature obfSignature; + private Map arguments; + private Mappings.EntryModifier modifier; + + public MethodMapping(String obfName, Signature obfSignature) { + this(obfName, obfSignature, null, Mappings.EntryModifier.UNCHANGED); + } + + public MethodMapping(String obfName, Signature obfSignature, String deobfName) { + this(obfName, obfSignature, deobfName, Mappings.EntryModifier.UNCHANGED); + } + + public MethodMapping(String obfName, Signature obfSignature, String deobfName, Mappings.EntryModifier modifier) { + if (obfName == null) { + throw new IllegalArgumentException("obf name cannot be null!"); + } + if (obfSignature == null) { + throw new IllegalArgumentException("obf signature cannot be null!"); + } + this.obfName = obfName; + this.deobfName = NameValidator.validateMethodName(deobfName); + this.obfSignature = obfSignature; + this.arguments = Maps.newTreeMap(); + this.modifier = modifier; + } + + public MethodMapping(MethodMapping other, ClassNameReplacer obfClassNameReplacer) { + this.obfName = other.obfName; + this.deobfName = other.deobfName; + this.modifier = other.modifier; + this.obfSignature = new Signature(other.obfSignature, obfClassNameReplacer); + this.arguments = Maps.newTreeMap(); + for (Map.Entry entry : other.arguments.entrySet()) { + this.arguments.put(entry.getKey(), new ArgumentMapping(entry.getValue())); + } + } + + @Override + public String getObfName() { + return this.obfName; + } + + public void setObfName(String name) { + try { + NameValidator.validateMethodName(name); + } catch (IllegalNameException ex) { + // Invalid name, damn obfuscation! Map to a deob name with another name to avoid issues + if (this.deobfName == null) { + System.err.println("WARNING: " + name + " is conflicting, auto deobfuscate to " + (name + "_auto_deob")); + setDeobfName(name + "_auto_deob"); + } + } + this.obfName = name; + } + + public String getDeobfName() { + return this.deobfName; + } + + public void setDeobfName(String val) { + this.deobfName = NameValidator.validateMethodName(val); + } + + public Signature getObfSignature() { + return this.obfSignature; + } + + public void setObfSignature(Signature val) { + this.obfSignature = val; + } + + public Iterable arguments() { + return this.arguments.values(); + } + + public void addArgumentMapping(ArgumentMapping argumentMapping) throws MappingConflict { + if (this.arguments.containsKey(argumentMapping.getIndex())) { + throw new MappingConflict("argument", argumentMapping.getName(), this.arguments.get(argumentMapping.getIndex()).getName()); + } + this.arguments.put(argumentMapping.getIndex(), argumentMapping); + } + + public String getObfArgumentName(int index) { + ArgumentMapping argumentMapping = this.arguments.get(index); + if (argumentMapping != null) { + return argumentMapping.getName(); + } + + return null; + } + + public String getDeobfArgumentName(int index) { + ArgumentMapping argumentMapping = this.arguments.get(index); + if (argumentMapping != null) { + return argumentMapping.getName(); + } + + return null; + } + + public void setArgumentName(int index, String name) { + ArgumentMapping argumentMapping = this.arguments.get(index); + if (argumentMapping == null) { + argumentMapping = new ArgumentMapping(index, name); + boolean wasAdded = this.arguments.put(index, argumentMapping) == null; + assert (wasAdded); + } else { + argumentMapping.setName(name); + } + } + + public void removeArgumentName(int index) { + boolean wasRemoved = this.arguments.remove(index) != null; + assert (wasRemoved); + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append("\t"); + buf.append(this.obfName); + buf.append(" <-> "); + buf.append(this.deobfName); + buf.append("\n"); + buf.append("\t"); + buf.append(this.obfSignature); + buf.append("\n"); + buf.append("\tArguments:\n"); + for (ArgumentMapping argumentMapping : this.arguments.values()) { + buf.append("\t\t"); + buf.append(argumentMapping.getIndex()); + buf.append(" -> "); + buf.append(argumentMapping.getName()); + buf.append("\n"); + } + return buf.toString(); + } + + @Override + public int compareTo(MethodMapping other) { + return (this.obfName + this.obfSignature).compareTo(other.obfName + other.obfSignature); + } + + public boolean containsArgument(String name) { + for (ArgumentMapping argumentMapping : this.arguments.values()) { + if (argumentMapping.getName().equals(name)) { + return true; + } + } + return false; + } + + public boolean renameObfClass(final String oldObfClassName, final String newObfClassName) { + // rename obf classes in the signature + Signature newSignature = new Signature(this.obfSignature, className -> + { + if (className.equals(oldObfClassName)) { + return newObfClassName; + } + return null; + }); + + if (!newSignature.equals(this.obfSignature)) { + this.obfSignature = newSignature; + return true; + } + return false; + } + + public boolean isConstructor() { + return this.obfName.startsWith("<"); + } + + @Override + public BehaviorEntry getObfEntry(ClassEntry classEntry) { + if (isConstructor()) { + return new ConstructorEntry(classEntry, this.obfSignature); + } else { + return new MethodEntry(classEntry, this.obfName, this.obfSignature); + } + } + + public Mappings.EntryModifier getModifier() { + return modifier; + } + + public void setModifier(Mappings.EntryModifier modifier) { + this.modifier = modifier; + } } diff --git a/src/main/java/cuchaz/enigma/mapping/NameValidator.java b/src/main/java/cuchaz/enigma/mapping/NameValidator.java index 6925b72a..aa3dc4de 100644 --- a/src/main/java/cuchaz/enigma/mapping/NameValidator.java +++ b/src/main/java/cuchaz/enigma/mapping/NameValidator.java @@ -8,61 +8,62 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.mapping; +import cuchaz.enigma.throwables.IllegalNameException; +import javassist.bytecode.Descriptor; + import java.util.Arrays; import java.util.List; import java.util.regex.Pattern; -import cuchaz.enigma.throwables.IllegalNameException; -import javassist.bytecode.Descriptor; - public class NameValidator { - private static final Pattern IdentifierPattern; - private static final Pattern ClassPattern; - private static final List ReservedWords = Arrays.asList( - "abstract", "continue", "for", "new", "switch", "assert", "default", "goto", "package", "synchronized", - "boolean", "do", "if", "private", "this", "break", "double", "implements", "protected", "throw", "byte", - "else", "import", "public", "throws", "case", "enum", "instanceof", "return", "transient", "catch", - "extends", "int", "short", "try", "char", "final", "interface", "static", "void", "class", "finally", - "long", "strictfp", "volatile", "const", "float", "native", "super", "while" - ); + private static final Pattern IdentifierPattern; + private static final Pattern ClassPattern; + private static final List ReservedWords = Arrays.asList( + "abstract", "continue", "for", "new", "switch", "assert", "default", "goto", "package", "synchronized", + "boolean", "do", "if", "private", "this", "break", "double", "implements", "protected", "throw", "byte", + "else", "import", "public", "throws", "case", "enum", "instanceof", "return", "transient", "catch", + "extends", "int", "short", "try", "char", "final", "interface", "static", "void", "class", "finally", + "long", "strictfp", "volatile", "const", "float", "native", "super", "while" + ); - static { - String identifierRegex = "[A-Za-z_<][A-Za-z0-9_>]*"; - IdentifierPattern = Pattern.compile(identifierRegex); - ClassPattern = Pattern.compile(String.format("^(%s(\\.|/))*(%s)$", identifierRegex, identifierRegex)); - } + static { + String identifierRegex = "[A-Za-z_<][A-Za-z0-9_>]*"; + IdentifierPattern = Pattern.compile(identifierRegex); + ClassPattern = Pattern.compile(String.format("^(%s(\\.|/))*(%s)$", identifierRegex, identifierRegex)); + } - public static String validateClassName(String name, boolean packageRequired) { - if (name == null) { - return null; - } - if (!ClassPattern.matcher(name).matches() || ReservedWords.contains(name)) { - throw new IllegalNameException(name, "This doesn't look like a legal class name"); - } - if (packageRequired && new ClassEntry(name).getPackageName() == null) { - throw new IllegalNameException(name, "Class must be in a package"); - } - return Descriptor.toJvmName(name); - } + public static String validateClassName(String name, boolean packageRequired) { + if (name == null) { + return null; + } + if (!ClassPattern.matcher(name).matches() || ReservedWords.contains(name)) { + throw new IllegalNameException(name, "This doesn't look like a legal class name"); + } + if (packageRequired && new ClassEntry(name).getPackageName() == null) { + throw new IllegalNameException(name, "Class must be in a package"); + } + return Descriptor.toJvmName(name); + } - public static String validateFieldName(String name) { - if (name == null) { - return null; - } - if (!IdentifierPattern.matcher(name).matches() || ReservedWords.contains(name)) { - throw new IllegalNameException(name, "This doesn't look like a legal identifier"); - } - return name; - } + public static String validateFieldName(String name) { + if (name == null) { + return null; + } + if (!IdentifierPattern.matcher(name).matches() || ReservedWords.contains(name)) { + throw new IllegalNameException(name, "This doesn't look like a legal identifier"); + } + return name; + } - public static String validateMethodName(String name) { - return validateFieldName(name); - } + public static String validateMethodName(String name) { + return validateFieldName(name); + } - public static String validateArgumentName(String name) { - return validateFieldName(name); - } + public static String validateArgumentName(String name) { + return validateFieldName(name); + } } diff --git a/src/main/java/cuchaz/enigma/mapping/ProcyonEntryFactory.java b/src/main/java/cuchaz/enigma/mapping/ProcyonEntryFactory.java index 51fed83e..33d930dc 100644 --- a/src/main/java/cuchaz/enigma/mapping/ProcyonEntryFactory.java +++ b/src/main/java/cuchaz/enigma/mapping/ProcyonEntryFactory.java @@ -8,6 +8,7 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.mapping; import com.strobel.assembler.metadata.*; @@ -16,57 +17,51 @@ import java.util.List; public class ProcyonEntryFactory { - private static String getErasedSignature(MemberReference def) - { - if (!(def instanceof MethodReference)) - return def.getErasedSignature(); - MethodReference methodReference = (MethodReference) def; - StringBuilder builder = new StringBuilder("("); - for (ParameterDefinition param : methodReference.getParameters()) - { - TypeReference paramType = param.getParameterType(); - if (paramType.getErasedSignature().equals("Ljava/lang/Object;") && paramType.hasExtendsBound() && paramType.getExtendsBound() instanceof CompoundTypeReference) - { - List interfaces = ((CompoundTypeReference) paramType.getExtendsBound()).getInterfaces(); - interfaces.forEach((inter) -> builder.append(inter.getErasedSignature())); - } - else - builder.append(paramType.getErasedSignature()); - } - builder.append(")"); + private static String getErasedSignature(MemberReference def) { + if (!(def instanceof MethodReference)) + return def.getErasedSignature(); + MethodReference methodReference = (MethodReference) def; + StringBuilder builder = new StringBuilder("("); + for (ParameterDefinition param : methodReference.getParameters()) { + TypeReference paramType = param.getParameterType(); + if (paramType.getErasedSignature().equals("Ljava/lang/Object;") && paramType.hasExtendsBound() && paramType.getExtendsBound() instanceof CompoundTypeReference) { + List interfaces = ((CompoundTypeReference) paramType.getExtendsBound()).getInterfaces(); + interfaces.forEach((inter) -> builder.append(inter.getErasedSignature())); + } else + builder.append(paramType.getErasedSignature()); + } + builder.append(")"); - TypeReference returnType = methodReference.getReturnType(); - if (returnType.getErasedSignature().equals("Ljava/lang/Object;") && returnType.hasExtendsBound() && returnType.getExtendsBound() instanceof CompoundTypeReference) - { - List interfaces = ((CompoundTypeReference) returnType.getExtendsBound()).getInterfaces(); - interfaces.forEach((inter) -> builder.append(inter.getErasedSignature())); - } - else - builder.append(returnType.getErasedSignature()); - return builder.toString(); - } + TypeReference returnType = methodReference.getReturnType(); + if (returnType.getErasedSignature().equals("Ljava/lang/Object;") && returnType.hasExtendsBound() && returnType.getExtendsBound() instanceof CompoundTypeReference) { + List interfaces = ((CompoundTypeReference) returnType.getExtendsBound()).getInterfaces(); + interfaces.forEach((inter) -> builder.append(inter.getErasedSignature())); + } else + builder.append(returnType.getErasedSignature()); + return builder.toString(); + } - public static FieldEntry getFieldEntry(MemberReference def) { - return new FieldEntry(new ClassEntry(def.getDeclaringType().getInternalName()), def.getName(), new Type(def.getErasedSignature())); - } + public static FieldEntry getFieldEntry(MemberReference def) { + return new FieldEntry(new ClassEntry(def.getDeclaringType().getInternalName()), def.getName(), new Type(def.getErasedSignature())); + } - public static MethodEntry getMethodEntry(MemberReference def) { - return new MethodEntry(new ClassEntry(def.getDeclaringType().getInternalName()), def.getName(), new Signature(getErasedSignature(def))); - } + public static MethodEntry getMethodEntry(MemberReference def) { + return new MethodEntry(new ClassEntry(def.getDeclaringType().getInternalName()), def.getName(), new Signature(getErasedSignature(def))); + } - public static ConstructorEntry getConstructorEntry(MethodReference def) { - if (def.isTypeInitializer()) { - return new ConstructorEntry(new ClassEntry(def.getDeclaringType().getInternalName())); - } else { - return new ConstructorEntry(new ClassEntry(def.getDeclaringType().getInternalName()), new Signature(def.getErasedSignature())); - } - } + public static ConstructorEntry getConstructorEntry(MethodReference def) { + if (def.isTypeInitializer()) { + return new ConstructorEntry(new ClassEntry(def.getDeclaringType().getInternalName())); + } else { + return new ConstructorEntry(new ClassEntry(def.getDeclaringType().getInternalName()), new Signature(def.getErasedSignature())); + } + } - public static BehaviorEntry getBehaviorEntry(MethodReference def) { - if (def.isConstructor() || def.isTypeInitializer()) { - return getConstructorEntry(def); - } else { - return getMethodEntry(def); - } - } + public static BehaviorEntry getBehaviorEntry(MethodReference def) { + if (def.isConstructor() || def.isTypeInitializer()) { + return getConstructorEntry(def); + } else { + return getMethodEntry(def); + } + } } diff --git a/src/main/java/cuchaz/enigma/mapping/Signature.java b/src/main/java/cuchaz/enigma/mapping/Signature.java index f30b6069..78130d6b 100644 --- a/src/main/java/cuchaz/enigma/mapping/Signature.java +++ b/src/main/java/cuchaz/enigma/mapping/Signature.java @@ -8,99 +8,99 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.mapping; import com.google.common.collect.Lists; +import cuchaz.enigma.utils.Utils; import java.util.List; -import cuchaz.enigma.utils.Utils; - public class Signature { - private List argumentTypes; - private Type returnType; + private List argumentTypes; + private Type returnType; - public Signature(String signature) { - try { - this.argumentTypes = Lists.newArrayList(); - int i = 0; - while (i < signature.length()) { - char c = signature.charAt(i); - if (c == '(') { - assert (this.argumentTypes.isEmpty()); - assert (this.returnType == null); - i++; - } else if (c == ')') { - i++; - break; - } else { - String type = Type.parseFirst(signature.substring(i)); - this.argumentTypes.add(new Type(type)); - i += type.length(); - } - } - this.returnType = new Type(Type.parseFirst(signature.substring(i))); - } catch (Exception ex) { - throw new IllegalArgumentException("Unable to parse signature: " + signature, ex); - } - } + public Signature(String signature) { + try { + this.argumentTypes = Lists.newArrayList(); + int i = 0; + while (i < signature.length()) { + char c = signature.charAt(i); + if (c == '(') { + assert (this.argumentTypes.isEmpty()); + assert (this.returnType == null); + i++; + } else if (c == ')') { + i++; + break; + } else { + String type = Type.parseFirst(signature.substring(i)); + this.argumentTypes.add(new Type(type)); + i += type.length(); + } + } + this.returnType = new Type(Type.parseFirst(signature.substring(i))); + } catch (Exception ex) { + throw new IllegalArgumentException("Unable to parse signature: " + signature, ex); + } + } - public Signature(Signature other, ClassNameReplacer replacer) { - this.argumentTypes = Lists.newArrayList(other.argumentTypes); - for (int i = 0; i < this.argumentTypes.size(); i++) { - this.argumentTypes.set(i, new Type(this.argumentTypes.get(i), replacer)); - } - this.returnType = new Type(other.returnType, replacer); - } + public Signature(Signature other, ClassNameReplacer replacer) { + this.argumentTypes = Lists.newArrayList(other.argumentTypes); + for (int i = 0; i < this.argumentTypes.size(); i++) { + this.argumentTypes.set(i, new Type(this.argumentTypes.get(i), replacer)); + } + this.returnType = new Type(other.returnType, replacer); + } - public List getArgumentTypes() { - return this.argumentTypes; - } + public List getArgumentTypes() { + return this.argumentTypes; + } - public Type getReturnType() { - return this.returnType; - } + public Type getReturnType() { + return this.returnType; + } - @Override - public String toString() { - StringBuilder buf = new StringBuilder(); - buf.append("("); - for (Type type : this.argumentTypes) { - buf.append(type.toString()); - } - buf.append(")"); - buf.append(this.returnType.toString()); - return buf.toString(); - } + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append("("); + for (Type type : this.argumentTypes) { + buf.append(type); + } + buf.append(")"); + buf.append(this.returnType); + return buf.toString(); + } - public Iterable types() { - List types = Lists.newArrayList(); - types.addAll(this.argumentTypes); - types.add(this.returnType); - return types; - } + public Iterable types() { + List types = Lists.newArrayList(); + types.addAll(this.argumentTypes); + types.add(this.returnType); + return types; + } - @Override - public boolean equals(Object other) { - return other instanceof Signature && equals((Signature) other); - } + @Override + public boolean equals(Object other) { + return other instanceof Signature && equals((Signature) other); + } - public boolean equals(Signature other) { - return this.argumentTypes.equals(other.argumentTypes) && this.returnType.equals(other.returnType); - } + public boolean equals(Signature other) { + return this.argumentTypes.equals(other.argumentTypes) && this.returnType.equals(other.returnType); + } - @Override - public int hashCode() { - return Utils.combineHashesOrdered(this.argumentTypes.hashCode(), this.returnType.hashCode()); - } + @Override + public int hashCode() { + return Utils.combineHashesOrdered(this.argumentTypes.hashCode(), this.returnType.hashCode()); + } - public boolean hasClass(ClassEntry classEntry) { - for (Type type : types()) { - if (type.hasClass() && type.getClassEntry().equals(classEntry)) { - return true; - } - } - return false; - } + public boolean hasClass(ClassEntry classEntry) { + for (Type type : types()) { + if (type.hasClass() && type.getClassEntry().equals(classEntry)) { + return true; + } + } + return false; + } } diff --git a/src/main/java/cuchaz/enigma/mapping/SignatureUpdater.java b/src/main/java/cuchaz/enigma/mapping/SignatureUpdater.java index 98643330..ddc5af4b 100644 --- a/src/main/java/cuchaz/enigma/mapping/SignatureUpdater.java +++ b/src/main/java/cuchaz/enigma/mapping/SignatureUpdater.java @@ -8,6 +8,7 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.mapping; import com.google.common.collect.Lists; @@ -18,74 +19,74 @@ import java.util.List; public class SignatureUpdater { - public interface ClassNameUpdater { - String update(String className); - } + public static String update(String signature, ClassNameUpdater updater) { + try { + StringBuilder buf = new StringBuilder(); - public static String update(String signature, ClassNameUpdater updater) { - try { - StringBuilder buf = new StringBuilder(); + // read the signature character-by-character + StringReader reader = new StringReader(signature); + int i; + while ((i = reader.read()) != -1) { + char c = (char) i; - // read the signature character-by-character - StringReader reader = new StringReader(signature); - int i; - while ((i = reader.read()) != -1) { - char c = (char) i; + // does this character start a class name? + if (c == 'L') { + // update the class name and add it to the buffer + buf.append('L'); + String className = readClass(reader); + if (className == null) { + throw new IllegalArgumentException("Malformed signature: " + signature); + } + buf.append(updater.update(className)); + buf.append(';'); + } else { + // copy the character into the buffer + buf.append(c); + } + } - // does this character start a class name? - if (c == 'L') { - // update the class name and add it to the buffer - buf.append('L'); - String className = readClass(reader); - if (className == null) { - throw new IllegalArgumentException("Malformed signature: " + signature); - } - buf.append(updater.update(className)); - buf.append(';'); - } else { - // copy the character into the buffer - buf.append(c); - } - } + return buf.toString(); + } catch (IOException ex) { + // I'm pretty sure a StringReader will never throw one of these + throw new Error(ex); + } + } - return buf.toString(); - } catch (IOException ex) { - // I'm pretty sure a StringReader will never throw one of these - throw new Error(ex); - } - } + private static String readClass(StringReader reader) throws IOException { + // read all the characters in the buffer until we hit a ';' + // remember to treat generics correctly + StringBuilder buf = new StringBuilder(); + int depth = 0; + int i; + while ((i = reader.read()) != -1) { + char c = (char) i; - private static String readClass(StringReader reader) throws IOException { - // read all the characters in the buffer until we hit a ';' - // remember to treat generics correctly - StringBuilder buf = new StringBuilder(); - int depth = 0; - int i; - while ((i = reader.read()) != -1) { - char c = (char) i; + if (c == '<') { + depth++; + } else if (c == '>') { + depth--; + } else if (depth == 0) { + if (c == ';') { + return buf.toString(); + } else { + buf.append(c); + } + } + } - if (c == '<') { - depth++; - } else if (c == '>') { - depth--; - } else if (depth == 0) { - if (c == ';') { - return buf.toString(); - } else { - buf.append(c); - } - } - } + return null; + } - return null; - } + public static List getClasses(String signature) { + final List classNames = Lists.newArrayList(); + update(signature, className -> { + classNames.add(className); + return className; + }); + return classNames; + } - public static List getClasses(String signature) { - final List classNames = Lists.newArrayList(); - update(signature, className -> { - classNames.add(className); - return className; - }); - return classNames; - } + public interface ClassNameUpdater { + String update(String className); + } } diff --git a/src/main/java/cuchaz/enigma/mapping/TranslationDirection.java b/src/main/java/cuchaz/enigma/mapping/TranslationDirection.java index 8329d0d2..17e31876 100644 --- a/src/main/java/cuchaz/enigma/mapping/TranslationDirection.java +++ b/src/main/java/cuchaz/enigma/mapping/TranslationDirection.java @@ -8,22 +8,23 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.mapping; public enum TranslationDirection { - Deobfuscating { - @Override - public T choose(T deobfChoice, T obfChoice) { - return deobfChoice; - } - }, - Obfuscating { - @Override - public T choose(T deobfChoice, T obfChoice) { - return obfChoice; - } - }; + Deobfuscating { + @Override + public T choose(T deobfChoice, T obfChoice) { + return deobfChoice; + } + }, + Obfuscating { + @Override + public T choose(T deobfChoice, T obfChoice) { + return obfChoice; + } + }; - public abstract T choose(T deobfChoice, T obfChoice); + public abstract T choose(T deobfChoice, T obfChoice); } diff --git a/src/main/java/cuchaz/enigma/mapping/Translator.java b/src/main/java/cuchaz/enigma/mapping/Translator.java index e94009ee..8d464fc4 100644 --- a/src/main/java/cuchaz/enigma/mapping/Translator.java +++ b/src/main/java/cuchaz/enigma/mapping/Translator.java @@ -8,348 +8,335 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.mapping; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import cuchaz.enigma.analysis.TranslationIndex; import java.util.List; import java.util.Map; -import cuchaz.enigma.analysis.TranslationIndex; - public class Translator { - private TranslationDirection direction; - private Map classes; - private TranslationIndex index; - - private ClassNameReplacer classNameReplacer = className -> translateEntry(new ClassEntry(className)).getName(); - - public Translator() { - this.direction = null; - this.classes = Maps.newHashMap(); - this.index = new TranslationIndex(); - } - - public Translator(TranslationDirection direction, Map classes, TranslationIndex index) { - this.direction = direction; - this.classes = classes; - this.index = index; - } - - public TranslationDirection getDirection() { - return direction; - } - - public TranslationIndex getTranslationIndex() { - return index; - } - - @SuppressWarnings("unchecked") - public T translateEntry(T entry) { - if (entry instanceof ClassEntry) { - return (T) translateEntry((ClassEntry) entry); - } else if (entry instanceof FieldEntry) { - return (T) translateEntry((FieldEntry) entry); - } else if (entry instanceof MethodEntry) { - return (T) translateEntry((MethodEntry) entry); - } else if (entry instanceof ConstructorEntry) { - return (T) translateEntry((ConstructorEntry) entry); - } else if (entry instanceof ArgumentEntry) { - return (T) translateEntry((ArgumentEntry) entry); - } else if (entry instanceof LocalVariableEntry) { - return (T) translateEntry((LocalVariableEntry) entry); - } else { - throw new Error("Unknown entry type: " + entry.getClass().getName()); - } - } - - public String translate(T entry) { - if (entry instanceof ClassEntry) { - return translate((ClassEntry) entry); - } else if (entry instanceof FieldEntry) { - return translate((FieldEntry) entry); - } else if (entry instanceof MethodEntry) { - return translate((MethodEntry) entry); - } else if (entry instanceof ConstructorEntry) { - return translate(entry); - } else if (entry instanceof ArgumentEntry) { - return translate((ArgumentEntry) entry); - } else if (entry instanceof LocalVariableEntry) { - return translate((LocalVariableEntry) entry); - } else { - throw new Error("Unknown entry type: " + entry.getClass().getName()); - } - } - - public String translate(LocalVariableEntry in) - { - LocalVariableEntry translated = translateEntry(in); - if (translated.equals(in)) { - return null; - } - return translated.getName(); - } - - public LocalVariableEntry translateEntry(LocalVariableEntry in) - { - // TODO: Implement it - return in; - } - - public String translate(ClassEntry in) { - ClassEntry translated = translateEntry(in); - if (translated.equals(in)) { - return null; - } - return translated.getName(); - } - - public String translateClass(String className) { - return translate(new ClassEntry(className)); - } - - public ClassEntry translateEntry(ClassEntry in) { - - if (in.isInnerClass()) { - - // translate as much of the class chain as we can - List mappingsChain = getClassMappingChain(in); - String[] obfClassNames = in.getName().split("\\$"); - StringBuilder buf = new StringBuilder(); - for (int i = 0; i < obfClassNames.length; i++) { - boolean isFirstClass = buf.length() == 0; - String className = null; - ClassMapping classMapping = mappingsChain.get(i); - if (classMapping != null) { - className = this.direction.choose( - classMapping.getDeobfName(), - isFirstClass ? classMapping.getObfFullName() : classMapping.getObfSimpleName() - ); - } - if (className == null) { - className = obfClassNames[i]; - } - if (!isFirstClass) { - buf.append("$"); - } - buf.append(className); - } - return new ClassEntry(buf.toString()); - - } else { - - // normal classes are easy - ClassMapping classMapping = this.classes.get(in.getName()); - if (classMapping == null) { - return in; - } - return this.direction.choose( - classMapping.getDeobfName() != null ? new ClassEntry(classMapping.getDeobfName()) : in, - new ClassEntry(classMapping.getObfFullName()) - ); - } - } - - public String translate(FieldEntry in) { - - // resolve the class entry - ClassEntry resolvedClassEntry = this.index.resolveEntryClass(in); - if (resolvedClassEntry != null) { - - // look for the class - ClassMapping classMapping = findClassMapping(resolvedClassEntry); - if (classMapping != null) { - - // look for the field - String translatedName = this.direction.choose( - classMapping.getDeobfFieldName(in.getName(), in.getType()), - classMapping.getObfFieldName(in.getName(), translateType(in.getType())) - ); - if (translatedName != null) { - return translatedName; - } - } - } - return null; - } - - public FieldEntry translateEntry(FieldEntry in) { - String name = translate(in); - if (name == null) { - name = in.getName(); - } - return new FieldEntry(translateEntry(in.getClassEntry()), name, translateType(in.getType())); - } - - public String translate(MethodEntry in) { - // resolve the class entry - ClassEntry resolvedClassEntry = this.index.resolveEntryClass(in, true); - if (resolvedClassEntry != null) { - - // look for class - ClassMapping classMapping = findClassMapping(resolvedClassEntry); - if (classMapping != null) { - - // look for the method - MethodMapping methodMapping = this.direction.choose( - classMapping.getMethodByObf(in.getName(), in.getSignature()), - classMapping.getMethodByDeobf(in.getName(), translateSignature(in.getSignature())) - ); - if (methodMapping != null) { - return this.direction.choose(methodMapping.getDeobfName(), methodMapping.getObfName()); - } - } - } - return null; - } - - public MethodEntry translateEntry(MethodEntry in) { - String name = translate(in); - if (name == null) { - name = in.getName(); - } - return new MethodEntry(translateEntry(in.getClassEntry()), name, translateSignature(in.getSignature())); - } - - public ConstructorEntry translateEntry(ConstructorEntry in) { - if (in.isStatic()) { - return new ConstructorEntry(translateEntry(in.getClassEntry())); - } else { - return new ConstructorEntry(translateEntry(in.getClassEntry()), translateSignature(in.getSignature())); - } - } - - public BehaviorEntry translateEntry(BehaviorEntry in) { - if (in instanceof MethodEntry) { - return translateEntry((MethodEntry) in); - } else if (in instanceof ConstructorEntry) { - return translateEntry((ConstructorEntry) in); - } - throw new Error("Wrong entry type!"); - } - - // TODO: support not identical behavior (specific to constructor) - public String translate(ArgumentEntry in) - { - String classTranslate = translateArgument(in); - - // Not found in this class - if (classTranslate == null) - { - List ancestry = this.index.getAncestry(in.getClassEntry()); - - // Check in mother class for the arg - for (ClassEntry entry : ancestry) - { - ArgumentEntry motherArg = in.cloneToNewClass(entry); - if (this.index.entryExists(motherArg)) - { - String result = translateArgument(motherArg); - if (result != null) - return result; - } - } - } - return classTranslate; - } - - public String translateArgument(ArgumentEntry in) { - // look for identical behavior in superclasses - ClassEntry entry = in.getClassEntry(); - - if (entry != null) - { - // look for the class - ClassMapping classMapping = findClassMapping(entry); - if (classMapping != null) { - - // look for the method - MethodMapping methodMapping = this.direction.choose( - classMapping.getMethodByObf(in.getMethodName(), in.getMethodSignature()), - classMapping.getMethodByDeobf(in.getMethodName(), translateSignature(in.getMethodSignature())) - ); - if (methodMapping != null) { - return this.direction.choose( - methodMapping.getDeobfArgumentName(in.getIndex()), - methodMapping.getObfArgumentName(in.getIndex()) - ); - } - } - } - return null; - } - - public ArgumentEntry translateEntry(ArgumentEntry in) { - String name = translate(in); - if (name == null) { - name = in.getName(); - } - return new ArgumentEntry(translateEntry(in.getBehaviorEntry()), in.getIndex(), name); - } - - public Type translateType(Type type) { - return new Type(type, this.classNameReplacer); - } - - public Signature translateSignature(Signature signature) { - return new Signature(signature, this.classNameReplacer); - } - - private ClassMapping findClassMapping(ClassEntry in) { - List mappingChain = getClassMappingChain(in); - return mappingChain.get(mappingChain.size() - 1); - } - - private List getClassMappingChain(ClassEntry in) { - - // get a list of all the classes in the hierarchy - String[] parts = in.getName().split("\\$"); - List mappingsChain = Lists.newArrayList(); - - // get mappings for the outer class - ClassMapping outerClassMapping = this.classes.get(parts[0]); - mappingsChain.add(outerClassMapping); - - for (int i = 1; i < parts.length; i++) { - - // get mappings for the inner class - ClassMapping innerClassMapping = null; - if (outerClassMapping != null) { - innerClassMapping = this.direction.choose( - outerClassMapping.getInnerClassByObfSimple(parts[i]), - outerClassMapping.getInnerClassByDeobfThenObfSimple(parts[i]) - ); - } - mappingsChain.add(innerClassMapping); - outerClassMapping = innerClassMapping; - } - - assert (mappingsChain.size() == parts.length); - return mappingsChain; - } - - public Mappings.EntryModifier getModifier(Entry entry) - { - ClassMapping classMapping = findClassMapping(entry.getClassEntry()); - if (classMapping != null && !entry.getName().equals("")) - { - if (entry instanceof ClassEntry) - return classMapping.getModifier(); - else if (entry instanceof FieldEntry) - { - FieldMapping fieldMapping = classMapping.getFieldByObf(entry.getName(), ((FieldEntry) entry).getType()); - return fieldMapping != null ? fieldMapping.getModifier() : Mappings.EntryModifier.UNCHANGED; - } - else if (entry instanceof BehaviorEntry) - { - MethodMapping methodMapping = classMapping.getMethodByObf(entry.getName(), ((BehaviorEntry) entry).getSignature()); - return methodMapping != null ? methodMapping.getModifier() : Mappings.EntryModifier.UNCHANGED; - } - else - throw new Error("Unknown entry type: " + entry.getClass().getName()); - } - return Mappings.EntryModifier.UNCHANGED; - } + private TranslationDirection direction; + private Map classes; + private TranslationIndex index; + + private ClassNameReplacer classNameReplacer = className -> translateEntry(new ClassEntry(className)).getName(); + + public Translator() { + this.direction = null; + this.classes = Maps.newHashMap(); + this.index = new TranslationIndex(); + } + + public Translator(TranslationDirection direction, Map classes, TranslationIndex index) { + this.direction = direction; + this.classes = classes; + this.index = index; + } + + public TranslationDirection getDirection() { + return direction; + } + + public TranslationIndex getTranslationIndex() { + return index; + } + + @SuppressWarnings("unchecked") + public T translateEntry(T entry) { + if (entry instanceof ClassEntry) { + return (T) translateEntry((ClassEntry) entry); + } else if (entry instanceof FieldEntry) { + return (T) translateEntry((FieldEntry) entry); + } else if (entry instanceof MethodEntry) { + return (T) translateEntry((MethodEntry) entry); + } else if (entry instanceof ConstructorEntry) { + return (T) translateEntry((ConstructorEntry) entry); + } else if (entry instanceof ArgumentEntry) { + return (T) translateEntry((ArgumentEntry) entry); + } else if (entry instanceof LocalVariableEntry) { + return (T) translateEntry((LocalVariableEntry) entry); + } else { + throw new Error("Unknown entry type: " + entry.getClass().getName()); + } + } + + public String translate(T entry) { + if (entry instanceof ClassEntry) { + return translate((ClassEntry) entry); + } else if (entry instanceof FieldEntry) { + return translate((FieldEntry) entry); + } else if (entry instanceof MethodEntry) { + return translate((MethodEntry) entry); + } else if (entry instanceof ConstructorEntry) { + return translate(entry); + } else if (entry instanceof ArgumentEntry) { + return translate((ArgumentEntry) entry); + } else if (entry instanceof LocalVariableEntry) { + return translate((LocalVariableEntry) entry); + } else { + throw new Error("Unknown entry type: " + entry.getClass().getName()); + } + } + + public String translate(LocalVariableEntry in) { + LocalVariableEntry translated = translateEntry(in); + if (translated.equals(in)) { + return null; + } + return translated.getName(); + } + + public LocalVariableEntry translateEntry(LocalVariableEntry in) { + // TODO: Implement it + return in; + } + + public String translate(ClassEntry in) { + ClassEntry translated = translateEntry(in); + if (translated.equals(in)) { + return null; + } + return translated.getName(); + } + + public String translateClass(String className) { + return translate(new ClassEntry(className)); + } + + public ClassEntry translateEntry(ClassEntry in) { + + if (in.isInnerClass()) { + + // translate as much of the class chain as we can + List mappingsChain = getClassMappingChain(in); + String[] obfClassNames = in.getName().split("\\$"); + StringBuilder buf = new StringBuilder(); + for (int i = 0; i < obfClassNames.length; i++) { + boolean isFirstClass = buf.length() == 0; + String className = null; + ClassMapping classMapping = mappingsChain.get(i); + if (classMapping != null) { + className = this.direction.choose( + classMapping.getDeobfName(), + isFirstClass ? classMapping.getObfFullName() : classMapping.getObfSimpleName() + ); + } + if (className == null) { + className = obfClassNames[i]; + } + if (!isFirstClass) { + buf.append("$"); + } + buf.append(className); + } + return new ClassEntry(buf.toString()); + + } else { + + // normal classes are easy + ClassMapping classMapping = this.classes.get(in.getName()); + if (classMapping == null) { + return in; + } + return this.direction.choose( + classMapping.getDeobfName() != null ? new ClassEntry(classMapping.getDeobfName()) : in, + new ClassEntry(classMapping.getObfFullName()) + ); + } + } + + public String translate(FieldEntry in) { + + // resolve the class entry + ClassEntry resolvedClassEntry = this.index.resolveEntryClass(in); + if (resolvedClassEntry != null) { + + // look for the class + ClassMapping classMapping = findClassMapping(resolvedClassEntry); + if (classMapping != null) { + + // look for the field + String translatedName = this.direction.choose( + classMapping.getDeobfFieldName(in.getName(), in.getType()), + classMapping.getObfFieldName(in.getName(), translateType(in.getType())) + ); + if (translatedName != null) { + return translatedName; + } + } + } + return null; + } + + public FieldEntry translateEntry(FieldEntry in) { + String name = translate(in); + if (name == null) { + name = in.getName(); + } + return new FieldEntry(translateEntry(in.getClassEntry()), name, translateType(in.getType())); + } + + public String translate(MethodEntry in) { + // resolve the class entry + ClassEntry resolvedClassEntry = this.index.resolveEntryClass(in, true); + if (resolvedClassEntry != null) { + + // look for class + ClassMapping classMapping = findClassMapping(resolvedClassEntry); + if (classMapping != null) { + + // look for the method + MethodMapping methodMapping = this.direction.choose( + classMapping.getMethodByObf(in.getName(), in.getSignature()), + classMapping.getMethodByDeobf(in.getName(), translateSignature(in.getSignature())) + ); + if (methodMapping != null) { + return this.direction.choose(methodMapping.getDeobfName(), methodMapping.getObfName()); + } + } + } + return null; + } + + public MethodEntry translateEntry(MethodEntry in) { + String name = translate(in); + if (name == null) { + name = in.getName(); + } + return new MethodEntry(translateEntry(in.getClassEntry()), name, translateSignature(in.getSignature())); + } + + public ConstructorEntry translateEntry(ConstructorEntry in) { + if (in.isStatic()) { + return new ConstructorEntry(translateEntry(in.getClassEntry())); + } else { + return new ConstructorEntry(translateEntry(in.getClassEntry()), translateSignature(in.getSignature())); + } + } + + public BehaviorEntry translateEntry(BehaviorEntry in) { + if (in instanceof MethodEntry) { + return translateEntry((MethodEntry) in); + } else if (in instanceof ConstructorEntry) { + return translateEntry((ConstructorEntry) in); + } + throw new Error("Wrong entry type!"); + } + + // TODO: support not identical behavior (specific to constructor) + public String translate(ArgumentEntry in) { + String classTranslate = translateArgument(in); + + // Not found in this class + if (classTranslate == null) { + List ancestry = this.index.getAncestry(in.getClassEntry()); + + // Check in mother class for the arg + for (ClassEntry entry : ancestry) { + ArgumentEntry motherArg = in.cloneToNewClass(entry); + if (this.index.entryExists(motherArg)) { + String result = translateArgument(motherArg); + if (result != null) + return result; + } + } + } + return classTranslate; + } + + public String translateArgument(ArgumentEntry in) { + // look for identical behavior in superclasses + ClassEntry entry = in.getClassEntry(); + + if (entry != null) { + // look for the class + ClassMapping classMapping = findClassMapping(entry); + if (classMapping != null) { + + // look for the method + MethodMapping methodMapping = this.direction.choose( + classMapping.getMethodByObf(in.getMethodName(), in.getMethodSignature()), + classMapping.getMethodByDeobf(in.getMethodName(), translateSignature(in.getMethodSignature())) + ); + if (methodMapping != null) { + return this.direction.choose( + methodMapping.getDeobfArgumentName(in.getIndex()), + methodMapping.getObfArgumentName(in.getIndex()) + ); + } + } + } + return null; + } + + public ArgumentEntry translateEntry(ArgumentEntry in) { + String name = translate(in); + if (name == null) { + name = in.getName(); + } + return new ArgumentEntry(translateEntry(in.getBehaviorEntry()), in.getIndex(), name); + } + + public Type translateType(Type type) { + return new Type(type, this.classNameReplacer); + } + + public Signature translateSignature(Signature signature) { + return new Signature(signature, this.classNameReplacer); + } + + private ClassMapping findClassMapping(ClassEntry in) { + List mappingChain = getClassMappingChain(in); + return mappingChain.get(mappingChain.size() - 1); + } + + private List getClassMappingChain(ClassEntry in) { + + // get a list of all the classes in the hierarchy + String[] parts = in.getName().split("\\$"); + List mappingsChain = Lists.newArrayList(); + + // get mappings for the outer class + ClassMapping outerClassMapping = this.classes.get(parts[0]); + mappingsChain.add(outerClassMapping); + + for (int i = 1; i < parts.length; i++) { + + // get mappings for the inner class + ClassMapping innerClassMapping = null; + if (outerClassMapping != null) { + innerClassMapping = this.direction.choose( + outerClassMapping.getInnerClassByObfSimple(parts[i]), + outerClassMapping.getInnerClassByDeobfThenObfSimple(parts[i]) + ); + } + mappingsChain.add(innerClassMapping); + outerClassMapping = innerClassMapping; + } + + assert (mappingsChain.size() == parts.length); + return mappingsChain; + } + + public Mappings.EntryModifier getModifier(Entry entry) { + ClassMapping classMapping = findClassMapping(entry.getClassEntry()); + if (classMapping != null && !entry.getName().equals("")) { + if (entry instanceof ClassEntry) + return classMapping.getModifier(); + else if (entry instanceof FieldEntry) { + FieldMapping fieldMapping = classMapping.getFieldByObf(entry.getName(), ((FieldEntry) entry).getType()); + return fieldMapping != null ? fieldMapping.getModifier() : Mappings.EntryModifier.UNCHANGED; + } else if (entry instanceof BehaviorEntry) { + MethodMapping methodMapping = classMapping.getMethodByObf(entry.getName(), ((BehaviorEntry) entry).getSignature()); + return methodMapping != null ? methodMapping.getModifier() : Mappings.EntryModifier.UNCHANGED; + } else + throw new Error("Unknown entry type: " + entry.getClass().getName()); + } + return Mappings.EntryModifier.UNCHANGED; + } } diff --git a/src/main/java/cuchaz/enigma/mapping/Type.java b/src/main/java/cuchaz/enigma/mapping/Type.java index 8136e13b..609bd64e 100644 --- a/src/main/java/cuchaz/enigma/mapping/Type.java +++ b/src/main/java/cuchaz/enigma/mapping/Type.java @@ -8,6 +8,7 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.mapping; import com.google.common.collect.Maps; @@ -16,219 +17,219 @@ import java.util.Map; public class Type { - public enum Primitive { - Byte('B'), - Character('C'), - Short('S'), - Integer('I'), - Long('J'), - Float('F'), - Double('D'), - Boolean('Z'); - - private static final Map lookup; - - static { - lookup = Maps.newTreeMap(); - for (Primitive val : values()) { - lookup.put(val.getCode(), val); - } - } - - public static Primitive get(char code) { - return lookup.get(code); - } - - private char code; - - Primitive(char code) { - this.code = code; - } - - public char getCode() { - return this.code; - } - } - - public static String parseFirst(String in) { - - if (in == null || in.length() <= 0) { - throw new IllegalArgumentException("No type to parse, input is empty!"); - } - - // read one type from the input - - char c = in.charAt(0); - - // first check for void - if (c == 'V') { - return "V"; - } - - // then check for primitives - Primitive primitive = Primitive.get(c); - if (primitive != null) { - return in.substring(0, 1); - } - - // then check for classes - if (c == 'L') { - return readClass(in); - } - - // then check for templates - if (c == 'T') { - return readClass(in); - } - - // then check for arrays - int dim = countArrayDimension(in); - if (dim > 0) { - String arrayType = Type.parseFirst(in.substring(dim)); - return in.substring(0, dim + arrayType.length()); - } - - throw new IllegalArgumentException("don't know how to parse: " + in); - } - - protected String name; - - public Type(String name) { - - // don't deal with generics - // this is just for raw jvm types - if (name.charAt(0) == 'T' || name.indexOf('<') >= 0 || name.indexOf('>') >= 0) { - throw new IllegalArgumentException("don't use with generic types or templates: " + name); - } - - this.name = name; - } - - public Type(Type other, ClassNameReplacer replacer) { - this.name = other.name; - if (other.isClass()) { - String replacedName = replacer.replace(other.getClassEntry().getClassName()); - if (replacedName != null) { - this.name = "L" + replacedName + ";"; - } - } else if (other.isArray() && other.hasClass()) { - String replacedName = replacer.replace(other.getClassEntry().getClassName()); - if (replacedName != null) { - this.name = Type.getArrayPrefix(other.getArrayDimension()) + "L" + replacedName + ";"; - } - } - } - - @Override - public String toString() { - return this.name; - } - - public boolean isVoid() { - return this.name.length() == 1 && this.name.charAt(0) == 'V'; - } - - public boolean isPrimitive() { - return this.name.length() == 1 && Primitive.get(this.name.charAt(0)) != null; - } - - public Primitive getPrimitive() { - if (!isPrimitive()) { - throw new IllegalStateException("not a primitive"); - } - return Primitive.get(this.name.charAt(0)); - } - - public boolean isClass() { - return this.name.charAt(0) == 'L' && this.name.charAt(this.name.length() - 1) == ';'; - } - - public ClassEntry getClassEntry() { - if (isClass()) { - String name = this.name.substring(1, this.name.length() - 1); - - int pos = name.indexOf('<'); - if (pos >= 0) { - // remove the parameters from the class name - name = name.substring(0, pos); - } - - return new ClassEntry(name); - - } else if (isArray() && getArrayType().isClass()) { - return getArrayType().getClassEntry(); - } else { - throw new IllegalStateException("type doesn't have a class"); - } - } - - public boolean isArray() { - return this.name.charAt(0) == '['; - } - - public int getArrayDimension() { - if (!isArray()) { - throw new IllegalStateException("not an array"); - } - return countArrayDimension(this.name); - } - - public Type getArrayType() { - if (!isArray()) { - throw new IllegalStateException("not an array"); - } - return new Type(this.name.substring(getArrayDimension(), this.name.length())); - } - - private static String getArrayPrefix(int dimension) { - StringBuilder buf = new StringBuilder(); - for (int i = 0; i < dimension; i++) { - buf.append("["); - } - return buf.toString(); - } - - public boolean hasClass() { - return isClass() || (isArray() && getArrayType().hasClass()); - } - - @Override - public boolean equals(Object other) { - return other instanceof Type && equals((Type) other); - } - - public boolean equals(Type other) { - return this.name.equals(other.name); - } - - public int hashCode() { - return this.name.hashCode(); - } - - private static int countArrayDimension(String in) { - int i = 0; - while (i < in.length() && in.charAt(i) == '[') - i++; - return i; - } - - private static String readClass(String in) { - // read all the characters in the buffer until we hit a ';' - // include the parameters too - StringBuilder buf = new StringBuilder(); - int depth = 0; - for (int i = 0; i < in.length(); i++) { - char c = in.charAt(i); - buf.append(c); - - if (c == '<') { - depth++; - } else if (c == '>') { - depth--; - } else if (depth == 0 && c == ';') { - return buf.toString(); - } - } - return null; - } + protected String name; + + public Type(String name) { + + // don't deal with generics + // this is just for raw jvm types + if (name.charAt(0) == 'T' || name.indexOf('<') >= 0 || name.indexOf('>') >= 0) { + throw new IllegalArgumentException("don't use with generic types or templates: " + name); + } + + this.name = name; + } + + public Type(Type other, ClassNameReplacer replacer) { + this.name = other.name; + if (other.isClass()) { + String replacedName = replacer.replace(other.getClassEntry().getClassName()); + if (replacedName != null) { + this.name = "L" + replacedName + ";"; + } + } else if (other.isArray() && other.hasClass()) { + String replacedName = replacer.replace(other.getClassEntry().getClassName()); + if (replacedName != null) { + this.name = Type.getArrayPrefix(other.getArrayDimension()) + "L" + replacedName + ";"; + } + } + } + + public static String parseFirst(String in) { + + if (in == null || in.length() <= 0) { + throw new IllegalArgumentException("No type to parse, input is empty!"); + } + + // read one type from the input + + char c = in.charAt(0); + + // first check for void + if (c == 'V') { + return "V"; + } + + // then check for primitives + Primitive primitive = Primitive.get(c); + if (primitive != null) { + return in.substring(0, 1); + } + + // then check for classes + if (c == 'L') { + return readClass(in); + } + + // then check for templates + if (c == 'T') { + return readClass(in); + } + + // then check for arrays + int dim = countArrayDimension(in); + if (dim > 0) { + String arrayType = Type.parseFirst(in.substring(dim)); + return in.substring(0, dim + arrayType.length()); + } + + throw new IllegalArgumentException("don't know how to parse: " + in); + } + + private static String getArrayPrefix(int dimension) { + StringBuilder buf = new StringBuilder(); + for (int i = 0; i < dimension; i++) { + buf.append("["); + } + return buf.toString(); + } + + private static int countArrayDimension(String in) { + int i = 0; + while (i < in.length() && in.charAt(i) == '[') + i++; + return i; + } + + private static String readClass(String in) { + // read all the characters in the buffer until we hit a ';' + // include the parameters too + StringBuilder buf = new StringBuilder(); + int depth = 0; + for (int i = 0; i < in.length(); i++) { + char c = in.charAt(i); + buf.append(c); + + if (c == '<') { + depth++; + } else if (c == '>') { + depth--; + } else if (depth == 0 && c == ';') { + return buf.toString(); + } + } + return null; + } + + @Override + public String toString() { + return this.name; + } + + public boolean isVoid() { + return this.name.length() == 1 && this.name.charAt(0) == 'V'; + } + + public boolean isPrimitive() { + return this.name.length() == 1 && Primitive.get(this.name.charAt(0)) != null; + } + + public Primitive getPrimitive() { + if (!isPrimitive()) { + throw new IllegalStateException("not a primitive"); + } + return Primitive.get(this.name.charAt(0)); + } + + public boolean isClass() { + return this.name.charAt(0) == 'L' && this.name.charAt(this.name.length() - 1) == ';'; + } + + public ClassEntry getClassEntry() { + if (isClass()) { + String name = this.name.substring(1, this.name.length() - 1); + + int pos = name.indexOf('<'); + if (pos >= 0) { + // remove the parameters from the class name + name = name.substring(0, pos); + } + + return new ClassEntry(name); + + } else if (isArray() && getArrayType().isClass()) { + return getArrayType().getClassEntry(); + } else { + throw new IllegalStateException("type doesn't have a class"); + } + } + + public boolean isArray() { + return this.name.charAt(0) == '['; + } + + public int getArrayDimension() { + if (!isArray()) { + throw new IllegalStateException("not an array"); + } + return countArrayDimension(this.name); + } + + public Type getArrayType() { + if (!isArray()) { + throw new IllegalStateException("not an array"); + } + return new Type(this.name.substring(getArrayDimension(), this.name.length())); + } + + public boolean hasClass() { + return isClass() || (isArray() && getArrayType().hasClass()); + } + + @Override + public boolean equals(Object other) { + return other instanceof Type && equals((Type) other); + } + + public boolean equals(Type other) { + return this.name.equals(other.name); + } + + public int hashCode() { + return this.name.hashCode(); + } + + public enum Primitive { + Byte('B'), + Character('C'), + Short('S'), + Integer('I'), + Long('J'), + Float('F'), + Double('D'), + Boolean('Z'); + + private static final Map lookup; + + static { + lookup = Maps.newTreeMap(); + for (Primitive val : values()) { + lookup.put(val.getCode(), val); + } + } + + private char code; + + Primitive(char code) { + this.code = code; + } + + public static Primitive get(char code) { + return lookup.get(code); + } + + public char getCode() { + return this.code; + } + } } diff --git a/src/main/java/cuchaz/enigma/throwables/IllegalNameException.java b/src/main/java/cuchaz/enigma/throwables/IllegalNameException.java index fa21a9e5..0155ad27 100644 --- a/src/main/java/cuchaz/enigma/throwables/IllegalNameException.java +++ b/src/main/java/cuchaz/enigma/throwables/IllegalNameException.java @@ -8,31 +8,32 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.throwables; public class IllegalNameException extends RuntimeException { - private String name; - private String reason; + private String name; + private String reason; - public IllegalNameException(String name, String reason) { - this.name = name; - this.reason = reason; - } + public IllegalNameException(String name, String reason) { + this.name = name; + this.reason = reason; + } - public String getReason() { - return this.reason; - } + public String getReason() { + return this.reason; + } - @Override - public String getMessage() { - StringBuilder buf = new StringBuilder(); - buf.append("Illegal name: "); - buf.append(this.name); - if (this.reason != null) { - buf.append(" because "); - buf.append(this.reason); - } - return buf.toString(); - } + @Override + public String getMessage() { + StringBuilder buf = new StringBuilder(); + buf.append("Illegal name: "); + buf.append(this.name); + if (this.reason != null) { + buf.append(" because "); + buf.append(this.reason); + } + return buf.toString(); + } } diff --git a/src/main/java/cuchaz/enigma/throwables/MappingConflict.java b/src/main/java/cuchaz/enigma/throwables/MappingConflict.java index 5924f32a..95cd4494 100644 --- a/src/main/java/cuchaz/enigma/throwables/MappingConflict.java +++ b/src/main/java/cuchaz/enigma/throwables/MappingConflict.java @@ -1,7 +1,7 @@ package cuchaz.enigma.throwables; public class MappingConflict extends Exception { - public MappingConflict(String clazz, String name, String nameExisting) { - super(String.format("Conflicting mappings found for %s. The mapping file is %s and the second is %s", clazz, name, nameExisting)); - } + public MappingConflict(String clazz, String name, String nameExisting) { + super(String.format("Conflicting mappings found for %s. The mapping file is %s and the second is %s", clazz, name, nameExisting)); + } } diff --git a/src/main/java/cuchaz/enigma/throwables/MappingParseException.java b/src/main/java/cuchaz/enigma/throwables/MappingParseException.java index d4c66734..cc5f650a 100644 --- a/src/main/java/cuchaz/enigma/throwables/MappingParseException.java +++ b/src/main/java/cuchaz/enigma/throwables/MappingParseException.java @@ -8,24 +8,25 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.throwables; import java.io.File; public class MappingParseException extends Exception { - private int line; - private String message; - private String filePath; + private int line; + private String message; + private String filePath; - public MappingParseException(File file, int line, String message) { - this.line = line; - this.message = message; - filePath = file.getAbsolutePath(); - } + public MappingParseException(File file, int line, String message) { + this.line = line; + this.message = message; + filePath = file.getAbsolutePath(); + } - @Override - public String getMessage() { - return "Line " + line + ": " + message + " in file " + filePath; - } + @Override + public String getMessage() { + return "Line " + line + ": " + message + " in file " + filePath; + } } diff --git a/src/main/java/cuchaz/enigma/utils/ReadableToken.java b/src/main/java/cuchaz/enigma/utils/ReadableToken.java index 008e28cf..de152fe3 100644 --- a/src/main/java/cuchaz/enigma/utils/ReadableToken.java +++ b/src/main/java/cuchaz/enigma/utils/ReadableToken.java @@ -8,22 +8,23 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.utils; public class ReadableToken { - public int line; - public int startColumn; - public int endColumn; + public int line; + public int startColumn; + public int endColumn; - public ReadableToken(int line, int startColumn, int endColumn) { - this.line = line; - this.startColumn = startColumn; - this.endColumn = endColumn; - } + public ReadableToken(int line, int startColumn, int endColumn) { + this.line = line; + this.startColumn = startColumn; + this.endColumn = endColumn; + } - @Override - public String toString() { - return "line " + line + " columns " + startColumn + "-" + endColumn; - } + @Override + public String toString() { + return "line " + line + " columns " + startColumn + "-" + endColumn; + } } diff --git a/src/main/java/cuchaz/enigma/utils/Utils.java b/src/main/java/cuchaz/enigma/utils/Utils.java index 474f7edf..8e502d47 100644 --- a/src/main/java/cuchaz/enigma/utils/Utils.java +++ b/src/main/java/cuchaz/enigma/utils/Utils.java @@ -8,12 +8,13 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.utils; import com.google.common.io.CharStreams; -import java.awt.Desktop; -import java.awt.Font; +import javax.swing.*; +import java.awt.*; import java.awt.event.MouseEvent; import java.io.IOException; import java.io.InputStream; @@ -23,69 +24,66 @@ import java.net.URISyntaxException; import java.util.Arrays; import java.util.List; -import javax.swing.*; - public class Utils { - public static int combineHashesOrdered(Object... objs) { - return combineHashesOrdered(Arrays.asList(objs)); - } + public static int combineHashesOrdered(Object... objs) { + return combineHashesOrdered(Arrays.asList(objs)); + } - public static int combineHashesOrdered(List objs) { - final int prime = 67; - int result = 1; - for (Object obj : objs) { - result *= prime; - if (obj != null) { - result += obj.hashCode(); - } - } - return result; - } + public static int combineHashesOrdered(List objs) { + final int prime = 67; + int result = 1; + for (Object obj : objs) { + result *= prime; + if (obj != null) { + result += obj.hashCode(); + } + } + return result; + } - public static String readStreamToString(InputStream in) throws IOException { - return CharStreams.toString(new InputStreamReader(in, "UTF-8")); - } + public static String readStreamToString(InputStream in) throws IOException { + return CharStreams.toString(new InputStreamReader(in, "UTF-8")); + } - public static String readResourceToString(String path) throws IOException { - InputStream in = Utils.class.getResourceAsStream(path); - if (in == null) { - throw new IllegalArgumentException("Resource not found! " + path); - } - return readStreamToString(in); - } + public static String readResourceToString(String path) throws IOException { + InputStream in = Utils.class.getResourceAsStream(path); + if (in == null) { + throw new IllegalArgumentException("Resource not found! " + path); + } + return readStreamToString(in); + } - public static void openUrl(String url) { - if (Desktop.isDesktopSupported()) { - Desktop desktop = Desktop.getDesktop(); - try { - desktop.browse(new URI(url)); - } catch (IOException ex) { - throw new Error(ex); - } catch (URISyntaxException ex) { - throw new IllegalArgumentException(ex); - } - } - } + public static void openUrl(String url) { + if (Desktop.isDesktopSupported()) { + Desktop desktop = Desktop.getDesktop(); + try { + desktop.browse(new URI(url)); + } catch (IOException ex) { + throw new Error(ex); + } catch (URISyntaxException ex) { + throw new IllegalArgumentException(ex); + } + } + } - public static JLabel unboldLabel(JLabel label) { - Font font = label.getFont(); - label.setFont(font.deriveFont(font.getStyle() & ~Font.BOLD)); - return label; - } + public static JLabel unboldLabel(JLabel label) { + Font font = label.getFont(); + label.setFont(font.deriveFont(font.getStyle() & ~Font.BOLD)); + return label; + } - public static void showToolTipNow(JComponent component) { - // HACKHACK: trick the tooltip manager into showing the tooltip right now - ToolTipManager manager = ToolTipManager.sharedInstance(); - int oldDelay = manager.getInitialDelay(); - manager.setInitialDelay(0); - manager.mouseMoved(new MouseEvent(component, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, 0, 0, 0, false)); - manager.setInitialDelay(oldDelay); - } + public static void showToolTipNow(JComponent component) { + // HACKHACK: trick the tooltip manager into showing the tooltip right now + ToolTipManager manager = ToolTipManager.sharedInstance(); + int oldDelay = manager.getInitialDelay(); + manager.setInitialDelay(0); + manager.mouseMoved(new MouseEvent(component, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, 0, 0, 0, false)); + manager.setInitialDelay(oldDelay); + } - public static boolean getSystemPropertyAsBoolean(String property, boolean defValue) - { - String value = System.getProperty(property); - return value == null ? defValue : Boolean.parseBoolean(value); - } + public static boolean getSystemPropertyAsBoolean(String property, boolean defValue) { + String value = System.getProperty(property); + return value == null ? defValue : Boolean.parseBoolean(value); + } } diff --git a/src/test/java/cuchaz/enigma/TestDeobfed.java b/src/test/java/cuchaz/enigma/TestDeobfed.java index 76a3d3b5..e6c1b746 100644 --- a/src/test/java/cuchaz/enigma/TestDeobfed.java +++ b/src/test/java/cuchaz/enigma/TestDeobfed.java @@ -4,38 +4,36 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ -package cuchaz.enigma; - - -import static cuchaz.enigma.TestEntryFactory.*; -import static org.hamcrest.MatcherAssert.*; -import static org.hamcrest.Matchers.*; -import java.util.jar.JarFile; +package cuchaz.enigma; +import cuchaz.enigma.analysis.JarIndex; import org.junit.BeforeClass; import org.junit.Test; -import cuchaz.enigma.analysis.JarIndex; +import java.util.jar.JarFile; +import static cuchaz.enigma.TestEntryFactory.newClass; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; public class TestDeobfed { - private static JarFile jar; + private static JarFile jar; private static JarIndex index; - + @BeforeClass public static void beforeClass() - throws Exception { + throws Exception { jar = new JarFile("build/test-deobf/translation.jar"); index = new JarIndex(); index.indexJar(jar, true); } - + @Test public void obfEntries() { assertThat(index.getObfClassEntries(), containsInAnyOrder( @@ -64,10 +62,10 @@ public class TestDeobfed { newClass("i$b") )); } - + @Test public void decompile() - throws Exception { + throws Exception { Deobfuscator deobfuscator = new Deobfuscator(jar); deobfuscator.getSourceTree("a"); deobfuscator.getSourceTree("b"); diff --git a/src/test/java/cuchaz/enigma/TestDeobfuscator.java b/src/test/java/cuchaz/enigma/TestDeobfuscator.java index 8c97ff34..62a52861 100644 --- a/src/test/java/cuchaz/enigma/TestDeobfuscator.java +++ b/src/test/java/cuchaz/enigma/TestDeobfuscator.java @@ -4,40 +4,39 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma; -import static org.junit.Assert.*; +import com.google.common.collect.Lists; +import cuchaz.enigma.mapping.ClassEntry; +import org.junit.Test; import java.io.IOException; import java.util.List; import java.util.jar.JarFile; -import org.junit.Test; - -import com.google.common.collect.Lists; - -import cuchaz.enigma.mapping.ClassEntry; +import static org.junit.Assert.assertEquals; public class TestDeobfuscator { - + private Deobfuscator getDeobfuscator() - throws IOException { + throws IOException { return new Deobfuscator(new JarFile("build/test-obf/loneClass.jar")); } - + @Test public void loadJar() - throws Exception { + throws Exception { getDeobfuscator(); } - + @Test public void getClasses() - throws Exception { + throws Exception { Deobfuscator deobfuscator = getDeobfuscator(); List obfClasses = Lists.newArrayList(); List deobfClasses = Lists.newArrayList(); @@ -47,10 +46,10 @@ public class TestDeobfuscator { assertEquals(1, deobfClasses.size()); assertEquals("cuchaz/enigma/inputs/Keep", deobfClasses.get(0).getName()); } - + @Test public void decompileClass() - throws Exception { + throws Exception { Deobfuscator deobfuscator = getDeobfuscator(); deobfuscator.getSource(deobfuscator.getSourceTree("a")); } diff --git a/src/test/java/cuchaz/enigma/TestEntryFactory.java b/src/test/java/cuchaz/enigma/TestEntryFactory.java index 4aa773b6..1c527f53 100644 --- a/src/test/java/cuchaz/enigma/TestEntryFactory.java +++ b/src/test/java/cuchaz/enigma/TestEntryFactory.java @@ -4,64 +4,59 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma; import cuchaz.enigma.analysis.EntryReference; -import cuchaz.enigma.mapping.BehaviorEntry; -import cuchaz.enigma.mapping.ClassEntry; -import cuchaz.enigma.mapping.ConstructorEntry; -import cuchaz.enigma.mapping.FieldEntry; -import cuchaz.enigma.mapping.MethodEntry; -import cuchaz.enigma.mapping.Signature; -import cuchaz.enigma.mapping.Type; +import cuchaz.enigma.mapping.*; public class TestEntryFactory { - + public static ClassEntry newClass(String name) { return new ClassEntry(name); } - + public static FieldEntry newField(String className, String fieldName, String fieldType) { return newField(newClass(className), fieldName, fieldType); } - + public static FieldEntry newField(ClassEntry classEntry, String fieldName, String fieldType) { return new FieldEntry(classEntry, fieldName, new Type(fieldType)); } - + public static MethodEntry newMethod(String className, String methodName, String methodSignature) { return newMethod(newClass(className), methodName, methodSignature); } - + public static MethodEntry newMethod(ClassEntry classEntry, String methodName, String methodSignature) { return new MethodEntry(classEntry, methodName, new Signature(methodSignature)); } - + public static ConstructorEntry newConstructor(String className, String signature) { return newConstructor(newClass(className), signature); } - + public static ConstructorEntry newConstructor(ClassEntry classEntry, String signature) { return new ConstructorEntry(classEntry, new Signature(signature)); } - - public static EntryReference newFieldReferenceByMethod(FieldEntry fieldEntry, String callerClassName, String callerName, String callerSignature) { - return new EntryReference(fieldEntry, "", newMethod(callerClassName, callerName, callerSignature)); + + public static EntryReference newFieldReferenceByMethod(FieldEntry fieldEntry, String callerClassName, String callerName, String callerSignature) { + return new EntryReference(fieldEntry, "", newMethod(callerClassName, callerName, callerSignature)); } - - public static EntryReference newFieldReferenceByConstructor(FieldEntry fieldEntry, String callerClassName, String callerSignature) { - return new EntryReference(fieldEntry, "", newConstructor(callerClassName, callerSignature)); + + public static EntryReference newFieldReferenceByConstructor(FieldEntry fieldEntry, String callerClassName, String callerSignature) { + return new EntryReference(fieldEntry, "", newConstructor(callerClassName, callerSignature)); } - - public static EntryReference newBehaviorReferenceByMethod(BehaviorEntry behaviorEntry, String callerClassName, String callerName, String callerSignature) { - return new EntryReference(behaviorEntry, "", newMethod(callerClassName, callerName, callerSignature)); + + public static EntryReference newBehaviorReferenceByMethod(BehaviorEntry behaviorEntry, String callerClassName, String callerName, String callerSignature) { + return new EntryReference(behaviorEntry, "", newMethod(callerClassName, callerName, callerSignature)); } - - public static EntryReference newBehaviorReferenceByConstructor(BehaviorEntry behaviorEntry, String callerClassName, String callerSignature) { - return new EntryReference(behaviorEntry, "", newConstructor(callerClassName, callerSignature)); + + public static EntryReference newBehaviorReferenceByConstructor(BehaviorEntry behaviorEntry, String callerClassName, String callerSignature) { + return new EntryReference(behaviorEntry, "", newConstructor(callerClassName, callerSignature)); } } diff --git a/src/test/java/cuchaz/enigma/TestInnerClasses.java b/src/test/java/cuchaz/enigma/TestInnerClasses.java index 64a695a9..38db0df9 100644 --- a/src/test/java/cuchaz/enigma/TestInnerClasses.java +++ b/src/test/java/cuchaz/enigma/TestInnerClasses.java @@ -4,29 +4,28 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ -package cuchaz.enigma; - -import static org.hamcrest.MatcherAssert.*; -import static org.hamcrest.Matchers.*; -import java.util.jar.JarFile; +package cuchaz.enigma; +import cuchaz.enigma.analysis.JarIndex; +import cuchaz.enigma.mapping.ClassEntry; import org.junit.Test; -import static cuchaz.enigma.TestEntryFactory.*; +import java.util.jar.JarFile; -import cuchaz.enigma.analysis.JarIndex; -import cuchaz.enigma.mapping.ClassEntry; +import static cuchaz.enigma.TestEntryFactory.newClass; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; public class TestInnerClasses { - - private JarIndex index; - private Deobfuscator deobfuscator; - + private static final ClassEntry AnonymousOuter = newClass("a"); private static final ClassEntry AnonymousInner = newClass("a$1"); private static final ClassEntry SimpleOuter = newClass("d"); @@ -41,15 +40,17 @@ public class TestInnerClasses { private static final ClassEntry ClassTreeLevel1 = newClass("f$a"); private static final ClassEntry ClassTreeLevel2 = newClass("f$a$a"); private static final ClassEntry ClassTreeLevel3 = newClass("f$a$a$a"); - + private JarIndex index; + private Deobfuscator deobfuscator; + public TestInnerClasses() - throws Exception { + throws Exception { index = new JarIndex(); JarFile jar = new JarFile("build/test-obf/innerClasses.jar"); index.indexJar(jar, true); deobfuscator = new Deobfuscator(jar); } - + @Test public void simple() { assertThat(index.getOuterClass(SimpleInner), is(SimpleOuter)); @@ -57,7 +58,7 @@ public class TestInnerClasses { assertThat(index.isAnonymousClass(SimpleInner), is(false)); decompile(SimpleOuter); } - + @Test public void anonymous() { assertThat(index.getOuterClass(AnonymousInner), is(AnonymousOuter)); @@ -65,7 +66,7 @@ public class TestInnerClasses { assertThat(index.isAnonymousClass(AnonymousInner), is(true)); decompile(AnonymousOuter); } - + @Test public void constructorArgs() { assertThat(index.getOuterClass(ConstructorArgsInner), is(ConstructorArgsOuter)); @@ -73,7 +74,7 @@ public class TestInnerClasses { assertThat(index.isAnonymousClass(ConstructorArgsInner), is(false)); decompile(ConstructorArgsOuter); } - + @Test public void anonymousWithScopeArgs() { assertThat(index.getOuterClass(AnonymousWithScopeArgsInner), is(AnonymousWithScopeArgsOuter)); @@ -81,7 +82,7 @@ public class TestInnerClasses { assertThat(index.isAnonymousClass(AnonymousWithScopeArgsInner), is(true)); decompile(AnonymousWithScopeArgsOuter); } - + @Test public void anonymousWithOuterAccess() { assertThat(index.getOuterClass(AnonymousWithOuterAccessInner), is(AnonymousWithOuterAccessOuter)); @@ -89,15 +90,15 @@ public class TestInnerClasses { assertThat(index.isAnonymousClass(AnonymousWithOuterAccessInner), is(true)); decompile(AnonymousWithOuterAccessOuter); } - + @Test public void classTree() { - + // root level assertThat(index.containsObfClass(ClassTreeRoot), is(true)); assertThat(index.getOuterClass(ClassTreeRoot), is(nullValue())); assertThat(index.getInnerClasses(ClassTreeRoot), containsInAnyOrder(ClassTreeLevel1)); - + // level 1 ClassEntry fullClassEntry = new ClassEntry(ClassTreeRoot.getName() + "$" + ClassTreeLevel1.getInnermostClassName() @@ -105,7 +106,7 @@ public class TestInnerClasses { assertThat(index.containsObfClass(fullClassEntry), is(true)); assertThat(index.getOuterClass(ClassTreeLevel1), is(ClassTreeRoot)); assertThat(index.getInnerClasses(ClassTreeLevel1), containsInAnyOrder(ClassTreeLevel2)); - + // level 2 fullClassEntry = new ClassEntry(ClassTreeRoot.getName() + "$" + ClassTreeLevel1.getInnermostClassName() @@ -114,7 +115,7 @@ public class TestInnerClasses { assertThat(index.containsObfClass(fullClassEntry), is(true)); assertThat(index.getOuterClass(ClassTreeLevel2), is(ClassTreeLevel1)); assertThat(index.getInnerClasses(ClassTreeLevel2), containsInAnyOrder(ClassTreeLevel3)); - + // level 3 fullClassEntry = new ClassEntry(ClassTreeRoot.getName() + "$" + ClassTreeLevel1.getInnermostClassName() @@ -125,7 +126,7 @@ public class TestInnerClasses { assertThat(index.getOuterClass(ClassTreeLevel3), is(ClassTreeLevel2)); assertThat(index.getInnerClasses(ClassTreeLevel3), is(empty())); } - + private void decompile(ClassEntry classEntry) { deobfuscator.getSourceTree(classEntry.getName()); } diff --git a/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java b/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java index 01d4bab6..edb859a7 100644 --- a/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java +++ b/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java @@ -4,62 +4,67 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma; -import static cuchaz.enigma.TestEntryFactory.*; -import static org.hamcrest.MatcherAssert.*; -import static org.hamcrest.Matchers.*; +import cuchaz.enigma.analysis.EntryReference; +import cuchaz.enigma.analysis.JarIndex; +import cuchaz.enigma.mapping.BehaviorEntry; +import cuchaz.enigma.mapping.ClassEntry; +import org.junit.Test; import java.io.File; import java.util.Collection; import java.util.jar.JarFile; -import org.junit.Test; - -import cuchaz.enigma.analysis.EntryReference; -import cuchaz.enigma.analysis.JarIndex; -import cuchaz.enigma.mapping.BehaviorEntry; -import cuchaz.enigma.mapping.ClassEntry; +import static cuchaz.enigma.TestEntryFactory.newBehaviorReferenceByConstructor; +import static cuchaz.enigma.TestEntryFactory.newBehaviorReferenceByMethod; +import static cuchaz.enigma.TestEntryFactory.newClass; +import static cuchaz.enigma.TestEntryFactory.newConstructor; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.is; public class TestJarIndexConstructorReferences { - + private JarIndex index; - - private ClassEntry baseClass = newClass("a"); - private ClassEntry subClass = newClass("d"); - private ClassEntry subsubClass = newClass("e"); + + private ClassEntry baseClass = newClass("a"); + private ClassEntry subClass = newClass("d"); + private ClassEntry subsubClass = newClass("e"); private ClassEntry defaultClass = newClass("c"); - private ClassEntry callerClass = newClass("b"); - + private ClassEntry callerClass = newClass("b"); + public TestJarIndexConstructorReferences() - throws Exception { + throws Exception { File jarFile = new File("build/test-obf/constructors.jar"); index = new JarIndex(); index.indexJar(new JarFile(jarFile), false); } - + @Test public void obfEntries() { assertThat(index.getObfClassEntries(), containsInAnyOrder(newClass("cuchaz/enigma/inputs/Keep"), baseClass, - subClass, subsubClass, defaultClass, callerClass)); + subClass, subsubClass, defaultClass, callerClass)); } - + @Test @SuppressWarnings("unchecked") public void baseDefault() { BehaviorEntry source = newConstructor(baseClass, "()V"); - Collection> references = index.getBehaviorReferences(source); + Collection> references = index.getBehaviorReferences(source); assertThat(references, containsInAnyOrder( newBehaviorReferenceByMethod(source, callerClass.getName(), "a", "()V"), newBehaviorReferenceByConstructor(source, subClass.getName(), "()V"), newBehaviorReferenceByConstructor(source, subClass.getName(), "(III)V") )); } - + @Test @SuppressWarnings("unchecked") public void baseInt() { @@ -68,7 +73,7 @@ public class TestJarIndexConstructorReferences { newBehaviorReferenceByMethod(source, callerClass.getName(), "b", "()V") )); } - + @Test @SuppressWarnings("unchecked") public void subDefault() { @@ -78,7 +83,7 @@ public class TestJarIndexConstructorReferences { newBehaviorReferenceByConstructor(source, subClass.getName(), "(I)V") )); } - + @Test @SuppressWarnings("unchecked") public void subInt() { @@ -89,7 +94,7 @@ public class TestJarIndexConstructorReferences { newBehaviorReferenceByConstructor(source, subsubClass.getName(), "(I)V") )); } - + @Test @SuppressWarnings("unchecked") public void subIntInt() { @@ -98,13 +103,13 @@ public class TestJarIndexConstructorReferences { newBehaviorReferenceByMethod(source, callerClass.getName(), "e", "()V") )); } - + @Test public void subIntIntInt() { BehaviorEntry source = newConstructor(subClass, "(III)V"); assertThat(index.getBehaviorReferences(source), is(empty())); } - + @Test @SuppressWarnings("unchecked") public void subsubInt() { @@ -113,7 +118,7 @@ public class TestJarIndexConstructorReferences { newBehaviorReferenceByMethod(source, callerClass.getName(), "f", "()V") )); } - + @Test @SuppressWarnings("unchecked") public void defaultConstructable() { diff --git a/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java b/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java index 4d9c8dc1..62469780 100644 --- a/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java +++ b/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java @@ -4,31 +4,12 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ -package cuchaz.enigma; - -import static cuchaz.enigma.TestEntryFactory.newBehaviorReferenceByConstructor; -import static cuchaz.enigma.TestEntryFactory.newBehaviorReferenceByMethod; -import static cuchaz.enigma.TestEntryFactory.newClass; -import static cuchaz.enigma.TestEntryFactory.newConstructor; -import static cuchaz.enigma.TestEntryFactory.newField; -import static cuchaz.enigma.TestEntryFactory.newFieldReferenceByConstructor; -import static cuchaz.enigma.TestEntryFactory.newFieldReferenceByMethod; -import static cuchaz.enigma.TestEntryFactory.newMethod; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.is; -import java.util.Collection; -import java.util.Set; -import java.util.jar.JarFile; - -import org.junit.Test; +package cuchaz.enigma; import cuchaz.enigma.analysis.Access; import cuchaz.enigma.analysis.EntryReference; @@ -38,70 +19,82 @@ import cuchaz.enigma.mapping.BehaviorEntry; import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.FieldEntry; import cuchaz.enigma.mapping.MethodEntry; +import org.junit.Test; + +import java.util.Collection; +import java.util.Set; +import java.util.jar.JarFile; + +import static cuchaz.enigma.TestEntryFactory.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.is; public class TestJarIndexInheritanceTree { - + private JarIndex index; - - private ClassEntry objectClass = newClass("java/lang/Object"); - private ClassEntry baseClass = newClass("a"); - private ClassEntry subClassA = newClass("b"); - private ClassEntry subClassAA = newClass("d"); - private ClassEntry subClassB = newClass("c"); - private FieldEntry nameField = newField(baseClass, "a", "Ljava/lang/String;"); + + private ClassEntry objectClass = newClass("java/lang/Object"); + private ClassEntry baseClass = newClass("a"); + private ClassEntry subClassA = newClass("b"); + private ClassEntry subClassAA = newClass("d"); + private ClassEntry subClassB = newClass("c"); + private FieldEntry nameField = newField(baseClass, "a", "Ljava/lang/String;"); private FieldEntry numThingsField = newField(subClassB, "a", "I"); - + public TestJarIndexInheritanceTree() - throws Exception { + throws Exception { index = new JarIndex(); index.indexJar(new JarFile("build/test-obf/inheritanceTree.jar"), false); } - + @Test public void obfEntries() { assertThat(index.getObfClassEntries(), containsInAnyOrder( newClass("cuchaz/enigma/inputs/Keep"), baseClass, subClassA, subClassAA, subClassB )); } - + @Test public void translationIndex() { - + TranslationIndex index = this.index.getTranslationIndex(); - + // base class assertThat(index.getSuperclass(baseClass), is(objectClass)); assertThat(index.getAncestry(baseClass), contains(objectClass)); assertThat(index.getSubclass(baseClass), containsInAnyOrder(subClassA, subClassB )); - + // subclass a assertThat(index.getSuperclass(subClassA), is(baseClass)); assertThat(index.getAncestry(subClassA), contains(baseClass, objectClass)); assertThat(index.getSubclass(subClassA), contains(subClassAA)); - + // subclass aa assertThat(index.getSuperclass(subClassAA), is(subClassA)); assertThat(index.getAncestry(subClassAA), contains(subClassA, baseClass, objectClass)); assertThat(index.getSubclass(subClassAA), is(empty())); - + // subclass b assertThat(index.getSuperclass(subClassB), is(baseClass)); assertThat(index.getAncestry(subClassB), contains(baseClass, objectClass)); assertThat(index.getSubclass(subClassB), is(empty())); } - + @Test public void access() { assertThat(index.getAccess(nameField), is(Access.PRIVATE)); assertThat(index.getAccess(numThingsField), is(Access.PRIVATE)); } - + @Test public void relatedMethodImplementations() { - + Set entries; - + // getName() entries = index.getRelatedMethodImplementations(newMethod(baseClass, "a", "()Ljava/lang/String;")); assertThat(entries, containsInAnyOrder( @@ -113,7 +106,7 @@ public class TestJarIndexInheritanceTree { newMethod(baseClass, "a", "()Ljava/lang/String;"), newMethod(subClassAA, "a", "()Ljava/lang/String;") )); - + // doBaseThings() entries = index.getRelatedMethodImplementations(newMethod(baseClass, "a", "()V")); assertThat(entries, containsInAnyOrder( @@ -133,24 +126,24 @@ public class TestJarIndexInheritanceTree { newMethod(subClassAA, "a", "()V"), newMethod(subClassB, "a", "()V") )); - + // doBThings entries = index.getRelatedMethodImplementations(newMethod(subClassB, "b", "()V")); assertThat(entries, containsInAnyOrder(newMethod(subClassB, "b", "()V"))); } - + @Test @SuppressWarnings("unchecked") public void fieldReferences() { - Collection> references; - + Collection> references; + // name references = index.getFieldReferences(nameField); assertThat(references, containsInAnyOrder( newFieldReferenceByConstructor(nameField, baseClass.getName(), "(Ljava/lang/String;)V"), newFieldReferenceByMethod(nameField, baseClass.getName(), "a", "()Ljava/lang/String;") )); - + // numThings references = index.getFieldReferences(numThingsField); assertThat(references, containsInAnyOrder( @@ -158,14 +151,14 @@ public class TestJarIndexInheritanceTree { newFieldReferenceByMethod(numThingsField, subClassB.getName(), "b", "()V") )); } - + @Test @SuppressWarnings("unchecked") public void behaviorReferences() { - + BehaviorEntry source; - Collection> references; - + Collection> references; + // baseClass constructor source = newConstructor(baseClass, "(Ljava/lang/String;)V"); references = index.getBehaviorReferences(source); @@ -173,14 +166,14 @@ public class TestJarIndexInheritanceTree { newBehaviorReferenceByConstructor(source, subClassA.getName(), "(Ljava/lang/String;)V"), newBehaviorReferenceByConstructor(source, subClassB.getName(), "()V") )); - + // subClassA constructor source = newConstructor(subClassA, "(Ljava/lang/String;)V"); references = index.getBehaviorReferences(source); assertThat(references, containsInAnyOrder( newBehaviorReferenceByConstructor(source, subClassAA.getName(), "()V") )); - + // baseClass.getName() source = newMethod(baseClass, "a", "()Ljava/lang/String;"); references = index.getBehaviorReferences(source); @@ -188,7 +181,7 @@ public class TestJarIndexInheritanceTree { newBehaviorReferenceByMethod(source, subClassAA.getName(), "a", "()Ljava/lang/String;"), newBehaviorReferenceByMethod(source, subClassB.getName(), "a", "()V") )); - + // subclassAA.getName() source = newMethod(subClassAA, "a", "()Ljava/lang/String;"); references = index.getBehaviorReferences(source); @@ -196,38 +189,38 @@ public class TestJarIndexInheritanceTree { newBehaviorReferenceByMethod(source, subClassAA.getName(), "a", "()V") )); } - + @Test public void containsEntries() { - + // classes assertThat(index.containsObfClass(baseClass), is(true)); assertThat(index.containsObfClass(subClassA), is(true)); assertThat(index.containsObfClass(subClassAA), is(true)); assertThat(index.containsObfClass(subClassB), is(true)); - + // fields assertThat(index.containsObfField(nameField), is(true)); assertThat(index.containsObfField(numThingsField), is(true)); - + // methods // getName() assertThat(index.containsObfBehavior(newMethod(baseClass, "a", "()Ljava/lang/String;")), is(true)); assertThat(index.containsObfBehavior(newMethod(subClassA, "a", "()Ljava/lang/String;")), is(false)); assertThat(index.containsObfBehavior(newMethod(subClassAA, "a", "()Ljava/lang/String;")), is(true)); assertThat(index.containsObfBehavior(newMethod(subClassB, "a", "()Ljava/lang/String;")), is(false)); - + // doBaseThings() assertThat(index.containsObfBehavior(newMethod(baseClass, "a", "()V")), is(true)); assertThat(index.containsObfBehavior(newMethod(subClassA, "a", "()V")), is(false)); assertThat(index.containsObfBehavior(newMethod(subClassAA, "a", "()V")), is(true)); assertThat(index.containsObfBehavior(newMethod(subClassB, "a", "()V")), is(true)); - + // doBThings() assertThat(index.containsObfBehavior(newMethod(baseClass, "b", "()V")), is(false)); assertThat(index.containsObfBehavior(newMethod(subClassA, "b", "()V")), is(false)); assertThat(index.containsObfBehavior(newMethod(subClassAA, "b", "()V")), is(false)); assertThat(index.containsObfBehavior(newMethod(subClassB, "b", "()V")), is(true)); - + } } diff --git a/src/test/java/cuchaz/enigma/TestJarIndexLoneClass.java b/src/test/java/cuchaz/enigma/TestJarIndexLoneClass.java index 8efa57c6..6cab1c84 100644 --- a/src/test/java/cuchaz/enigma/TestJarIndexLoneClass.java +++ b/src/test/java/cuchaz/enigma/TestJarIndexLoneClass.java @@ -4,44 +4,35 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma; -import static cuchaz.enigma.TestEntryFactory.*; -import static org.hamcrest.MatcherAssert.*; -import static org.hamcrest.Matchers.*; +import cuchaz.enigma.analysis.*; +import cuchaz.enigma.mapping.*; +import org.junit.Test; import java.util.Collection; import java.util.Set; import java.util.jar.JarFile; -import org.junit.Test; - -import cuchaz.enigma.analysis.Access; -import cuchaz.enigma.analysis.ClassImplementationsTreeNode; -import cuchaz.enigma.analysis.ClassInheritanceTreeNode; -import cuchaz.enigma.analysis.EntryReference; -import cuchaz.enigma.analysis.JarIndex; -import cuchaz.enigma.analysis.MethodInheritanceTreeNode; -import cuchaz.enigma.mapping.BehaviorEntry; -import cuchaz.enigma.mapping.ClassEntry; -import cuchaz.enigma.mapping.FieldEntry; -import cuchaz.enigma.mapping.MethodEntry; -import cuchaz.enigma.mapping.Translator; +import static cuchaz.enigma.TestEntryFactory.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; public class TestJarIndexLoneClass { - + private JarIndex index; - + public TestJarIndexLoneClass() - throws Exception { + throws Exception { index = new JarIndex(); index.indexJar(new JarFile("build/test-obf/loneClass.jar"), false); } - + @Test public void obfEntries() { assertThat(index.getObfClassEntries(), containsInAnyOrder( @@ -49,7 +40,7 @@ public class TestJarIndexLoneClass { newClass("a") )); } - + @Test public void translationIndex() { assertThat(index.getTranslationIndex().getSuperclass(new ClassEntry("a")), is(new ClassEntry("java/lang/Object"))); @@ -59,7 +50,7 @@ public class TestJarIndexLoneClass { assertThat(index.getTranslationIndex().getSubclass(new ClassEntry("a")), is(empty())); assertThat(index.getTranslationIndex().getSubclass(new ClassEntry("cuchaz/enigma/inputs/Keep")), is(empty())); } - + @Test public void access() { assertThat(index.getAccess(newField("a", "a", "Ljava/lang/String;")), is(Access.PRIVATE)); @@ -67,7 +58,7 @@ public class TestJarIndexLoneClass { assertThat(index.getAccess(newField("a", "b", "Ljava/lang/String;")), is(nullValue())); assertThat(index.getAccess(newField("a", "a", "LFoo;")), is(nullValue())); } - + @Test public void classInheritance() { ClassInheritanceTreeNode node = index.getClassInheritance(new Translator(), newClass("a")); @@ -75,7 +66,7 @@ public class TestJarIndexLoneClass { assertThat(node.getObfClassName(), is("a")); assertThat(node.getChildCount(), is(0)); } - + @Test public void methodInheritance() { MethodEntry source = newMethod("a", "a", "()Ljava/lang/String;"); @@ -84,19 +75,19 @@ public class TestJarIndexLoneClass { assertThat(node.getMethodEntry(), is(source)); assertThat(node.getChildCount(), is(0)); } - + @Test public void classImplementations() { ClassImplementationsTreeNode node = index.getClassImplementations(new Translator(), newClass("a")); assertThat(node, is(nullValue())); } - + @Test public void methodImplementations() { MethodEntry source = newMethod("a", "a", "()Ljava/lang/String;"); assertThat(index.getMethodImplementations(new Translator(), source), is(empty())); } - + @Test public void relatedMethodImplementations() { Set entries = index.getRelatedMethodImplementations(newMethod("a", "a", "()Ljava/lang/String;")); @@ -104,53 +95,53 @@ public class TestJarIndexLoneClass { newMethod("a", "a", "()Ljava/lang/String;") )); } - + @Test @SuppressWarnings("unchecked") public void fieldReferences() { FieldEntry source = newField("a", "a", "Ljava/lang/String;"); - Collection> references = index.getFieldReferences(source); + Collection> references = index.getFieldReferences(source); assertThat(references, containsInAnyOrder( newFieldReferenceByConstructor(source, "a", "(Ljava/lang/String;)V"), newFieldReferenceByMethod(source, "a", "a", "()Ljava/lang/String;") )); } - + @Test public void behaviorReferences() { assertThat(index.getBehaviorReferences(newMethod("a", "a", "()Ljava/lang/String;")), is(empty())); } - + @Test public void innerClasses() { assertThat(index.getInnerClasses(newClass("a")), is(empty())); } - + @Test public void outerClass() { assertThat(index.getOuterClass(newClass("a")), is(nullValue())); } - + @Test public void isAnonymousClass() { assertThat(index.isAnonymousClass(newClass("a")), is(false)); } - + @Test public void interfaces() { assertThat(index.getInterfaces("a"), is(empty())); } - + @Test public void implementingClasses() { assertThat(index.getImplementingClasses("a"), is(empty())); } - + @Test public void isInterface() { assertThat(index.isInterface("a"), is(false)); } - + @Test public void testContains() { assertThat(index.containsObfClass(newClass("a")), is(true)); diff --git a/src/test/java/cuchaz/enigma/TestSignature.java b/src/test/java/cuchaz/enigma/TestSignature.java index 8537adfb..534b43ae 100644 --- a/src/test/java/cuchaz/enigma/TestSignature.java +++ b/src/test/java/cuchaz/enigma/TestSignature.java @@ -4,31 +4,33 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ -package cuchaz.enigma; - -import static org.hamcrest.MatcherAssert.*; -import static org.hamcrest.Matchers.*; -import org.junit.Test; +package cuchaz.enigma; import cuchaz.enigma.mapping.ClassNameReplacer; import cuchaz.enigma.mapping.Signature; import cuchaz.enigma.mapping.Type; +import org.junit.Test; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; public class TestSignature { - + @Test public void easiest() { final Signature sig = new Signature("()V"); assertThat(sig.getArgumentTypes(), is(empty())); assertThat(sig.getReturnType(), is(new Type("V"))); } - + @Test public void primitives() { { @@ -56,7 +58,7 @@ public class TestSignature { assertThat(sig.getReturnType(), is(new Type("Z"))); } } - + @Test public void classes() { { @@ -82,7 +84,7 @@ public class TestSignature { assertThat(sig.getReturnType(), is(new Type("LBar;"))); } } - + @Test public void arrays() { { @@ -109,7 +111,7 @@ public class TestSignature { assertThat(sig.getReturnType(), is(new Type("[D"))); } } - + @Test public void mixed() { { @@ -131,7 +133,7 @@ public class TestSignature { assertThat(sig.getReturnType(), is(new Type("[LFoo;"))); } } - + @Test public void replaceClasses() { { @@ -195,7 +197,7 @@ public class TestSignature { assertThat(sig.getReturnType(), is(new Type("LCow;"))); } } - + @Test public void replaceArrayClasses() { { @@ -217,13 +219,13 @@ public class TestSignature { assertThat(sig.getReturnType(), is(new Type("[[[LBeer;"))); } } - + @Test public void equals() { - + // base assertThat(new Signature("()V"), is(new Signature("()V"))); - + // arguments assertThat(new Signature("(I)V"), is(new Signature("(I)V"))); assertThat(new Signature("(ZIZ)V"), is(new Signature("(ZIZ)V"))); @@ -238,7 +240,7 @@ public class TestSignature { assertThat(new Signature("([[Z)V"), is(not(new Signature("([[LFoo;)V")))); assertThat(new Signature("(LFoo;LBar;)V"), is(not(new Signature("(LFoo;LCow;)V")))); assertThat(new Signature("([LFoo;LBar;)V"), is(not(new Signature("(LFoo;LCow;)V")))); - + // return type assertThat(new Signature("()I"), is(new Signature("()I"))); assertThat(new Signature("()Z"), is(new Signature("()Z"))); @@ -246,7 +248,7 @@ public class TestSignature { assertThat(new Signature("()[[[Z"), is(new Signature("()[[[Z"))); assertThat(new Signature("()LFoo;"), is(new Signature("()LFoo;"))); assertThat(new Signature("()[LFoo;"), is(new Signature("()[LFoo;"))); - + assertThat(new Signature("()I"), is(not(new Signature("()Z")))); assertThat(new Signature("()Z"), is(not(new Signature("()I")))); assertThat(new Signature("()[D"), is(not(new Signature("()[J")))); @@ -254,7 +256,7 @@ public class TestSignature { assertThat(new Signature("()LFoo;"), is(not(new Signature("()LBar;")))); assertThat(new Signature("()[LFoo;"), is(not(new Signature("()[LBar;")))); } - + @Test public void testToString() { assertThat(new Signature("()V").toString(), is("()V")); diff --git a/src/test/java/cuchaz/enigma/TestSourceIndex.java b/src/test/java/cuchaz/enigma/TestSourceIndex.java index 58d9ca91..6e9e5aec 100644 --- a/src/test/java/cuchaz/enigma/TestSourceIndex.java +++ b/src/test/java/cuchaz/enigma/TestSourceIndex.java @@ -4,27 +4,26 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ -package cuchaz.enigma; - -import java.io.File; -import java.util.Set; -import java.util.jar.JarFile; -import org.junit.Test; +package cuchaz.enigma; import com.google.common.collect.Sets; import com.strobel.decompiler.languages.java.ast.CompilationUnit; - import cuchaz.enigma.mapping.ClassEntry; +import org.junit.Test; + +import java.io.File; +import java.util.Set; +import java.util.jar.JarFile; public class TestSourceIndex { @Test public void indexEverything() - throws Exception { + throws Exception { // Figure out where Minecraft is... final String mcDir = System.getProperty("enigma.test.minecraftdir"); File mcJar = null; @@ -32,20 +31,17 @@ public class TestSourceIndex { String osname = System.getProperty("os.name").toLowerCase(); if (osname.contains("nix") || osname.contains("nux") || osname.contains("solaris")) { mcJar = new File(System.getProperty("user.home"), ".minecraft/versions/1.8.3/1.8.3.jar"); - } - else if (osname.contains("mac") || osname.contains("darwin")) { + } else if (osname.contains("mac") || osname.contains("darwin")) { mcJar = new File(System.getProperty("user.home"), "Library/Application Support/minecraft/versions/1.8.3/1.8.3.jar"); - } - else if (osname.contains("win")) { + } else if (osname.contains("win")) { mcJar = new File(System.getenv("AppData"), ".minecraft/versions/1.8.3/1.8.3.jar"); } - } - else { + } else { mcJar = new File(mcDir, "versions/1.8.3/1.8.3.jar"); } Deobfuscator deobfuscator = new Deobfuscator(new JarFile(mcJar)); - + // get all classes that aren't inner classes Set classEntries = Sets.newHashSet(); for (ClassEntry obfClassEntry : deobfuscator.getJarIndex().getObfClassEntries()) { @@ -53,7 +49,7 @@ public class TestSourceIndex { classEntries.add(obfClassEntry); } } - + for (ClassEntry obfClassEntry : classEntries) { try { CompilationUnit tree = deobfuscator.getSourceTree(obfClassEntry.getName()); diff --git a/src/test/java/cuchaz/enigma/TestTokensConstructors.java b/src/test/java/cuchaz/enigma/TestTokensConstructors.java index 890a4fd3..e40d5fdc 100644 --- a/src/test/java/cuchaz/enigma/TestTokensConstructors.java +++ b/src/test/java/cuchaz/enigma/TestTokensConstructors.java @@ -4,35 +4,40 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma; -import static cuchaz.enigma.TestEntryFactory.*; -import static org.hamcrest.MatcherAssert.*; -import static org.hamcrest.Matchers.*; +import cuchaz.enigma.mapping.BehaviorEntry; +import org.junit.Test; import java.util.jar.JarFile; -import org.junit.Test; - -import cuchaz.enigma.mapping.BehaviorEntry; +import static cuchaz.enigma.TestEntryFactory.newBehaviorReferenceByConstructor; +import static cuchaz.enigma.TestEntryFactory.newBehaviorReferenceByMethod; +import static cuchaz.enigma.TestEntryFactory.newConstructor; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; public class TestTokensConstructors extends TokenChecker { - + public TestTokensConstructors() - throws Exception { + throws Exception { super(new JarFile("build/test-obf/constructors.jar")); } - + @Test public void baseDeclarations() { assertThat(getDeclarationToken(newConstructor("a", "()V")), is("a")); assertThat(getDeclarationToken(newConstructor("a", "(I)V")), is("a")); } - + @Test public void subDeclarations() { assertThat(getDeclarationToken(newConstructor("d", "()V")), is("d")); @@ -40,17 +45,17 @@ public class TestTokensConstructors extends TokenChecker { assertThat(getDeclarationToken(newConstructor("d", "(II)V")), is("d")); assertThat(getDeclarationToken(newConstructor("d", "(III)V")), is("d")); } - + @Test public void subsubDeclarations() { assertThat(getDeclarationToken(newConstructor("e", "(I)V")), is("e")); } - + @Test public void defaultDeclarations() { assertThat(getDeclarationToken(newConstructor("c", "()V")), nullValue()); } - + @Test public void baseDefaultReferences() { BehaviorEntry source = newConstructor("a", "()V"); @@ -67,7 +72,7 @@ public class TestTokensConstructors extends TokenChecker { is(empty()) // implicit call, not decompiled to token ); } - + @Test public void baseIntReferences() { BehaviorEntry source = newConstructor("a", "(I)V"); @@ -76,7 +81,7 @@ public class TestTokensConstructors extends TokenChecker { containsInAnyOrder("a") ); } - + @Test public void subDefaultReferences() { BehaviorEntry source = newConstructor("d", "()V"); @@ -89,7 +94,7 @@ public class TestTokensConstructors extends TokenChecker { containsInAnyOrder("this") ); } - + @Test public void subIntReferences() { BehaviorEntry source = newConstructor("d", "(I)V"); @@ -106,7 +111,7 @@ public class TestTokensConstructors extends TokenChecker { containsInAnyOrder("super") ); } - + @Test public void subIntIntReferences() { BehaviorEntry source = newConstructor("d", "(II)V"); @@ -115,7 +120,7 @@ public class TestTokensConstructors extends TokenChecker { containsInAnyOrder("d") ); } - + @Test public void subsubIntReferences() { BehaviorEntry source = newConstructor("e", "(I)V"); @@ -124,7 +129,7 @@ public class TestTokensConstructors extends TokenChecker { containsInAnyOrder("e") ); } - + @Test public void defaultConstructableReferences() { BehaviorEntry source = newConstructor("c", "()V"); diff --git a/src/test/java/cuchaz/enigma/TestTranslator.java b/src/test/java/cuchaz/enigma/TestTranslator.java index 2c54603b..b63dff86 100644 --- a/src/test/java/cuchaz/enigma/TestTranslator.java +++ b/src/test/java/cuchaz/enigma/TestTranslator.java @@ -8,28 +8,29 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ -package cuchaz.enigma; - -import static cuchaz.enigma.TestEntryFactory.*; -import org.junit.BeforeClass; -import org.junit.Test; +package cuchaz.enigma; import cuchaz.enigma.mapping.Entry; import cuchaz.enigma.mapping.Mappings; import cuchaz.enigma.mapping.Translator; +import org.junit.BeforeClass; +import org.junit.Test; +import static cuchaz.enigma.TestEntryFactory.newClass; +import static cuchaz.enigma.TestEntryFactory.newField; +import static cuchaz.enigma.TestEntryFactory.newMethod; public class TestTranslator { private static Deobfuscator deobfuscator; - private static Mappings mappings; - private static Translator deobfTranslator; - private static Translator obfTranslator; + private static Mappings mappings; + private static Translator deobfTranslator; + private static Translator obfTranslator; @BeforeClass public static void beforeClass() - throws Exception { + throws Exception { //TODO FIx //deobfuscator = new Deobfuscator(new JarFile("build/test-obf/translation.jar")); //try (InputStream in = TestTranslator.class.getResourceAsStream("/cuchaz/enigma/resources/translation.mappings")) { diff --git a/src/test/java/cuchaz/enigma/TestType.java b/src/test/java/cuchaz/enigma/TestType.java index 01c235b8..43dacb0c 100644 --- a/src/test/java/cuchaz/enigma/TestType.java +++ b/src/test/java/cuchaz/enigma/TestType.java @@ -4,23 +4,23 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ -package cuchaz.enigma; - -import static cuchaz.enigma.TestEntryFactory.*; -import static org.hamcrest.MatcherAssert.*; -import static org.hamcrest.Matchers.*; -import org.junit.Test; +package cuchaz.enigma; import cuchaz.enigma.mapping.Type; +import org.junit.Test; +import static cuchaz.enigma.TestEntryFactory.newClass; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; public class TestType { - + @Test public void isVoid() { assertThat(new Type("V").isVoid(), is(true)); @@ -34,7 +34,7 @@ public class TestType { assertThat(new Type("LFoo;").isVoid(), is(false)); assertThat(new Type("[I").isVoid(), is(false)); } - + @Test public void isPrimitive() { assertThat(new Type("V").isPrimitive(), is(false)); @@ -48,7 +48,7 @@ public class TestType { assertThat(new Type("LFoo;").isPrimitive(), is(false)); assertThat(new Type("[I").isPrimitive(), is(false)); } - + @Test public void getPrimitive() { assertThat(new Type("Z").getPrimitive(), is(Type.Primitive.Boolean)); @@ -59,7 +59,7 @@ public class TestType { assertThat(new Type("F").getPrimitive(), is(Type.Primitive.Float)); assertThat(new Type("D").getPrimitive(), is(Type.Primitive.Double)); } - + @Test public void isClass() { assertThat(new Type("V").isClass(), is(false)); @@ -73,19 +73,19 @@ public class TestType { assertThat(new Type("LFoo;").isClass(), is(true)); assertThat(new Type("[I").isClass(), is(false)); } - + @Test public void getClassEntry() { assertThat(new Type("LFoo;").getClassEntry(), is(newClass("Foo"))); assertThat(new Type("Ljava/lang/String;").getClassEntry(), is(newClass("java/lang/String"))); } - + @Test public void getArrayClassEntry() { assertThat(new Type("[LFoo;").getClassEntry(), is(newClass("Foo"))); assertThat(new Type("[[[Ljava/lang/String;").getClassEntry(), is(newClass("java/lang/String"))); } - + @Test public void isArray() { assertThat(new Type("V").isArray(), is(false)); @@ -99,14 +99,14 @@ public class TestType { assertThat(new Type("LFoo;").isArray(), is(false)); assertThat(new Type("[I").isArray(), is(true)); } - + @Test public void getArrayDimension() { assertThat(new Type("[I").getArrayDimension(), is(1)); assertThat(new Type("[[I").getArrayDimension(), is(2)); assertThat(new Type("[[[I").getArrayDimension(), is(3)); } - + @Test public void getArrayType() { assertThat(new Type("[I").getArrayType(), is(new Type("I"))); @@ -114,7 +114,7 @@ public class TestType { assertThat(new Type("[[[I").getArrayType(), is(new Type("I"))); assertThat(new Type("[Ljava/lang/String;").getArrayType(), is(new Type("Ljava/lang/String;"))); } - + @Test public void hasClass() { assertThat(new Type("LFoo;").hasClass(), is(true)); @@ -127,7 +127,7 @@ public class TestType { assertThat(new Type("[[[I").hasClass(), is(false)); assertThat(new Type("Z").hasClass(), is(false)); } - + @Test public void parseVoid() { final String answer = "V"; @@ -138,7 +138,7 @@ public class TestType { assertThat(Type.parseFirst("VLFoo;"), is(answer)); assertThat(Type.parseFirst("V[LFoo;"), is(answer)); } - + @Test public void parsePrimitive() { final String answer = "I"; @@ -149,7 +149,7 @@ public class TestType { assertThat(Type.parseFirst("ILFoo;"), is(answer)); assertThat(Type.parseFirst("I[LFoo;"), is(answer)); } - + @Test public void parseClass() { { @@ -199,7 +199,7 @@ public class TestType { assertThat(Type.parseFirst("[LFoo;LFoo;"), is(answer)); } } - + @Test public void equals() { assertThat(new Type("V"), is(new Type("V"))); @@ -214,7 +214,7 @@ public class TestType { assertThat(new Type("[I"), is(new Type("[I"))); assertThat(new Type("[[[I"), is(new Type("[[[I"))); assertThat(new Type("[LFoo;"), is(new Type("[LFoo;"))); - + assertThat(new Type("V"), is(not(new Type("I")))); assertThat(new Type("I"), is(not(new Type("J")))); assertThat(new Type("I"), is(not(new Type("LBar;")))); @@ -224,7 +224,7 @@ public class TestType { assertThat(new Type("[[[I"), is(not(new Type("[I")))); assertThat(new Type("[LFoo;"), is(not(new Type("[LBar;")))); } - + @Test public void testToString() { assertThat(new Type("V").toString(), is("V")); diff --git a/src/test/java/cuchaz/enigma/TokenChecker.java b/src/test/java/cuchaz/enigma/TokenChecker.java index 07463206..c6ced488 100644 --- a/src/test/java/cuchaz/enigma/TokenChecker.java +++ b/src/test/java/cuchaz/enigma/TokenChecker.java @@ -4,34 +4,34 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ -package cuchaz.enigma; -import java.io.IOException; -import java.util.Collection; -import java.util.List; -import java.util.jar.JarFile; +package cuchaz.enigma; import com.google.common.collect.Lists; import com.strobel.decompiler.languages.java.ast.CompilationUnit; - import cuchaz.enigma.analysis.EntryReference; import cuchaz.enigma.analysis.SourceIndex; import cuchaz.enigma.analysis.Token; import cuchaz.enigma.mapping.Entry; +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.jar.JarFile; + public class TokenChecker { - + private Deobfuscator deobfuscator; - + protected TokenChecker(JarFile jarFile) - throws IOException { + throws IOException { deobfuscator = new Deobfuscator(jarFile); } - + protected String getDeclarationToken(Entry entry) { // decompile the class CompilationUnit tree = deobfuscator.getSourceTree(entry.getClassName()); @@ -39,7 +39,7 @@ public class TokenChecker { // tree.acceptVisitor( new TreeDumpVisitor( new File( "tree." + entry.getClassName().replace( '/', '.' ) + ".txt" ) ), null ); String source = deobfuscator.getSource(tree); SourceIndex index = deobfuscator.getSourceIndex(tree, source); - + // get the token value Token token = index.getDeclarationToken(entry); if (token == null) { @@ -47,17 +47,17 @@ public class TokenChecker { } return source.substring(token.start, token.end); } - + @SuppressWarnings("unchecked") - protected Collection getReferenceTokens(EntryReference reference) { + protected Collection getReferenceTokens(EntryReference reference) { // decompile the class CompilationUnit tree = deobfuscator.getSourceTree(reference.context.getClassName()); String source = deobfuscator.getSource(tree); SourceIndex index = deobfuscator.getSourceIndex(tree, source); - + // get the token values List values = Lists.newArrayList(); - for (Token token : index.getReferenceTokens((EntryReference)reference)) { + for (Token token : index.getReferenceTokens((EntryReference) reference)) { values.add(source.substring(token.start, token.end)); } return values; diff --git a/src/test/java/cuchaz/enigma/inputs/Keep.java b/src/test/java/cuchaz/enigma/inputs/Keep.java index f04875f5..4dbe8e2f 100644 --- a/src/test/java/cuchaz/enigma/inputs/Keep.java +++ b/src/test/java/cuchaz/enigma/inputs/Keep.java @@ -4,10 +4,11 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.inputs; public class Keep { diff --git a/src/test/java/cuchaz/enigma/inputs/constructors/BaseClass.java b/src/test/java/cuchaz/enigma/inputs/constructors/BaseClass.java index ad5e950e..f07e1f8b 100644 --- a/src/test/java/cuchaz/enigma/inputs/constructors/BaseClass.java +++ b/src/test/java/cuchaz/enigma/inputs/constructors/BaseClass.java @@ -4,20 +4,21 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.inputs.constructors; // a public class BaseClass { - + // ()V public BaseClass() { System.out.println("Default constructor"); } - + // (I)V public BaseClass(int i) { System.out.println("Int constructor " + i); diff --git a/src/test/java/cuchaz/enigma/inputs/constructors/Caller.java b/src/test/java/cuchaz/enigma/inputs/constructors/Caller.java index dcd96173..71439fd1 100644 --- a/src/test/java/cuchaz/enigma/inputs/constructors/Caller.java +++ b/src/test/java/cuchaz/enigma/inputs/constructors/Caller.java @@ -4,51 +4,52 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.inputs.constructors; // b public class Caller { - + // a()V public void callBaseDefault() { // a.()V System.out.println(new BaseClass()); } - + // b()V public void callBaseInt() { // a.(I)V System.out.println(new BaseClass(5)); } - + // c()V public void callSubDefault() { // d.()V System.out.println(new SubClass()); } - + // d()V public void callSubInt() { // d.(I)V System.out.println(new SubClass(6)); } - + // e()V public void callSubIntInt() { // d.(II)V System.out.println(new SubClass(4, 2)); } - + // f()V public void callSubSubInt() { // e.(I)V System.out.println(new SubSubClass(3)); } - + // g()V public void callDefaultConstructable() { // c.()V diff --git a/src/test/java/cuchaz/enigma/inputs/constructors/DefaultConstructable.java b/src/test/java/cuchaz/enigma/inputs/constructors/DefaultConstructable.java index 655f4da3..c3d41705 100644 --- a/src/test/java/cuchaz/enigma/inputs/constructors/DefaultConstructable.java +++ b/src/test/java/cuchaz/enigma/inputs/constructors/DefaultConstructable.java @@ -4,10 +4,11 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.inputs.constructors; public class DefaultConstructable { diff --git a/src/test/java/cuchaz/enigma/inputs/constructors/SubClass.java b/src/test/java/cuchaz/enigma/inputs/constructors/SubClass.java index b2934a27..bc56b3b2 100644 --- a/src/test/java/cuchaz/enigma/inputs/constructors/SubClass.java +++ b/src/test/java/cuchaz/enigma/inputs/constructors/SubClass.java @@ -4,33 +4,34 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.inputs.constructors; // d extends a public class SubClass extends BaseClass { - + // ()V public SubClass() { // a.()V } - + // (I)V public SubClass(int num) { // ()V this(); System.out.println("SubClass " + num); } - + // (II)V public SubClass(int a, int b) { // (I)V this(a + b); } - + // (III)V public SubClass(int a, int b, int c) { // a.()V diff --git a/src/test/java/cuchaz/enigma/inputs/constructors/SubSubClass.java b/src/test/java/cuchaz/enigma/inputs/constructors/SubSubClass.java index c1725fea..87b69d32 100644 --- a/src/test/java/cuchaz/enigma/inputs/constructors/SubSubClass.java +++ b/src/test/java/cuchaz/enigma/inputs/constructors/SubSubClass.java @@ -4,15 +4,16 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.inputs.constructors; // e extends d public class SubSubClass extends SubClass { - + // (I)V public SubSubClass(int i) { // c.(I)V diff --git a/src/test/java/cuchaz/enigma/inputs/inheritanceTree/BaseClass.java b/src/test/java/cuchaz/enigma/inputs/inheritanceTree/BaseClass.java index 1b1f3694..b9c4929c 100644 --- a/src/test/java/cuchaz/enigma/inputs/inheritanceTree/BaseClass.java +++ b/src/test/java/cuchaz/enigma/inputs/inheritanceTree/BaseClass.java @@ -4,28 +4,29 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.inputs.inheritanceTree; // a public abstract class BaseClass { - + // a private String name; - + // (Ljava/lang/String;)V protected BaseClass(String name) { this.name = name; } - + // a()Ljava/lang/String; public String getName() { return name; } - + // a()V public abstract void doBaseThings(); } diff --git a/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassA.java b/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassA.java index d0213a37..50e963c0 100644 --- a/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassA.java +++ b/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassA.java @@ -4,15 +4,16 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.inputs.inheritanceTree; // b extends a public abstract class SubclassA extends BaseClass { - + // (Ljava/lang/String;)V protected SubclassA(String name) { // call to a.(Ljava/lang/String)V diff --git a/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassB.java b/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassB.java index 6d3b0d0f..d0dd664d 100644 --- a/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassB.java +++ b/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassB.java @@ -4,34 +4,35 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.inputs.inheritanceTree; // c extends a public class SubclassB extends BaseClass { - + // a private int numThings; - + // ()V protected SubclassB() { // a.(Ljava/lang/String;)V super("B"); - + // access to a numThings = 4; } - + @Override // a()V public void doBaseThings() { // call to a.a()Ljava/lang/String; System.out.println("Base things by B! " + getName()); } - + // b()V public void doBThings() { // access to a diff --git a/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubsubclassAA.java b/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubsubclassAA.java index a5b25fd5..c5845702 100644 --- a/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubsubclassAA.java +++ b/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubsubclassAA.java @@ -4,27 +4,28 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.inputs.inheritanceTree; // d extends b public class SubsubclassAA extends SubclassA { - + protected SubsubclassAA() { // call to b.(Ljava/lang/String;)V super("AA"); } - + @Override // a()Ljava/lang/String; public String getName() { // call to b.a()Ljava/lang/String; return "subsub" + super.getName(); } - + @Override // a()V public void doBaseThings() { diff --git a/src/test/java/cuchaz/enigma/inputs/innerClasses/A_Anonymous.java b/src/test/java/cuchaz/enigma/inputs/innerClasses/A_Anonymous.java index f6444396..f652d875 100644 --- a/src/test/java/cuchaz/enigma/inputs/innerClasses/A_Anonymous.java +++ b/src/test/java/cuchaz/enigma/inputs/innerClasses/A_Anonymous.java @@ -4,14 +4,15 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.inputs.innerClasses; public class A_Anonymous { - + public void foo() { Runnable runnable = new Runnable() { @Override diff --git a/src/test/java/cuchaz/enigma/inputs/innerClasses/B_AnonymousWithScopeArgs.java b/src/test/java/cuchaz/enigma/inputs/innerClasses/B_AnonymousWithScopeArgs.java index d78be847..d1b7601f 100644 --- a/src/test/java/cuchaz/enigma/inputs/innerClasses/B_AnonymousWithScopeArgs.java +++ b/src/test/java/cuchaz/enigma/inputs/innerClasses/B_AnonymousWithScopeArgs.java @@ -4,14 +4,15 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.inputs.innerClasses; public class B_AnonymousWithScopeArgs { - + public static void foo(final D_Simple arg) { System.out.println(new Object() { @Override diff --git a/src/test/java/cuchaz/enigma/inputs/innerClasses/C_ConstructorArgs.java b/src/test/java/cuchaz/enigma/inputs/innerClasses/C_ConstructorArgs.java index eb03489d..94061faa 100644 --- a/src/test/java/cuchaz/enigma/inputs/innerClasses/C_ConstructorArgs.java +++ b/src/test/java/cuchaz/enigma/inputs/innerClasses/C_ConstructorArgs.java @@ -4,27 +4,28 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.inputs.innerClasses; @SuppressWarnings("unused") public class C_ConstructorArgs { - + + Inner i; + + public void foo() { + i = new Inner(5); + } + class Inner { - + private int a; - + public Inner(int a) { this.a = a; } } - - Inner i; - - public void foo() { - i = new Inner(5); - } } diff --git a/src/test/java/cuchaz/enigma/inputs/innerClasses/D_Simple.java b/src/test/java/cuchaz/enigma/inputs/innerClasses/D_Simple.java index 0e9bf827..71b3a6d8 100644 --- a/src/test/java/cuchaz/enigma/inputs/innerClasses/D_Simple.java +++ b/src/test/java/cuchaz/enigma/inputs/innerClasses/D_Simple.java @@ -4,14 +4,15 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.inputs.innerClasses; public class D_Simple { - + class Inner { // nothing to do } diff --git a/src/test/java/cuchaz/enigma/inputs/innerClasses/E_AnonymousWithOuterAccess.java b/src/test/java/cuchaz/enigma/inputs/innerClasses/E_AnonymousWithOuterAccess.java index 255434d1..976ec426 100644 --- a/src/test/java/cuchaz/enigma/inputs/innerClasses/E_AnonymousWithOuterAccess.java +++ b/src/test/java/cuchaz/enigma/inputs/innerClasses/E_AnonymousWithOuterAccess.java @@ -4,17 +4,18 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.inputs.innerClasses; public class E_AnonymousWithOuterAccess { - + // reproduction of error case documented at: // https://bitbucket.org/cuchaz/enigma/issue/61/stackoverflowerror-when-deobfuscating - + public Object makeInner() { outerMethod(); return new Object() { @@ -24,7 +25,7 @@ public class E_AnonymousWithOuterAccess { } }; } - + private String outerMethod() { return "foo"; } diff --git a/src/test/java/cuchaz/enigma/inputs/innerClasses/F_ClassTree.java b/src/test/java/cuchaz/enigma/inputs/innerClasses/F_ClassTree.java index 7d1dab41..b1de3c9a 100644 --- a/src/test/java/cuchaz/enigma/inputs/innerClasses/F_ClassTree.java +++ b/src/test/java/cuchaz/enigma/inputs/innerClasses/F_ClassTree.java @@ -4,25 +4,25 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ -package cuchaz.enigma.inputs.innerClasses; +package cuchaz.enigma.inputs.innerClasses; public class F_ClassTree { - + public class Level1 { - + public int f1; - + public class Level2 { - + public int f2; - + public class Level3 { - + public int f3; } } diff --git a/src/test/java/cuchaz/enigma/inputs/loneClass/LoneClass.java b/src/test/java/cuchaz/enigma/inputs/loneClass/LoneClass.java index d28ae97c..ddc4e319 100644 --- a/src/test/java/cuchaz/enigma/inputs/loneClass/LoneClass.java +++ b/src/test/java/cuchaz/enigma/inputs/loneClass/LoneClass.java @@ -4,20 +4,21 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.inputs.loneClass; public class LoneClass { - + private String name; - + public LoneClass(String name) { this.name = name; } - + public String getName() { return name; } diff --git a/src/test/java/cuchaz/enigma/inputs/translation/A_Basic.java b/src/test/java/cuchaz/enigma/inputs/translation/A_Basic.java index 26acac8a..26f3718c 100644 --- a/src/test/java/cuchaz/enigma/inputs/translation/A_Basic.java +++ b/src/test/java/cuchaz/enigma/inputs/translation/A_Basic.java @@ -4,28 +4,29 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.inputs.translation; public class A_Basic { - + public int one; public float two; public String three; - + public void m1() { } - + public int m2() { return 42; } - + public void m3(int a1) { } - + public int m4(int a1) { return 5; // chosen by fair die roll, guaranteed to be random } diff --git a/src/test/java/cuchaz/enigma/inputs/translation/B_BaseClass.java b/src/test/java/cuchaz/enigma/inputs/translation/B_BaseClass.java index 035e3299..fd7f6e7e 100644 --- a/src/test/java/cuchaz/enigma/inputs/translation/B_BaseClass.java +++ b/src/test/java/cuchaz/enigma/inputs/translation/B_BaseClass.java @@ -4,21 +4,22 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.inputs.translation; public class B_BaseClass { - + public int f1; public char f2; - + public int m1() { return 5; } - + public int m2() { return 42; } diff --git a/src/test/java/cuchaz/enigma/inputs/translation/C_SubClass.java b/src/test/java/cuchaz/enigma/inputs/translation/C_SubClass.java index 6026a8d5..9d74e443 100644 --- a/src/test/java/cuchaz/enigma/inputs/translation/C_SubClass.java +++ b/src/test/java/cuchaz/enigma/inputs/translation/C_SubClass.java @@ -4,23 +4,24 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.inputs.translation; public class C_SubClass extends B_BaseClass { - + public char f2; // shadows B_BaseClass.f2 public int f3; public int f4; - + @Override public int m1() { return 32; } - + public int m3() { return 7; } diff --git a/src/test/java/cuchaz/enigma/inputs/translation/D_AnonymousTesting.java b/src/test/java/cuchaz/enigma/inputs/translation/D_AnonymousTesting.java index a1827f98..99c83bbf 100644 --- a/src/test/java/cuchaz/enigma/inputs/translation/D_AnonymousTesting.java +++ b/src/test/java/cuchaz/enigma/inputs/translation/D_AnonymousTesting.java @@ -4,17 +4,18 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.inputs.translation; import java.util.ArrayList; import java.util.List; public class D_AnonymousTesting { - + public List getObjs() { List objs = new ArrayList(); objs.add(new Object() { diff --git a/src/test/java/cuchaz/enigma/inputs/translation/E_Bridges.java b/src/test/java/cuchaz/enigma/inputs/translation/E_Bridges.java index 769eb70e..0b8cf2a5 100644 --- a/src/test/java/cuchaz/enigma/inputs/translation/E_Bridges.java +++ b/src/test/java/cuchaz/enigma/inputs/translation/E_Bridges.java @@ -4,15 +4,15 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.inputs.translation; import java.util.Iterator; - public class E_Bridges implements Iterator { @Override diff --git a/src/test/java/cuchaz/enigma/inputs/translation/F_ObjectMethods.java b/src/test/java/cuchaz/enigma/inputs/translation/F_ObjectMethods.java index 845d62b0..8a92792a 100644 --- a/src/test/java/cuchaz/enigma/inputs/translation/F_ObjectMethods.java +++ b/src/test/java/cuchaz/enigma/inputs/translation/F_ObjectMethods.java @@ -4,17 +4,18 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.inputs.translation; @SuppressWarnings("FinalizeCalledExplicitly") public class F_ObjectMethods { - + public void callEmAll() - throws Throwable { + throws Throwable { clone(); equals(this); finalize(); diff --git a/src/test/java/cuchaz/enigma/inputs/translation/G_OuterClass.java b/src/test/java/cuchaz/enigma/inputs/translation/G_OuterClass.java index a2e0dafb..a1e6a85c 100644 --- a/src/test/java/cuchaz/enigma/inputs/translation/G_OuterClass.java +++ b/src/test/java/cuchaz/enigma/inputs/translation/G_OuterClass.java @@ -4,30 +4,30 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ -package cuchaz.enigma.inputs.translation; +package cuchaz.enigma.inputs.translation; public class G_OuterClass { - + public class A_InnerClass { - + public int f1; public String f2; - + public void m1() {} - + public class A_InnerInnerClass { - + public int f3; - + public void m2() {} } } - + public class B_NamelessClass { public class A_NamedInnerClass { public int f4; diff --git a/src/test/java/cuchaz/enigma/inputs/translation/H_NamelessClass.java b/src/test/java/cuchaz/enigma/inputs/translation/H_NamelessClass.java index 1b718a54..013c55ae 100644 --- a/src/test/java/cuchaz/enigma/inputs/translation/H_NamelessClass.java +++ b/src/test/java/cuchaz/enigma/inputs/translation/H_NamelessClass.java @@ -4,34 +4,36 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ -package cuchaz.enigma.inputs.translation; +package cuchaz.enigma.inputs.translation; public class H_NamelessClass { - + public class A_InnerClass { - + public int f1; public String f2; - + public void m1() {} - + public class A_InnerInnerClass { - + public int f3; - + public void m2() {} } } - + public class B_NamelessClass { public class A_NamedInnerClass { public int f4; + public class A_AnotherInnerClass {} + public class B_YetAnotherInnerClass {} } } diff --git a/src/test/java/cuchaz/enigma/inputs/translation/I_Generics.java b/src/test/java/cuchaz/enigma/inputs/translation/I_Generics.java index 3490f9d9..fd2ebdd5 100644 --- a/src/test/java/cuchaz/enigma/inputs/translation/I_Generics.java +++ b/src/test/java/cuchaz/enigma/inputs/translation/I_Generics.java @@ -4,32 +4,32 @@ * are made available under the terms of the GNU Lesser General Public * License v3.0 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html - * + * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.inputs.translation; import java.util.List; import java.util.Map; - public class I_Generics { - - public class A_Type { - } - + public List f1; public List f2; - public Map f3; - + public Map f3; + public B_Generic f5; + public B_Generic f6; + + public class A_Type { + } + public class B_Generic { public T f4; + public T m1() { return null; } } - - public B_Generic f5; - public B_Generic f6; } -- cgit v1.2.3