From 00fcd0550fcdda621c2e4662f6ddd55ce673b931 Mon Sep 17 00:00:00 2001 From: Gegy Date: Thu, 24 Jan 2019 14:48:32 +0200 Subject: [WIP] Mapping rework (#91) * Move packages * Mapping & entry refactor: first pass * Fix deobf -> obf tree remapping * Resolve various issues * Give all entries the potential for parents and treat inner classes as children * Deobf UI tree elements * Tests pass * Sort mapping output * Fix delta tracking * Index separation and first pass for #97 * Keep track of remapped jar index * Fix child entries not being remapped * Drop non-root entries * Track dropped mappings * Fix enigma mapping ordering * EntryTreeNode interface * Small tweaks * Naive full index remap on rename * Entries can resolve to more than one root entry * Support alternative resolution strategies * Bridge method resolution * Tests pass * Fix mappings being used where there are none * Fix methods with different descriptors being considered unique. closes #89 --- src/main/java/cuchaz/enigma/CommandMain.java | 57 +- src/main/java/cuchaz/enigma/Deobfuscator.java | 483 ++++------------ .../java/cuchaz/enigma/ITranslatingTypeLoader.java | 2 +- src/main/java/cuchaz/enigma/Main.java | 13 +- src/main/java/cuchaz/enigma/ProgressListener.java | 17 + .../java/cuchaz/enigma/SynchronizedTypeLoader.java | 2 +- .../java/cuchaz/enigma/TranslatingTypeLoader.java | 55 +- src/main/java/cuchaz/enigma/analysis/Access.java | 2 +- .../analysis/ClassImplementationsTreeNode.java | 35 +- .../enigma/analysis/ClassInheritanceTreeNode.java | 32 +- .../enigma/analysis/ClassReferenceTreeNode.java | 30 +- .../cuchaz/enigma/analysis/EntryReference.java | 34 +- .../java/cuchaz/enigma/analysis/EntryRenamer.java | 167 ------ .../enigma/analysis/FieldReferenceTreeNode.java | 39 +- .../cuchaz/enigma/analysis/IndexClassVisitor.java | 43 -- .../enigma/analysis/IndexInnerClassVisitor.java | 29 - .../enigma/analysis/IndexReferenceVisitor.java | 77 --- .../cuchaz/enigma/analysis/IndexTreeBuilder.java | 87 +++ src/main/java/cuchaz/enigma/analysis/JarIndex.java | 583 ------------------- .../analysis/MethodImplementationsTreeNode.java | 48 +- .../enigma/analysis/MethodInheritanceTreeNode.java | 46 +- .../enigma/analysis/MethodReferenceTreeNode.java | 67 ++- .../java/cuchaz/enigma/analysis/ParsedJar.java | 10 +- .../cuchaz/enigma/analysis/ReferenceTreeNode.java | 4 +- .../java/cuchaz/enigma/analysis/SourceIndex.java | 53 +- .../enigma/analysis/SourceIndexClassVisitor.java | 15 +- .../enigma/analysis/SourceIndexMethodVisitor.java | 19 +- .../cuchaz/enigma/analysis/SourceIndexVisitor.java | 8 +- .../cuchaz/enigma/analysis/TranslationIndex.java | 275 --------- .../enigma/analysis/index/BridgeMethodIndex.java | 77 +++ .../cuchaz/enigma/analysis/index/EntryIndex.java | 109 ++++ .../enigma/analysis/index/IndexClassVisitor.java | 40 ++ .../analysis/index/IndexReferenceVisitor.java | 83 +++ .../enigma/analysis/index/InheritanceIndex.java | 97 ++++ .../cuchaz/enigma/analysis/index/JarIndex.java | 165 ++++++ .../cuchaz/enigma/analysis/index/JarIndexer.java | 24 + .../enigma/analysis/index/ReferenceIndex.java | 83 +++ .../enigma/analysis/index/RemappableIndex.java | 9 + .../java/cuchaz/enigma/bytecode/AccessFlags.java | 105 ---- .../bytecode/translators/AsmObjectTranslator.java | 46 ++ .../translators/TranslationAnnotationVisitor.java | 20 +- .../translators/TranslationClassVisitor.java | 74 ++- .../translators/TranslationFieldVisitor.java | 10 +- .../translators/TranslationMethodVisitor.java | 55 +- src/main/java/cuchaz/enigma/gui/ClassSelector.java | 5 +- src/main/java/cuchaz/enigma/gui/CodeReader.java | 57 +- src/main/java/cuchaz/enigma/gui/Gui.java | 80 +-- src/main/java/cuchaz/enigma/gui/GuiController.java | 209 +++---- src/main/java/cuchaz/enigma/gui/GuiTricks.java | 42 -- .../java/cuchaz/enigma/gui/ScoredClassEntry.java | 44 -- .../cuchaz/enigma/gui/dialog/ProgressDialog.java | 4 +- .../java/cuchaz/enigma/gui/elements/MenuBar.java | 45 +- .../enigma/gui/node/ClassSelectorClassNode.java | 2 +- .../java/cuchaz/enigma/gui/panels/PanelObf.java | 6 +- .../java/cuchaz/enigma/mapping/ClassMapping.java | 627 --------------------- .../enigma/mapping/DirectionalTranslator.java | 371 ------------ .../java/cuchaz/enigma/mapping/FieldMapping.java | 100 ---- .../enigma/mapping/LocalVariableMapping.java | 58 -- src/main/java/cuchaz/enigma/mapping/Mappings.java | 268 --------- .../cuchaz/enigma/mapping/MappingsChecker.java | 101 ---- .../enigma/mapping/MappingsEnigmaReader.java | 186 ------ .../enigma/mapping/MappingsEnigmaWriter.java | 160 ------ .../cuchaz/enigma/mapping/MappingsRenamer.java | 365 ------------ .../cuchaz/enigma/mapping/MappingsSRGWriter.java | 80 --- .../cuchaz/enigma/mapping/MappingsTinyReader.java | 130 ----- .../java/cuchaz/enigma/mapping/MemberMapping.java | 21 - .../cuchaz/enigma/mapping/MethodDescriptor.java | 114 ---- .../java/cuchaz/enigma/mapping/MethodMapping.java | 210 ------- .../java/cuchaz/enigma/mapping/NameValidator.java | 73 --- src/main/java/cuchaz/enigma/mapping/Signature.java | 82 --- .../cuchaz/enigma/mapping/SignatureUpdater.java | 92 --- .../enigma/mapping/TranslationDirection.java | 36 -- .../java/cuchaz/enigma/mapping/Translator.java | 109 ---- .../java/cuchaz/enigma/mapping/TypeDescriptor.java | 258 --------- .../cuchaz/enigma/mapping/entry/ClassDefEntry.java | 38 -- .../cuchaz/enigma/mapping/entry/ClassEntry.java | 175 ------ .../java/cuchaz/enigma/mapping/entry/DefEntry.java | 7 - .../java/cuchaz/enigma/mapping/entry/Entry.java | 22 - .../cuchaz/enigma/mapping/entry/EntryFactory.java | 49 -- .../cuchaz/enigma/mapping/entry/FieldDefEntry.java | 44 -- .../cuchaz/enigma/mapping/entry/FieldEntry.java | 77 --- .../mapping/entry/LocalVariableDefEntry.java | 61 -- .../enigma/mapping/entry/LocalVariableEntry.java | 93 --- .../enigma/mapping/entry/MethodDefEntry.java | 61 -- .../cuchaz/enigma/mapping/entry/MethodEntry.java | 80 --- .../enigma/mapping/entry/ProcyonEntryFactory.java | 48 -- .../enigma/mapping/entry/ReferencedEntryPool.java | 59 -- .../enigma/translation/MappingTranslator.java | 24 + .../enigma/translation/SignatureUpdater.java | 92 +++ .../cuchaz/enigma/translation/Translatable.java | 9 + .../enigma/translation/TranslationDirection.java | 36 ++ .../java/cuchaz/enigma/translation/Translator.java | 54 ++ .../cuchaz/enigma/translation/VoidTranslator.java | 10 + .../enigma/translation/mapping/AccessModifier.java | 25 + .../enigma/translation/mapping/EntryMap.java | 24 + .../enigma/translation/mapping/EntryMapping.java | 30 + .../enigma/translation/mapping/EntryRemapper.java | 201 +++++++ .../enigma/translation/mapping/EntryResolver.java | 41 ++ .../translation/mapping/IndexEntryResolver.java | 225 ++++++++ .../enigma/translation/mapping/MappingDelta.java | 56 ++ .../enigma/translation/mapping/MappingPair.java | 28 + .../translation/mapping/MappingValidator.java | 45 ++ .../translation/mapping/MappingsChecker.java | 91 +++ .../enigma/translation/mapping/NameValidator.java | 56 ++ .../translation/mapping/ResolutionStrategy.java | 6 + .../translation/mapping/VoidEntryResolver.java | 27 + .../mapping/serde/EnigmaMappingsReader.java | 260 +++++++++ .../mapping/serde/EnigmaMappingsWriter.java | 260 +++++++++ .../translation/mapping/serde/MappingFormat.java | 54 ++ .../translation/mapping/serde/MappingsReader.java | 12 + .../translation/mapping/serde/MappingsWriter.java | 12 + .../mapping/serde/SrgMappingsWriter.java | 115 ++++ .../mapping/serde/TinyMappingsReader.java | 100 ++++ .../mapping/tree/DeltaTrackingTree.java | 113 ++++ .../enigma/translation/mapping/tree/EntryTree.java | 20 + .../translation/mapping/tree/EntryTreeNode.java | 36 ++ .../translation/mapping/tree/HashEntryTree.java | 159 ++++++ .../translation/mapping/tree/HashTreeNode.java | 72 +++ .../translation/representation/AccessFlags.java | 112 ++++ .../representation/MethodDescriptor.java | 132 +++++ .../representation/ProcyonEntryFactory.java | 45 ++ .../representation/ReferencedEntryPool.java | 60 ++ .../translation/representation/Signature.java | 93 +++ .../translation/representation/TypeDescriptor.java | 268 +++++++++ .../representation/entry/ClassDefEntry.java | 92 +++ .../representation/entry/ClassEntry.java | 180 ++++++ .../translation/representation/entry/DefEntry.java | 7 + .../translation/representation/entry/Entry.java | 99 ++++ .../representation/entry/FieldDefEntry.java | 61 ++ .../representation/entry/FieldEntry.java | 86 +++ .../entry/LocalVariableDefEntry.java | 45 ++ .../representation/entry/LocalVariableEntry.java | 92 +++ .../representation/entry/MethodDefEntry.java | 77 +++ .../representation/entry/MethodEntry.java | 95 ++++ .../representation/entry/ParentedEntry.java | 71 +++ 135 files changed, 5377 insertions(+), 6609 deletions(-) create mode 100644 src/main/java/cuchaz/enigma/ProgressListener.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/EntryRenamer.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/IndexClassVisitor.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/IndexInnerClassVisitor.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/IndexReferenceVisitor.java create mode 100644 src/main/java/cuchaz/enigma/analysis/IndexTreeBuilder.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/JarIndex.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/TranslationIndex.java create mode 100644 src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java create mode 100644 src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java create mode 100644 src/main/java/cuchaz/enigma/analysis/index/IndexClassVisitor.java create mode 100644 src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java create mode 100644 src/main/java/cuchaz/enigma/analysis/index/InheritanceIndex.java create mode 100644 src/main/java/cuchaz/enigma/analysis/index/JarIndex.java create mode 100644 src/main/java/cuchaz/enigma/analysis/index/JarIndexer.java create mode 100644 src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java create mode 100644 src/main/java/cuchaz/enigma/analysis/index/RemappableIndex.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/AccessFlags.java create mode 100644 src/main/java/cuchaz/enigma/bytecode/translators/AsmObjectTranslator.java delete mode 100644 src/main/java/cuchaz/enigma/gui/GuiTricks.java delete mode 100644 src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/ClassMapping.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/DirectionalTranslator.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/FieldMapping.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/LocalVariableMapping.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/Mappings.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/MappingsChecker.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/MappingsEnigmaReader.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/MappingsEnigmaWriter.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/MappingsRenamer.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/MappingsSRGWriter.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/MappingsTinyReader.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/MemberMapping.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/MethodDescriptor.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/MethodMapping.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/NameValidator.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/Signature.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/SignatureUpdater.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/TranslationDirection.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/Translator.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/TypeDescriptor.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/entry/ClassDefEntry.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/entry/ClassEntry.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/entry/DefEntry.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/entry/Entry.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/entry/EntryFactory.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/entry/FieldDefEntry.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/entry/FieldEntry.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/entry/LocalVariableDefEntry.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/entry/LocalVariableEntry.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/entry/MethodDefEntry.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/entry/MethodEntry.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/entry/ProcyonEntryFactory.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/entry/ReferencedEntryPool.java create mode 100644 src/main/java/cuchaz/enigma/translation/MappingTranslator.java create mode 100644 src/main/java/cuchaz/enigma/translation/SignatureUpdater.java create mode 100644 src/main/java/cuchaz/enigma/translation/Translatable.java create mode 100644 src/main/java/cuchaz/enigma/translation/TranslationDirection.java create mode 100644 src/main/java/cuchaz/enigma/translation/Translator.java create mode 100644 src/main/java/cuchaz/enigma/translation/VoidTranslator.java create mode 100644 src/main/java/cuchaz/enigma/translation/mapping/AccessModifier.java create mode 100644 src/main/java/cuchaz/enigma/translation/mapping/EntryMap.java create mode 100644 src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java create mode 100644 src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java create mode 100644 src/main/java/cuchaz/enigma/translation/mapping/EntryResolver.java create mode 100644 src/main/java/cuchaz/enigma/translation/mapping/IndexEntryResolver.java create mode 100644 src/main/java/cuchaz/enigma/translation/mapping/MappingDelta.java create mode 100644 src/main/java/cuchaz/enigma/translation/mapping/MappingPair.java create mode 100644 src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java create mode 100644 src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java create mode 100644 src/main/java/cuchaz/enigma/translation/mapping/NameValidator.java create mode 100644 src/main/java/cuchaz/enigma/translation/mapping/ResolutionStrategy.java create mode 100644 src/main/java/cuchaz/enigma/translation/mapping/VoidEntryResolver.java create mode 100644 src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsReader.java create mode 100644 src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsWriter.java create mode 100644 src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java create mode 100644 src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsReader.java create mode 100644 src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsWriter.java create mode 100644 src/main/java/cuchaz/enigma/translation/mapping/serde/SrgMappingsWriter.java create mode 100644 src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsReader.java create mode 100644 src/main/java/cuchaz/enigma/translation/mapping/tree/DeltaTrackingTree.java create mode 100644 src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTree.java create mode 100644 src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTreeNode.java create mode 100644 src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java create mode 100644 src/main/java/cuchaz/enigma/translation/mapping/tree/HashTreeNode.java create mode 100644 src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java create mode 100644 src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java create mode 100644 src/main/java/cuchaz/enigma/translation/representation/ProcyonEntryFactory.java create mode 100644 src/main/java/cuchaz/enigma/translation/representation/ReferencedEntryPool.java create mode 100644 src/main/java/cuchaz/enigma/translation/representation/Signature.java create mode 100644 src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java create mode 100644 src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java create mode 100644 src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java create mode 100644 src/main/java/cuchaz/enigma/translation/representation/entry/DefEntry.java create mode 100644 src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java create mode 100644 src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java create mode 100644 src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java create mode 100644 src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java create mode 100644 src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java create mode 100644 src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java create mode 100644 src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java create mode 100644 src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java (limited to 'src/main') diff --git a/src/main/java/cuchaz/enigma/CommandMain.java b/src/main/java/cuchaz/enigma/CommandMain.java index 6c8caa4..a84cd5e 100644 --- a/src/main/java/cuchaz/enigma/CommandMain.java +++ b/src/main/java/cuchaz/enigma/CommandMain.java @@ -11,11 +11,15 @@ package cuchaz.enigma; -import cuchaz.enigma.Deobfuscator.ProgressListener; -import cuchaz.enigma.mapping.Mappings; -import cuchaz.enigma.mapping.MappingsEnigmaReader; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.serde.MappingFormat; +import cuchaz.enigma.translation.mapping.tree.EntryTree; import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Locale; import java.util.jar.JarFile; public class CommandMain { @@ -52,7 +56,7 @@ public class CommandMain { 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)); + Path fileMappings = getReadablePath(getArg(args, 3, "mappings file", false)); Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn)); deobfuscator.writeSources(fileJarOut, new ConsoleProgressListener()); } @@ -60,43 +64,47 @@ public class CommandMain { 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)); + Path fileMappings = getReadablePath(getArg(args, 3, "mappings file", false)); Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn)); deobfuscator.writeJar(fileJarOut, new ConsoleProgressListener()); } - private static Deobfuscator getDeobfuscator(File fileMappings, JarFile jar) throws Exception { + private static Deobfuscator getDeobfuscator(Path 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); + EntryTree mappings = chooseEnigmaFormat(fileMappings).read(fileMappings); deobfuscator.setMappings(mappings); } return deobfuscator; } private static void convertMappings(String[] args) throws Exception { - File fileMappings = getReadableFile(getArg(args, 1, "enigma mapping", true)); + Path fileMappings = getReadablePath(getArg(args, 1, "enigma mapping", true)); File result = getWritableFile(getArg(args, 2, "enigma mapping", true)); String name = getArg(args, 3, "format desc", true); - Mappings.FormatType formatType; + MappingFormat saveFormat; try { - formatType = Mappings.FormatType.valueOf(name.toUpperCase()); + saveFormat = MappingFormat.valueOf(name.toUpperCase(Locale.ROOT)); } catch (IllegalArgumentException e) { throw new IllegalArgumentException(name + "is not a valid mapping format!"); } System.out.println("Reading mappings..."); - Mappings mappings = new MappingsEnigmaReader().read(fileMappings); + + MappingFormat readFormat = chooseEnigmaFormat(fileMappings); + EntryTree mappings = readFormat.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; + + saveFormat.write(mappings, result.toPath(), new ConsoleProgressListener()); + } + + private static MappingFormat chooseEnigmaFormat(Path path) { + if (Files.isDirectory(path)) { + return MappingFormat.ENIGMA_DIRECTORY; + } else { + return MappingFormat.ENIGMA_FILE; } } @@ -149,6 +157,17 @@ public class CommandMain { return file; } + private static Path getReadablePath(String path) { + if (path == null) { + return null; + } + Path file = Paths.get(path).toAbsolutePath(); + if (!Files.exists(file)) { + throw new IllegalArgumentException("Cannot find file: " + file.toString()); + } + return file; + } + public static class ConsoleProgressListener implements ProgressListener { private static final int ReportTime = 5000; // 5s @@ -166,7 +185,7 @@ public class CommandMain { } @Override - public void onProgress(int numDone, String message) { + public void step(int numDone, String message) { long now = System.currentTimeMillis(); boolean isLastUpdate = numDone == this.totalWork; boolean shouldReport = isLastUpdate || now - this.lastReportTime > ReportTime; diff --git a/src/main/java/cuchaz/enigma/Deobfuscator.java b/src/main/java/cuchaz/enigma/Deobfuscator.java index 4a945cd..076c546 100644 --- a/src/main/java/cuchaz/enigma/Deobfuscator.java +++ b/src/main/java/cuchaz/enigma/Deobfuscator.java @@ -13,8 +13,6 @@ package cuchaz.enigma; import com.google.common.base.Stopwatch; import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; import com.strobel.assembler.metadata.ITypeLoader; import com.strobel.assembler.metadata.MetadataSystem; import com.strobel.assembler.metadata.TypeDefinition; @@ -28,16 +26,17 @@ import com.strobel.decompiler.languages.java.ast.CompilationUnit; import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor; import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; import cuchaz.enigma.analysis.*; +import cuchaz.enigma.analysis.index.JarIndex; import cuchaz.enigma.api.EnigmaPlugin; -import cuchaz.enigma.mapping.*; -import cuchaz.enigma.mapping.entry.*; -import cuchaz.enigma.throwables.IllegalNameException; +import cuchaz.enigma.translation.mapping.*; +import cuchaz.enigma.translation.mapping.tree.DeltaTrackingTree; +import cuchaz.enigma.translation.mapping.tree.EntryTree; +import cuchaz.enigma.translation.representation.ReferencedEntryPool; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.Entry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; import cuchaz.enigma.utils.Utils; -import oml.ast.transformers.InvalidIdentifierFix; -import oml.ast.transformers.Java8Generics; -import oml.ast.transformers.ObfuscatedEnumSwitchRewriterTransform; -import oml.ast.transformers.RemoveObjectCasts; -import oml.ast.transformers.VarargsFixer; +import oml.ast.transformers.*; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.tree.ClassNode; @@ -49,6 +48,7 @@ import java.util.function.Consumer; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; +import java.util.stream.Collectors; public class Deobfuscator { @@ -57,24 +57,24 @@ public class Deobfuscator { private final ParsedJar parsedJar; private final DecompilerSettings settings; private final JarIndex jarIndex; - private final MappingsRenamer renamer; - private final Map translatorCache; - private Mappings mappings; + private final IndexTreeBuilder indexTreeBuilder; + private EntryRemapper mapper; public Deobfuscator(ParsedJar jar, Consumer listener) { this.parsedJar = jar; // build the jar index - listener.accept("Indexing JAR..."); - this.jarIndex = new JarIndex(entryPool); - this.jarIndex.indexJar(this.parsedJar, true); + this.jarIndex = JarIndex.empty(); + this.jarIndex.indexJar(this.parsedJar, listener); - listener.accept("Initializing plugins..."); + listener.accept("Initializing plugins..."); for (EnigmaPlugin plugin : getPlugins()) { plugin.onClassesLoaded(parsedJar.getClassDataMap(), parsedJar::getClassNode); } - listener.accept("Preparing..."); + this.indexTreeBuilder = new IndexTreeBuilder(jarIndex); + + listener.accept("Preparing..."); // config the decompiler this.settings = DecompilerSettings.javaDefaults(); this.settings.setMergeVariables(Utils.getSystemPropertyAsBoolean("enigma.mergeVariables", true)); @@ -85,11 +85,8 @@ public class Deobfuscator { 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, this.entryPool); // init mappings - setMappings(new Mappings()); + mapper = new EntryRemapper(jarIndex); } public Deobfuscator(JarFile jar, Consumer listener) throws IOException { @@ -97,12 +94,14 @@ public class Deobfuscator { } public Deobfuscator(ParsedJar jar) throws IOException { - this(jar, (msg) -> {}); - } + this(jar, (msg) -> { + }); + } - public Deobfuscator(JarFile jar) throws IOException { - this(jar, (msg) -> {}); - } + public Deobfuscator(JarFile jar) throws IOException { + this(jar, (msg) -> { + }); + } public ServiceLoader getPlugins() { return plugins; @@ -116,56 +115,50 @@ public class Deobfuscator { return this.jarIndex; } - public Mappings getMappings() { - return this.mappings; + public IndexTreeBuilder getIndexTreeBuilder() { + return indexTreeBuilder; } - public void setMappings(Mappings val) { - setMappings(val, true); + public EntryRemapper getMapper() { + return this.mapper; } - public void setMappings(Mappings val, boolean warnAboutDrops) { - if (val == null) { - val = new Mappings(); - } + public void setMappings(EntryTree mappings) { + if (mappings != null) { + Collection> dropped = dropMappings(mappings); + mapper = new EntryRemapper(jarIndex, mappings); - // drop mappings that don't match the jar - MappingsChecker checker = new MappingsChecker(this.jarIndex); - checker.dropBrokenMappings(val); - if (warnAboutDrops) { - for (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 (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 (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 (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."); + DeltaTrackingTree deobfToObf = mapper.getDeobfToObf(); + for (Entry entry : dropped) { + deobfToObf.trackDeletion(entry); } + } else { + mapper = new EntryRemapper(jarIndex); } - - 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())); + private Collection> dropMappings(EntryTree mappings) { + // drop mappings that don't match the jar + MappingsChecker checker = new MappingsChecker(jarIndex, mappings); + MappingsChecker.Dropped dropped = checker.dropBrokenMappings(); + + Map, String> droppedMappings = dropped.getDroppedMappings(); + for (Map.Entry, String> mapping : droppedMappings.entrySet()) { + System.out.println("WARNING: Couldn't find " + mapping.getKey() + " (" + mapping.getValue() + ") in jar. Mapping was dropped."); + } + + return droppedMappings.keySet(); } public void getSeparatedClasses(List obfClasses, List deobfClasses) { - for (ClassEntry obfClassEntry : this.jarIndex.getObfClassEntries()) { + for (ClassEntry obfClassEntry : this.jarIndex.getEntryIndex().getClasses()) { // skip inner classes if (obfClassEntry.isInnerClass()) { continue; } // separate the classes - ClassEntry deobfClassEntry = deobfuscateEntry(obfClassEntry); + ClassEntry deobfClassEntry = mapper.deobfuscate(obfClassEntry); if (!deobfClassEntry.equals(obfClassEntry)) { // if the class has a mapping, clearly it's deobfuscated deobfClasses.add(deobfClassEntry); @@ -184,8 +177,8 @@ public class Deobfuscator { this.parsedJar, this.jarIndex, this.entryPool, - getTranslator(TranslationDirection.OBFUSCATING), - getTranslator(TranslationDirection.DEOBFUSCATING) + this.mapper.getObfuscator(), + this.mapper.getDeobfuscator() ); } @@ -203,14 +196,7 @@ public class Deobfuscator { // 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(); - } + String deobfClassName = mapper.deobfuscate(new ClassEntry(className)).getFullName(); // set the desc loader this.settings.setTypeLoader(loader); @@ -236,43 +222,23 @@ public class Deobfuscator { } public SourceIndex getSourceIndex(CompilationUnit sourceTree, String source) { - return getSourceIndex(sourceTree, source, null); + return getSourceIndex(sourceTree, source, true); } - public SourceIndex getSourceIndex(CompilationUnit sourceTree, String source, Boolean ignoreBadTokens) { + 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); - } + SourceIndex index = new SourceIndex(source, ignoreBadTokens); sourceTree.acceptVisitor(new SourceIndexVisitor(entryPool), 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); + EntryResolver resolver = mapper.getDeobfResolver(); - // try to resolve the class - ClassEntry resolvedObfClassEntry = this.jarIndex.getTranslationIndex().resolveEntryOwner(obfEntry); - if (resolvedObfClassEntry != null && !resolvedObfClassEntry.equals(obfEntry.getOwnerClassEntry())) { - // change the class of the entry - obfEntry = obfEntry.updateOwnership(resolvedObfClassEntry); - - // save the new deobfuscated reference - deobfReference.entry = deobfuscateEntry(obfEntry); - index.replaceDeobfReference(token, deobfReference); - } + Collection tokens = Lists.newArrayList(index.referenceTokens()); - // DEBUG - // System.out.println( token + " -> " + reference + " -> " + index.getReferenceToken( reference ) ); + // resolve all the classes in the source references + for (Token token : tokens) { + EntryReference, Entry> deobfReference = index.getDeobfReference(token); + index.replaceDeobfReference(token, resolver.resolveFirstReference(deobfReference, ResolutionStrategy.RESOLVE_CLOSEST)); } return index; @@ -288,15 +254,9 @@ public class Deobfuscator { 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); - } + Set classEntries = jarIndex.getEntryIndex().getClasses().stream() + .filter(classEntry -> !classEntry.isInnerClass()) + .collect(Collectors.toSet()); if (progress != null) { progress.init(classEntries.size(), "Decompiling classes..."); @@ -313,9 +273,9 @@ public class Deobfuscator { Stopwatch stopwatch = Stopwatch.createStarted(); AtomicInteger count = new AtomicInteger(); classEntries.parallelStream().forEach(obfClassEntry -> { - ClassEntry deobfClassEntry = deobfuscateEntry(new ClassEntry(obfClassEntry)); + ClassEntry deobfClassEntry = mapper.deobfuscate(obfClassEntry); if (progress != null) { - progress.onProgress(count.getAndIncrement(), deobfClassEntry.toString()); + progress.step(count.getAndIncrement(), deobfClassEntry.toString()); } try { @@ -332,131 +292,17 @@ public class Deobfuscator { } 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 + ")"); + System.err.println("Unable to decompile class " + deobfClassEntry + " (" + obfClassEntry + ")"); t.printStackTrace(System.err); } }); stopwatch.stop(); System.out.println("writeSources Done in : " + stopwatch.toString()); if (progress != null) { - progress.onProgress(count.get(), "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); - } - } - - public boolean isMethodProvider(MethodEntry methodEntry) { - Set classEntries = new HashSet<>(); - addAllPotentialAncestors(classEntries, methodEntry.getOwnerClassEntry()); - - for (ClassEntry parentEntry : classEntries) { - MethodEntry ancestorMethodEntry = entryPool.getMethod(parentEntry, methodEntry.getName(), methodEntry.getDesc()); - if (jarIndex.containsObfMethod(ancestorMethodEntry)) { - return false; - } - } - - return true; - } - - @Deprecated - public boolean isMethodProvider(ClassEntry classObfEntry, MethodEntry methodEntry) { - Set classEntries = new HashSet<>(); - addAllPotentialAncestors(classEntries, classObfEntry); - - for (ClassEntry parentEntry : classEntries) { - MethodEntry ancestorMethodEntry = entryPool.getMethod(parentEntry, methodEntry.getName(), methodEntry.getDesc()); - if (jarIndex.containsObfMethod(ancestorMethodEntry)) { - return false; - } - } - - return true; - } - - public void rebuildMethodNames(ProgressListener progress) { - final AtomicInteger i = new AtomicInteger(); - Map> renameClassMap = new ConcurrentHashMap<>(); - - progress.init(getMappings().classes().size() * 3, "Rebuilding method names"); - - Lists.newArrayList(getMappings().classes()).parallelStream().forEach(classMapping -> { - progress.onProgress(i.getAndIncrement(), classMapping.getDeobfName()); - rebuildMethodNames(classMapping, renameClassMap); - }); - - - renameClassMap.entrySet().stream().forEach(renameClassMapEntry -> { - progress.onProgress(i.getAndIncrement(), renameClassMapEntry.getKey().getDeobfName()); - for (Map.Entry entry : renameClassMapEntry.getValue().entrySet()) { - Entry obfEntry = entry.getKey(); - - removeMapping(obfEntry, false); - } - }); - - translatorCache.clear(); - - renameClassMap.entrySet().stream().forEach(renameClassMapEntry -> { - progress.onProgress(i.getAndIncrement(), renameClassMapEntry.getKey().getDeobfName()); - - for (Map.Entry entry : renameClassMapEntry.getValue().entrySet()) { - Entry obfEntry = entry.getKey(); - String name = entry.getValue(); - - if (name != null) { - try { - rename(obfEntry, name); - } catch (IllegalNameException exception) { - System.out.println("WARNING: " + exception.getMessage()); - } - } - } - }); - } - - private void rebuildMethodNames(ClassMapping classMapping, Map> renameClassMap) { - Map renameEntries = new HashMap<>(); - - for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) { - ClassEntry classObfEntry = classMapping.getObfEntry(); - MethodEntry obfEntry = methodMapping.getObfEntry(classObfEntry); - boolean isProvider = isMethodProvider(obfEntry); - - if (hasDeobfuscatedName(obfEntry) - && !(methodMapping.getDeobfName().equals(methodMapping.getObfName()))) { - renameEntries.put(obfEntry, isProvider ? methodMapping.getDeobfName() : null); - } - - if (isProvider) { - for (LocalVariableMapping localVariableMapping : methodMapping.arguments()) { - Entry argObfEntry = localVariableMapping.getObfEntry(obfEntry); - if (hasDeobfuscatedName(argObfEntry)) { - renameEntries.put(argObfEntry, deobfuscateEntry(argObfEntry).getName()); - } - } - } - } - - classMapping.markDirty(); - renameClassMap.put(classMapping, renameEntries); - for (ClassMapping innerClass : classMapping.innerClasses()) { - rebuildMethodNames(innerClass, renameClassMap); + progress.step(count.get(), "Done:"); } } - public void writeJar(File out, ProgressListener progress) { transformJar(out, progress, createTypeLoader()::transformInto); } @@ -470,7 +316,7 @@ public class Deobfuscator { AtomicInteger i = new AtomicInteger(); parsedJar.visitNode(node -> { if (progress != null) { - progress.onProgress(i.getAndIncrement(), node.name); + progress.step(i.getAndIncrement(), node.name); } try { @@ -485,50 +331,31 @@ public class Deobfuscator { }); if (progress != null) { - progress.onProgress(i.get(), "Done!"); + progress.step(i.get(), "Done!"); } } catch (IOException ex) { throw new Error("Unable to write to Jar file!"); } } - public T obfuscateEntry(T deobfEntry) { - if (deobfEntry == null) { - return null; - } - T translatedEntry = getTranslator(TranslationDirection.OBFUSCATING).getTranslatedEntry(deobfEntry); - if (translatedEntry == null) { - return deobfEntry; - } - return translatedEntry; - } - - public T deobfuscateEntry(T obfEntry) { - if (obfEntry == null) { - return null; - } - T translatedEntry = getTranslator(TranslationDirection.DEOBFUSCATING).getTranslatedEntry(obfEntry); - if (translatedEntry == null) { - return obfEntry; - } - return translatedEntry; - } - - public EntryReference obfuscateReference(EntryReference deobfReference) { - if (deobfReference == null) { - return null; + public AccessModifier getModifier(Entry entry) { + EntryMapping mapping = mapper.getDeobfMapping(entry); + if (mapping == null) { + return AccessModifier.UNCHANGED; } - return new EntryReference<>(obfuscateEntry(deobfReference.entry), obfuscateEntry(deobfReference.context), deobfReference); + return mapping.getAccessModifier(); } - public EntryReference deobfuscateReference(EntryReference obfReference) { - if (obfReference == null) { - return null; + public void changeModifier(Entry entry, AccessModifier modifier) { + EntryMapping mapping = mapper.getDeobfMapping(entry); + if (mapping != null) { + mapper.mapFromObf(entry, new EntryMapping(mapping.getTargetName(), modifier)); + } else { + mapper.mapFromObf(entry, new EntryMapping(entry.getName(), modifier)); } - return new EntryReference<>(deobfuscateEntry(obfReference.entry), deobfuscateEntry(obfReference.context), obfReference); } - public boolean isObfuscatedIdentifier(Entry obfEntry) { + public boolean isObfuscatedIdentifier(Entry obfEntry) { if (obfEntry instanceof MethodEntry) { // HACKHACK: Object methods are not obfuscated identifiers MethodEntry obfMethodEntry = (MethodEntry) obfEntry; @@ -559,142 +386,30 @@ public class Deobfuscator { } } - return this.jarIndex.containsObfEntry(obfEntry); + return this.jarIndex.getEntryIndex().hasEntry(obfEntry); } - public boolean isRenameable(EntryReference obfReference) { + public boolean isRenameable(EntryReference, Entry> obfReference) { return obfReference.isNamed() && isObfuscatedIdentifier(obfReference.getNameableEntry()); } - 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.hasFieldMapping((FieldEntry) obfEntry); - } else if (obfEntry instanceof MethodEntry) { - MethodEntry methodEntry = (MethodEntry) obfEntry; - if (methodEntry.isConstructor()) { - return false; - } - return translator.hasMethodMapping(methodEntry); - } else if (obfEntry instanceof LocalVariableEntry) { - return translator.hasLocalVariableMapping((LocalVariableEntry) obfEntry); - } else { - throw new Error("Unknown entry desc: " + obfEntry.getClass().getName()); - } + public boolean hasDeobfuscatedName(Entry obfEntry) { + return mapper.hasDeobfMapping(obfEntry); } - public void rename(Entry obfEntry, String newName) { - rename(obfEntry, newName, true); + public void rename(Entry obfEntry, String newName) { + mapper.mapFromObf(obfEntry, new EntryMapping(newName)); } - // 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, newName); - } else if (obfEntry instanceof FieldEntry) { - this.renamer.setFieldName((FieldEntry) obfEntry, newName); - } else if (obfEntry instanceof MethodEntry) { - if (((MethodEntry) obfEntry).isConstructor()) { - throw new IllegalArgumentException("Cannot rename constructors"); - } - - this.renamer.setMethodTreeName((MethodEntry) obfEntry, newName); - } else if (obfEntry instanceof LocalVariableEntry) { - // TODO: Discern between arguments (propagate) and local vars (don't) - this.renamer.setLocalVariableTreeName((LocalVariableEntry) obfEntry, newName); - } else { - throw new Error("Unknown entry desc: " + obfEntry.getClass().getName()); - } - - // clear caches - if (clearCache) - this.translatorCache.clear(); + public void removeMapping(Entry obfEntry) { + mapper.removeByObf(obfEntry); } - public void removeMapping(Entry obfEntry) { - removeMapping(obfEntry, true); + public void markAsDeobfuscated(Entry obfEntry) { + mapper.mapFromObf(obfEntry, new EntryMapping(mapper.deobfuscate(obfEntry).getName())); } - public void removeMapping(Entry obfEntry, boolean clearCache) { - if (obfEntry instanceof ClassEntry) { - this.renamer.removeClassMapping((ClassEntry) obfEntry); - } else if (obfEntry instanceof FieldEntry) { - this.renamer.removeFieldMapping((FieldEntry) obfEntry); - } else if (obfEntry instanceof MethodEntry) { - if (((MethodEntry) obfEntry).isConstructor()) { - throw new IllegalArgumentException("Cannot rename constructors"); - } - this.renamer.removeMethodTreeMapping((MethodEntry) obfEntry); - } else if (obfEntry instanceof LocalVariableEntry) { - this.renamer.removeLocalVariableMapping((LocalVariableEntry) obfEntry); - } else { - throw new Error("Unknown entry desc: " + obfEntry); - } - - // clear caches - if (clearCache) - this.translatorCache.clear(); - } - - public void markAsDeobfuscated(Entry obfEntry) { - markAsDeobfuscated(obfEntry, true); - } - - public void markAsDeobfuscated(Entry obfEntry, boolean clearCache) { - if (obfEntry instanceof ClassEntry) { - this.renamer.markClassAsDeobfuscated((ClassEntry) obfEntry); - } else if (obfEntry instanceof FieldEntry) { - this.renamer.markFieldAsDeobfuscated((FieldEntry) obfEntry); - } else if (obfEntry instanceof MethodEntry) { - MethodEntry methodEntry = (MethodEntry) obfEntry; - if (methodEntry.isConstructor()) { - throw new IllegalArgumentException("Cannot rename constructors"); - } - this.renamer.markMethodTreeAsDeobfuscated(methodEntry); - } else if (obfEntry instanceof LocalVariableEntry) { - this.renamer.markArgumentAsDeobfuscated((LocalVariableEntry) obfEntry); - } else { - throw new Error("Unknown entry desc: " + obfEntry); - } - - // clear caches - if (clearCache) - 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 MethodEntry) - this.renamer.setMethodModifier((MethodEntry) obfEntry, modifierEntry); - else - throw new Error("Unknown entry desc: " + obfEntry); - } - - public Mappings.EntryModifier getModifier(Entry obfEntry) { - Entry entry = obfuscateEntry(obfEntry); - if (entry != null) - obfEntry = entry; - if (obfEntry instanceof ClassEntry) - return this.renamer.getClassModifier((ClassEntry) obfEntry); - else if (obfEntry instanceof FieldEntry) - return this.renamer.getFieldModifier((FieldEntry) obfEntry); - else if (obfEntry instanceof MethodEntry) - return this.renamer.getMethodModfifier((MethodEntry) obfEntry); - else - throw new Error("Unknown entry desc: " + obfEntry); - } - - public static void runCustomTransforms(AstBuilder builder, DecompilerContext context){ + public static void runCustomTransforms(AstBuilder builder, DecompilerContext context) { List transformers = Arrays.asList( new ObfuscatedEnumSwitchRewriterTransform(context), new VarargsFixer(context), @@ -707,12 +422,6 @@ public class Deobfuscator { } } - public interface ProgressListener { - void init(int totalWork, String title); - - void onProgress(int numDone, String message); - } - public interface ClassTransformer { String transform(ClassNode node, ClassWriter writer); } diff --git a/src/main/java/cuchaz/enigma/ITranslatingTypeLoader.java b/src/main/java/cuchaz/enigma/ITranslatingTypeLoader.java index 547ed0b..24822dd 100644 --- a/src/main/java/cuchaz/enigma/ITranslatingTypeLoader.java +++ b/src/main/java/cuchaz/enigma/ITranslatingTypeLoader.java @@ -1,7 +1,7 @@ package cuchaz.enigma; import com.strobel.assembler.metadata.ITypeLoader; -import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.ClassEntry; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.tree.ClassNode; diff --git a/src/main/java/cuchaz/enigma/Main.java b/src/main/java/cuchaz/enigma/Main.java index 0f15193..ccfc51f 100644 --- a/src/main/java/cuchaz/enigma/Main.java +++ b/src/main/java/cuchaz/enigma/Main.java @@ -11,12 +11,12 @@ package cuchaz.enigma; -import cuchaz.enigma.config.Config; -import cuchaz.enigma.config.Themes; import cuchaz.enigma.gui.Gui; +import cuchaz.enigma.translation.mapping.serde.MappingFormat; -import javax.swing.*; import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.jar.JarFile; public class Main { @@ -29,7 +29,12 @@ public class Main { gui.getController().openJar(new JarFile(getFile(args[0]))); } if (args.length >= 2) { - gui.getController().openEnigmaMappings(getFile(args[1])); + Path mappingsFile = getFile(args[1]).toPath(); + if (Files.isDirectory(mappingsFile)) { + gui.getController().openMappings(MappingFormat.ENIGMA_DIRECTORY, mappingsFile); + } else { + gui.getController().openMappings(MappingFormat.ENIGMA_FILE, mappingsFile); + } } // DEBUG diff --git a/src/main/java/cuchaz/enigma/ProgressListener.java b/src/main/java/cuchaz/enigma/ProgressListener.java new file mode 100644 index 0000000..ffce297 --- /dev/null +++ b/src/main/java/cuchaz/enigma/ProgressListener.java @@ -0,0 +1,17 @@ +package cuchaz.enigma; + +public interface ProgressListener { + ProgressListener VOID = new ProgressListener() { + @Override + public void init(int totalWork, String title) { + } + + @Override + public void step(int numDone, String message) { + } + }; + + void init(int totalWork, String title); + + void step(int numDone, String message); +} diff --git a/src/main/java/cuchaz/enigma/SynchronizedTypeLoader.java b/src/main/java/cuchaz/enigma/SynchronizedTypeLoader.java index f4a7fe0..657bee4 100644 --- a/src/main/java/cuchaz/enigma/SynchronizedTypeLoader.java +++ b/src/main/java/cuchaz/enigma/SynchronizedTypeLoader.java @@ -1,7 +1,7 @@ package cuchaz.enigma; import com.strobel.assembler.metadata.Buffer; -import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.ClassEntry; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.tree.ClassNode; diff --git a/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java b/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java index 42ceec4..4c1f695 100644 --- a/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java +++ b/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java @@ -14,12 +14,12 @@ package cuchaz.enigma; import com.google.common.collect.Lists; import com.strobel.assembler.metadata.Buffer; import com.strobel.assembler.metadata.ITypeLoader; -import cuchaz.enigma.analysis.JarIndex; +import cuchaz.enigma.analysis.index.JarIndex; import cuchaz.enigma.analysis.ParsedJar; import cuchaz.enigma.bytecode.translators.TranslationClassVisitor; -import cuchaz.enigma.mapping.entry.ClassEntry; -import cuchaz.enigma.mapping.entry.ReferencedEntryPool; -import cuchaz.enigma.mapping.Translator; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.representation.ReferencedEntryPool; +import cuchaz.enigma.translation.representation.entry.ClassEntry; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AbstractInsnNode; @@ -47,12 +47,12 @@ public class TranslatingTypeLoader extends CachingTypeLoader implements ITransla this.deobfuscatingTranslator = deobfuscatingTranslator; } - protected byte[] doLoad(String className){ + protected byte[] doLoad(String className) { byte[] data = loadType(className); if (data == null) { // chain to default desc loader Buffer parentBuf = new Buffer(); - if (defaultTypeLoader.tryLoadType(className, parentBuf)){ + if (defaultTypeLoader.tryLoadType(className, parentBuf)) { return parentBuf.array(); } return EMPTY_ARRAY;//need to return *something* as null means no store @@ -64,21 +64,10 @@ public class TranslatingTypeLoader extends CachingTypeLoader implements ITransla // NOTE: don't know if class name is obf or deobf ClassEntry classEntry = new ClassEntry(className); - ClassEntry obfClassEntry = this.obfuscatingTranslator.getTranslatedClass(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; - } - } + ClassEntry obfClassEntry = this.obfuscatingTranslator.translate(classEntry); // is this a class we should even know about? - if (!this.jarIndex.containsObfClass(obfClassEntry)) { + if (!jarIndex.getEntryIndex().hasClass(obfClassEntry)) { return null; } @@ -97,15 +86,15 @@ public class TranslatingTypeLoader extends CachingTypeLoader implements ITransla // DUP // INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class; // POP - for (MethodNode methodNode : node.methods){ + for (MethodNode methodNode : node.methods) { AbstractInsnNode insnNode = methodNode.instructions.getFirst(); - while (insnNode != null){ - if (insnNode instanceof MethodInsnNode && insnNode.getOpcode() == Opcodes.INVOKEVIRTUAL){ - MethodInsnNode methodInsnNode = (MethodInsnNode)insnNode; - if (methodInsnNode.name.equals("getClass") && methodInsnNode.owner.equals("java/lang/Object") && methodInsnNode.desc.equals("()Ljava/lang/Class;")){ + while (insnNode != null) { + if (insnNode instanceof MethodInsnNode && insnNode.getOpcode() == Opcodes.INVOKEVIRTUAL) { + MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode; + if (methodInsnNode.name.equals("getClass") && methodInsnNode.owner.equals("java/lang/Object") && methodInsnNode.desc.equals("()Ljava/lang/Class;")) { AbstractInsnNode previous = methodInsnNode.getPrevious(); AbstractInsnNode next = methodInsnNode.getNext(); - if (previous.getOpcode() == Opcodes.DUP && next.getOpcode() == Opcodes.POP){ + if (previous.getOpcode() == Opcodes.DUP && next.getOpcode() == Opcodes.POP) { insnNode = previous.getPrevious();//reset the iterator so it gets the new next instruction methodNode.instructions.remove(previous); methodNode.instructions.remove(methodInsnNode); @@ -140,24 +129,26 @@ public class TranslatingTypeLoader extends CachingTypeLoader implements ITransla @Override public List getClassNamesToTry(String className) { - return getClassNamesToTry(this.obfuscatingTranslator.getTranslatedClass(new ClassEntry(className))); + return getClassNamesToTry(this.obfuscatingTranslator.translate(new ClassEntry(className))); } @Override 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()); + classNamesToTry.add(obfClassEntry.getFullName()); + + ClassEntry outerClass = obfClassEntry.getOuterClass(); + if (outerClass != null) { + classNamesToTry.addAll(getClassNamesToTry(outerClass)); } + return classNamesToTry; } @Override public String transformInto(ClassNode node, ClassWriter writer) { - node.accept(new TranslationClassVisitor(deobfuscatingTranslator, jarIndex, entryPool, Opcodes.ASM5, writer)); - return deobfuscatingTranslator.getTranslatedClass(new ClassEntry(node.name)).getName(); + node.accept(new TranslationClassVisitor(deobfuscatingTranslator, entryPool, Opcodes.ASM5, writer)); + return deobfuscatingTranslator.translate(new ClassEntry(node.name)).getFullName(); } } diff --git a/src/main/java/cuchaz/enigma/analysis/Access.java b/src/main/java/cuchaz/enigma/analysis/Access.java index 8181418..82ca669 100644 --- a/src/main/java/cuchaz/enigma/analysis/Access.java +++ b/src/main/java/cuchaz/enigma/analysis/Access.java @@ -11,7 +11,7 @@ package cuchaz.enigma.analysis; -import cuchaz.enigma.bytecode.AccessFlags; +import cuchaz.enigma.translation.representation.AccessFlags; import java.lang.reflect.Modifier; diff --git a/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java b/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java index e876bb0..0fc44ca 100644 --- a/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java +++ b/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java @@ -12,26 +12,28 @@ package cuchaz.enigma.analysis; import com.google.common.collect.Lists; -import cuchaz.enigma.mapping.entry.ClassEntry; -import cuchaz.enigma.mapping.entry.MethodEntry; -import cuchaz.enigma.mapping.Translator; +import cuchaz.enigma.analysis.index.InheritanceIndex; +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; import javax.swing.tree.DefaultMutableTreeNode; +import java.util.Collection; import java.util.List; public class ClassImplementationsTreeNode extends DefaultMutableTreeNode { - - private final Translator deobfuscatingTranslator; + private final Translator translator; private final ClassEntry entry; - public ClassImplementationsTreeNode(Translator deobfuscatingTranslator, ClassEntry entry) { - this.deobfuscatingTranslator = deobfuscatingTranslator; + public ClassImplementationsTreeNode(Translator translator, ClassEntry entry) { + this.translator = translator; this.entry = entry; } public static ClassImplementationsTreeNode findNode(ClassImplementationsTreeNode node, MethodEntry entry) { // is this the node? - if (node.entry.equals(entry.getOwnerClassEntry())) { + if (node.entry.equals(entry.getParent())) { return node; } @@ -49,24 +51,19 @@ public class ClassImplementationsTreeNode extends DefaultMutableTreeNode { return this.entry; } - public String getDeobfClassName() { - return this.deobfuscatingTranslator.getTranslatedClass(entry).getClassName(); - } - @Override public String toString() { - String className = getDeobfClassName(); - if (className == null) { - className = this.entry.getClassName(); - } - return className; + return translator.translate(entry).toString(); } 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))); + InheritanceIndex inheritanceIndex = index.getInheritanceIndex(); + + Collection inheritors = inheritanceIndex.getChildren(entry); + for (ClassEntry inheritor : inheritors) { + nodes.add(new ClassImplementationsTreeNode(translator, inheritor)); } // add them to this node diff --git a/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java index b8ee17d..7904c5f 100644 --- a/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java +++ b/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java @@ -12,25 +12,25 @@ package cuchaz.enigma.analysis; import com.google.common.collect.Lists; -import cuchaz.enigma.mapping.entry.ClassEntry; -import cuchaz.enigma.mapping.Translator; +import cuchaz.enigma.analysis.index.InheritanceIndex; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.representation.entry.ClassEntry; import javax.swing.tree.DefaultMutableTreeNode; import java.util.List; public class ClassInheritanceTreeNode extends DefaultMutableTreeNode { - - private final Translator deobfuscatingTranslator; + private final Translator translator; private final ClassEntry obfClassEntry; - public ClassInheritanceTreeNode(Translator deobfuscatingTranslator, String obfClassName) { - this.deobfuscatingTranslator = deobfuscatingTranslator; + public ClassInheritanceTreeNode(Translator translator, String obfClassName) { + this.translator = translator; this.obfClassEntry = new ClassEntry(obfClassName); } public static ClassInheritanceTreeNode findNode(ClassInheritanceTreeNode node, ClassEntry entry) { // is this the node? - if (node.getObfClassName().equals(entry.getName())) { + if (node.getObfClassName().equals(entry.getFullName())) { return node; } @@ -45,27 +45,19 @@ public class ClassInheritanceTreeNode extends DefaultMutableTreeNode { } public String getObfClassName() { - return this.obfClassEntry.getClassName(); - } - - public String getDeobfClassName() { - return this.deobfuscatingTranslator.getTranslatedClass(this.obfClassEntry).getClassName(); + return this.obfClassEntry.getFullName(); } @Override public String toString() { - String deobfClassName = getDeobfClassName(); - if (deobfClassName != null) { - return deobfClassName; - } - return this.obfClassEntry.getName(); + return translator.translate(obfClassEntry).getFullName(); } - public void load(TranslationIndex ancestries, boolean recurse) { + public void load(InheritanceIndex ancestries, boolean recurse) { // get all the child nodes List nodes = Lists.newArrayList(); - for (ClassEntry subclassEntry : ancestries.getSubclass(this.obfClassEntry)) { - nodes.add(new ClassInheritanceTreeNode(this.deobfuscatingTranslator, subclassEntry.getName())); + for (ClassEntry inheritor : ancestries.getChildren(this.obfClassEntry)) { + nodes.add(new ClassInheritanceTreeNode(translator, inheritor.getFullName())); } // add them to this node diff --git a/src/main/java/cuchaz/enigma/analysis/ClassReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/ClassReferenceTreeNode.java index ff5f2e9..90d8a6c 100644 --- a/src/main/java/cuchaz/enigma/analysis/ClassReferenceTreeNode.java +++ b/src/main/java/cuchaz/enigma/analysis/ClassReferenceTreeNode.java @@ -12,12 +12,12 @@ package cuchaz.enigma.analysis; import com.google.common.collect.Sets; -import cuchaz.enigma.bytecode.AccessFlags; -import cuchaz.enigma.mapping.Translator; -import cuchaz.enigma.mapping.entry.ClassEntry; -import cuchaz.enigma.mapping.entry.Entry; -import cuchaz.enigma.mapping.entry.MethodDefEntry; -import cuchaz.enigma.mapping.entry.MethodEntry; +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.analysis.index.ReferenceIndex; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.Entry; +import cuchaz.enigma.translation.representation.entry.MethodDefEntry; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreeNode; @@ -29,7 +29,6 @@ public class ClassReferenceTreeNode extends DefaultMutableTreeNode private Translator deobfuscatingTranslator; private ClassEntry entry; private EntryReference reference; - private AccessFlags access; public ClassReferenceTreeNode(Translator deobfuscatingTranslator, ClassEntry entry) { this.deobfuscatingTranslator = deobfuscatingTranslator; @@ -37,12 +36,10 @@ public class ClassReferenceTreeNode extends DefaultMutableTreeNode this.reference = null; } - public ClassReferenceTreeNode(Translator deobfuscatingTranslator, - EntryReference reference, AccessFlags access) { + public ClassReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference reference) { this.deobfuscatingTranslator = deobfuscatingTranslator; this.entry = reference.entry; this.reference = reference; - this.access = access; } @Override @@ -58,16 +55,17 @@ public class ClassReferenceTreeNode extends DefaultMutableTreeNode @Override public String toString() { if (this.reference != null) { - return String.format("%s (%s)", this.deobfuscatingTranslator.getTranslatedMethodDef(this.reference.context), - this.access); + return String.format("%s", this.deobfuscatingTranslator.translate(this.reference.context)); } - return this.deobfuscatingTranslator.getTranslatedClass(this.entry).getName(); + return this.deobfuscatingTranslator.translate(this.entry).getFullName(); } public void load(JarIndex index, boolean recurse) { + ReferenceIndex referenceIndex = index.getReferenceIndex(); + // get all the child nodes - for (EntryReference reference : index.getMethodsReferencing(this.entry)) { - add(new ClassReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccessFlags(this.entry))); + for (EntryReference reference : referenceIndex.getReferencesToClass(this.entry)) { + add(new ClassReferenceTreeNode(this.deobfuscatingTranslator, reference)); } if (recurse && this.children != null) { @@ -76,7 +74,7 @@ public class ClassReferenceTreeNode extends DefaultMutableTreeNode ClassReferenceTreeNode node = (ClassReferenceTreeNode) child; // don't recurse into ancestor - Set ancestors = Sets.newHashSet(); + Set> ancestors = Sets.newHashSet(); TreeNode n = node; while (n.getParent() != null) { n = n.getParent(); diff --git a/src/main/java/cuchaz/enigma/analysis/EntryReference.java b/src/main/java/cuchaz/enigma/analysis/EntryReference.java index df36c23..e122210 100644 --- a/src/main/java/cuchaz/enigma/analysis/EntryReference.java +++ b/src/main/java/cuchaz/enigma/analysis/EntryReference.java @@ -11,15 +11,20 @@ package cuchaz.enigma.analysis; -import cuchaz.enigma.mapping.entry.ClassEntry; -import cuchaz.enigma.mapping.entry.Entry; -import cuchaz.enigma.mapping.entry.MethodEntry; +import cuchaz.enigma.translation.Translatable; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.mapping.EntryMap; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.Entry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; import cuchaz.enigma.utils.Utils; import java.util.Arrays; import java.util.List; -public class EntryReference { +public class EntryReference, C extends Entry> implements Translatable { private static final List CONSTRUCTOR_NON_NAMES = Arrays.asList("this", "super", "static"); public E entry; @@ -53,32 +58,24 @@ public class EntryReference { public ClassEntry getLocationClassEntry() { if (context != null) { - return context.getOwnerClassEntry(); + return context.getContainingClass(); } - return entry.getOwnerClassEntry(); + return entry.getContainingClass(); } public boolean isNamed() { return this.sourceName; } - public Entry getNameableEntry() { + public Entry getNameableEntry() { if (entry instanceof MethodEntry && ((MethodEntry) entry).isConstructor()) { // renaming a constructor really means renaming the class - return entry.getOwnerClassEntry(); + return entry.getContainingClass(); } return entry; } public String getNameableName() { - 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(); } @@ -121,4 +118,9 @@ public class EntryReference { } return buf.toString(); } + + @Override + public Translatable translate(Translator translator, EntryResolver resolver, EntryMap mappings) { + return new EntryReference<>(translator.translate(entry), translator.translate(context), this); + } } diff --git a/src/main/java/cuchaz/enigma/analysis/EntryRenamer.java b/src/main/java/cuchaz/enigma/analysis/EntryRenamer.java deleted file mode 100644 index c474d68..0000000 --- a/src/main/java/cuchaz/enigma/analysis/EntryRenamer.java +++ /dev/null @@ -1,167 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * 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.analysis; - -import com.google.common.collect.Lists; -import com.google.common.collect.Multimap; -import com.google.common.collect.Sets; -import cuchaz.enigma.mapping.*; -import cuchaz.enigma.mapping.entry.*; - -import java.util.AbstractMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -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 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 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()); - } - } - - @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.getOwnerClassEntry(), - newMethodEntry.getName(), - methodEntry.getDesc() - ); - } - return thing; - } else if (thing instanceof LocalVariableEntry) { - LocalVariableEntry variableEntry = (LocalVariableEntry) thing; - return (T) new LocalVariableEntry( - renameMethodsInThing(renames, variableEntry.getOwnerEntry()), - variableEntry.getIndex(), - variableEntry.getName(), - variableEntry.isParameter() - ); - } 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 FieldDefEntry) { - FieldDefEntry fieldEntry = (FieldDefEntry) thing; - return (T) new FieldDefEntry( - renameClassesInThing(renames, fieldEntry.getOwnerClassEntry()), - fieldEntry.getName(), - renameClassesInThing(renames, fieldEntry.getDesc()), - renameClassesInThing(renames, fieldEntry.getSignature()), - fieldEntry.getAccess() - ); - } else if (thing instanceof MethodDefEntry) { - MethodDefEntry methodEntry = (MethodDefEntry) thing; - return (T) new MethodDefEntry( - renameClassesInThing(renames, methodEntry.getOwnerClassEntry()), - methodEntry.getName(), - renameClassesInThing(renames, methodEntry.getDesc()), - renameClassesInThing(renames, methodEntry.getSignature()), - methodEntry.getAccess() - ); - } else if (thing instanceof MethodEntry) { - MethodEntry methodEntry = (MethodEntry) thing; - return (T) new MethodEntry( - renameClassesInThing(renames, methodEntry.getOwnerClassEntry()), - methodEntry.getName(), - renameClassesInThing(renames, methodEntry.getDesc()) - ); - } else if (thing instanceof LocalVariableEntry) { - LocalVariableEntry argumentEntry = (LocalVariableEntry) thing; - return (T) new LocalVariableEntry(renameClassesInThing(renames, argumentEntry.getOwnerEntry()), argumentEntry.getIndex(), argumentEntry.getName(), argumentEntry.isParameter()); - } 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 MethodDescriptor) { - return (T) ((MethodDescriptor) thing).remap(className -> renameClassesInThing(renames, className)); - } else if (thing instanceof TypeDescriptor) { - return (T) ((TypeDescriptor) thing).remap(className -> renameClassesInThing(renames, className)); - } else if (thing instanceof Signature) { - return (T) ((Signature) thing).remap(className -> renameClassesInThing(renames, className)); - } - - return thing; - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java index 2318a2b..4beab7f 100644 --- a/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java +++ b/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java @@ -11,32 +11,31 @@ package cuchaz.enigma.analysis; -import cuchaz.enigma.bytecode.AccessFlags; -import cuchaz.enigma.mapping.*; -import cuchaz.enigma.mapping.entry.FieldEntry; -import cuchaz.enigma.mapping.entry.MethodDefEntry; -import cuchaz.enigma.mapping.entry.MethodEntry; +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.analysis.index.ReferenceIndex; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.representation.entry.FieldEntry; +import cuchaz.enigma.translation.representation.entry.MethodDefEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; import javax.swing.tree.DefaultMutableTreeNode; public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode { - private Translator deobfuscatingTranslator; + private final Translator translator; private FieldEntry entry; private EntryReference reference; - private AccessFlags access; - public FieldReferenceTreeNode(Translator deobfuscatingTranslator, FieldEntry entry) { - this.deobfuscatingTranslator = deobfuscatingTranslator; + public FieldReferenceTreeNode(Translator translator, FieldEntry entry) { + this.translator = translator; this.entry = entry; this.reference = null; } - private FieldReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference reference, AccessFlags access) { - this.deobfuscatingTranslator = deobfuscatingTranslator; + private FieldReferenceTreeNode(Translator translator, EntryReference reference) { + this.translator = translator; this.entry = reference.entry; this.reference = reference; - this.access = access; } @Override @@ -52,27 +51,29 @@ public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements Re @Override public String toString() { if (this.reference != null) { - return String.format("%s (%s)", this.deobfuscatingTranslator.getTranslatedMethodDef(this.reference.context), this.access); + return String.format("%s", translator.translate(this.reference.context)); } - return deobfuscatingTranslator.getTranslatedField(entry).getName(); + return translator.translate(entry).toString(); } public void load(JarIndex index, boolean recurse) { + ReferenceIndex referenceIndex = index.getReferenceIndex(); + // get all the child nodes if (this.reference == null) { - for (EntryReference reference : index.getFieldReferences(this.entry)) { - add(new FieldReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccessFlags(this.entry))); + for (EntryReference reference : referenceIndex.getReferencesToField(this.entry)) { + add(new FieldReferenceTreeNode(translator, reference)); } } else { - for (EntryReference reference : index.getMethodsReferencing(this.reference.context)) { - add(new MethodReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccessFlags(this.reference.context))); + for (EntryReference reference : referenceIndex.getReferencesToMethod(this.reference.context)) { + add(new MethodReferenceTreeNode(translator, reference)); } } if (recurse && children != null) { for (Object node : children) { if (node instanceof MethodReferenceTreeNode) { - ((MethodReferenceTreeNode) node).load(index, true); + ((MethodReferenceTreeNode) node).load(index, true, false); } else if (node instanceof FieldReferenceTreeNode) { ((FieldReferenceTreeNode) node).load(index, true); } diff --git a/src/main/java/cuchaz/enigma/analysis/IndexClassVisitor.java b/src/main/java/cuchaz/enigma/analysis/IndexClassVisitor.java deleted file mode 100644 index 4d5e803..0000000 --- a/src/main/java/cuchaz/enigma/analysis/IndexClassVisitor.java +++ /dev/null @@ -1,43 +0,0 @@ -package cuchaz.enigma.analysis; - -import cuchaz.enigma.mapping.entry.ClassDefEntry; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.FieldVisitor; -import org.objectweb.asm.MethodVisitor; - -public class IndexClassVisitor extends ClassVisitor { - private final JarIndex index; - private ClassDefEntry classEntry; - - public IndexClassVisitor(JarIndex index, int api) { - super(api); - this.index = index; - } - - public IndexClassVisitor(JarIndex index, int api, ClassVisitor cv) { - super(api, cv); - this.index = index; - } - - @Override - public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - this.classEntry = this.index.indexClass(access, name, signature, superName, interfaces); - super.visit(version, access, name, signature, superName, interfaces); - } - - @Override - public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { - if (this.classEntry != null) { - this.index.indexField(this.classEntry, access, name, desc, signature); - } - return super.visitField(access, name, desc, signature, value); - } - - @Override - public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { - if (this.classEntry != null) { - this.index.indexMethod(this.classEntry, access, name, desc, signature); - } - return super.visitMethod(access, name, desc, signature, exceptions); - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/IndexInnerClassVisitor.java b/src/main/java/cuchaz/enigma/analysis/IndexInnerClassVisitor.java deleted file mode 100644 index b6ab2d5..0000000 --- a/src/main/java/cuchaz/enigma/analysis/IndexInnerClassVisitor.java +++ /dev/null @@ -1,29 +0,0 @@ -package cuchaz.enigma.analysis; - -import cuchaz.enigma.mapping.entry.ClassEntry; -import org.objectweb.asm.ClassVisitor; - -public class IndexInnerClassVisitor extends ClassVisitor { - private final JarIndex index; - - public IndexInnerClassVisitor(JarIndex index, int api) { - super(api); - this.index = index; - } - - public IndexInnerClassVisitor(JarIndex index, int api, ClassVisitor cv) { - super(api, cv); - this.index = index; - } - - @Override - public void visitInnerClass(String name, String outerName, String innerName, int access) { - ClassEntry entry = new ClassEntry(name); - // Ignore anonymous classes - if (innerName != null && outerName != null) { - ClassEntry outerEntry = new ClassEntry(outerName); - index.indexInnerClass(entry, outerEntry); - } - super.visitInnerClass(name, outerName, innerName, access); - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/IndexReferenceVisitor.java b/src/main/java/cuchaz/enigma/analysis/IndexReferenceVisitor.java deleted file mode 100644 index f37f1e9..0000000 --- a/src/main/java/cuchaz/enigma/analysis/IndexReferenceVisitor.java +++ /dev/null @@ -1,77 +0,0 @@ -package cuchaz.enigma.analysis; - -import cuchaz.enigma.bytecode.AccessFlags; -import cuchaz.enigma.mapping.MethodDescriptor; -import cuchaz.enigma.mapping.Signature; -import cuchaz.enigma.mapping.entry.ClassEntry; -import cuchaz.enigma.mapping.entry.MethodDefEntry; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.Handle; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; - -public class IndexReferenceVisitor extends ClassVisitor { - private final JarIndex index; - private ClassEntry classEntry; - - public IndexReferenceVisitor(JarIndex index, int api) { - super(api); - this.index = index; - } - - @Override - public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - this.classEntry = new ClassEntry(name); - } - - @Override - public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { - MethodDefEntry entry = new MethodDefEntry(classEntry, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access)); - return new Method(this.index, entry, this.api); - } - - private class Method extends MethodVisitor { - private final JarIndex index; - private final MethodDefEntry callerEntry; - - public Method(JarIndex index, MethodDefEntry callerEntry, int api) { - super(api); - this.index = index; - this.callerEntry = callerEntry; - } - - @Override - public void visitFieldInsn(int opcode, String owner, String name, String desc) { - this.index.indexFieldAccess(callerEntry, owner, name, desc); - } - - @Override - public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { - this.index.indexMethodCall(callerEntry, owner, name, desc); - } - - @Override - public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { - for (Object bsmArg : bsmArgs){ - if (bsmArg instanceof Handle){ - Handle handle = (Handle)bsmArg; - switch (handle.getTag()){ - case Opcodes.H_GETFIELD: - case Opcodes.H_GETSTATIC: - case Opcodes.H_PUTFIELD: - case Opcodes.H_PUTSTATIC: - this.index.indexFieldAccess(callerEntry, handle.getOwner(), handle.getName(), handle.getDesc()); - break; - case Opcodes.H_INVOKEINTERFACE: - case Opcodes.H_INVOKESPECIAL: - case Opcodes.H_INVOKESTATIC: - case Opcodes.H_INVOKEVIRTUAL: - case Opcodes.H_NEWINVOKESPECIAL: - this.index.indexMethodCall(callerEntry, handle.getOwner(), handle.getName(), handle.getDesc()); - break; - } - } - } - } - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/IndexTreeBuilder.java b/src/main/java/cuchaz/enigma/analysis/IndexTreeBuilder.java new file mode 100644 index 0000000..4ca7cd1 --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/IndexTreeBuilder.java @@ -0,0 +1,87 @@ +package cuchaz.enigma.analysis; + +import com.google.common.collect.Lists; +import cuchaz.enigma.analysis.index.EntryIndex; +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.ResolutionStrategy; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; + +import java.util.List; + +public class IndexTreeBuilder { + private final JarIndex index; + + public IndexTreeBuilder(JarIndex index) { + this.index = index; + } + + public ClassInheritanceTreeNode buildClassInheritance(Translator translator, ClassEntry obfClassEntry) { + // get the root node + List ancestry = Lists.newArrayList(); + ancestry.add(obfClassEntry.getFullName()); + for (ClassEntry classEntry : index.getInheritanceIndex().getAncestors(obfClassEntry)) { + ancestry.add(classEntry.getFullName()); + } + + ClassInheritanceTreeNode rootNode = new ClassInheritanceTreeNode(translator, ancestry.get(ancestry.size() - 1)); + + // expand all children recursively + rootNode.load(index.getInheritanceIndex(), true); + + return rootNode; + } + + public ClassImplementationsTreeNode buildClassImplementations(Translator translator, ClassEntry obfClassEntry) { + if (index.getInheritanceIndex().isParent(obfClassEntry)) { + ClassImplementationsTreeNode node = new ClassImplementationsTreeNode(translator, obfClassEntry); + node.load(index); + return node; + } + return null; + } + + public MethodInheritanceTreeNode buildMethodInheritance(Translator translator, MethodEntry obfMethodEntry) { + MethodEntry resolvedEntry = index.getEntryResolver().resolveFirstEntry(obfMethodEntry, ResolutionStrategy.RESOLVE_ROOT); + + // make a root node at the base + MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode( + translator, resolvedEntry, + index.getEntryIndex().hasMethod(resolvedEntry) + ); + + // expand the full tree + rootNode.load(index, true); + + return rootNode; + } + + public List buildMethodImplementations(Translator translator, MethodEntry obfMethodEntry) { + EntryIndex entryIndex = index.getEntryIndex(); + + List ancestorMethodEntries = Lists.newArrayList(); + + if (entryIndex.hasMethod(obfMethodEntry)) { + ancestorMethodEntries.add(obfMethodEntry); + } + + for (ClassEntry ancestorEntry : index.getInheritanceIndex().getAncestors(obfMethodEntry.getParent())) { + MethodEntry ancestorMethod = obfMethodEntry.withParent(ancestorEntry); + if (entryIndex.hasMethod(ancestorMethod)) { + ancestorMethodEntries.add(ancestorMethod); + } + } + + List nodes = Lists.newArrayList(); + if (!ancestorMethodEntries.isEmpty()) { + for (MethodEntry interfaceMethodEntry : ancestorMethodEntries) { + MethodImplementationsTreeNode node = new MethodImplementationsTreeNode(translator, interfaceMethodEntry); + node.load(index); + nodes.add(node); + } + } + + return nodes; + } +} diff --git a/src/main/java/cuchaz/enigma/analysis/JarIndex.java b/src/main/java/cuchaz/enigma/analysis/JarIndex.java deleted file mode 100644 index 361c8e7..0000000 --- a/src/main/java/cuchaz/enigma/analysis/JarIndex.java +++ /dev/null @@ -1,583 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * 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.analysis; - -import com.google.common.collect.*; -import cuchaz.enigma.bytecode.AccessFlags; -import cuchaz.enigma.mapping.*; -import cuchaz.enigma.mapping.entry.*; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.Opcodes; - -import java.util.*; - -public class JarIndex { - - private final ReferencedEntryPool entryPool; - - private Set obfClassEntries; - private TranslationIndex translationIndex; - private Map access; - private Multimap fields; - private Multimap methods; - private Multimap methodImplementations; - private Multimap> methodsReferencing; - private Multimap> methodsReferencingClasses; - private Multimap methodReferences; - private Multimap> fieldReferences; - private Multimap innerClassesByOuter; - private Map outerClassesByInner; - private Map bridgedMethods; - private Set syntheticMethods; - - public JarIndex(ReferencedEntryPool entryPool) { - this.entryPool = entryPool; - this.obfClassEntries = Sets.newHashSet(); - this.translationIndex = new TranslationIndex(entryPool); - this.access = Maps.newHashMap(); - this.fields = HashMultimap.create(); - this.methods = HashMultimap.create(); - this.methodImplementations = HashMultimap.create(); - this.methodsReferencingClasses = HashMultimap.create(); - this.methodsReferencing = HashMultimap.create(); - this.methodReferences = HashMultimap.create(); - this.fieldReferences = HashMultimap.create(); - this.innerClassesByOuter = HashMultimap.create(); - this.outerClassesByInner = Maps.newHashMap(); - this.bridgedMethods = Maps.newHashMap(); - this.syntheticMethods = Sets.newHashSet(); - } - - public void indexJar(ParsedJar jar, boolean buildInnerClasses) { - - // step 1: read the class names - obfClassEntries.addAll(jar.getClassEntries()); - - // step 2: index classes, fields, methods, interfaces - if (buildInnerClasses) { - // + step 5: index inner classes - jar.visitReader(name -> new IndexClassVisitor(this, Opcodes.ASM5, new IndexInnerClassVisitor(this, Opcodes.ASM5)), ClassReader.SKIP_CODE); - } else { - jar.visitReader(name -> new IndexClassVisitor(this, Opcodes.ASM5), ClassReader.SKIP_CODE); - } - - // step 3: index field, method, constructor references - jar.visitReader(name -> new IndexReferenceVisitor(this, Opcodes.ASM5), ClassReader.SKIP_FRAMES); - - // step 4: index access and bridged methods - for (MethodDefEntry methodEntry : methods.values()) { - // look for access and bridged methods - MethodEntry accessedMethod = findAccessMethod(methodEntry); - if (accessedMethod != null) { - if (isBridgedMethod(accessedMethod, methodEntry)) { - this.bridgedMethods.put(methodEntry, accessedMethod); - } - } - } - - if (buildInnerClasses) { - // 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.methodsReferencingClasses); - EntryRenamer.renameClassesInMultimap(renames, this.methodsReferencing); - EntryRenamer.renameClassesInMultimap(renames, this.methodReferences); - EntryRenamer.renameClassesInMultimap(renames, this.fieldReferences); - EntryRenamer.renameClassesInMap(renames, this.access); - } - } - - protected ClassDefEntry indexClass(int access, String name, String signature, String superName, String[] interfaces) { - for (String interfaceName : interfaces) { - if (name.equals(interfaceName)) { - throw new IllegalArgumentException("Class cannot be its own interface! " + name); - } - } - ClassDefEntry entry = this.translationIndex.indexClass(access, name, signature, superName, interfaces); - this.access.put(entry, entry.getAccess()); - return entry; - } - - protected void indexField(ClassDefEntry owner, int access, String name, String desc, String signature) { - FieldDefEntry fieldEntry = new FieldDefEntry(owner, name, new TypeDescriptor(desc), Signature.createTypedSignature(signature), new AccessFlags(access)); - this.translationIndex.indexField(fieldEntry); - this.access.put(fieldEntry, fieldEntry.getAccess()); - this.fields.put(fieldEntry.getOwnerClassEntry(), fieldEntry); - } - - protected void indexMethod(ClassDefEntry owner, int access, String name, String desc, String signature) { - MethodDefEntry methodEntry = new MethodDefEntry(owner, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access)); - this.translationIndex.indexMethod(methodEntry); - this.access.put(methodEntry, methodEntry.getAccess()); - this.methods.put(methodEntry.getOwnerClassEntry(), methodEntry); - - if (new AccessFlags(access).isSynthetic()) { - syntheticMethods.add(methodEntry); - } - - // we don't care about constructors here - if (!methodEntry.isConstructor()) { - // index implementation - this.methodImplementations.put(methodEntry.getClassName(), methodEntry); - } - } - - protected void indexMethodCall(MethodDefEntry callerEntry, String owner, String name, String desc) { - ClassEntry referencedClass = entryPool.getClass(owner); - MethodEntry referencedMethod = new MethodEntry(referencedClass, name, new MethodDescriptor(desc)); - ClassEntry resolvedClassEntry = translationIndex.resolveEntryOwner(referencedMethod); - if (resolvedClassEntry != null && !resolvedClassEntry.equals(referencedMethod.getOwnerClassEntry())) { - referencedMethod = referencedMethod.updateOwnership(resolvedClassEntry); - } - methodsReferencing.put(referencedMethod, new EntryReference<>(referencedMethod, referencedMethod.getName(), callerEntry)); - if (referencedMethod.isConstructor()) { - methodsReferencingClasses.put(referencedClass, new EntryReference<>(referencedClass, referencedMethod.getName(), callerEntry)); - } - methodReferences.put(callerEntry, referencedMethod); - } - - protected void indexFieldAccess(MethodDefEntry callerEntry, String owner, String name, String desc) { - FieldEntry referencedField = new FieldEntry(entryPool.getClass(owner), name, new TypeDescriptor(desc)); - ClassEntry resolvedClassEntry = translationIndex.resolveEntryOwner(referencedField); - if (resolvedClassEntry != null && !resolvedClassEntry.equals(referencedField.getOwnerClassEntry())) { - referencedField = referencedField.updateOwnership(resolvedClassEntry); - } - fieldReferences.put(referencedField, new EntryReference<>(referencedField, referencedField.getName(), callerEntry)); - } - - public void indexInnerClass(ClassEntry innerEntry, ClassEntry outerEntry) { - this.innerClassesByOuter.put(outerEntry, innerEntry); - this.outerClassesByInner.putIfAbsent(innerEntry, outerEntry); - } - - private MethodEntry findAccessMethod(MethodDefEntry method) { - - // we want to find all compiler-added methods that directly call another with no processing - - // skip non-synthetic methods - if (!method.getAccess().isSynthetic()) { - return null; - } - - // get all the methods that we call - final Collection referencedMethods = methodReferences.get(method); - - // is there just one? - if (referencedMethods.size() != 1) { - return null; - } - - return referencedMethods.stream().findFirst().orElse(null); - } - - private boolean isBridgedMethod(MethodEntry called, MethodEntry access) { - // Bridged methods will always have the same name as the method they are calling - // They will also have the same amount of parameters (though equal descriptors cannot be guaranteed) - if (!called.getName().equals(access.getName()) || called.getDesc().getArgumentDescs().size() != access.getDesc().getArgumentDescs().size()) { - return false; - } - - TypeDescriptor accessReturn = access.getDesc().getReturnDesc(); - TypeDescriptor calledReturn = called.getDesc().getReturnDesc(); - if (calledReturn.isVoid() || calledReturn.isPrimitive() || accessReturn.isVoid() || accessReturn.isPrimitive()) { - return false; - } - - // Bridged methods will never have the same type as what they are calling - if (accessReturn.equals(calledReturn)) { - return false; - } - - String accessType = accessReturn.toString(); - - // If we're casting down from generic type to type-erased Object we're a bridge method - if (accessType.equals("Ljava/lang/Object;")) { - return true; - } - - // Now we need to detect cases where we are being casted down to a higher type bound - List calledAncestry = translationIndex.getAncestry(calledReturn.getTypeEntry()); - return calledAncestry.contains(accessReturn.getTypeEntry()); - } - - 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.methods.values(); - } - - public Collection getObfBehaviorEntries(ClassEntry classEntry) { - return this.methods.get(classEntry); - } - - public TranslationIndex getTranslationIndex() { - return this.translationIndex; - } - - @Deprecated - public Access getAccess(Entry entry) { - AccessFlags flags = getAccessFlags(entry); - return flags != null ? Access.get(flags) : null; - } - - public AccessFlags getAccessFlags(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 - LinkedList entries = new LinkedList<>(); - entries.add(obfMethodEntry.getOwnerClassEntry()); - - // TODO: This could be optimized to not go through interfaces repeatedly... - - ClassEntry baseImplementationClassEntry = obfMethodEntry.getOwnerClassEntry(); - - for (ClassEntry itf : getInterfaces(obfMethodEntry.getOwnerClassEntry().getClassName())) { - MethodEntry itfMethodEntry = entryPool.getMethod(itf, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString()); - if (itfMethodEntry != null && containsObfMethod(itfMethodEntry)) { - baseImplementationClassEntry = itf; - } - } - - for (ClassEntry ancestorClassEntry : this.translationIndex.getAncestry(entries.remove())) { - MethodEntry ancestorMethodEntry = entryPool.getMethod(ancestorClassEntry, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString()); - if (ancestorMethodEntry != null) { - if (containsObfMethod(ancestorMethodEntry)) { - baseImplementationClassEntry = ancestorClassEntry; - } - - for (ClassEntry itf : getInterfaces(ancestorClassEntry.getClassName())) { - MethodEntry itfMethodEntry = entryPool.getMethod(itf, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString()); - if (itfMethodEntry != null && containsObfMethod(itfMethodEntry)) { - baseImplementationClassEntry = itf; - } - } - } - } - - // make a root node at the base - MethodEntry methodEntry = entryPool.getMethod(baseImplementationClassEntry, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString()); - MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode( - deobfuscatingTranslator, - methodEntry, - containsObfMethod(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 = entryPool.getMethod(interfaceEntry, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString()); - if (methodInterface != null && containsObfMethod(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) { - AccessFlags flags = getAccessFlags(obfMethodEntry); - if (flags.isPrivate() || flags.isStatic()) { - return Collections.singleton(obfMethodEntry); - } - - Set methodEntries = Sets.newHashSet(); - getRelatedMethodImplementations(methodEntries, getMethodInheritance(new DirectionalTranslator(entryPool), obfMethodEntry)); - return methodEntries; - } - - private void getRelatedMethodImplementations(Set methodEntries, MethodInheritanceTreeNode node) { - MethodEntry methodEntry = node.getMethodEntry(); - if (methodEntries.contains(methodEntry)) { - return; - } - - if (containsObfMethod(methodEntry)) { - AccessFlags flags = getAccessFlags(methodEntry); - if (!flags.isPrivate() && !flags.isStatic()) { - // collect the entry - methodEntries.add(methodEntry); - } - } - - // look at bridge methods! - MethodEntry bridgedMethod = getBridgedMethod(methodEntry); - while (bridgedMethod != null) { - methodEntries.addAll(getRelatedMethodImplementations(bridgedMethod)); - bridgedMethod = getBridgedMethod(bridgedMethod); - } - - // look at interface methods too - for (MethodImplementationsTreeNode implementationsNode : getMethodImplementations(new DirectionalTranslator(entryPool), 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 (containsObfMethod(methodEntry)) { - AccessFlags flags = getAccessFlags(methodEntry); - if (!flags.isPrivate() && !flags.isStatic()) { - // collect the entry - methodEntries.add(methodEntry); - } - } - - // look at bridge methods! - MethodEntry bridgedMethod = getBridgedMethod(methodEntry); - while (bridgedMethod != null) { - methodEntries.addAll(getRelatedMethodImplementations(bridgedMethod)); - bridgedMethod = getBridgedMethod(bridgedMethod); - } - - // 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(MethodDefEntry methodEntry) { - // linear search is fast enough for now - Set fieldEntries = Sets.newHashSet(); - for (EntryReference reference : this.fieldReferences.values()) { - if (reference.context == methodEntry) { - fieldEntries.add(reference.entry); - } - } - return fieldEntries; - } - - public Collection> getMethodsReferencing(ClassEntry classEntry) { - return this.methodsReferencingClasses.get(classEntry); - } - - @Deprecated - public Collection> getMethodsReferencing(MethodEntry methodEntry) { - return getMethodsReferencing(methodEntry, false); - } - - public Collection> getMethodsReferencing(MethodEntry methodEntry, boolean recurse) { - if (!recurse) { - return this.methodsReferencing.get(methodEntry); - } - - List> references = new ArrayList<>(); - Set methodEntries = getRelatedMethodImplementations(methodEntry); - for (MethodEntry entry : methodEntries) { - references.addAll(getMethodsReferencing(entry, false)); - } - return references; - } - - public Collection getReferencedMethods(MethodDefEntry methodEntry) { - return this.methodReferences.get(methodEntry); - } - - public Collection getInnerClasses(ClassEntry obfOuterClassEntry) { - return this.innerClassesByOuter.get(obfOuterClassEntry); - } - - public ClassEntry getOuterClass(ClassEntry obfInnerClassEntry) { - return this.outerClassesByInner.get(obfInnerClassEntry); - } - - public boolean isSyntheticMethod(MethodEntry methodEntry) { - return this.syntheticMethods.contains(methodEntry); - } - - public Set getInterfaces(String className) { - ClassEntry classEntry = entryPool.getClass(className); - Set interfaces = new HashSet<>(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(entryPool.getClass(className)); - } - - public boolean containsObfClass(ClassEntry obfClassEntry) { - return this.obfClassEntries.contains(obfClassEntry); - } - - public boolean containsObfField(FieldEntry obfFieldEntry) { - return this.access.containsKey(obfFieldEntry); - } - - public boolean containsObfMethod(MethodEntry obfMethodEntry) { - return this.access.containsKey(obfMethodEntry); - } - - 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 containsObfVariable(LocalVariableEntry obfVariableEntry) { - // check the behavior - if (!containsObfMethod(obfVariableEntry.getOwnerEntry())) { - return false; - } - - return true; - } - - 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 MethodEntry) { - return containsObfMethod((MethodEntry) obfEntry); - } else if (obfEntry instanceof LocalVariableEntry) { - return containsObfVariable((LocalVariableEntry) obfEntry); - } else { - throw new Error("Entry desc 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 4b47c5f..e4b0304 100644 --- a/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java +++ b/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java @@ -12,24 +12,28 @@ package cuchaz.enigma.analysis; import com.google.common.collect.Lists; -import cuchaz.enigma.mapping.entry.ClassEntry; -import cuchaz.enigma.mapping.entry.MethodEntry; -import cuchaz.enigma.mapping.Translator; +import cuchaz.enigma.analysis.index.EntryIndex; +import cuchaz.enigma.analysis.index.InheritanceIndex; +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; import javax.swing.tree.DefaultMutableTreeNode; +import java.util.Collection; import java.util.List; public class MethodImplementationsTreeNode extends DefaultMutableTreeNode { - private Translator deobfuscatingTranslator; + private final Translator translator; private MethodEntry entry; - public MethodImplementationsTreeNode(Translator deobfuscatingTranslator, MethodEntry entry) { + public MethodImplementationsTreeNode(Translator translator, MethodEntry entry) { + this.translator = translator; if (entry == null) { throw new IllegalArgumentException("Entry cannot be null!"); } - this.deobfuscatingTranslator = deobfuscatingTranslator; this.entry = entry; } @@ -53,35 +57,25 @@ public class MethodImplementationsTreeNode extends DefaultMutableTreeNode { return this.entry; } - public String getDeobfClassName() { - return this.deobfuscatingTranslator.getTranslatedClass(this.entry.getOwnerClassEntry()).getClassName(); - } - - public String getDeobfMethodName() { - return this.deobfuscatingTranslator.getTranslatedMethod(this.entry).getName(); - } - @Override public String toString() { - String className = getDeobfClassName(); - if (className == null) { - className = this.entry.getClassName(); - } - - String methodName = getDeobfMethodName(); - if (methodName == null) { - methodName = this.entry.getName(); - } + MethodEntry translatedEntry = translator.translate(entry); + String className = translatedEntry.getParent().getFullName(); + String methodName = translatedEntry.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.getDesc()); - if (index.containsObfMethod(methodEntry)) { - nodes.add(new MethodImplementationsTreeNode(this.deobfuscatingTranslator, methodEntry)); + EntryIndex entryIndex = index.getEntryIndex(); + InheritanceIndex inheritanceIndex = index.getInheritanceIndex(); + + Collection inheritors = inheritanceIndex.getChildren(entry.getParent()); + for (ClassEntry inheritor : inheritors) { + MethodEntry methodEntry = entry.withParent(inheritor); + if (entryIndex.hasMethod(methodEntry)) { + nodes.add(new MethodImplementationsTreeNode(translator, methodEntry)); } } diff --git a/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java index dc34197..f0fd1d2 100644 --- a/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java +++ b/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java @@ -12,21 +12,24 @@ package cuchaz.enigma.analysis; import com.google.common.collect.Lists; -import cuchaz.enigma.mapping.entry.ClassEntry; -import cuchaz.enigma.mapping.entry.MethodEntry; -import cuchaz.enigma.mapping.Translator; +import cuchaz.enigma.analysis.index.EntryIndex; +import cuchaz.enigma.analysis.index.InheritanceIndex; +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; import javax.swing.tree.DefaultMutableTreeNode; import java.util.List; public class MethodInheritanceTreeNode extends DefaultMutableTreeNode { - private Translator deobfuscatingTranslator; + private final Translator translator; private MethodEntry entry; private boolean isImplemented; - public MethodInheritanceTreeNode(Translator deobfuscatingTranslator, MethodEntry entry, boolean isImplemented) { - this.deobfuscatingTranslator = deobfuscatingTranslator; + public MethodInheritanceTreeNode(Translator translator, MethodEntry entry, boolean isImplemented) { + this.translator = translator; this.entry = entry; this.isImplemented = isImplemented; } @@ -51,32 +54,19 @@ public class MethodInheritanceTreeNode extends DefaultMutableTreeNode { return this.entry; } - public String getDeobfClassName() { - return this.deobfuscatingTranslator.getTranslatedClass(this.entry.getOwnerClassEntry()).getName(); - } - - public String getDeobfMethodName() { - return this.deobfuscatingTranslator.getTranslatedMethod(this.entry).getName(); - } - public boolean isImplemented() { return this.isImplemented; } @Override public String toString() { - String className = getDeobfClassName(); - if (className == null) { - className = this.entry.getClassName(); - } + MethodEntry translatedEntry = translator.translate(entry); + String className = translatedEntry.getContainingClass().getFullName(); if (!this.isImplemented) { return className; } else { - String methodName = getDeobfMethodName(); - if (methodName == null) { - methodName = this.entry.getName(); - } + String methodName = translatedEntry.getName(); return className + "." + methodName + "()"; } } @@ -84,14 +74,12 @@ public class MethodInheritanceTreeNode extends DefaultMutableTreeNode { public void load(JarIndex index, boolean recurse) { // get all the child nodes List nodes = Lists.newArrayList(); - for (ClassEntry subclassEntry : index.getTranslationIndex().getSubclass(this.entry.getOwnerClassEntry())) { - MethodEntry methodEntry = new MethodEntry(subclassEntry, this.entry.getName(), this.entry.getDesc()); - nodes.add(new MethodInheritanceTreeNode(this.deobfuscatingTranslator, methodEntry, index.containsObfMethod(methodEntry))); - } + EntryIndex entryIndex = index.getEntryIndex(); + InheritanceIndex inheritanceIndex = index.getInheritanceIndex(); - for (ClassEntry subclassEntry : index.getTranslationIndex().getImplementers(this.entry.getOwnerClassEntry())) { - MethodEntry methodEntry = new MethodEntry(subclassEntry, this.entry.getName(), this.entry.getDesc()); - nodes.add(new MethodInheritanceTreeNode(this.deobfuscatingTranslator, methodEntry, index.containsObfMethod(methodEntry))); + for (ClassEntry inheritorEntry : inheritanceIndex.getChildren(this.entry.getParent())) { + MethodEntry methodEntry = new MethodEntry(inheritorEntry, this.entry.getName(), this.entry.getDesc()); + nodes.add(new MethodInheritanceTreeNode(translator, methodEntry, entryIndex.hasMethod(methodEntry))); } // add them to this node diff --git a/src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java index ac05acd..8995eb5 100644 --- a/src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java +++ b/src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java @@ -12,36 +12,36 @@ package cuchaz.enigma.analysis; import com.google.common.collect.Sets; -import cuchaz.enigma.bytecode.AccessFlags; -import cuchaz.enigma.mapping.*; -import cuchaz.enigma.mapping.entry.Entry; -import cuchaz.enigma.mapping.entry.MethodDefEntry; -import cuchaz.enigma.mapping.entry.MethodEntry; +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.analysis.index.ReferenceIndex; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.representation.entry.Entry; +import cuchaz.enigma.translation.representation.entry.MethodDefEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreeNode; +import java.util.ArrayList; +import java.util.Collection; import java.util.Set; -public class MethodReferenceTreeNode extends DefaultMutableTreeNode - implements ReferenceTreeNode { +public class MethodReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode { - private Translator deobfuscatingTranslator; + private final Translator translator; private MethodEntry entry; private EntryReference reference; - private AccessFlags access; - public MethodReferenceTreeNode(Translator deobfuscatingTranslator, MethodEntry entry) { - this.deobfuscatingTranslator = deobfuscatingTranslator; + public MethodReferenceTreeNode(Translator translator, MethodEntry entry) { + this.translator = translator; this.entry = entry; this.reference = null; } - public MethodReferenceTreeNode(Translator deobfuscatingTranslator, - EntryReference reference, AccessFlags access) { - this.deobfuscatingTranslator = deobfuscatingTranslator; + public MethodReferenceTreeNode(Translator translator, EntryReference reference) { + this.translator = translator; this.entry = reference.entry; this.reference = reference; - this.access = access; } @Override @@ -57,21 +57,17 @@ public class MethodReferenceTreeNode extends DefaultMutableTreeNode @Override public String toString() { if (this.reference != null) { - return String.format("%s (%s)", this.deobfuscatingTranslator.getTranslatedMethodDef(this.reference.context), - this.access); + return String.format("%s", translator.translate(this.reference.context)); } - return this.deobfuscatingTranslator.getTranslatedMethod(this.entry).getName(); - } - - @Deprecated - public void load(JarIndex index, boolean recurse) { - load(index, recurse, false); + return translator.translate(this.entry).getName(); } public void load(JarIndex index, boolean recurse, boolean recurseMethod) { // get all the child nodes - for (EntryReference reference : index.getMethodsReferencing(this.entry, recurseMethod)) { - add(new MethodReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccessFlags(this.entry))); + Collection> references = getReferences(index, recurseMethod); + + for (EntryReference reference : references) { + add(new MethodReferenceTreeNode(translator, reference)); } if (recurse && this.children != null) { @@ -80,7 +76,7 @@ public class MethodReferenceTreeNode extends DefaultMutableTreeNode MethodReferenceTreeNode node = (MethodReferenceTreeNode) child; // don't recurse into ancestor - Set ancestors = Sets.newHashSet(); + Set> ancestors = Sets.newHashSet(); TreeNode n = node; while (n.getParent() != null) { n = n.getParent(); @@ -92,9 +88,26 @@ public class MethodReferenceTreeNode extends DefaultMutableTreeNode continue; } - node.load(index, true); + node.load(index, true, false); } } } } + + private Collection> getReferences(JarIndex index, boolean recurseMethod) { + ReferenceIndex referenceIndex = index.getReferenceIndex(); + + if (recurseMethod) { + Collection> references = new ArrayList<>(); + + EntryResolver entryResolver = index.getEntryResolver(); + for (MethodEntry methodEntry : entryResolver.resolveEquivalentMethods(entry)) { + references.addAll(referenceIndex.getReferencesToMethod(methodEntry)); + } + + return references; + } else { + return referenceIndex.getReferencesToMethod(entry); + } + } } diff --git a/src/main/java/cuchaz/enigma/analysis/ParsedJar.java b/src/main/java/cuchaz/enigma/analysis/ParsedJar.java index 86655d0..ad3aceb 100644 --- a/src/main/java/cuchaz/enigma/analysis/ParsedJar.java +++ b/src/main/java/cuchaz/enigma/analysis/ParsedJar.java @@ -12,11 +12,12 @@ package cuchaz.enigma.analysis; import com.google.common.io.ByteStreams; -import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.ClassEntry; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.tree.ClassNode; +import javax.annotation.Nullable; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; @@ -100,9 +101,14 @@ public class ParsedJar { return entries; } + @Nullable public ClassNode getClassNode(String name) { return nodeCache.computeIfAbsent(name, (n) -> { - ClassReader reader = new ClassReader(classBytes.get(name)); + byte[] bytes = classBytes.get(name); + if (bytes == null) { + return null; + } + ClassReader reader = new ClassReader(bytes); ClassNode node = new ClassNode(); reader.accept(node, 0); return node; diff --git a/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java index 3950d16..c0a3a75 100644 --- a/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java +++ b/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java @@ -11,9 +11,9 @@ package cuchaz.enigma.analysis; -import cuchaz.enigma.mapping.entry.Entry; +import cuchaz.enigma.translation.representation.entry.Entry; -public interface ReferenceTreeNode { +public interface ReferenceTreeNode, C extends Entry> { E getEntry(); EntryReference getReference(); diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndex.java b/src/main/java/cuchaz/enigma/analysis/SourceIndex.java index 3e0d66b..abdec92 100644 --- a/src/main/java/cuchaz/enigma/analysis/SourceIndex.java +++ b/src/main/java/cuchaz/enigma/analysis/SourceIndex.java @@ -11,17 +11,15 @@ 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.google.common.collect.*; import com.strobel.decompiler.languages.Region; import com.strobel.decompiler.languages.java.ast.AstNode; import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration; import com.strobel.decompiler.languages.java.ast.Identifier; import com.strobel.decompiler.languages.java.ast.TypeDeclaration; -import cuchaz.enigma.mapping.entry.Entry; +import cuchaz.enigma.translation.representation.entry.Entry; +import javax.annotation.Nullable; import java.util.*; import java.util.regex.Pattern; @@ -29,9 +27,9 @@ public class SourceIndex { private static Pattern ANONYMOUS_INNER = Pattern.compile("\\$\\d+$"); private String source; - private TreeMap> tokenToReference; - private Multimap, Token> referenceToTokens; - private Map declarationToToken; + private TreeMap, Entry>> tokenToReference; + private Multimap, Entry>, Token> referenceToTokens; + private Map, Token> declarationToToken; private List lineOffsets; private boolean ignoreBadTokens; @@ -42,7 +40,7 @@ public class SourceIndex { public SourceIndex(String source, boolean ignoreBadTokens) { this.source = source; this.ignoreBadTokens = ignoreBadTokens; - this.tokenToReference = Maps.newTreeMap(); + this.tokenToReference = new TreeMap<>(); this.referenceToTokens = HashMultimap.create(); this.declarationToToken = Maps.newHashMap(); calculateLineOffsets(); @@ -63,12 +61,12 @@ public class SourceIndex { this.source = source; calculateLineOffsets(); - for (Entry entry : Lists.newArrayList(declarationToToken.keySet())) { + for (Entry entry : Lists.newArrayList(declarationToToken.keySet())) { Token token = declarationToToken.get(entry); declarationToToken.put(entry, tokenMap.getOrDefault(token, token)); } - for (EntryReference ref : referenceToTokens.keySet()) { + for (EntryReference, Entry> ref : referenceToTokens.keySet()) { Collection oldTokens = referenceToTokens.get(ref); List newTokens = new ArrayList<>(oldTokens.size()); @@ -79,7 +77,8 @@ public class SourceIndex { referenceToTokens.replaceValues(ref, newTokens); } - Map> tokenToReferenceCopy = Maps.newHashMap(tokenToReference); + TreeMap, Entry>> tokenToReferenceCopy = new TreeMap<>(tokenToReference); + tokenToReference.clear(); for (Token token : tokenToReferenceCopy.keySet()) { tokenToReference.put(tokenMap.getOrDefault(token, token), tokenToReferenceCopy.get(token)); @@ -112,9 +111,9 @@ public class SourceIndex { return null; } - if (node instanceof Identifier && name.indexOf('$') >=0 && node.getParent() instanceof ConstructorDeclaration && name.lastIndexOf('$') >= 0 && !ANONYMOUS_INNER.matcher(name).matches()){ + if (node instanceof Identifier && name.indexOf('$') >= 0 && node.getParent() instanceof ConstructorDeclaration && name.lastIndexOf('$') >= 0 && !ANONYMOUS_INNER.matcher(name).matches()) { TypeDeclaration type = node.getParent().getParent() instanceof TypeDeclaration ? (TypeDeclaration) node.getParent().getParent() : null; - if (type != null){ + if (type != null) { name = type.getName(); token.end = token.start + name.length(); } @@ -133,19 +132,19 @@ public class SourceIndex { return token; } - public void addReference(AstNode node, Entry deobfEntry, Entry deobfContext) { + public void addReference(AstNode node, Entry deobfEntry, Entry deobfContext) { Token token = getToken(node); if (token != null) { - EntryReference deobfReference = new EntryReference<>(deobfEntry, token.text, deobfContext); + EntryReference, Entry> deobfReference = new EntryReference<>(deobfEntry, token.text, deobfContext); this.tokenToReference.put(token, deobfReference); this.referenceToTokens.put(deobfReference, token); } } - public void addDeclaration(AstNode node, Entry deobfEntry) { + public void addDeclaration(AstNode node, Entry deobfEntry) { Token token = getToken(node); if (token != null) { - EntryReference reference = new EntryReference<>(deobfEntry, token.text); + EntryReference, Entry> reference = new EntryReference<>(deobfEntry, token.text); this.tokenToReference.put(token, reference); this.referenceToTokens.put(reference, token); this.declarationToToken.put(deobfEntry, token); @@ -160,22 +159,22 @@ public class SourceIndex { return null; } - public Collection getReferenceTokens(EntryReference deobfReference) { + public Collection getReferenceTokens(EntryReference, Entry> deobfReference) { return this.referenceToTokens.get(deobfReference); } - public EntryReference getDeobfReference(Token token) { + @Nullable + public EntryReference, Entry> 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); + public void replaceDeobfReference(Token token, EntryReference, Entry> newDeobfReference) { + EntryReference, Entry> oldDeobfReferences = this.tokenToReference.replace(token, newDeobfReference); + + Collection tokens = this.referenceToTokens.removeAll(oldDeobfReferences); this.referenceToTokens.putAll(newDeobfReference, tokens); } @@ -187,11 +186,11 @@ public class SourceIndex { return this.declarationToToken.values(); } - public Iterable declarations() { + public Iterable> declarations() { return this.declarationToToken.keySet(); } - public Token getDeclarationToken(Entry deobfEntry) { + public Token getDeclarationToken(Entry deobfEntry) { return this.declarationToToken.get(deobfEntry); } diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java b/src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java index cad0857..486603c 100644 --- a/src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java +++ b/src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java @@ -17,9 +17,12 @@ import com.strobel.assembler.metadata.TypeDefinition; import com.strobel.assembler.metadata.TypeReference; import com.strobel.decompiler.languages.TextLocation; import com.strobel.decompiler.languages.java.ast.*; -import cuchaz.enigma.bytecode.AccessFlags; -import cuchaz.enigma.mapping.Signature; -import cuchaz.enigma.mapping.entry.*; +import cuchaz.enigma.translation.representation.ProcyonEntryFactory; +import cuchaz.enigma.translation.representation.ReferencedEntryPool; +import cuchaz.enigma.translation.representation.entry.ClassDefEntry; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.FieldDefEntry; +import cuchaz.enigma.translation.representation.entry.MethodDefEntry; public class SourceIndexClassVisitor extends SourceIndexVisitor { private final ReferencedEntryPool entryPool; @@ -37,7 +40,7 @@ public class SourceIndexClassVisitor extends SourceIndexVisitor { public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) { // is this this class, or a subtype? TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION); - ClassDefEntry classEntry = new ClassDefEntry(def.getInternalName(), Signature.createSignature(def.getSignature()), new AccessFlags(def.getModifiers())); + ClassDefEntry classEntry = ClassDefEntry.parse(def); if (!classEntry.equals(this.classEntry)) { // it's a subtype, recurse index.addDeclaration(node.getNameToken(), classEntry); @@ -68,7 +71,7 @@ public class SourceIndexClassVisitor extends SourceIndexVisitor { tokenNode = node.getModifiers().firstOrNullObject(); } index.addDeclaration(tokenNode, methodEntry); - return node.acceptVisitor(new SourceIndexMethodVisitor(entryPool, classEntry, methodEntry), index); + return node.acceptVisitor(new SourceIndexMethodVisitor(entryPool, methodEntry), index); } @Override @@ -76,7 +79,7 @@ public class SourceIndexClassVisitor extends SourceIndexVisitor { MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION); MethodDefEntry methodEntry = entryFactory.getMethodDefEntry(def); index.addDeclaration(node.getNameToken(), methodEntry); - return node.acceptVisitor(new SourceIndexMethodVisitor(entryPool, classEntry, methodEntry), index); + return node.acceptVisitor(new SourceIndexMethodVisitor(entryPool, methodEntry), index); } @Override diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndexMethodVisitor.java b/src/main/java/cuchaz/enigma/analysis/SourceIndexMethodVisitor.java index 139fcea..73db28f 100644 --- a/src/main/java/cuchaz/enigma/analysis/SourceIndexMethodVisitor.java +++ b/src/main/java/cuchaz/enigma/analysis/SourceIndexMethodVisitor.java @@ -17,8 +17,9 @@ import com.strobel.assembler.metadata.*; import com.strobel.decompiler.ast.Variable; import com.strobel.decompiler.languages.TextLocation; import com.strobel.decompiler.languages.java.ast.*; -import cuchaz.enigma.mapping.TypeDescriptor; -import cuchaz.enigma.mapping.entry.*; +import cuchaz.enigma.translation.representation.TypeDescriptor; +import cuchaz.enigma.translation.representation.*; +import cuchaz.enigma.translation.representation.entry.*; import java.lang.Error; import java.util.HashMap; @@ -26,19 +27,15 @@ import java.util.Map; public class SourceIndexMethodVisitor extends SourceIndexVisitor { private final ReferencedEntryPool entryPool; - private final ProcyonEntryFactory entryFactory; - private final ClassDefEntry ownerEntry; private final MethodDefEntry methodEntry; private Multimap unmatchedIdentifier = HashMultimap.create(); - private Map identifierEntryCache = new HashMap<>(); + private Map> identifierEntryCache = new HashMap<>(); - public SourceIndexMethodVisitor(ReferencedEntryPool entryPool, ClassDefEntry ownerEntry, MethodDefEntry methodEntry) { + public SourceIndexMethodVisitor(ReferencedEntryPool entryPool, MethodDefEntry methodEntry) { super(entryPool); this.entryPool = entryPool; - this.entryFactory = new ProcyonEntryFactory(entryPool); - this.ownerEntry = ownerEntry; this.methodEntry = methodEntry; } @@ -86,7 +83,7 @@ public class SourceIndexMethodVisitor extends SourceIndexVisitor { ClassEntry classEntry = entryPool.getClass(ref.getDeclaringType().getInternalName()); FieldEntry fieldEntry = entryPool.getField(classEntry, ref.getName(), new TypeDescriptor(erasedSignature)); if (fieldEntry == null) { - throw new Error("Failed to find field " + ref.getName() + " on " + classEntry.getName()); + throw new Error("Failed to find field " + ref.getName() + " on " + classEntry.getFullName()); } index.addReference(node.getMemberNameToken(), fieldEntry, this.methodEntry); } @@ -128,7 +125,7 @@ public class SourceIndexMethodVisitor extends SourceIndexVisitor { ClassEntry classEntry = entryPool.getClass(ref.getDeclaringType().getInternalName()); FieldEntry fieldEntry = entryPool.getField(classEntry, ref.getName(), new TypeDescriptor(ref.getErasedSignature())); if (fieldEntry == null) { - throw new Error("Failed to find field " + ref.getName() + " on " + classEntry.getName()); + throw new Error("Failed to find field " + ref.getName() + " on " + classEntry.getFullName()); } index.addReference(node.getIdentifierToken(), fieldEntry, this.methodEntry); } else @@ -144,7 +141,7 @@ public class SourceIndexMethodVisitor extends SourceIndexVisitor { } private void addDeclarationToUnmatched(String key, SourceIndex index) { - Entry entry = identifierEntryCache.get(key); + Entry entry = identifierEntryCache.get(key); // This cannot happened in theory if (entry == null) diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java b/src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java index e588d24..564830c 100644 --- a/src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java +++ b/src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java @@ -14,10 +14,8 @@ 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.bytecode.AccessFlags; -import cuchaz.enigma.mapping.Signature; -import cuchaz.enigma.mapping.entry.ClassDefEntry; -import cuchaz.enigma.mapping.entry.ReferencedEntryPool; +import cuchaz.enigma.translation.representation.ReferencedEntryPool; +import cuchaz.enigma.translation.representation.entry.ClassDefEntry; public class SourceIndexVisitor implements IAstVisitor { private final ReferencedEntryPool entryPool; @@ -29,7 +27,7 @@ public class SourceIndexVisitor implements IAstVisitor { @Override public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) { TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION); - ClassDefEntry classEntry = new ClassDefEntry(def.getInternalName(), Signature.createSignature(def.getSignature()), new AccessFlags(def.getModifiers())); + ClassDefEntry classEntry = ClassDefEntry.parse(def); index.addDeclaration(node.getNameToken(), classEntry); return node.acceptVisitor(new SourceIndexClassVisitor(entryPool, classEntry), index); diff --git a/src/main/java/cuchaz/enigma/analysis/TranslationIndex.java b/src/main/java/cuchaz/enigma/analysis/TranslationIndex.java deleted file mode 100644 index 984d84b..0000000 --- a/src/main/java/cuchaz/enigma/analysis/TranslationIndex.java +++ /dev/null @@ -1,275 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * 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.analysis; - -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -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 cuchaz.enigma.bytecode.AccessFlags; -import cuchaz.enigma.mapping.*; -import cuchaz.enigma.mapping.entry.*; - -import java.util.*; - -public class TranslationIndex { - - private final ReferencedEntryPool entryPool; - private Map superclasses; - private Map defEntries = new HashMap<>(); - private Multimap fieldEntries; - private Multimap methodEntries; - private Multimap interfaces; - - public TranslationIndex(ReferencedEntryPool entryPool) { - this.entryPool = entryPool; - this.superclasses = Maps.newHashMap(); - this.fieldEntries = HashMultimap.create(); - this.methodEntries = HashMultimap.create(); - this.interfaces = HashMultimap.create(); - - for (FieldDefEntry entry : fieldEntries.values()) { - defEntries.put(entry, entry); - } - - for (MethodDefEntry entry : methodEntries.values()) { - defEntries.put(entry, entry); - } - } - - public TranslationIndex(TranslationIndex other, Translator translator) { - this.entryPool = other.entryPool; - - // translate the superclasses - this.superclasses = Maps.newHashMap(); - for (Map.Entry mapEntry : other.superclasses.entrySet()) { - this.superclasses.put(translator.getTranslatedClass(mapEntry.getKey()), translator.getTranslatedClass(mapEntry.getValue())); - } - - // translate the interfaces - this.interfaces = HashMultimap.create(); - for (Map.Entry mapEntry : other.interfaces.entries()) { - this.interfaces.put( - translator.getTranslatedClass(mapEntry.getKey()), - translator.getTranslatedClass(mapEntry.getValue()) - ); - } - - // translate the fields - this.fieldEntries = HashMultimap.create(); - for (Map.Entry mapEntry : other.fieldEntries.entries()) { - this.fieldEntries.put( - translator.getTranslatedClass(mapEntry.getKey()), - translator.getTranslatedFieldDef(mapEntry.getValue()) - ); - } - - this.methodEntries = HashMultimap.create(); - for (Map.Entry mapEntry : other.methodEntries.entries()) { - this.methodEntries.put( - translator.getTranslatedClass(mapEntry.getKey()), - translator.getTranslatedMethodDef(mapEntry.getValue()) - ); - } - - for (FieldDefEntry entry : fieldEntries.values()) { - defEntries.put(entry, entry); - } - - for (MethodDefEntry entry : methodEntries.values()) { - defEntries.put(entry, entry); - } - } - - protected ClassDefEntry indexClass(int access, String name, String signature, String superName, String[] interfaces) { - ClassDefEntry classEntry = new ClassDefEntry(name, Signature.createSignature(signature), new AccessFlags(access)); - if (isJre(classEntry)) { - return null; - } - - // add the superclass - ClassEntry superclassEntry = entryPool.getClass(superName); - if (superclassEntry != null) { - this.superclasses.put(classEntry, superclassEntry); - } - - // add the interfaces - for (String interfaceClassName : interfaces) { - ClassEntry interfaceClassEntry = entryPool.getClass(interfaceClassName); - if (!isJre(interfaceClassEntry)) { - this.interfaces.put(classEntry, interfaceClassEntry); - } - } - - return classEntry; - } - - protected void indexField(FieldDefEntry fieldEntry) { - this.fieldEntries.put(fieldEntry.getOwnerClassEntry(), fieldEntry); - this.defEntries.put(fieldEntry, fieldEntry); - } - - protected void indexMethod(MethodDefEntry methodEntry) { - this.methodEntries.put(methodEntry.getOwnerClassEntry(), methodEntry); - this.defEntries.put(methodEntry, methodEntry); - } - - public void renameClasses(Map renames) { - EntryRenamer.renameClassesInMap(renames, this.superclasses); - EntryRenamer.renameClassesInMultimap(renames, this.fieldEntries); - EntryRenamer.renameClassesInMultimap(renames, this.methodEntries); - - this.defEntries.clear(); - for (FieldDefEntry entry : fieldEntries.values()) { - defEntries.put(entry, entry); - } - - for (MethodDefEntry entry : methodEntries.values()) { - defEntries.put(entry, entry); - } - } - - 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 getImplementers(ClassEntry classEntry) { - // linear search is fast enough for now - List implementers = Lists.newArrayList(); - for (ClassEntry itf : this.interfaces.keySet()) { - if (this.interfaces.containsEntry(itf, classEntry)) { - implementers.add(itf); - } - } - return implementers; - } - - 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 == null) { - return false; - } - - if (entry instanceof FieldEntry) { - return fieldExists((FieldEntry) entry); - } else if (entry instanceof MethodEntry) { - return methodExists((MethodEntry) entry); - } else if (entry instanceof LocalVariableEntry) { - return methodExists(((LocalVariableEntry) entry).getOwnerEntry()); - } - throw new IllegalArgumentException("Cannot check existence for " + entry.getClass()); - } - - public boolean fieldExists(FieldEntry fieldEntry) { - return this.fieldEntries.containsEntry(fieldEntry.getOwnerClassEntry(), fieldEntry); - } - - public boolean methodExists(MethodEntry methodEntry) { - return this.methodEntries.containsEntry(methodEntry.getOwnerClassEntry(), methodEntry); - } - - public ClassEntry resolveEntryOwner(Entry entry) { - if (entry instanceof ClassEntry) { - return (ClassEntry) entry; - } - - if (entryExists(entry)) { - return entry.getOwnerClassEntry(); - } - - DefEntry def = defEntries.get(entry); - if (def != null && (def.getAccess().isPrivate())) { - return null; - } - - // if we're protected/public/non-static, chances are we're somewhere down - LinkedList classEntries = new LinkedList<>(); - classEntries.add(entry.getOwnerClassEntry()); - while (!classEntries.isEmpty()) { - ClassEntry c = classEntries.remove(); - Entry cEntry = entry.updateOwnership(c); - - if (entryExists(cEntry)) { - def = defEntries.get(cEntry); - if (def == null || (!def.getAccess().isPrivate())) { - return cEntry.getOwnerClassEntry(); - } - } - - ClassEntry superC = getSuperclass(c); - if (superC != null) { - classEntries.add(superC); - } - if (entry instanceof MethodEntry) { - classEntries.addAll(getInterfaces(c)); - } - } - - 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/index/BridgeMethodIndex.java b/src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java new file mode 100644 index 0000000..e1903d9 --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java @@ -0,0 +1,77 @@ +package cuchaz.enigma.analysis.index; + +import com.google.common.collect.Maps; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.representation.AccessFlags; +import cuchaz.enigma.translation.representation.entry.MethodEntry; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.Map; + +public class BridgeMethodIndex implements JarIndexer, RemappableIndex { + private final EntryIndex entryIndex; + private final ReferenceIndex referenceIndex; + + private Map accessedToBridge = Maps.newHashMap(); + + public BridgeMethodIndex(EntryIndex entryIndex, ReferenceIndex referenceIndex) { + this.entryIndex = entryIndex; + this.referenceIndex = referenceIndex; + } + + @Override + public void remap(Translator translator) { + accessedToBridge = translator.translate(accessedToBridge); + } + + @Override + public BridgeMethodIndex remapped(Translator translator) { + BridgeMethodIndex index = new BridgeMethodIndex(entryIndex, referenceIndex); + index.accessedToBridge = translator.translate(accessedToBridge); + + return index; + } + + @Override + public void processIndex(EntryResolver resolver) { + // look for access and bridged methods + for (MethodEntry methodEntry : entryIndex.getMethods()) { + AccessFlags access = entryIndex.getMethodAccess(methodEntry); + if (access == null || !access.isSynthetic()) { + continue; + } + + indexSyntheticMethod(methodEntry, access); + } + } + + private void indexSyntheticMethod(MethodEntry syntheticMethod, AccessFlags access) { + if (access.isBridge()) { + MethodEntry accessedMethod = findAccessMethod(syntheticMethod); + if (accessedMethod != null) { + accessedToBridge.put(accessedMethod, syntheticMethod); + } + } + } + + private MethodEntry findAccessMethod(MethodEntry method) { + // we want to find all compiler-added methods that directly call another with no processing + + // get all the methods that we call + final Collection referencedMethods = referenceIndex.getMethodsReferencedBy(method); + + // is there just one? + if (referencedMethods.size() != 1) { + return null; + } + + return referencedMethods.stream().findFirst().orElse(null); + } + + @Nullable + public MethodEntry getBridgeFromAccessed(MethodEntry entry) { + return accessedToBridge.get(entry); + } +} diff --git a/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java b/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java new file mode 100644 index 0000000..55bfbc2 --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java @@ -0,0 +1,109 @@ +package cuchaz.enigma.analysis.index; + +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.representation.AccessFlags; +import cuchaz.enigma.translation.representation.entry.*; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +public class EntryIndex implements JarIndexer, RemappableIndex { + private Map classes = new HashMap<>(); + private Map fields = new HashMap<>(); + private Map methods = new HashMap<>(); + + @Override + public void remap(Translator translator) { + classes = translator.translateKeys(classes); + fields = translator.translateKeys(fields); + methods = translator.translateKeys(methods); + } + + @Override + public EntryIndex remapped(Translator translator) { + EntryIndex index = new EntryIndex(); + index.classes = translator.translateKeys(classes); + index.fields = translator.translateKeys(fields); + index.methods = translator.translateKeys(methods); + + return index; + } + + @Override + public void indexClass(ClassDefEntry classEntry) { + classes.put(classEntry, classEntry.getAccess()); + } + + @Override + public void indexMethod(MethodDefEntry methodEntry) { + methods.put(methodEntry, methodEntry.getAccess()); + } + + @Override + public void indexField(FieldDefEntry fieldEntry) { + fields.put(fieldEntry, fieldEntry.getAccess()); + } + + public boolean hasClass(ClassEntry entry) { + return classes.containsKey(entry); + } + + public boolean hasMethod(MethodEntry entry) { + return methods.containsKey(entry); + } + + public boolean hasField(FieldEntry entry) { + return fields.containsKey(entry); + } + + public boolean hasEntry(Entry entry) { + if (entry instanceof ClassEntry) { + return hasClass((ClassEntry) entry); + } else if (entry instanceof MethodEntry) { + return hasMethod((MethodEntry) entry); + } else if (entry instanceof FieldEntry) { + return hasField((FieldEntry) entry); + } else if (entry instanceof LocalVariableEntry) { + return hasMethod(((LocalVariableEntry) entry).getParent()); + } + + return false; + } + + @Nullable + public AccessFlags getMethodAccess(MethodEntry entry) { + return methods.get(entry); + } + + @Nullable + public AccessFlags getFieldAccess(FieldEntry entry) { + return fields.get(entry); + } + + @Nullable + public AccessFlags getEntryAccess(Entry entry) { + if (entry instanceof MethodEntry) { + return getMethodAccess((MethodEntry) entry); + } else if (entry instanceof FieldEntry) { + return getFieldAccess((FieldEntry) entry); + } else if (entry instanceof LocalVariableEntry) { + return getMethodAccess(((LocalVariableEntry) entry).getParent()); + } + + return null; + } + + public Collection getClasses() { + return classes.keySet(); + } + + public Collection getMethods() { + return methods.keySet(); + } + + public Collection getFields() { + return fields.keySet(); + } +} diff --git a/src/main/java/cuchaz/enigma/analysis/index/IndexClassVisitor.java b/src/main/java/cuchaz/enigma/analysis/index/IndexClassVisitor.java new file mode 100644 index 0000000..f9cb23c --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/index/IndexClassVisitor.java @@ -0,0 +1,40 @@ +package cuchaz.enigma.analysis.index; + +import cuchaz.enigma.translation.representation.entry.ClassDefEntry; +import cuchaz.enigma.translation.representation.entry.FieldDefEntry; +import cuchaz.enigma.translation.representation.entry.MethodDefEntry; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; + +public class IndexClassVisitor extends ClassVisitor { + private final JarIndexer indexer; + private ClassDefEntry classEntry; + + public IndexClassVisitor(JarIndex indexer, int api) { + super(api); + this.indexer = indexer; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + classEntry = ClassDefEntry.parse(access, name, signature, superName, interfaces); + indexer.indexClass(classEntry); + + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { + indexer.indexField(FieldDefEntry.parse(classEntry, access, name, desc, signature)); + + return super.visitField(access, name, desc, signature, value); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + indexer.indexMethod(MethodDefEntry.parse(classEntry, access, name, desc, signature)); + + return super.visitMethod(access, name, desc, signature, exceptions); + } +} diff --git a/src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java b/src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java new file mode 100644 index 0000000..ba5d3b6 --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java @@ -0,0 +1,83 @@ +package cuchaz.enigma.analysis.index; + +import cuchaz.enigma.translation.representation.AccessFlags; +import cuchaz.enigma.translation.representation.MethodDescriptor; +import cuchaz.enigma.translation.representation.Signature; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.FieldEntry; +import cuchaz.enigma.translation.representation.entry.MethodDefEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Handle; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +public class IndexReferenceVisitor extends ClassVisitor { + private final JarIndexer indexer; + private ClassEntry classEntry; + + public IndexReferenceVisitor(JarIndexer indexer, int api) { + super(api); + this.indexer = indexer; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + this.classEntry = new ClassEntry(name); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + MethodDefEntry entry = new MethodDefEntry(classEntry, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access)); + return new Method(this.indexer, entry, this.api); + } + + private static class Method extends MethodVisitor { + private final JarIndexer indexer; + private final MethodDefEntry callerEntry; + + public Method(JarIndexer indexer, MethodDefEntry callerEntry, int api) { + super(api); + this.indexer = indexer; + this.callerEntry = callerEntry; + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + FieldEntry fieldEntry = FieldEntry.parse(owner, name, desc); + this.indexer.indexFieldReference(callerEntry, fieldEntry); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + MethodEntry methodEntry = MethodEntry.parse(owner, name, desc); + this.indexer.indexMethodReference(callerEntry, methodEntry); + } + + @Override + public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { + for (Object bsmArg : bsmArgs) { + if (bsmArg instanceof Handle) { + Handle handle = (Handle) bsmArg; + switch (handle.getTag()) { + case Opcodes.H_GETFIELD: + case Opcodes.H_GETSTATIC: + case Opcodes.H_PUTFIELD: + case Opcodes.H_PUTSTATIC: + FieldEntry fieldEntry = FieldEntry.parse(handle.getOwner(), handle.getName(), handle.getDesc()); + this.indexer.indexFieldReference(callerEntry, fieldEntry); + break; + case Opcodes.H_INVOKEINTERFACE: + case Opcodes.H_INVOKESPECIAL: + case Opcodes.H_INVOKESTATIC: + case Opcodes.H_INVOKEVIRTUAL: + case Opcodes.H_NEWINVOKESPECIAL: + MethodEntry methodEntry = MethodEntry.parse(handle.getOwner(), handle.getName(), handle.getDesc()); + this.indexer.indexMethodReference(callerEntry, methodEntry); + break; + } + } + } + } + } +} diff --git a/src/main/java/cuchaz/enigma/analysis/index/InheritanceIndex.java b/src/main/java/cuchaz/enigma/analysis/index/InheritanceIndex.java new file mode 100644 index 0000000..d165cc8 --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/index/InheritanceIndex.java @@ -0,0 +1,97 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * 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.analysis.index; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.representation.entry.ClassDefEntry; +import cuchaz.enigma.translation.representation.entry.ClassEntry; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.Set; + +public class InheritanceIndex implements JarIndexer, RemappableIndex { + private Multimap classParents = HashMultimap.create(); + private Multimap classChildren = HashMultimap.create(); + + @Override + public void remap(Translator translator) { + classChildren = translator.translate(classChildren); + classParents = translator.translate(classParents); + } + + @Override + public InheritanceIndex remapped(Translator translator) { + InheritanceIndex index = new InheritanceIndex(); + index.classParents = translator.translate(classParents); + index.classChildren = translator.translate(classChildren); + + return index; + } + + @Override + public void indexClass(ClassDefEntry classEntry) { + ClassEntry superClass = classEntry.getSuperClass(); + if (superClass != null) { + indexParent(classEntry, superClass); + } + + for (ClassEntry interfaceEntry : classEntry.getInterfaces()) { + indexParent(classEntry, interfaceEntry); + } + } + + private void indexParent(ClassEntry childEntry, ClassEntry parentEntry) { + if (childEntry.isJre() || parentEntry.isJre()) { + return; + } + classParents.put(childEntry, parentEntry); + classChildren.put(parentEntry, childEntry); + } + + public Collection getParents(ClassEntry classEntry) { + return classParents.get(classEntry); + } + + public Collection getChildren(ClassEntry classEntry) { + return classChildren.get(classEntry); + } + + public Set getAncestors(ClassEntry classEntry) { + Set ancestors = Sets.newHashSet(); + + LinkedList ancestorQueue = new LinkedList<>(); + ancestorQueue.push(classEntry); + + while (!ancestorQueue.isEmpty()) { + ClassEntry ancestor = ancestorQueue.pop(); + Collection parents = getParents(ancestor); + + parents.forEach(ancestorQueue::push); + ancestors.addAll(parents); + } + + return ancestors; + } + + public boolean isParent(ClassEntry classEntry) { + return classChildren.containsKey(classEntry); + } + + public boolean hasParents(ClassEntry classEntry) { + Collection parents = classParents.get(classEntry); + return parents != null && !parents.isEmpty(); + } +} diff --git a/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java b/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java new file mode 100644 index 0000000..0880244 --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java @@ -0,0 +1,165 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * 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.analysis.index; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import cuchaz.enigma.analysis.ParsedJar; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.mapping.IndexEntryResolver; +import cuchaz.enigma.translation.representation.entry.*; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.Opcodes; + +import java.util.Arrays; +import java.util.Collection; +import java.util.function.Consumer; + +public class JarIndex implements JarIndexer, RemappableIndex { + private final EntryIndex entryIndex; + private final InheritanceIndex inheritanceIndex; + private final ReferenceIndex referenceIndex; + private final BridgeMethodIndex bridgeMethodIndex; + private final EntryResolver entryResolver; + + private final Collection indexers; + + private final Multimap methodImplementations = HashMultimap.create(); + + public JarIndex(EntryIndex entryIndex, InheritanceIndex inheritanceIndex, ReferenceIndex referenceIndex, BridgeMethodIndex bridgeMethodIndex) { + this.entryIndex = entryIndex; + this.inheritanceIndex = inheritanceIndex; + this.referenceIndex = referenceIndex; + this.bridgeMethodIndex = bridgeMethodIndex; + this.indexers = Arrays.asList(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex); + this.entryResolver = new IndexEntryResolver(this); + } + + public static JarIndex empty() { + EntryIndex entryIndex = new EntryIndex(); + InheritanceIndex inheritanceIndex = new InheritanceIndex(); + ReferenceIndex referenceIndex = new ReferenceIndex(); + BridgeMethodIndex bridgeMethodIndex = new BridgeMethodIndex(entryIndex, referenceIndex); + return new JarIndex(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex); + } + + @Override + public void remap(Translator translator) { + entryIndex.remap(translator); + inheritanceIndex.remap(translator); + bridgeMethodIndex.remap(translator); + } + + @Override + public JarIndex remapped(Translator translator) { + EntryIndex entryIndex = this.entryIndex.remapped(translator); + InheritanceIndex inheritanceIndex = this.inheritanceIndex.remapped(translator); + BridgeMethodIndex bridgeMethodIndex = this.bridgeMethodIndex.remapped(translator); + + JarIndex remappedIndex = new JarIndex(entryIndex, inheritanceIndex, this.referenceIndex, bridgeMethodIndex); + remappedIndex.methodImplementations.putAll(methodImplementations); + + return remappedIndex; + } + + public void indexJar(ParsedJar jar, Consumer progress) { + progress.accept("Indexing entries (1/3)"); + jar.visitReader(name -> new IndexClassVisitor(this, Opcodes.ASM5), ClassReader.SKIP_CODE); + + progress.accept("Indexing entry references (2/3)"); + jar.visitReader(name -> new IndexReferenceVisitor(this, Opcodes.ASM5), ClassReader.SKIP_FRAMES); + + progress.accept("Processing index (3/3)"); + processIndex(entryResolver); + } + + @Override + public void processIndex(EntryResolver resolver) { + indexers.forEach(indexer -> indexer.processIndex(entryResolver)); + } + + @Override + public void indexClass(ClassDefEntry classEntry) { + if (classEntry.isJre()) { + return; + } + + for (ClassEntry interfaceEntry : classEntry.getInterfaces()) { + if (classEntry.equals(interfaceEntry)) { + throw new IllegalArgumentException("Class cannot be its own interface! " + classEntry); + } + } + + indexers.forEach(indexer -> indexer.indexClass(classEntry)); + } + + @Override + public void indexField(FieldDefEntry fieldEntry) { + if (fieldEntry.getParent().isJre()) { + return; + } + + indexers.forEach(indexer -> indexer.indexField(fieldEntry)); + } + + @Override + public void indexMethod(MethodDefEntry methodEntry) { + if (methodEntry.getParent().isJre()) { + return; + } + + indexers.forEach(indexer -> indexer.indexMethod(methodEntry)); + + if (!methodEntry.isConstructor()) { + methodImplementations.put(methodEntry.getParent().getFullName(), methodEntry); + } + } + + @Override + public void indexMethodReference(MethodDefEntry callerEntry, MethodEntry referencedEntry) { + if (callerEntry.getParent().isJre()) { + return; + } + + indexers.forEach(indexer -> indexer.indexMethodReference(callerEntry, referencedEntry)); + } + + @Override + public void indexFieldReference(MethodDefEntry callerEntry, FieldEntry referencedEntry) { + if (callerEntry.getParent().isJre()) { + return; + } + + indexers.forEach(indexer -> indexer.indexFieldReference(callerEntry, referencedEntry)); + } + + public EntryIndex getEntryIndex() { + return entryIndex; + } + + public InheritanceIndex getInheritanceIndex() { + return this.inheritanceIndex; + } + + public ReferenceIndex getReferenceIndex() { + return referenceIndex; + } + + public BridgeMethodIndex getBridgeMethodIndex() { + return bridgeMethodIndex; + } + + public EntryResolver getEntryResolver() { + return entryResolver; + } +} diff --git a/src/main/java/cuchaz/enigma/analysis/index/JarIndexer.java b/src/main/java/cuchaz/enigma/analysis/index/JarIndexer.java new file mode 100644 index 0000000..a087e59 --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/index/JarIndexer.java @@ -0,0 +1,24 @@ +package cuchaz.enigma.analysis.index; + +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.representation.entry.*; + +public interface JarIndexer { + default void indexClass(ClassDefEntry classEntry) { + } + + default void indexField(FieldDefEntry fieldEntry) { + } + + default void indexMethod(MethodDefEntry methodEntry) { + } + + default void indexMethodReference(MethodDefEntry callerEntry, MethodEntry referencedEntry) { + } + + default void indexFieldReference(MethodDefEntry callerEntry, FieldEntry referencedEntry) { + } + + default void processIndex(EntryResolver resolver) { + } +} diff --git a/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java b/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java new file mode 100644 index 0000000..ac11da4 --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java @@ -0,0 +1,83 @@ +package cuchaz.enigma.analysis.index; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import cuchaz.enigma.analysis.EntryReference; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.mapping.ResolutionStrategy; +import cuchaz.enigma.translation.representation.entry.*; + +import java.util.Collection; +import java.util.Map; + +public class ReferenceIndex implements JarIndexer { + private Multimap methodReferences = HashMultimap.create(); + + private Multimap> referencesToMethods = HashMultimap.create(); + private Multimap> referencesToClasses = HashMultimap.create(); + private Multimap> referencesToFields = HashMultimap.create(); + + @Override + public void indexMethodReference(MethodDefEntry callerEntry, MethodEntry referencedEntry) { + referencesToMethods.put(referencedEntry, new EntryReference<>(referencedEntry, referencedEntry.getName(), callerEntry)); + methodReferences.put(callerEntry, referencedEntry); + + if (referencedEntry.isConstructor()) { + ClassEntry referencedClass = referencedEntry.getParent(); + referencesToClasses.put(referencedClass, new EntryReference<>(referencedClass, referencedEntry.getName(), callerEntry)); + } + } + + @Override + public void indexFieldReference(MethodDefEntry callerEntry, FieldEntry referencedEntry) { + referencesToFields.put(referencedEntry, new EntryReference<>(referencedEntry, referencedEntry.getName(), callerEntry)); + } + + @Override + public void processIndex(EntryResolver resolver) { + methodReferences = resolveReferences(resolver, methodReferences); + referencesToMethods = resolveReferencesTo(resolver, referencesToMethods); + referencesToClasses = resolveReferencesTo(resolver, referencesToClasses); + referencesToFields = resolveReferencesTo(resolver, referencesToFields); + } + + private , V extends Entry> Multimap resolveReferences(EntryResolver resolver, Multimap multimap) { + Multimap resolved = HashMultimap.create(); + for (Map.Entry entry : multimap.entries()) { + resolved.put(resolve(resolver, entry.getKey()), resolve(resolver, entry.getValue())); + } + return resolved; + } + + private , C extends Entry> Multimap> resolveReferencesTo(EntryResolver resolver, Multimap> multimap) { + Multimap> resolved = HashMultimap.create(); + for (Map.Entry> entry : multimap.entries()) { + resolved.put(resolve(resolver, entry.getKey()), resolve(resolver, entry.getValue())); + } + return resolved; + } + + private > E resolve(EntryResolver resolver, E entry) { + return resolver.resolveFirstEntry(entry, ResolutionStrategy.RESOLVE_CLOSEST); + } + + private , C extends Entry> EntryReference resolve(EntryResolver resolver, EntryReference reference) { + return resolver.resolveFirstReference(reference, ResolutionStrategy.RESOLVE_CLOSEST); + } + + public Collection getMethodsReferencedBy(MethodEntry entry) { + return methodReferences.get(entry); + } + + public Collection> getReferencesToField(FieldEntry entry) { + return referencesToFields.get(entry); + } + + public Collection> getReferencesToClass(ClassEntry entry) { + return referencesToClasses.get(entry); + } + + public Collection> getReferencesToMethod(MethodEntry entry) { + return referencesToMethods.get(entry); + } +} diff --git a/src/main/java/cuchaz/enigma/analysis/index/RemappableIndex.java b/src/main/java/cuchaz/enigma/analysis/index/RemappableIndex.java new file mode 100644 index 0000000..537e772 --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/index/RemappableIndex.java @@ -0,0 +1,9 @@ +package cuchaz.enigma.analysis.index; + +import cuchaz.enigma.translation.Translator; + +public interface RemappableIndex { + void remap(Translator translator); + + RemappableIndex remapped(Translator translator); +} diff --git a/src/main/java/cuchaz/enigma/bytecode/AccessFlags.java b/src/main/java/cuchaz/enigma/bytecode/AccessFlags.java deleted file mode 100644 index 31c8691..0000000 --- a/src/main/java/cuchaz/enigma/bytecode/AccessFlags.java +++ /dev/null @@ -1,105 +0,0 @@ -package cuchaz.enigma.bytecode; - -import cuchaz.enigma.analysis.Access; -import org.objectweb.asm.Opcodes; - -import java.lang.reflect.Modifier; - -public class AccessFlags { - private int flags; - - public AccessFlags(int flags) { - this.flags = flags; - } - - public boolean isPrivate() { - return Modifier.isPrivate(this.flags); - } - - public boolean isProtected() { - return Modifier.isProtected(this.flags); - } - - public boolean isPublic() { - return Modifier.isPublic(this.flags); - } - - public boolean isSynthetic() { - return (this.flags & Opcodes.ACC_SYNTHETIC) != 0; - } - - public boolean isStatic() { - return Modifier.isStatic(this.flags); - } - - public boolean isEnum() { - return (flags & Opcodes.ACC_ENUM) != 0; - } - - public boolean isBridge() { - return (flags & Opcodes.ACC_BRIDGE) != 0; - } - - public AccessFlags setPrivate() { - this.setVisibility(Opcodes.ACC_PRIVATE); - return this; - } - - public AccessFlags setProtected() { - this.setVisibility(Opcodes.ACC_PROTECTED); - return this; - } - - public AccessFlags setPublic() { - this.setVisibility(Opcodes.ACC_PUBLIC); - return this; - } - - public AccessFlags setBridge() { - flags |= Opcodes.ACC_BRIDGE; - return this; - } - - @Deprecated - public AccessFlags setBridged() { - return setBridge(); - } - - public void setVisibility(int visibility) { - this.resetVisibility(); - this.flags |= visibility; - } - - private void resetVisibility() { - this.flags &= ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED | Opcodes.ACC_PUBLIC); - } - - public int getFlags() { - return this.flags; - } - - @Override - public boolean equals(Object obj) { - return obj instanceof AccessFlags && ((AccessFlags) obj).flags == flags; - } - - @Override - public int hashCode() { - return flags; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(Access.get(this).toString().toLowerCase()); - if (isStatic()) { - builder.append(" static"); - } - if (isSynthetic()) { - builder.append(" synthetic"); - } - if (isBridge()) { - builder.append(" bridge"); - } - return builder.toString(); - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/AsmObjectTranslator.java b/src/main/java/cuchaz/enigma/bytecode/translators/AsmObjectTranslator.java new file mode 100644 index 0000000..1a2b47f --- /dev/null +++ b/src/main/java/cuchaz/enigma/bytecode/translators/AsmObjectTranslator.java @@ -0,0 +1,46 @@ +package cuchaz.enigma.bytecode.translators; + +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.representation.MethodDescriptor; +import cuchaz.enigma.translation.representation.TypeDescriptor; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; +import org.objectweb.asm.Handle; +import org.objectweb.asm.Type; + +public class AsmObjectTranslator { + public static Type translateType(Translator translator, Type type) { + String descString = type.getDescriptor(); + switch (type.getSort()) { + case Type.OBJECT: { + ClassEntry classEntry = new ClassEntry(type.getInternalName()); + return Type.getObjectType(translator.translate(classEntry).getFullName()); + } + case Type.ARRAY: { + TypeDescriptor descriptor = new TypeDescriptor(descString); + return Type.getType(translator.translate(descriptor).toString()); + } + case Type.METHOD: { + MethodDescriptor descriptor = new MethodDescriptor(descString); + return Type.getMethodType(translator.translate(descriptor).toString()); + } + } + return type; + } + + public static Handle translateHandle(Translator translator, Handle handle) { + MethodEntry entry = new MethodEntry(new ClassEntry(handle.getOwner()), handle.getName(), new MethodDescriptor(handle.getDesc())); + MethodEntry translatedMethod = translator.translate(entry); + ClassEntry ownerClass = translatedMethod.getParent(); + return new Handle(handle.getTag(), ownerClass.getFullName(), translatedMethod.getName(), translatedMethod.getDesc().toString(), handle.isInterface()); + } + + public static Object translateValue(Translator translator, Object value) { + if (value instanceof Type) { + return translateType(translator, (Type) value); + } else if (value instanceof Handle) { + return translateHandle(translator, (Handle) value); + } + return value; + } +} diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationAnnotationVisitor.java b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationAnnotationVisitor.java index 2e5b54d..cb843ad 100644 --- a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationAnnotationVisitor.java +++ b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationAnnotationVisitor.java @@ -1,9 +1,9 @@ package cuchaz.enigma.bytecode.translators; -import cuchaz.enigma.mapping.Translator; -import cuchaz.enigma.mapping.TypeDescriptor; -import cuchaz.enigma.mapping.entry.ClassEntry; -import cuchaz.enigma.mapping.entry.FieldEntry; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.representation.TypeDescriptor; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.FieldEntry; import org.objectweb.asm.AnnotationVisitor; public class TranslationAnnotationVisitor extends AnnotationVisitor { @@ -18,7 +18,7 @@ public class TranslationAnnotationVisitor extends AnnotationVisitor { @Override public void visit(String name, Object value) { - super.visit(name, translator.getTranslatedValue(value)); + super.visit(name, AsmObjectTranslator.translateValue(translator, value)); } @Override @@ -30,22 +30,22 @@ public class TranslationAnnotationVisitor extends AnnotationVisitor { public AnnotationVisitor visitAnnotation(String name, String desc) { TypeDescriptor type = new TypeDescriptor(desc); if (name != null) { - FieldEntry annotationField = translator.getTranslatedField(new FieldEntry(annotationEntry, name, type)); + FieldEntry annotationField = translator.translate(new FieldEntry(annotationEntry, name, type)); return super.visitAnnotation(annotationField.getName(), annotationField.getDesc().toString()); } else { - return super.visitAnnotation(null, translator.getTranslatedTypeDesc(type).toString()); + return super.visitAnnotation(null, translator.translate(type).toString()); } } @Override public void visitEnum(String name, String desc, String value) { TypeDescriptor type = new TypeDescriptor(desc); - FieldEntry enumField = translator.getTranslatedField(new FieldEntry(type.getTypeEntry(), value, type)); + FieldEntry enumField = translator.translate(new FieldEntry(type.getTypeEntry(), value, type)); if (name != null) { - FieldEntry annotationField = translator.getTranslatedField(new FieldEntry(annotationEntry, name, type)); + FieldEntry annotationField = translator.translate(new FieldEntry(annotationEntry, name, type)); super.visitEnum(annotationField.getName(), annotationField.getDesc().toString(), enumField.getName()); } else { - super.visitEnum(null, translator.getTranslatedTypeDesc(type).toString(), enumField.getName()); + super.visitEnum(null, translator.translate(type).toString(), enumField.getName()); } } } diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationClassVisitor.java b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationClassVisitor.java index 5b16138..53d09bb 100644 --- a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationClassVisitor.java +++ b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationClassVisitor.java @@ -11,61 +11,53 @@ package cuchaz.enigma.bytecode.translators; -import cuchaz.enigma.analysis.JarIndex; -import cuchaz.enigma.bytecode.AccessFlags; -import cuchaz.enigma.mapping.MethodDescriptor; -import cuchaz.enigma.mapping.Signature; -import cuchaz.enigma.mapping.Translator; -import cuchaz.enigma.mapping.TypeDescriptor; -import cuchaz.enigma.mapping.entry.*; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.representation.MethodDescriptor; +import cuchaz.enigma.translation.representation.ReferencedEntryPool; +import cuchaz.enigma.translation.representation.TypeDescriptor; +import cuchaz.enigma.translation.representation.entry.*; import org.objectweb.asm.*; +import java.util.Arrays; + public class TranslationClassVisitor extends ClassVisitor { private final Translator translator; - private final JarIndex jarIndex; private final ReferencedEntryPool entryPool; private ClassDefEntry obfClassEntry; - private Signature obfSignature; - public TranslationClassVisitor(Translator translator, JarIndex jarIndex, ReferencedEntryPool entryPool, int api, ClassVisitor cv) { + public TranslationClassVisitor(Translator translator, ReferencedEntryPool entryPool, int api, ClassVisitor cv) { super(api, cv); this.translator = translator; - this.jarIndex = jarIndex; this.entryPool = entryPool; } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - obfSignature = Signature.createSignature(signature); - obfClassEntry = new ClassDefEntry(name, obfSignature, new AccessFlags(access)); - ClassDefEntry translatedEntry = translator.getTranslatedClassDef(obfClassEntry); - ClassEntry superEntry = translator.getTranslatedClass(entryPool.getClass(superName)); - String[] translatedInterfaces = new String[interfaces.length]; - for (int i = 0; i < interfaces.length; i++) { - translatedInterfaces[i] = translator.getTranslatedClass(entryPool.getClass(interfaces[i])).getName(); - } - super.visit(version, translatedEntry.getAccess().getFlags(), translatedEntry.getName(), translatedEntry.getSignature().toString(), superEntry.getName(), translatedInterfaces); + obfClassEntry = ClassDefEntry.parse(access, name, signature, superName, interfaces); + + ClassDefEntry translatedEntry = translator.translate(obfClassEntry); + String translatedSuper = translatedEntry.getSuperClass() != null ? translatedEntry.getSuperClass().getFullName() : null; + String[] translatedInterfaces = Arrays.stream(translatedEntry.getInterfaces()).map(ClassEntry::getFullName).toArray(String[]::new); + + super.visit(version, translatedEntry.getAccess().getFlags(), translatedEntry.getFullName(), translatedEntry.getSignature().toString(), translatedSuper, translatedInterfaces); } @Override public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { - FieldDefEntry entry = new FieldDefEntry(obfClassEntry, name, new TypeDescriptor(desc), Signature.createTypedSignature(signature), new AccessFlags(access)); - FieldDefEntry translatedEntry = translator.getTranslatedFieldDef(entry); + FieldDefEntry entry = FieldDefEntry.parse(obfClassEntry, access, name, desc, signature); + FieldDefEntry translatedEntry = translator.translate(entry); FieldVisitor fv = super.visitField(translatedEntry.getAccess().getFlags(), translatedEntry.getName(), translatedEntry.getDesc().toString(), translatedEntry.getSignature().toString(), value); return new TranslationFieldVisitor(translator, translatedEntry, api, fv); } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { - MethodDefEntry entry = new MethodDefEntry(obfClassEntry, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access)); - MethodDefEntry translatedEntry = translator.getTranslatedMethodDef(entry); - if (jarIndex.getBridgedMethod(entry) != null) { - translatedEntry.getAccess().setBridge(); - } + MethodDefEntry entry = MethodDefEntry.parse(obfClassEntry, access, name, desc, signature); + MethodDefEntry translatedEntry = translator.translate(entry); String[] translatedExceptions = new String[exceptions.length]; for (int i = 0; i < exceptions.length; i++) { - translatedExceptions[i] = translator.getTranslatedClass(entryPool.getClass(exceptions[i])).getName(); + translatedExceptions[i] = translator.translate(entryPool.getClass(exceptions[i])).getFullName(); } MethodVisitor mv = super.visitMethod(translatedEntry.getAccess().getFlags(), translatedEntry.getName(), translatedEntry.getDesc().toString(), translatedEntry.getSignature().toString(), translatedExceptions); return new TranslationMethodVisitor(translator, obfClassEntry, entry, api, mv); @@ -73,25 +65,25 @@ public class TranslationClassVisitor extends ClassVisitor { @Override public void visitInnerClass(String name, String outerName, String innerName, int access) { - ClassDefEntry translatedEntry = translator.getTranslatedClassDef(new ClassDefEntry(name, obfSignature, new AccessFlags(access))); - String translatedName = translatedEntry.getName(); - int separatorIndex = translatedName.lastIndexOf("$"); - String parentName = translatedName.substring(0, separatorIndex); - String childName = translatedName.substring(separatorIndex + 1); - - ClassEntry outerEntry = translator.getTranslatedClass(entryPool.getClass(parentName)); + ClassDefEntry classEntry = ClassDefEntry.parse(access, name, obfClassEntry.getSignature().toString(), null, new String[0]); + ClassDefEntry translatedEntry = translator.translate(classEntry); + ClassEntry translatedOuterClass = translatedEntry.getOuterClass(); + if (translatedOuterClass == null) { + throw new IllegalStateException("Translated inner class did not have outer class"); + } // Anonymous classes do not specify an outer or inner name. As we do not translate from the given parameter, ignore if the input is null - String translatedOuterName = outerName != null ? outerEntry.getName() : null; - String translatedInnerName = innerName != null ? childName : null; + String translatedName = translatedEntry.getFullName(); + String translatedOuterName = outerName != null ? translatedOuterClass.getFullName() : null; + String translatedInnerName = innerName != null ? translatedEntry.getName() : null; super.visitInnerClass(translatedName, translatedOuterName, translatedInnerName, translatedEntry.getAccess().getFlags()); } @Override public void visitOuterClass(String owner, String name, String desc) { if (desc != null) { - MethodEntry translatedEntry = translator.getTranslatedMethod(new MethodEntry(new ClassEntry(owner), name, new MethodDescriptor(desc))); - super.visitOuterClass(translatedEntry.getClassName(), translatedEntry.getName(), translatedEntry.getDesc().toString()); + MethodEntry translatedEntry = translator.translate(new MethodEntry(new ClassEntry(owner), name, new MethodDescriptor(desc))); + super.visitOuterClass(translatedEntry.getParent().getFullName(), translatedEntry.getName(), translatedEntry.getDesc().toString()); } else { super.visitOuterClass(owner, name, desc); } @@ -99,14 +91,14 @@ public class TranslationClassVisitor extends ClassVisitor { @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { - TypeDescriptor translatedDesc = translator.getTranslatedTypeDesc(new TypeDescriptor(desc)); + TypeDescriptor translatedDesc = translator.translate(new TypeDescriptor(desc)); AnnotationVisitor av = super.visitAnnotation(translatedDesc.toString(), visible); return new TranslationAnnotationVisitor(translator, translatedDesc.getTypeEntry(), api, av); } @Override public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { - TypeDescriptor translatedDesc = translator.getTranslatedTypeDesc(new TypeDescriptor(desc)); + TypeDescriptor translatedDesc = translator.translate(new TypeDescriptor(desc)); AnnotationVisitor av = super.visitTypeAnnotation(typeRef, typePath, translatedDesc.toString(), visible); return new TranslationAnnotationVisitor(translator, translatedDesc.getTypeEntry(), api, av); } diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationFieldVisitor.java b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationFieldVisitor.java index e4695fb..28fc199 100644 --- a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationFieldVisitor.java +++ b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationFieldVisitor.java @@ -1,8 +1,8 @@ package cuchaz.enigma.bytecode.translators; -import cuchaz.enigma.mapping.Translator; -import cuchaz.enigma.mapping.TypeDescriptor; -import cuchaz.enigma.mapping.entry.FieldDefEntry; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.representation.TypeDescriptor; +import cuchaz.enigma.translation.representation.entry.FieldDefEntry; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.TypePath; @@ -19,14 +19,14 @@ public class TranslationFieldVisitor extends FieldVisitor { @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { - TypeDescriptor typeDesc = translator.getTranslatedTypeDesc(new TypeDescriptor(desc)); + TypeDescriptor typeDesc = translator.translate(new TypeDescriptor(desc)); AnnotationVisitor av = super.visitAnnotation(typeDesc.toString(), visible); return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av); } @Override public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { - TypeDescriptor typeDesc = translator.getTranslatedTypeDesc(new TypeDescriptor(desc)); + TypeDescriptor typeDesc = translator.translate(new TypeDescriptor(desc)); AnnotationVisitor av = super.visitAnnotation(typeDesc.toString(), visible); return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av); } diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationMethodVisitor.java b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationMethodVisitor.java index 6d0d550..a5a33e6 100644 --- a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationMethodVisitor.java +++ b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationMethodVisitor.java @@ -1,7 +1,11 @@ package cuchaz.enigma.bytecode.translators; -import cuchaz.enigma.mapping.*; -import cuchaz.enigma.mapping.entry.*; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.NameValidator; +import cuchaz.enigma.translation.representation.MethodDescriptor; +import cuchaz.enigma.translation.representation.Signature; +import cuchaz.enigma.translation.representation.TypeDescriptor; +import cuchaz.enigma.translation.representation.entry.*; import org.objectweb.asm.*; import java.util.Collection; @@ -26,15 +30,15 @@ public class TranslationMethodVisitor extends MethodVisitor { @Override public void visitFieldInsn(int opcode, String owner, String name, String desc) { FieldEntry entry = new FieldEntry(new ClassEntry(owner), name, new TypeDescriptor(desc)); - FieldEntry translatedEntry = translator.getTranslatedField(entry); - super.visitFieldInsn(opcode, translatedEntry.getClassName(), translatedEntry.getName(), translatedEntry.getDesc().toString()); + FieldEntry translatedEntry = translator.translate(entry); + super.visitFieldInsn(opcode, translatedEntry.getParent().getFullName(), translatedEntry.getName(), translatedEntry.getDesc().toString()); } @Override public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { MethodEntry entry = new MethodEntry(new ClassEntry(owner), name, new MethodDescriptor(desc)); - MethodEntry translatedEntry = translator.getTranslatedMethod(entry); - super.visitMethodInsn(opcode, translatedEntry.getClassName(), translatedEntry.getName(), translatedEntry.getDesc().toString(), itf); + MethodEntry translatedEntry = translator.translate(entry); + super.visitMethodInsn(opcode, translatedEntry.getParent().getFullName(), translatedEntry.getName(), translatedEntry.getDesc().toString(), itf); } @Override @@ -52,7 +56,7 @@ public class TranslationMethodVisitor extends MethodVisitor { Object object = array[i]; if (object instanceof String) { String type = (String) object; - array[i] = translator.getTranslatedClass(new ClassEntry(type)).getName(); + array[i] = translator.translate(new ClassEntry(type)).getFullName(); } } return array; @@ -60,21 +64,21 @@ public class TranslationMethodVisitor extends MethodVisitor { @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { - TypeDescriptor typeDesc = translator.getTranslatedTypeDesc(new TypeDescriptor(desc)); + TypeDescriptor typeDesc = translator.translate(new TypeDescriptor(desc)); AnnotationVisitor av = super.visitAnnotation(typeDesc.toString(), visible); return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av); } @Override public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { - TypeDescriptor typeDesc = translator.getTranslatedTypeDesc(new TypeDescriptor(desc)); + TypeDescriptor typeDesc = translator.translate(new TypeDescriptor(desc)); AnnotationVisitor av = super.visitParameterAnnotation(parameter, typeDesc.toString(), visible); return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av); } @Override public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { - TypeDescriptor typeDesc = translator.getTranslatedTypeDesc(new TypeDescriptor(desc)); + TypeDescriptor typeDesc = translator.translate(new TypeDescriptor(desc)); AnnotationVisitor av = super.visitTypeAnnotation(typeRef, typePath, typeDesc.toString(), visible); return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av); } @@ -83,19 +87,18 @@ public class TranslationMethodVisitor extends MethodVisitor { public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { hasParameterMeta = true; - String translatedSignature = translator.getTranslatedSignature(Signature.createTypedSignature(signature)).toString(); + String translatedSignature = translator.translate(Signature.createTypedSignature(signature)).toString(); int argumentIndex = methodEntry.getArgumentIndex(ownerEntry, index); if (argumentIndex >= 0) { - LocalVariableDefEntry entry = new LocalVariableDefEntry(methodEntry, index, name, new TypeDescriptor(desc)); - LocalVariableDefEntry translatedEntry = translator.getTranslatedVariableDef(entry); + LocalVariableDefEntry entry = new LocalVariableDefEntry(methodEntry, index, name, true, new TypeDescriptor(desc)); + LocalVariableDefEntry translatedEntry = translator.translate(entry); String translatedName = translatedEntry.getName(); - // TODO: Better name inference if (translatedName.equals(entry.getName())) { List arguments = methodEntry.getDesc().getArgumentDescs(); List translatedArguments = arguments.stream() - .map(translator::getTranslatedTypeDesc) + .map(translator::translate) .collect(Collectors.toList()); boolean argument = argumentIndex < arguments.size(); @@ -109,42 +112,42 @@ public class TranslationMethodVisitor extends MethodVisitor { super.visitLocalVariable(translatedName, translatedEntry.getDesc().toString(), translatedSignature, start, end, index); } else { // Handle "this" variable - TypeDescriptor translatedDesc = translator.getTranslatedTypeDesc(new TypeDescriptor(desc)); + TypeDescriptor translatedDesc = translator.translate(new TypeDescriptor(desc)); super.visitLocalVariable(name, translatedDesc.toString(), translatedSignature, start, end, index); } } @Override public void visitTypeInsn(int opcode, String type) { - ClassEntry translatedEntry = translator.getTranslatedClass(new ClassEntry(type)); - super.visitTypeInsn(opcode, translatedEntry.getName()); + ClassEntry translatedEntry = translator.translate(new ClassEntry(type)); + super.visitTypeInsn(opcode, translatedEntry.getFullName()); } @Override public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { - MethodDescriptor translatedMethodDesc = translator.getTranslatedMethodDesc(new MethodDescriptor(desc)); + MethodDescriptor translatedMethodDesc = translator.translate(new MethodDescriptor(desc)); Object[] translatedBsmArgs = new Object[bsmArgs.length]; for (int i = 0; i < bsmArgs.length; i++) { - translatedBsmArgs[i] = translator.getTranslatedValue(bsmArgs[i]); + translatedBsmArgs[i] = AsmObjectTranslator.translateValue(translator, bsmArgs[i]); } - super.visitInvokeDynamicInsn(name, translatedMethodDesc.toString(), translator.getTranslatedHandle(bsm), translatedBsmArgs); + super.visitInvokeDynamicInsn(name, translatedMethodDesc.toString(), AsmObjectTranslator.translateHandle(translator, bsm), translatedBsmArgs); } @Override public void visitLdcInsn(Object cst) { - super.visitLdcInsn(translator.getTranslatedValue(cst)); + super.visitLdcInsn(AsmObjectTranslator.translateValue(translator, cst)); } @Override public void visitMultiANewArrayInsn(String desc, int dims) { - super.visitMultiANewArrayInsn(translator.getTranslatedTypeDesc(new TypeDescriptor(desc)).toString(), dims); + super.visitMultiANewArrayInsn(translator.translate(new TypeDescriptor(desc)).toString(), dims); } @Override public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { if (type != null) { - ClassEntry translatedEntry = translator.getTranslatedClass(new ClassEntry(type)); - super.visitTryCatchBlock(start, end, handler, translatedEntry.getName()); + ClassEntry translatedEntry = translator.translate(new ClassEntry(type)); + super.visitTryCatchBlock(start, end, handler, translatedEntry.getFullName()); } else { super.visitTryCatchBlock(start, end, handler, type); } @@ -159,7 +162,7 @@ public class TranslationMethodVisitor extends MethodVisitor { for (int argumentIndex = 0; argumentIndex < arguments.size(); argumentIndex++) { LocalVariableEntry entry = new LocalVariableEntry(methodEntry, offset, "", true); - LocalVariableEntry translatedEntry = translator.getTranslatedVariable(entry); + LocalVariableEntry translatedEntry = translator.translate(entry); String translatedName = translatedEntry.getName(); if (translatedName.equals(entry.getName())) { super.visitParameter(inferArgumentName(argumentIndex, arguments.get(argumentIndex), arguments), 0); diff --git a/src/main/java/cuchaz/enigma/gui/ClassSelector.java b/src/main/java/cuchaz/enigma/gui/ClassSelector.java index 8863386..c3b7288 100644 --- a/src/main/java/cuchaz/enigma/gui/ClassSelector.java +++ b/src/main/java/cuchaz/enigma/gui/ClassSelector.java @@ -17,14 +17,13 @@ import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import cuchaz.enigma.gui.node.ClassSelectorClassNode; import cuchaz.enigma.gui.node.ClassSelectorPackageNode; -import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.ClassEntry; import cuchaz.enigma.throwables.IllegalNameException; import javax.swing.*; import javax.swing.event.CellEditorListener; import javax.swing.event.ChangeEvent; import javax.swing.tree.*; -import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.*; @@ -32,7 +31,7 @@ import java.util.List; public class ClassSelector extends JTree { - public static final Comparator DEOBF_CLASS_COMPARATOR = Comparator.comparing(ClassEntry::getName); + public static final Comparator DEOBF_CLASS_COMPARATOR = Comparator.comparing(ClassEntry::getFullName); private DefaultMutableTreeNode rootNodes; private ClassSelectionListener selectionListener; private RenameSelectionListener renameSelectionListener; diff --git a/src/main/java/cuchaz/enigma/gui/CodeReader.java b/src/main/java/cuchaz/enigma/gui/CodeReader.java index 137c730..0810043 100644 --- a/src/main/java/cuchaz/enigma/gui/CodeReader.java +++ b/src/main/java/cuchaz/enigma/gui/CodeReader.java @@ -16,9 +16,8 @@ import cuchaz.enigma.Deobfuscator; import cuchaz.enigma.analysis.EntryReference; import cuchaz.enigma.analysis.SourceIndex; import cuchaz.enigma.analysis.Token; -import cuchaz.enigma.gui.highlight.SelectionHighlightPainter; -import cuchaz.enigma.mapping.entry.ClassEntry; -import cuchaz.enigma.mapping.entry.Entry; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.Entry; import de.sciss.syntaxpane.DefaultSyntaxKit; import javax.swing.*; @@ -33,7 +32,6 @@ public class CodeReader extends JEditorPane { private static final long serialVersionUID = 3673180950485748810L; private static final Object lock = new Object(); - private SelectionHighlightPainter selectionHighlightPainter; private SourceIndex sourceIndex; private SelectionListener selectionListener; @@ -58,8 +56,6 @@ public class CodeReader extends JEditorPane { } } }); - - selectionHighlightPainter = new SelectionHighlightPainter(); } // HACKHACK: someday we can update the main GUI to use this code reader @@ -144,7 +140,7 @@ public class CodeReader extends JEditorPane { // decompile it - CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getOutermostClassName()); + CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getName()); String source = deobfuscator.getSource(sourceTree); setCode(source); sourceIndex = deobfuscator.getSourceIndex(sourceTree, source, ignoreBadTokens); @@ -155,52 +151,7 @@ public class CodeReader extends JEditorPane { }).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.getOwnerClassEntry().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); + void onSelect(EntryReference, Entry> reference); } } diff --git a/src/main/java/cuchaz/enigma/gui/Gui.java b/src/main/java/cuchaz/enigma/gui/Gui.java index 53500aa..d119735 100644 --- a/src/main/java/cuchaz/enigma/gui/Gui.java +++ b/src/main/java/cuchaz/enigma/gui/Gui.java @@ -29,9 +29,9 @@ import cuchaz.enigma.gui.panels.PanelDeobf; import cuchaz.enigma.gui.panels.PanelEditor; import cuchaz.enigma.gui.panels.PanelIdentifier; import cuchaz.enigma.gui.panels.PanelObf; -import cuchaz.enigma.mapping.*; -import cuchaz.enigma.mapping.entry.*; import cuchaz.enigma.throwables.IllegalNameException; +import cuchaz.enigma.translation.mapping.AccessModifier; +import cuchaz.enigma.translation.representation.entry.*; import cuchaz.enigma.utils.Utils; import de.sciss.syntaxpane.DefaultSyntaxKit; @@ -44,8 +44,8 @@ import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import java.awt.*; import java.awt.event.*; -import java.io.File; import java.io.IOException; +import java.nio.file.Path; import java.util.*; import java.util.List; import java.util.function.Function; @@ -58,7 +58,7 @@ public class Gui { private final MenuBar menuBar; // state - public EntryReference reference; + public EntryReference, Entry> reference; public FileDialog jarFileChooser; public FileDialog tinyMappingsFileChooser; public JFileChooser enigmaMappingsFileChooser; @@ -222,7 +222,7 @@ public class Gui { Object node = path.getLastPathComponent(); if (node instanceof ReferenceTreeNode) { - ReferenceTreeNode referenceNode = ((ReferenceTreeNode) node); + ReferenceTreeNode, Entry> referenceNode = ((ReferenceTreeNode, Entry>) node); if (referenceNode.getReference() != null) { navigateTo(referenceNode.getReference()); } else { @@ -250,10 +250,10 @@ public class Gui { 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) + JSplitPane.VERTICAL_SPLIT, + true, + new JScrollPane(callsTree), + new JScrollPane(tokens) ); callPanel.setResizeWeight(1); // let the top side take all the slack callPanel.resetToPreferredSizes(); @@ -368,9 +368,9 @@ public class Gui { this.deobfPanel.deobfClasses.setClasses(deobfClasses); } - public void setMappingsFile(File file) { - this.enigmaMappingsFileChooser.setSelectedFile(file); - this.menuBar.saveMappingsMenu.setEnabled(file != null); + public void setMappingsFile(Path path) { + this.enigmaMappingsFileChooser.setSelectedFile(path != null ? path.toFile() : null); + this.menuBar.saveMappingsMenu.setEnabled(path != null); } public void setSource(String source) { @@ -427,7 +427,7 @@ public class Gui { } } - private void showReference(EntryReference reference) { + private void showReference(EntryReference, Entry> reference) { if (reference == null) { infoPanel.clearReference(); return; @@ -453,29 +453,29 @@ public class Gui { private void showLocalVariableEntry(LocalVariableEntry entry) { addNameValue(infoPanel, "Variable", entry.getName()); - addNameValue(infoPanel, "Class", entry.getOwnerClassEntry().getName()); - addNameValue(infoPanel, "Method", entry.getOwnerEntry().getName()); + addNameValue(infoPanel, "Class", entry.getContainingClass().getFullName()); + addNameValue(infoPanel, "Method", entry.getParent().getName()); addNameValue(infoPanel, "Index", Integer.toString(entry.getIndex())); } private void showClassEntry(ClassEntry entry) { - addNameValue(infoPanel, "Class", entry.getName()); + addNameValue(infoPanel, "Class", entry.getFullName()); addModifierComboBox(infoPanel, "Modifier", entry); } private void showFieldEntry(FieldEntry entry) { addNameValue(infoPanel, "Field", entry.getName()); - addNameValue(infoPanel, "Class", entry.getOwnerClassEntry().getName()); + addNameValue(infoPanel, "Class", entry.getParent().getFullName()); addNameValue(infoPanel, "TypeDescriptor", entry.getDesc().toString()); addModifierComboBox(infoPanel, "Modifier", entry); } private void showMethodEntry(MethodEntry entry) { if (entry.isConstructor()) { - addNameValue(infoPanel, "Constructor", entry.getOwnerClassEntry().getName()); + addNameValue(infoPanel, "Constructor", entry.getParent().getFullName()); } else { addNameValue(infoPanel, "Method", entry.getName()); - addNameValue(infoPanel, "Class", entry.getOwnerClassEntry().getName()); + addNameValue(infoPanel, "Class", entry.getParent().getFullName()); } addNameValue(infoPanel, "MethodDescriptor", entry.getDesc().toString()); addModifierComboBox(infoPanel, "Modifier", entry); @@ -494,7 +494,7 @@ public class Gui { container.add(panel); } - private JComboBox addModifierComboBox(JPanel container, String name, Entry entry) { + private JComboBox addModifierComboBox(JPanel container, String name, Entry entry) { if (!getController().entryIsInJar(entry)) return null; JPanel panel = new JPanel(); @@ -502,7 +502,7 @@ public class Gui { 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()); + JComboBox combo = new JComboBox<>(AccessModifier.values()); ((JLabel) combo.getRenderer()).setHorizontalAlignment(JLabel.LEFT); combo.setPreferredSize(new Dimension(100, label.getPreferredSize().height)); combo.setSelectedIndex(getController().getDeobfuscator().getModifier(entry).ordinal()); @@ -520,11 +520,13 @@ public class Gui { 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 && !((MethodEntry) reference.entry).isConstructor(); - boolean isConstructorEntry = isToken && reference.entry instanceof MethodEntry && ((MethodEntry) reference.entry).isConstructor(); - boolean isInJar = isToken && this.controller.entryIsInJar(reference.entry); + + Entry referenceEntry = reference != null ? reference.entry : null; + boolean isClassEntry = isToken && referenceEntry instanceof ClassEntry; + boolean isFieldEntry = isToken && referenceEntry instanceof FieldEntry; + boolean isMethodEntry = isToken && referenceEntry instanceof MethodEntry && !((MethodEntry) referenceEntry).isConstructor(); + boolean isConstructorEntry = isToken && referenceEntry instanceof MethodEntry && ((MethodEntry) referenceEntry).isConstructor(); + boolean isInJar = isToken && this.controller.entryIsInJar(referenceEntry); boolean isRenameable = isToken && this.controller.referenceIsRenameable(reference); if (isToken) { @@ -542,14 +544,14 @@ public class Gui { this.popupMenu.openPreviousMenu.setEnabled(this.controller.hasPreviousLocation()); this.popupMenu.toggleMappingMenu.setEnabled(isRenameable); - if (isToken && this.controller.entryHasDeobfuscatedName(reference.entry)) { + if (isToken && this.controller.entryHasDeobfuscatedName(referenceEntry)) { this.popupMenu.toggleMappingMenu.setText("Reset to obfuscated"); } else { this.popupMenu.toggleMappingMenu.setText("Mark as deobfuscated"); } } - public void navigateTo(Entry entry) { + public void navigateTo(Entry entry) { if (!this.controller.entryIsInJar(entry)) { // entry is not in the jar. Ignore it return; @@ -560,7 +562,7 @@ public class Gui { this.controller.openDeclaration(entry); } - private void navigateTo(EntryReference reference) { + private void navigateTo(EntryReference, Entry> reference) { if (!this.controller.entryIsInJar(reference.getLocationClassEntry())) { return; } @@ -613,7 +615,7 @@ public class Gui { String newName = text.getText(); if (saveName && newName != null && !newName.isEmpty()) { try { - this.controller.rename(reference, newName); + this.controller.rename(reference, newName, true); } catch (IllegalNameException ex) { text.setBorder(BorderFactory.createLineBorder(Color.red, 1)); text.setToolTipText(ex.getReason()); @@ -737,13 +739,13 @@ public class Gui { 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]); + 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()); + this.controller.saveMappings(this.enigmaMappingsFileChooser.getSelectedFile().toPath()); } public void close() { @@ -782,7 +784,7 @@ public class Gui { 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()); + this.controller.rename(new EntryReference<>(prevDataChild, prevDataChild.getFullName()), dataChild.getFullName(), false); childNode.setUserObject(dataChild); } node.setUserObject(data); @@ -791,19 +793,19 @@ public class Gui { } // class rename else if (data instanceof ClassEntry) - this.controller.rename(new EntryReference<>((ClassEntry) prevData, ((ClassEntry) prevData).getName()), ((ClassEntry) data).getName(), false, true); + this.controller.rename(new EntryReference<>((ClassEntry) prevData, ((ClassEntry) prevData).getFullName()), ((ClassEntry) data).getFullName(), false); } - public void moveClassTree(EntryReference deobfReference, String newName) { - String oldEntry = deobfReference.entry.getOwnerClassEntry().getPackageName(); + public void moveClassTree(EntryReference, Entry> deobfReference, String newName) { + String oldEntry = deobfReference.entry.getContainingClass().getPackageName(); String newEntry = new ClassEntry(newName).getPackageName(); moveClassTree(deobfReference, newName, oldEntry == null, - newEntry == null); + newEntry == null); } // TODO: getExpansionState will *not* actually update itself based on name changes! - public void moveClassTree(EntryReference deobfReference, String newName, boolean isOldOb, boolean isNewOb) { - ClassEntry oldEntry = deobfReference.entry.getOwnerClassEntry(); + public void moveClassTree(EntryReference, Entry> deobfReference, String newName, boolean isOldOb, boolean isNewOb) { + ClassEntry oldEntry = deobfReference.entry.getContainingClass(); ClassEntry newEntry = new ClassEntry(newName); // Ob -> deob diff --git a/src/main/java/cuchaz/enigma/gui/GuiController.java b/src/main/java/cuchaz/enigma/gui/GuiController.java index 69aefe5..06cb33e 100644 --- a/src/main/java/cuchaz/enigma/gui/GuiController.java +++ b/src/main/java/cuchaz/enigma/gui/GuiController.java @@ -20,19 +20,25 @@ import cuchaz.enigma.analysis.*; import cuchaz.enigma.api.EnigmaPlugin; import cuchaz.enigma.config.Config; import cuchaz.enigma.gui.dialog.ProgressDialog; -import cuchaz.enigma.mapping.*; -import cuchaz.enigma.mapping.entry.ClassEntry; -import cuchaz.enigma.mapping.entry.Entry; -import cuchaz.enigma.mapping.entry.FieldEntry; -import cuchaz.enigma.mapping.entry.MethodEntry; import cuchaz.enigma.throwables.MappingParseException; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.*; +import cuchaz.enigma.translation.mapping.serde.MappingFormat; +import cuchaz.enigma.translation.mapping.tree.EntryTree; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.Entry; +import cuchaz.enigma.translation.representation.entry.FieldEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; import cuchaz.enigma.utils.ReadableToken; +import javax.annotation.Nullable; import java.awt.event.ItemEvent; import java.io.File; import java.io.IOException; +import java.nio.file.Path; import java.util.*; import java.util.jar.JarFile; +import java.util.stream.Collectors; public class GuiController { @@ -40,27 +46,26 @@ public class GuiController { private Gui gui; private SourceIndex index; private ClassEntry currentObfClass; - private boolean isDirty; - private Deque> referenceStack; + private Deque, Entry>> referenceStack; + + private Path loadedMappingPath; + private MappingFormat loadedMappingFormat; 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; + return deobfuscator.getMapper().isDirty(); } public void openJar(final JarFile jar) throws IOException { this.gui.onStartOpenJar("Loading JAR..."); - this.deobfuscator = new Deobfuscator(jar, (msg) -> { - this.gui.onStartOpenJar(msg); - }); + this.deobfuscator = new Deobfuscator(jar, this.gui::onStartOpenJar); this.gui.onFinishOpenJar(jar.getName()); refreshClasses(); } @@ -70,43 +75,37 @@ public class GuiController { 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); + public void openMappings(MappingFormat format, Path path) throws IOException, MappingParseException { + EntryTree mappings = format.read(path); + deobfuscator.setMappings(mappings); + + gui.setMappingsFile(path); + loadedMappingFormat = format; + refreshClasses(); refreshCurrentClass(); } - public void openTinyMappings(File file) throws IOException, MappingParseException { - this.deobfuscator.setMappings(new MappingsTinyReader().read(file)); - this.isDirty = false; - this.gui.setMappingsFile(file); - refreshClasses(); - refreshCurrentClass(); + public void saveMappings(Path path) { + saveMappings(loadedMappingFormat, path); } - 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 saveMappings(MappingFormat format, Path path) { + EntryRemapper mapper = deobfuscator.getMapper(); - } + MappingDelta delta = mapper.takeMappingDelta(); + boolean saveAll = !path.equals(loadedMappingPath); - public void saveEnigmaMappings(File file, boolean isDirectoryFormat) throws IOException { - this.deobfuscator.getMappings().saveEnigmaMappings(file, isDirectoryFormat); - this.isDirty = false; - } + ProgressDialog.runInThread(this.gui.getFrame(), progress -> { + if (saveAll) { + format.write(mapper.getObfToDeobf(), path, progress); + } else { + format.write(mapper.getObfToDeobf(), delta, path, progress); + } + }); - public void saveSRGMappings(File file) throws IOException { - this.deobfuscator.getMappings().saveSRGMappings(file); - this.isDirty = false; + loadedMappingFormat = format; + loadedMappingPath = path; } public void closeMappings() { @@ -116,11 +115,6 @@ public class GuiController { refreshCurrentClass(); } - public void rebuildMethodNames() { - ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.rebuildMethodNames(progress)); - this.isDirty = true; - } - public void exportSource(final File dirOut) { ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.writeSources(dirOut, progress)); } @@ -136,7 +130,8 @@ public class GuiController { return this.index.getReferenceToken(pos); } - public EntryReference getDeobfReference(Token token) { + @Nullable + public EntryReference, Entry> getDeobfReference(Token token) { if (this.index == null) { return null; } @@ -148,44 +143,52 @@ public class GuiController { return null; } return new ReadableToken( - this.index.getLineNumber(token.start), - this.index.getColumnNumber(token.start), - this.index.getColumnNumber(token.end) + 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 entryHasDeobfuscatedName(Entry deobfEntry) { + EntryResolver resolver = this.deobfuscator.getMapper().getDeobfResolver(); + Entry resolvedEntry = resolver.resolveFirstEntry(deobfEntry, ResolutionStrategy.RESOLVE_ROOT); + return this.deobfuscator.hasDeobfuscatedName(this.deobfuscator.getMapper().obfuscate(resolvedEntry)); } - public boolean entryIsInJar(Entry deobfEntry) { - return this.deobfuscator.isObfuscatedIdentifier(this.deobfuscator.obfuscateEntry(deobfEntry)); + public boolean entryIsInJar(Entry deobfEntry) { + if (deobfEntry == null) return false; + return this.deobfuscator.isObfuscatedIdentifier(this.deobfuscator.getMapper().obfuscate(deobfEntry)); } - public boolean referenceIsRenameable(EntryReference deobfReference) { - return this.deobfuscator.isRenameable(this.deobfuscator.obfuscateReference(deobfReference)); + public boolean referenceIsRenameable(EntryReference, Entry> deobfReference) { + if (deobfReference == null) return false; + return this.deobfuscator.isRenameable(this.deobfuscator.getMapper().obfuscate(deobfReference)); } public ClassInheritanceTreeNode getClassInheritance(ClassEntry deobfClassEntry) { - ClassEntry obfClassEntry = this.deobfuscator.obfuscateEntry(deobfClassEntry); - ClassInheritanceTreeNode rootNode = this.deobfuscator.getJarIndex().getClassInheritance(this.deobfuscator.getTranslator(TranslationDirection.DEOBFUSCATING), obfClassEntry); + ClassEntry obfClassEntry = this.deobfuscator.getMapper().obfuscate(deobfClassEntry); + Translator translator = this.deobfuscator.getMapper().getDeobfuscator(); + ClassInheritanceTreeNode rootNode = this.deobfuscator.getIndexTreeBuilder().buildClassInheritance(translator, 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); + ClassEntry obfClassEntry = this.deobfuscator.getMapper().obfuscate(deobfClassEntry); + Translator translator = this.deobfuscator.getMapper().getDeobfuscator(); + return this.deobfuscator.getIndexTreeBuilder().buildClassImplementations(translator, obfClassEntry); } public MethodInheritanceTreeNode getMethodInheritance(MethodEntry deobfMethodEntry) { - MethodEntry obfMethodEntry = this.deobfuscator.obfuscateEntry(deobfMethodEntry); - MethodInheritanceTreeNode rootNode = this.deobfuscator.getJarIndex().getMethodInheritance(this.deobfuscator.getTranslator(TranslationDirection.DEOBFUSCATING), obfMethodEntry); + MethodEntry obfMethodEntry = this.deobfuscator.getMapper().obfuscate(deobfMethodEntry); + Translator translator = this.deobfuscator.getMapper().getDeobfuscator(); + MethodInheritanceTreeNode rootNode = this.deobfuscator.getIndexTreeBuilder().buildMethodInheritance(translator, 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); + MethodEntry obfMethodEntry = this.deobfuscator.getMapper().obfuscate(deobfMethodEntry); + Translator translator = this.deobfuscator.getMapper().getDeobfuscator(); + List rootNodes = this.deobfuscator.getIndexTreeBuilder().buildMethodImplementations(translator, obfMethodEntry); if (rootNodes.isEmpty()) { return null; } @@ -196,34 +199,32 @@ public class GuiController { } public ClassReferenceTreeNode getClassReferences(ClassEntry deobfClassEntry) { - ClassEntry obfClassEntry = this.deobfuscator.obfuscateEntry(deobfClassEntry); - ClassReferenceTreeNode rootNode = new ClassReferenceTreeNode(this.deobfuscator.getTranslator(TranslationDirection.DEOBFUSCATING), obfClassEntry); + ClassEntry obfClassEntry = this.deobfuscator.getMapper().obfuscate(deobfClassEntry); + Translator deobfuscator = this.deobfuscator.getMapper().getDeobfuscator(); + ClassReferenceTreeNode rootNode = new ClassReferenceTreeNode(deobfuscator, obfClassEntry); rootNode.load(this.deobfuscator.getJarIndex(), true); return rootNode; } public FieldReferenceTreeNode getFieldReferences(FieldEntry deobfFieldEntry) { - FieldEntry obfFieldEntry = this.deobfuscator.obfuscateEntry(deobfFieldEntry); - FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(this.deobfuscator.getTranslator(TranslationDirection.DEOBFUSCATING), obfFieldEntry); + FieldEntry obfFieldEntry = this.deobfuscator.getMapper().obfuscate(deobfFieldEntry); + Translator translator = this.deobfuscator.getMapper().getDeobfuscator(); + FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(translator, obfFieldEntry); rootNode.load(this.deobfuscator.getJarIndex(), true); return rootNode; } public MethodReferenceTreeNode getMethodReferences(MethodEntry deobfMethodEntry, boolean recursive) { - MethodEntry obfMethodEntry = this.deobfuscator.obfuscateEntry(deobfMethodEntry); - MethodReferenceTreeNode rootNode = new MethodReferenceTreeNode(this.deobfuscator.getTranslator(TranslationDirection.DEOBFUSCATING), obfMethodEntry); + MethodEntry obfMethodEntry = this.deobfuscator.getMapper().obfuscate(deobfMethodEntry); + Translator translator = this.deobfuscator.getMapper().getDeobfuscator(); + MethodReferenceTreeNode rootNode = new MethodReferenceTreeNode(translator, obfMethodEntry); rootNode.load(this.deobfuscator.getJarIndex(), true, recursive); 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; + public void rename(EntryReference, Entry> deobfReference, String newName, boolean refreshClassTree) { + EntryReference, Entry> obfReference = this.deobfuscator.getMapper().obfuscate(deobfReference); + this.deobfuscator.rename(obfReference.getNameableEntry(), newName); if (refreshClassTree && deobfReference.entry instanceof ClassEntry && !((ClassEntry) deobfReference.entry).isInnerClass()) this.gui.moveClassTree(deobfReference, newName); @@ -231,39 +232,37 @@ public class GuiController { } - public void removeMapping(EntryReference deobfReference) { - EntryReference obfReference = this.deobfuscator.obfuscateReference(deobfReference); + public void removeMapping(EntryReference, Entry> deobfReference) { + EntryReference, Entry> obfReference = this.deobfuscator.getMapper().obfuscate(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); + public void markAsDeobfuscated(EntryReference, Entry> deobfReference) { + EntryReference, Entry> obfReference = this.deobfuscator.getMapper().obfuscate(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) { + 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) { + public void openReference(EntryReference, Entry> 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(); + EntryReference, Entry> obfReference = this.deobfuscator.getMapper().obfuscate(deobfReference); + ClassEntry obfClassEntry = obfReference.getLocationClassEntry(); if (!this.deobfuscator.isObfuscatedIdentifier(obfClassEntry)) { throw new IllegalArgumentException("Obfuscated class " + obfClassEntry + " was not found in the jar!"); } @@ -276,24 +275,30 @@ public class GuiController { } } - private void showReference(EntryReference obfReference) { - EntryReference deobfReference = this.deobfuscator.deobfuscateReference(obfReference); - Collection tokens = this.index.getReferenceTokens(deobfReference); + private void showReference(EntryReference, Entry> obfReference) { + EntryRemapper mapper = this.deobfuscator.getMapper(); + + Collection tokens = mapper.getObfResolver().resolveReference(obfReference, ResolutionStrategy.RESOLVE_ROOT) + .stream() + .map(mapper::deobfuscate) + .flatMap(reference -> index.getReferenceTokens(reference).stream()) + .collect(Collectors.toList()); + if (tokens.isEmpty()) { // DEBUG - System.err.println(String.format("WARNING: no tokens found for %s in %s", deobfReference, this.currentObfClass)); + System.err.println(String.format("WARNING: no tokens found for %s in %s", tokens, this.currentObfClass)); } else { this.gui.showTokens(tokens); } } - public void savePreviousReference(EntryReference deobfReference) { - this.referenceStack.push(this.deobfuscator.obfuscateReference(deobfReference)); + public void savePreviousReference(EntryReference, Entry> deobfReference) { + this.referenceStack.push(this.deobfuscator.getMapper().obfuscate(deobfReference)); } public void openPreviousReference() { if (hasPreviousLocation()) { - openReference(this.deobfuscator.deobfuscateReference(this.referenceStack.pop())); + openReference(this.deobfuscator.getMapper().deobfuscate(this.referenceStack.pop())); } } @@ -313,13 +318,13 @@ public class GuiController { refreshCurrentClass(null); } - private void refreshCurrentClass(EntryReference obfReference) { + private void refreshCurrentClass(EntryReference, Entry> obfReference) { if (this.currentObfClass != null) { deobfuscate(this.currentObfClass, obfReference); } } - private void deobfuscate(final ClassEntry classEntry, final EntryReference obfReference) { + private void deobfuscate(final ClassEntry classEntry, final EntryReference, Entry> obfReference) { this.gui.setSource("(deobfuscating...)"); @@ -327,7 +332,7 @@ public class GuiController { new Thread(() -> { // decompile,deobfuscate the bytecode - CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getClassName()); + CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getFullName()); if (sourceTree == null) { // decompilation of this class is not supported gui.setSource("Unable to find class: " + classEntry); @@ -349,17 +354,18 @@ public class GuiController { boolean remapped = false; for (Token inToken : index.referenceTokens()) { - EntryReference reference = index.getDeobfReference(inToken); Token token = inToken.move(offset); + EntryReference, Entry> reference = index.getDeobfReference(inToken); if (referenceIsRenameable(reference)) { boolean added = false; if (!entryHasDeobfuscatedName(reference.getNameableEntry())) { - Entry obfEntry = deobfuscator.obfuscateEntry(reference.getNameableEntry()); + Entry obfEntry = deobfuscator.getMapper().obfuscate(reference.getNameableEntry()); if (obfEntry instanceof FieldEntry) { for (EnigmaPlugin plugin : deobfuscator.getPlugins()) { - String proposal = plugin.proposeFieldName(obfEntry.getClassName(), obfEntry.getName(), ((FieldEntry) obfEntry).getDesc().toString()); + String owner = obfEntry.getContainingClass().getFullName(); + String proposal = plugin.proposeFieldName(owner, obfEntry.getName(), ((FieldEntry) obfEntry).getDesc().toString()); if (proposal != null) { proposedTokens.add(token); offset += token.getRenameOffset(proposal); @@ -411,8 +417,7 @@ public class GuiController { public void modifierChange(ItemEvent event) { if (event.getStateChange() == ItemEvent.SELECTED) { - deobfuscator.changeModifier(gui.reference.entry, (Mappings.EntryModifier) event.getItem()); - this.isDirty = true; + deobfuscator.changeModifier(gui.reference.entry, (AccessModifier) event.getItem()); refreshCurrentClass(); } } diff --git a/src/main/java/cuchaz/enigma/gui/GuiTricks.java b/src/main/java/cuchaz/enigma/gui/GuiTricks.java deleted file mode 100644 index 9208455..0000000 --- a/src/main/java/cuchaz/enigma/gui/GuiTricks.java +++ /dev/null @@ -1,42 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * 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.gui; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.ActionListener; - -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 void deactivateButton(JButton button) { - button.setEnabled(false); - button.setText(""); - for (ActionListener listener : button.getActionListeners()) { - button.removeActionListener(listener); - } - } - - public static void activateButton(JButton button, String text, ActionListener newListener) { - button.setText(text); - button.setEnabled(true); - for (ActionListener listener : button.getActionListeners()) { - button.removeActionListener(listener); - } - button.addActionListener(newListener); - } -} diff --git a/src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java b/src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java deleted file mode 100644 index 34ec26e..0000000 --- a/src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java +++ /dev/null @@ -1,44 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * 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.gui; - -import cuchaz.enigma.mapping.entry.ClassEntry; - -public class ScoredClassEntry extends ClassEntry { - - private static final long serialVersionUID = -8798725308554217105L; - - private float score; - - public ScoredClassEntry(ClassEntry other, float score) { - super(other); - this.score = score; - } - - public float getScore() { - return score; - } - - @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); - } - - public boolean equals(ScoredClassEntry other) { - return other != null && Float.compare(score, other.score) == 0; - } -} diff --git a/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java index 5f04833..84fe7c8 100644 --- a/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java +++ b/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java @@ -12,7 +12,7 @@ package cuchaz.enigma.gui.dialog; import cuchaz.enigma.Constants; -import cuchaz.enigma.Deobfuscator.ProgressListener; +import cuchaz.enigma.ProgressListener; import cuchaz.enigma.utils.Utils; import javax.swing.*; @@ -81,7 +81,7 @@ public class ProgressDialog implements ProgressListener, AutoCloseable { } @Override - public void onProgress(int numDone, String message) { + public void step(int numDone, String message) { this.labelText.setText(message); this.progress.setValue(numDone); diff --git a/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java b/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java index 609aecb..f4f0277 100644 --- a/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java +++ b/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java @@ -5,6 +5,7 @@ import cuchaz.enigma.config.Themes; import cuchaz.enigma.gui.Gui; import cuchaz.enigma.gui.dialog.AboutDialog; import cuchaz.enigma.throwables.MappingParseException; +import cuchaz.enigma.translation.mapping.serde.MappingFormat; import javax.swing.*; import java.awt.event.InputEvent; @@ -23,7 +24,6 @@ public class MenuBar extends JMenuBar { 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; @@ -68,7 +68,9 @@ public class MenuBar extends JMenuBar { item.addActionListener(event -> { if (this.gui.enigmaMappingsFileChooser.showOpenDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { try { - this.gui.getController().openEnigmaMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile()); + File selectedFile = this.gui.enigmaMappingsFileChooser.getSelectedFile(); + MappingFormat format = selectedFile.isDirectory() ? MappingFormat.ENIGMA_DIRECTORY : MappingFormat.ENIGMA_FILE; + this.gui.getController().openMappings(format, selectedFile.toPath()); } catch (IOException ex) { throw new Error(ex); } catch (MappingParseException ex) { @@ -85,7 +87,7 @@ public class MenuBar extends JMenuBar { File file = new File(this.gui.tinyMappingsFileChooser.getDirectory() + File.separator + this.gui.tinyMappingsFileChooser.getFile()); if (file.exists()) { try { - this.gui.getController().openTinyMappings(file); + this.gui.getController().openMappings(MappingFormat.TINY_FILE, file.toPath()); } catch (IOException ex) { throw new Error(ex); } catch (MappingParseException ex) { @@ -99,11 +101,7 @@ public class MenuBar extends JMenuBar { 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); - } + this.gui.getController().saveMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath()); }); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK)); this.saveMappingsMenu = item; @@ -116,12 +114,8 @@ public class MenuBar extends JMenuBar { 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.gui.getController().saveMappings(MappingFormat.ENIGMA_FILE, this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath()); + this.saveMappingsMenu.setEnabled(true); } }); this.saveMappingEnigmaFileMenu = item; @@ -132,12 +126,8 @@ public class MenuBar extends JMenuBar { 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); - } + this.gui.getController().saveMappings(MappingFormat.ENIGMA_DIRECTORY, this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath()); + this.saveMappingsMenu.setEnabled(true); } }); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK)); @@ -149,12 +139,8 @@ public class MenuBar extends JMenuBar { 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.gui.getController().saveMappings(MappingFormat.SRG_FILE, this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath()); + this.saveMappingsMenu.setEnabled(true); } }); this.saveMappingsSrgMenu = item; @@ -183,13 +169,6 @@ public class MenuBar extends JMenuBar { 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); diff --git a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java index a965a8f..bf6b178 100644 --- a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java +++ b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java @@ -11,7 +11,7 @@ package cuchaz.enigma.gui.node; -import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.ClassEntry; import javax.swing.tree.DefaultMutableTreeNode; diff --git a/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java b/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java index 9eb8f8f..ccdc9f8 100644 --- a/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java +++ b/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java @@ -2,7 +2,7 @@ package cuchaz.enigma.gui.panels; import cuchaz.enigma.gui.ClassSelector; import cuchaz.enigma.gui.Gui; -import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.ClassEntry; import javax.swing.*; import java.awt.*; @@ -17,8 +17,8 @@ public class PanelObf extends JPanel { this.gui = gui; Comparator obfClassComparator = (a, b) -> { - String aname = a.getName(); - String bname = b.getName(); + String aname = a.getFullName(); + String bname = b.getFullName(); if (aname.length() != bname.length()) { return aname.length() - bname.length(); } diff --git a/src/main/java/cuchaz/enigma/mapping/ClassMapping.java b/src/main/java/cuchaz/enigma/mapping/ClassMapping.java deleted file mode 100644 index 9c193ef..0000000 --- a/src/main/java/cuchaz/enigma/mapping/ClassMapping.java +++ /dev/null @@ -1,627 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * 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.collect.Maps; -import cuchaz.enigma.mapping.entry.ClassEntry; -import cuchaz.enigma.mapping.entry.FieldEntry; -import cuchaz.enigma.mapping.entry.MethodEntry; -import cuchaz.enigma.throwables.MappingConflict; - -import java.util.ArrayList; -import java.util.Map; - -// 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 deobfFullName; - 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; - } - - public String getTranslatedName(TranslationDirection direction) { - return direction.choose(deobfName, obfFullName); - } - - //// 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, TypeDescriptor obfDesc) { - return fieldsByObf.containsKey(getFieldKey(obfName, obfDesc)); - } - - public boolean containsDeobfField(String deobfName, TypeDescriptor deobfDesc) { - return fieldsByDeobf.containsKey(getFieldKey(deobfName, deobfDesc)); - } - - public void addFieldMapping(FieldMapping fieldMapping) { - String obfKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfDesc()); - if (fieldsByObf.containsKey(obfKey)) { - throw new Error("Already have mapping for " + obfFullName + "." + obfKey); - } - if (fieldMapping.getDeobfName() != null) { - String deobfKey = getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfDesc()); - 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.getObfDesc())) != null; - assert (obfWasRemoved); - if (fieldMapping.getDeobfName() != null) { - boolean deobfWasRemoved = fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfDesc())) != null; - assert (deobfWasRemoved); - } - this.isDirty = true; - } - - public FieldMapping getFieldByObf(String obfName, TypeDescriptor obfDesc) { - return fieldsByObf.get(getFieldKey(obfName, obfDesc)); - } - - public FieldMapping getFieldByObf(FieldEntry field) { - return getFieldByObf(field.getName(), field.getDesc()); - } - - public FieldMapping getFieldByDeobf(String deobfName, TypeDescriptor obfDesc) { - return fieldsByDeobf.get(getFieldKey(deobfName, obfDesc)); - } - - public String getObfFieldName(String deobfName, TypeDescriptor obfDesc) { - FieldMapping fieldMapping = fieldsByDeobf.get(getFieldKey(deobfName, obfDesc)); - if (fieldMapping != null) { - return fieldMapping.getObfName(); - } - return null; - } - - public String getDeobfFieldName(String obfName, TypeDescriptor obfDesc) { - FieldMapping fieldMapping = fieldsByObf.get(getFieldKey(obfName, obfDesc)); - if (fieldMapping != null) { - return fieldMapping.getDeobfName(); - } - return null; - } - - private String getFieldKey(String name, TypeDescriptor desc) { - if (name == null) { - throw new IllegalArgumentException("name cannot be null!"); - } - if (desc == null) { - throw new IllegalArgumentException("desc cannot be null!"); - } - return name + ":" + desc; - } - - public void setFieldName(String obfName, TypeDescriptor obfDesc, String deobfName) { - assert (deobfName != null); - FieldMapping fieldMapping = fieldsByObf.get(getFieldKey(obfName, obfDesc)); - if (fieldMapping == null) { - fieldMapping = new FieldMapping(obfName, obfDesc, deobfName, Mappings.EntryModifier.UNCHANGED); - boolean obfWasAdded = fieldsByObf.put(getFieldKey(obfName, obfDesc), fieldMapping) == null; - assert (obfWasAdded); - } else { - boolean wasRemoved = fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), obfDesc)) != null; - assert (wasRemoved); - } - fieldMapping.setDeobfName(deobfName); - if (deobfName != null) { - boolean wasAdded = fieldsByDeobf.put(getFieldKey(deobfName, obfDesc), fieldMapping) == null; - assert (wasAdded); - } - this.isDirty = true; - } - - //// METHODS //////// - - public void setFieldObfNameAndType(String oldObfName, TypeDescriptor obfDesc, String newObfName, TypeDescriptor newObfDesc) { - assert (newObfName != null); - FieldMapping fieldMapping = fieldsByObf.remove(getFieldKey(oldObfName, obfDesc)); - assert (fieldMapping != null); - fieldMapping.setObfName(newObfName); - fieldMapping.setObfDesc(newObfDesc); - boolean obfWasAdded = fieldsByObf.put(getFieldKey(newObfName, newObfDesc), fieldMapping) == null; - assert (obfWasAdded); - this.isDirty = true; - } - - public Iterable methods() { - assert (methodsByObf.size() >= methodsByDeobf.size()); - return methodsByObf.values(); - } - - public boolean containsObfMethod(String obfName, MethodDescriptor obfDescriptor) { - return methodsByObf.containsKey(getMethodKey(obfName, obfDescriptor)); - } - - public boolean containsDeobfMethod(String deobfName, MethodDescriptor obfDescriptor) { - return methodsByDeobf.containsKey(getMethodKey(deobfName, obfDescriptor)); - } - - public void addMethodMapping(MethodMapping methodMapping) { - String obfKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfDesc()); - if (methodsByObf.containsKey(obfKey)) { - throw new Error("Already have mapping for " + obfFullName + "." + obfKey); - } - boolean wasAdded = methodsByObf.put(obfKey, methodMapping) == null; - assert (wasAdded); - if (!methodMapping.isObfuscated()) { - String deobfKey = getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfDesc()); - 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.getObfDesc())) != null; - assert (obfWasRemoved); - if (!methodMapping.isObfuscated()) { - boolean deobfWasRemoved = methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfDesc())) != null; - assert (deobfWasRemoved); - } - this.isDirty = true; - } - - public MethodMapping getMethodByObf(String obfName, MethodDescriptor obfDescriptor) { - return methodsByObf.get(getMethodKey(obfName, obfDescriptor)); - } - - public MethodMapping getMethodByObf(MethodEntry method) { - return getMethodByObf(method.getName(), method.getDesc()); - } - - public MethodMapping getMethodByDeobf(String deobfName, MethodDescriptor obfDescriptor) { - return methodsByDeobf.get(getMethodKey(deobfName, obfDescriptor)); - } - - private String getMethodKey(String name, MethodDescriptor descriptor) { - if (name == null) { - throw new IllegalArgumentException("name cannot be null!"); - } - if (descriptor == null) { - throw new IllegalArgumentException("descriptor cannot be null!"); - } - return name + descriptor; - } - - public void setMethodName(String obfName, MethodDescriptor obfDescriptor, String deobfName) { - MethodMapping methodMapping = methodsByObf.get(getMethodKey(obfName, obfDescriptor)); - if (methodMapping == null) { - methodMapping = createMethodMapping(obfName, obfDescriptor); - } else if (!methodMapping.isObfuscated()) { - boolean wasRemoved = methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfDesc())) != null; - assert (wasRemoved); - } - methodMapping.setDeobfName(deobfName); - if (deobfName != null) { - boolean wasAdded = methodsByDeobf.put(getMethodKey(deobfName, obfDescriptor), methodMapping) == null; - assert (wasAdded); - } - this.isDirty = true; - } - - //// ARGUMENTS //////// - - public void setMethodObfNameAndSignature(String oldObfName, MethodDescriptor obfDescriptor, String newObfName, MethodDescriptor newObfDescriptor) { - assert (newObfName != null); - MethodMapping methodMapping = methodsByObf.remove(getMethodKey(oldObfName, obfDescriptor)); - assert (methodMapping != null); - methodMapping.setObfName(newObfName); - methodMapping.setObfDescriptor(newObfDescriptor); - boolean obfWasAdded = methodsByObf.put(getMethodKey(newObfName, newObfDescriptor), methodMapping) == null; - assert (obfWasAdded); - this.isDirty = true; - } - - public void setArgumentName(String obfMethodName, MethodDescriptor obfMethodDescriptor, int argumentIndex, String argumentName) { - assert (argumentName != null); - MethodMapping methodMapping = methodsByObf.get(getMethodKey(obfMethodName, obfMethodDescriptor)); - if (methodMapping == null) { - methodMapping = createMethodMapping(obfMethodName, obfMethodDescriptor); - } - methodMapping.setLocalVariableName(argumentIndex, argumentName); - this.isDirty = true; - } - - public void removeArgumentName(String obfMethodName, MethodDescriptor obfMethodDescriptor, int argumentIndex) { - methodsByObf.get(getMethodKey(obfMethodName, obfMethodDescriptor)).removeLocalVariableName(argumentIndex); - this.isDirty = true; - } - - private MethodMapping createMethodMapping(String obfName, MethodDescriptor obfDescriptor) { - MethodMapping methodMapping = new MethodMapping(obfName, obfDescriptor); - boolean wasAdded = methodsByObf.put(getMethodKey(obfName, obfDescriptor), 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.getObfDesc()); - if (fieldMapping.renameObfClass(oldObfClassName, newObfClassName)) { - boolean wasRemoved = fieldsByObf.remove(oldFieldKey) != null; - assert (wasRemoved); - boolean wasAdded = fieldsByObf - .put(getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfDesc()), fieldMapping) == null; - assert (wasAdded); - } - } - - // rename method signatures - for (MethodMapping methodMapping : new ArrayList<>(methodsByObf.values())) { - String oldMethodKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfDesc()); - if (methodMapping.renameObfClass(oldObfClassName, newObfClassName)) { - boolean wasRemoved = methodsByObf.remove(oldMethodKey) != null; - assert (wasRemoved); - boolean wasAdded = methodsByObf - .put(getMethodKey(methodMapping.getObfName(), methodMapping.getObfDesc()), methodMapping) == null; - assert (wasAdded); - } - } - - if (obfFullName.equals(oldObfClassName)) { - // rename this class - obfFullName = newObfClassName; - return true; - } - this.isDirty = true; - return false; - } - - public boolean containsArgument(MethodEntry obfMethodEntry, String name) { - MethodMapping methodMapping = methodsByObf.get(getMethodKey(obfMethodEntry.getName(), obfMethodEntry.getDesc())); - return methodMapping != null && methodMapping.containsLocalVariable(name); - } - - public ClassEntry getObfEntry() { - return new ClassEntry(obfFullName); - } - - public ClassEntry getDeObfEntry() { - return deobfFullName != null ? new ClassEntry(deobfFullName) : null; - } - - public boolean isObfuscated() { - return this.deobfName == null || this.deobfName.equals(this.obfFullName); - } - - public String getSaveName() { - return this.isObfuscated() ? this.obfFullName : this.deobfName; - } - - public boolean isDirty() { - return isDirty || areInnersDirty(); - } - - private boolean areInnersDirty(){ - for (ClassMapping c : this.innerClasses()){ - if (c.isDirty()){ - return true; - } - } - return false; - } - - public void resetDirty() { - this.isDirty = false; - } - - public void markDirty() { - this.isDirty = true; - } - - 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, TypeDescriptor obfDesc, Mappings.EntryModifier modifier) { - FieldMapping fieldMapping = fieldsByObf.computeIfAbsent(getFieldKey(obfName, obfDesc), - k -> new FieldMapping(obfName, obfDesc, null, Mappings.EntryModifier.UNCHANGED)); - - if (fieldMapping.getModifier() != modifier) { - fieldMapping.setModifier(modifier); - this.isDirty = true; - } - } - - public void setMethodModifier(String obfName, MethodDescriptor 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; - } - } - - // Used for tiny parsing to keep track of deobfuscate inner classes - public ClassMapping setDeobfInner(String deobName) { - this.deobfFullName = deobName; - return this; - } - - public ClassMapping copy() { - ClassMapping copied = new ClassMapping(this.obfFullName); - copied.obfSimpleName= this.obfSimpleName; - copied.modifier = this.modifier; - copied.deobfFullName = this.deobfFullName; - copied.deobfName = this.deobfName; - copied.innerClassesByDeobf = this.innerClassesByDeobf; - copied.innerClassesByObfFull = this.innerClassesByObfFull; - copied.innerClassesByObfSimple = this.innerClassesByObfSimple; - copied.fieldsByObf = this.fieldsByObf; - copied.fieldsByDeobf = this.fieldsByDeobf; - copied.methodsByObf = this.methodsByObf; - copied.methodsByDeobf = this.methodsByDeobf; - return copied; - } - - @Override - public int hashCode() { - return this.obfFullName.hashCode(); - } - - @Override - public boolean equals(Object obj) { - return obj instanceof ClassMapping && ((ClassMapping) obj).obfFullName.equals(this.obfFullName); - } - - public boolean isEmpty() { - if (fieldsByDeobf.isEmpty() && methodsByDeobf.isEmpty() && deobfFullName == null && deobfName == null - && innerClassesByObfSimple.values().stream().allMatch(ClassMapping::isEmpty)) { - - // check args - for (MethodMapping mapping : methodsByObf.values()) { - if (mapping.arguments().iterator().hasNext()) { - return false; - } - } - - return true; - } - - return false; - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/DirectionalTranslator.java b/src/main/java/cuchaz/enigma/mapping/DirectionalTranslator.java deleted file mode 100644 index 388e7ac..0000000 --- a/src/main/java/cuchaz/enigma/mapping/DirectionalTranslator.java +++ /dev/null @@ -1,371 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * 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.collect.Lists; -import com.google.common.collect.Maps; -import cuchaz.enigma.analysis.TranslationIndex; -import cuchaz.enigma.bytecode.AccessFlags; -import cuchaz.enigma.mapping.entry.*; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.ServiceLoader; - -public class DirectionalTranslator implements Translator { - private final TranslationDirection direction; - private final Map classes; - private final TranslationIndex index; - - public DirectionalTranslator(ReferencedEntryPool entryPool) { - this.direction = null; - this.classes = Maps.newHashMap(); - this.index = new TranslationIndex(entryPool); - } - - public DirectionalTranslator(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; - } - - @Override - public ClassEntry getTranslatedClass(ClassEntry entry) { - String className; - if (entry.isArray()) { - className = this.getTranslatedTypeDesc(new TypeDescriptor(entry.getName())).toString(); - } else { - className = entry.isInnerClass() ? translateInnerClassName(entry) : translateClassName(entry); - } - return new ClassEntry(className); - } - - @Override - public ClassDefEntry getTranslatedClassDef(ClassDefEntry entry) { - String className; - if (entry.isArray()) { - className = this.getTranslatedTypeDesc(new TypeDescriptor(entry.getName())).toString(); - } else { - className = entry.isInnerClass() ? translateInnerClassName(entry) : translateClassName(entry); - } - Signature translatedSignature = this.getTranslatedSignature(entry.getSignature()); - return new ClassDefEntry(className, translatedSignature, getClassModifier(entry).transform(entry.getAccess())); - } - - private String translateClassName(ClassEntry entry) { - // normal classes are easy - ClassMapping classMapping = this.classes.get(entry.getName()); - if (classMapping == null) { - return entry.getName(); - } - return classMapping.getTranslatedName(direction); - } - - private String translateInnerClassName(ClassEntry entry) { - // translate as much of the class chain as we can - List mappingsChain = getClassMappingChain(entry); - String[] obfClassNames = entry.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 buf.toString(); - } - - @Override - public FieldDefEntry getTranslatedFieldDef(FieldDefEntry entry) { - String translatedName = translateFieldName(entry); - if (translatedName == null) { - translatedName = entry.getName(); - } - ClassEntry translatedOwner = getTranslatedClass(entry.getOwnerClassEntry()); - TypeDescriptor translatedDesc = getTranslatedTypeDesc(entry.getDesc()); - Signature translatedSignature = getTranslatedSignature(entry.getSignature()); - AccessFlags translatedAccess = getFieldModifier(entry).transform(entry.getAccess()); - return new FieldDefEntry(translatedOwner, translatedName, translatedDesc, translatedSignature, translatedAccess); - } - - @Override - public FieldEntry getTranslatedField(FieldEntry entry) { - String translatedName = translateFieldName(entry); - if (translatedName == null) { - translatedName = entry.getName(); - } - ClassEntry translatedOwner = getTranslatedClass(entry.getOwnerClassEntry()); - TypeDescriptor translatedDesc = getTranslatedTypeDesc(entry.getDesc()); - return new FieldEntry(translatedOwner, translatedName, translatedDesc); - } - - private String translateFieldName(FieldEntry entry) { - // resolve the class entry - ClassEntry resolvedClassEntry = this.index.resolveEntryOwner(entry); - if (resolvedClassEntry != null) { - // look for the class - ClassMapping classMapping = findClassMapping(resolvedClassEntry); - if (classMapping != null) { - // look for the field - FieldMapping mapping = this.direction.choose( - classMapping.getFieldByObf(entry.getName(), entry.getDesc()), - classMapping.getFieldByDeobf(entry.getName(), getTranslatedTypeDesc(entry.getDesc())) - ); - if (mapping != null) { - return this.direction.choose(mapping.getDeobfName(), mapping.getObfName()); - } - } - } - return null; - } - - @Override - public MethodDefEntry getTranslatedMethodDef(MethodDefEntry entry) { - String translatedName = translateMethodName(entry); - if (translatedName == null) { - translatedName = entry.getName(); - } - ClassEntry translatedOwner = getTranslatedClass(entry.getOwnerClassEntry()); - MethodDescriptor translatedDesc = getTranslatedMethodDesc(entry.getDesc()); - Signature translatedSignature = getTranslatedSignature(entry.getSignature()); - AccessFlags access = getMethodModifier(entry).transform(entry.getAccess()); - return new MethodDefEntry(translatedOwner, translatedName, translatedDesc, translatedSignature, access); - } - - @Override - public MethodEntry getTranslatedMethod(MethodEntry entry) { - String translatedName = translateMethodName(entry); - if (translatedName == null) { - translatedName = entry.getName(); - } - ClassEntry translatedOwner = getTranslatedClass(entry.getOwnerClassEntry()); - MethodDescriptor translatedDesc = getTranslatedMethodDesc(entry.getDesc()); - return new MethodEntry(translatedOwner, translatedName, translatedDesc); - } - - private String translateMethodName(MethodEntry entry) { - // resolve the class entry - ClassEntry resolvedOwner = this.index.resolveEntryOwner(entry); - if (resolvedOwner != null) { - // look for class - ClassMapping classMapping = findClassMapping(resolvedOwner); - if (classMapping != null) { - // look for the method - MethodMapping mapping = this.direction.choose( - classMapping.getMethodByObf(entry.getName(), entry.getDesc()), - classMapping.getMethodByDeobf(entry.getName(), getTranslatedMethodDesc(entry.getDesc())) - ); - if (mapping != null) { - return this.direction.choose(mapping.getDeobfName(), mapping.getObfName()); - } - } - } - return null; - } - - @Override - public LocalVariableEntry getTranslatedVariable(LocalVariableEntry entry) { - String translatedArgumentName = translateLocalVariableName(entry); - if (translatedArgumentName == null) { - translatedArgumentName = inheritLocalVariableName(entry); - } - if (translatedArgumentName == null) { - translatedArgumentName = entry.getName(); - } - // TODO: Translating arguments calls method translation.. Can we refactor the code in such a way that we don't need this? - MethodEntry translatedOwner = getTranslatedMethod(entry.getOwnerEntry()); - return new LocalVariableEntry(translatedOwner, entry.getIndex(), translatedArgumentName, entry.isParameter()); - } - - @Override - public LocalVariableDefEntry getTranslatedVariableDef(LocalVariableDefEntry entry) { - String translatedArgumentName = translateLocalVariableName(entry); - if (translatedArgumentName == null) { - translatedArgumentName = inheritLocalVariableName(entry); - } - // TODO: Translating arguments calls method translation.. Can we refactor the code in such a way that we don't need this? - MethodDefEntry translatedOwner = getTranslatedMethodDef(entry.getOwnerEntry()); - TypeDescriptor translatedTypeDesc = getTranslatedTypeDesc(entry.getDesc()); - return new LocalVariableDefEntry(translatedOwner, entry.getIndex(), translatedArgumentName != null ? translatedArgumentName : entry.getName(), entry.isParameter(), translatedTypeDesc); - } - - @Override - public boolean hasClassMapping(ClassEntry entry) { - return classes.containsKey(entry.getName()); - } - - @Override - public boolean hasFieldMapping(FieldEntry entry) { - return translateFieldName(entry) != null; - } - - @Override - public boolean hasMethodMapping(MethodEntry entry) { - return translateMethodName(entry) != null; - } - - @Override - public boolean hasLocalVariableMapping(LocalVariableEntry entry) { - return translateLocalVariableName(entry) != null || inheritLocalVariableName(entry) != null; - } - - // TODO: support not identical behavior (specific to constructor) - private String translateLocalVariableName(LocalVariableEntry entry) { - // look for identical behavior in superclasses - ClassEntry ownerEntry = entry.getOwnerClassEntry(); - if (ownerEntry != null) { - // look for the class - ClassMapping classMapping = findClassMapping(ownerEntry); - if (classMapping != null) { - // look for the method - MethodMapping methodMapping = this.direction.choose( - classMapping.getMethodByObf(entry.getMethodName(), entry.getMethodDesc()), - classMapping.getMethodByDeobf(entry.getMethodName(), getTranslatedMethodDesc(entry.getMethodDesc())) - ); - if (methodMapping != null) { - int index = entry.getIndex(); - return this.direction.choose( - methodMapping.getDeobfLocalVariableName(index), - methodMapping.getObfLocalVariableName(index) - ); - } - } - } - return null; - } - - private String inheritLocalVariableName(LocalVariableEntry entry) { - List ancestry = this.index.getAncestry(entry.getOwnerClassEntry()); - // Check in mother class for the arg - for (ClassEntry ancestorEntry : ancestry) { - LocalVariableEntry motherArg = entry.updateOwnership(ancestorEntry); - if (this.index.entryExists(motherArg)) { - String result = translateLocalVariableName(motherArg); - if (result != null) { - return result; - } - } - } - return null; - } - - @Override - public TypeDescriptor getTranslatedTypeDesc(TypeDescriptor desc) { - return desc.remap(this::remapClass); - } - - @Override - public MethodDescriptor getTranslatedMethodDesc(MethodDescriptor descriptor) { - List arguments = descriptor.getArgumentDescs(); - List translatedArguments = new ArrayList<>(arguments.size()); - for (TypeDescriptor argument : arguments) { - translatedArguments.add(getTranslatedTypeDesc(argument)); - } - return new MethodDescriptor(translatedArguments, getTranslatedTypeDesc(descriptor.getReturnDesc())); - } - - @Override - public Signature getTranslatedSignature(Signature signature) { - if (signature == null) { - return null; - } - return signature.remap(this::remapClass); - } - - private ClassMapping findClassMapping(ClassEntry entry) { - List mappingChain = getClassMappingChain(entry); - return mappingChain.get(mappingChain.size() - 1); - } - - private List getClassMappingChain(ClassEntry entry) { - - // get a list of all the classes in the hierarchy - String[] parts = entry.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; - } - - private Mappings.EntryModifier getClassModifier(ClassEntry entry) { - ClassMapping classMapping = findClassMapping(entry); - if (classMapping != null) { - return classMapping.getModifier(); - } - return Mappings.EntryModifier.UNCHANGED; - } - - private Mappings.EntryModifier getFieldModifier(FieldEntry entry) { - ClassMapping classMapping = findClassMapping(entry.getOwnerClassEntry()); - if (classMapping != null) { - FieldMapping fieldMapping = classMapping.getFieldByObf(entry); - if (fieldMapping != null) { - return fieldMapping.getModifier(); - } - } - return Mappings.EntryModifier.UNCHANGED; - } - - private Mappings.EntryModifier getMethodModifier(MethodEntry entry) { - ClassMapping classMapping = findClassMapping(entry.getOwnerClassEntry()); - if (classMapping != null) { - MethodMapping methodMapping = classMapping.getMethodByObf(entry); - if (methodMapping != null) { - return methodMapping.getModifier(); - } - } - return Mappings.EntryModifier.UNCHANGED; - } - - private String remapClass(String name) { - return getTranslatedClass(new ClassEntry(name)).getName(); - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/FieldMapping.java b/src/main/java/cuchaz/enigma/mapping/FieldMapping.java deleted file mode 100644 index 8fbe095..0000000 --- a/src/main/java/cuchaz/enigma/mapping/FieldMapping.java +++ /dev/null @@ -1,100 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * 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 cuchaz.enigma.mapping.entry.ClassEntry; -import cuchaz.enigma.mapping.entry.FieldEntry; -import cuchaz.enigma.throwables.IllegalNameException; - -public class FieldMapping implements Comparable, MemberMapping { - - private String obfName; - private String deobfName; - private TypeDescriptor obfDesc; - private Mappings.EntryModifier modifier; - - public FieldMapping(String obfName, TypeDescriptor obfDesc, String deobfName, Mappings.EntryModifier modifier) { - this.obfName = obfName; - this.deobfName = NameValidator.validateFieldName(deobfName); - this.obfDesc = obfDesc; - this.modifier = modifier; - } - - @Override - public FieldEntry getObfEntry(ClassEntry classEntry) { - return new FieldEntry(classEntry, this.obfName, this.obfDesc); - } - - @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 TypeDescriptor getObfDesc() { - return this.obfDesc; - } - - public void setObfDesc(TypeDescriptor val) { - this.obfDesc = 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.obfDesc).compareTo(other.obfName + other.obfDesc); - } - - public boolean renameObfClass(final String oldObfClassName, final String newObfClassName) { - // rename obf classes in the desc - TypeDescriptor newDesc = this.obfDesc.remap(className -> { - if (className.equals(oldObfClassName)) { - return newObfClassName; - } - return className; - }); - - if (!newDesc.equals(this.obfDesc)) { - this.obfDesc = newDesc; - return true; - } - return false; - } - -} diff --git a/src/main/java/cuchaz/enigma/mapping/LocalVariableMapping.java b/src/main/java/cuchaz/enigma/mapping/LocalVariableMapping.java deleted file mode 100644 index bfe66b2..0000000 --- a/src/main/java/cuchaz/enigma/mapping/LocalVariableMapping.java +++ /dev/null @@ -1,58 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * 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 cuchaz.enigma.mapping.entry.LocalVariableEntry; -import cuchaz.enigma.mapping.entry.MethodEntry; - -public class LocalVariableMapping implements Comparable { - - private int index; - private String name; - - // NOTE: this argument order is important for the MethodReader/MethodWriter - public LocalVariableMapping(int index, String name) { - this.index = index; - this.name = NameValidator.validateArgumentName(name); - } - - public LocalVariableMapping(LocalVariableMapping other) { - this.index = other.index; - this.name = other.name; - } - - public int getIndex() { - return this.index; - } - - public String getName() { - return this.name; - } - - public void setName(String val) { - this.name = NameValidator.validateArgumentName(val); - } - - @Deprecated - public LocalVariableEntry getObfEntry(MethodEntry methodEntry) { - return new LocalVariableEntry(methodEntry, index, name); - } - - public LocalVariableEntry getObfEntry(MethodEntry methodEntry, boolean parameter) { - return new LocalVariableEntry(methodEntry, index, name, parameter); - } - - @Override - public int compareTo(LocalVariableMapping other) { - return Integer.compare(this.index, other.index); - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/Mappings.java b/src/main/java/cuchaz/enigma/mapping/Mappings.java deleted file mode 100644 index c865079..0000000 --- a/src/main/java/cuchaz/enigma/mapping/Mappings.java +++ /dev/null @@ -1,268 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * 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.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; -import cuchaz.enigma.analysis.TranslationIndex; -import cuchaz.enigma.api.EnigmaPlugin; -import cuchaz.enigma.bytecode.AccessFlags; -import cuchaz.enigma.mapping.entry.ClassEntry; -import cuchaz.enigma.mapping.entry.MethodEntry; -import cuchaz.enigma.throwables.MappingConflict; - -import java.io.File; -import java.io.IOException; -import java.util.*; -import java.util.stream.Collectors; - -public class Mappings { - - 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(); - } - - 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 DirectionalTranslator(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 DirectionalTranslator(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 (TypeDescriptor desc : methodMapping.getObfDesc().types()) { - if (desc.containsType()) { - classNames.add(desc.getTypeEntry().getClassName()); - } - } - } - } - return classNames; - } - - public boolean containsDeobfClass(String deobfName) { - return this.classesByDeobf.containsKey(deobfName); - } - - public boolean containsDeobfField(ClassEntry obfClassEntry, String deobfName, TypeDescriptor obfDesc) { - ClassMapping classMapping = this.classesByObf.get(obfClassEntry.getName()); - return classMapping != null && classMapping.containsDeobfField(deobfName, obfDesc); - } - - 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, MethodDescriptor obfDescriptor) { - ClassMapping classMapping = this.classesByObf.get(obfClassEntry.getName()); - return classMapping != null && classMapping.containsDeobfMethod(deobfName, obfDescriptor); - } - - public boolean containsArgument(MethodEntry obfMethodEntry, String name) { - ClassMapping classMapping = this.classesByObf.get(obfMethodEntry.getClassName()); - return classMapping != null && classMapping.containsArgument(obfMethodEntry, 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 = new HashMap<>(); - for (Map.Entry entry : this.classesByDeobf.entrySet()) { - this.previousState.classesByDeobf.put(entry.getKey(), entry.getValue().copy()); - } - this.previousState.classesByObf = new HashMap<>(); - for (Map.Entry entry : this.classesByObf.entrySet()) { - this.previousState.classesByObf.put(entry.getKey(), entry.getValue().copy()); - } - 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, TINY_FILE, SRG_FILE - } - - public enum EntryModifier { - UNCHANGED, PUBLIC, PROTECTED, PRIVATE; - - public String getFormattedName() { - return " ACC:" + super.toString(); - } - - public AccessFlags transform(AccessFlags access) { - switch (this) { - case PUBLIC: - return access.setPublic(); - case PROTECTED: - return access.setProtected(); - case PRIVATE: - return access.setPrivate(); - case UNCHANGED: - default: - return access; - } - } - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/MappingsChecker.java b/src/main/java/cuchaz/enigma/mapping/MappingsChecker.java deleted file mode 100644 index a42f255..0000000 --- a/src/main/java/cuchaz/enigma/mapping/MappingsChecker.java +++ /dev/null @@ -1,101 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * 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.collect.Lists; -import com.google.common.collect.Maps; -import cuchaz.enigma.analysis.JarIndex; -import cuchaz.enigma.mapping.entry.ClassEntry; -import cuchaz.enigma.mapping.entry.EntryFactory; -import cuchaz.enigma.mapping.entry.FieldEntry; -import cuchaz.enigma.mapping.entry.MethodEntry; - -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())) { - MethodEntry obfMethodEntry = EntryFactory.getObfMethodEntry(classEntry, methodMapping); - if (!this.index.containsObfMethod(obfMethodEntry)) { - classMapping.removeMethodMapping(methodMapping); - this.droppedMethodMappings.put(obfMethodEntry, 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 deleted file mode 100644 index ddbee76..0000000 --- a/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaReader.java +++ /dev/null @@ -1,186 +0,0 @@ -package cuchaz.enigma.mapping; - -import com.google.common.base.Charsets; -import com.google.common.collect.Queues; -import cuchaz.enigma.throwables.MappingConflict; -import cuchaz.enigma.throwables.MappingParseException; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.Deque; -import java.util.function.Supplier; - -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 { - return readFileStream(mappings, new FileInputStream(file), file::getAbsolutePath); - } - - public Mappings readFileStream(Mappings mappings, InputStream stream, Supplier filenameSupplier) throws IOException, MappingParseException { - try (BufferedReader in = new BufferedReader(new InputStreamReader(stream, 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(filenameSupplier, 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(filenameSupplier, 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(filenameSupplier, 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(filenameSupplier, lineNumber, "Unexpected ARG entry here!"); - } - ((MethodMapping) mappingStack.peek()).addArgumentMapping(readArgument(parts)); - } - } catch (ArrayIndexOutOfBoundsException | IllegalArgumentException ex) { - throw new MappingParseException(filenameSupplier, lineNumber, "Malformed line:\n" + line); - } catch (MappingConflict e) { - throw new MappingParseException(filenameSupplier, lineNumber, e.getMessage()); - } - } - return mappings; - } - } - - private LocalVariableMapping readArgument(String[] parts) { - return new LocalVariableMapping(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 TypeDescriptor(parts[2]), null, - Mappings.EntryModifier.valueOf(parts[3].substring(4))); - else - mapping = new FieldMapping(parts[1], new TypeDescriptor(parts[3]), parts[2], Mappings.EntryModifier.UNCHANGED); - } else if (parts.length == 5) - mapping = new FieldMapping(parts[1], new TypeDescriptor(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 MethodDescriptor(parts[2])); - else if (parts.length == 4) { - boolean access = parts[3].startsWith("ACC:"); - if (access) - mapping = new MethodMapping(parts[1], new MethodDescriptor(parts[2]), null, Mappings.EntryModifier.valueOf(parts[3].substring(4))); - else - mapping = new MethodMapping(parts[1], new MethodDescriptor(parts[3]), parts[2]); - } else if (parts.length == 5) - mapping = new MethodMapping(parts[1], new MethodDescriptor(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 deleted file mode 100644 index e3302b1..0000000 --- a/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaWriter.java +++ /dev/null @@ -1,160 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * 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; - -import java.io.*; -import java.util.*; - -public class MappingsEnigmaWriter { - - public void write(File out, Mappings mappings, boolean isDirectoryFormat) throws IOException { - if (!isDirectoryFormat) { - PrintWriter outputWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(out), Charsets.UTF_8)); - write(outputWriter, mappings); - outputWriter.close(); - } else - writeAsDirectory(out, mappings); - } - - public void writeAsDirectory(File target, Mappings mappings) throws IOException { - if (!target.exists() && !target.mkdirs()) - throw new IOException("Cannot create mapping directory!"); - - Mappings previousState = mappings.getPreviousState(); - for (ClassMapping classMapping : sorted(mappings.classes())) { - File result = new File(target, classMapping.getSaveName() + ".mapping"); - - if (!classMapping.isDirty()) { - continue; - } - - if (classMapping.isEmpty()) { - if (result.exists()) { - result.delete(); - } - continue; - } - - if (previousState != null) { - ClassMapping previousClass = previousState.classesByObf.get(classMapping.getObfFullName()); - File previousFile; - if (previousClass != null) { - previousFile = new File(target, previousClass.getSaveName() + ".mapping"); - } else { - previousFile = new File(target, classMapping.getObfFullName() + ".mapping"); - } - if (previousFile.exists() && !previousFile.delete()) { - System.err.println("Failed to delete old class mapping " + previousFile.getName()); - } - } - - File packageFile = result.getParentFile(); - if (!packageFile.exists()) { - packageFile.mkdirs(); - } - result.createNewFile(); - - try (PrintWriter outputWriter = new PrintWriter(new BufferedWriter(new FileWriter(result)))) { - write(outputWriter, classMapping, 0); - } - } - - // Remove dropped mappings - if (previousState != null) { - Set droppedClassMappings = new HashSet<>(previousState.classes()); - droppedClassMappings.removeAll(mappings.classes()); - for (ClassMapping droppedMapping : droppedClassMappings) { - File result = new File(target, droppedMapping.getSaveName() + ".mapping"); - if (!result.exists()) { - continue; - } - if (!result.delete()) { - System.err.println("Failed to delete dropped class mapping " + result.getName()); - } - } - } - } - - public void write(PrintWriter out, Mappings mappings) throws IOException { - for (ClassMapping classMapping : sorted(mappings.classes())) { - write(out, classMapping, 0); - } - } - - protected 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()); - } else { - 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.getObfDesc().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.getObfDesc().toString(), - fieldMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : fieldMapping.getModifier().getFormattedName()); - } - - private void write(PrintWriter out, MethodMapping methodMapping, int depth) throws IOException { - if (methodMapping.isObfuscated()) { - out.format("%sMETHOD %s %s%s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getObfDesc(), - methodMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : methodMapping.getModifier().getFormattedName()); - } else { - out.format("%sMETHOD %s %s %s%s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getDeobfName(), methodMapping.getObfDesc(), - methodMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : methodMapping.getModifier().getFormattedName()); - } - - for (LocalVariableMapping localVariableMapping : sorted(methodMapping.arguments())) { - write(out, localVariableMapping, depth + 1); - } - } - - private void write(PrintWriter out, LocalVariableMapping localVariableMapping, int depth) { - out.format("%sARG %d %s\n", getIndent(depth), localVariableMapping.getIndex(), localVariableMapping.getName()); - } - - protected > List sorted(Iterable classes) { - List out = new ArrayList<>(); - for (T t : classes) { - out.add(t); - } - Collections.sort(out); - return out; - } - - private String getIndent(int depth) { - StringBuilder buf = new StringBuilder(); - for (int i = 0; i < depth; i++) { - buf.append("\t"); - } - return buf.toString(); - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/MappingsRenamer.java b/src/main/java/cuchaz/enigma/mapping/MappingsRenamer.java deleted file mode 100644 index 8ef4f12..0000000 --- a/src/main/java/cuchaz/enigma/mapping/MappingsRenamer.java +++ /dev/null @@ -1,365 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * 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.collect.Lists; -import cuchaz.enigma.analysis.JarIndex; -import cuchaz.enigma.mapping.entry.*; -import cuchaz.enigma.throwables.IllegalNameException; -import cuchaz.enigma.throwables.MappingConflict; - -import java.io.IOException; -import java.io.ObjectOutputStream; -import java.io.OutputStream; -import java.util.List; -import java.util.Set; -import java.util.zip.GZIPOutputStream; - -public class MappingsRenamer { - - private final JarIndex index; - private final ReferencedEntryPool entryPool; - private Mappings mappings; - - public MappingsRenamer(JarIndex index, Mappings mappings, ReferencedEntryPool entryPool) { - this.index = index; - this.mappings = mappings; - this.entryPool = entryPool; - } - - 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(entryPool.getClass(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 = entryPool.getField(obf.getOwnerClassEntry(), deobfName, obf.getDesc()); - ClassEntry definedClass = null; - if (mappings.containsDeobfField(obf.getOwnerClassEntry(), deobfName) || index.containsEntryWithSameName(targetEntry)) { - definedClass = obf.getOwnerClassEntry(); - } else { - for (ClassEntry ancestorEntry : this.index.getTranslationIndex().getAncestry(obf.getOwnerClassEntry())) { - if (mappings.containsDeobfField(ancestorEntry, deobfName) || index.containsEntryWithSameName(targetEntry.updateOwnership(ancestorEntry))) { - definedClass = ancestorEntry; - break; - } - } - } - - if (definedClass != null) { - Translator translator = mappings.getTranslator(TranslationDirection.DEOBFUSCATING, index.getTranslationIndex()); - String className = translator.getTranslatedClass(entryPool.getClass(definedClass.getClassName())).getName(); - if (className == null) - className = definedClass.getClassName(); - throw new IllegalNameException(deobfName, "There is already a field with that name in " + className); - } - - ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); - classMapping.setFieldName(obf.getName(), obf.getDesc(), deobfName); - } - - public void removeFieldMapping(FieldEntry obf) { - ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); - classMapping.removeFieldMapping(classMapping.getFieldByObf(obf.getName(), obf.getDesc())); - } - - public void markFieldAsDeobfuscated(FieldEntry obf) { - ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); - classMapping.setFieldName(obf.getName(), obf.getDesc(), obf.getName()); - } - - private void validateMethodTreeName(MethodEntry entry, String deobfName) { - MethodEntry targetEntry = entryPool.getMethod(entry.getOwnerClassEntry(), deobfName, entry.getDesc()); - - // TODO: Verify if I don't break things - ClassMapping classMapping = mappings.getClassByObf(entry.getOwnerClassEntry()); - if ((classMapping != null && classMapping.containsDeobfMethod(deobfName, entry.getDesc()) && classMapping.getMethodByObf(entry.getName(), entry.getDesc()) != classMapping.getMethodByDeobf(deobfName, entry.getDesc())) - || index.containsObfMethod(targetEntry)) { - Translator translator = mappings.getTranslator(TranslationDirection.DEOBFUSCATING, index.getTranslationIndex()); - String deobfClassName = translator.getTranslatedClass(entryPool.getClass(entry.getClassName())).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.getOwnerClassEntry())) { - validateMethodTreeName(entry.updateOwnership(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 = entryPool.getMethod(obf.getOwnerClassEntry(), deobfName, obf.getDesc()); - ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); - - // TODO: Verify if I don't break things - if ((mappings.containsDeobfMethod(obf.getOwnerClassEntry(), deobfName, obf.getDesc()) && classMapping.getMethodByObf(obf.getName(), obf.getDesc()) != classMapping.getMethodByDeobf(deobfName, obf.getDesc())) - || index.containsObfMethod(targetEntry)) { - Translator translator = mappings.getTranslator(TranslationDirection.DEOBFUSCATING, index.getTranslationIndex()); - String deobfClassName = translator.getTranslatedClass(entryPool.getClass(obf.getClassName())).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.getDesc(), deobfName); - } - - public void removeMethodTreeMapping(MethodEntry obf) { - index.getRelatedMethodImplementations(obf).forEach(this::removeMethodMapping); - } - - public void removeMethodMapping(MethodEntry obf) { - ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); - classMapping.setMethodName(obf.getName(), obf.getDesc(), null); - } - - public void markMethodTreeAsDeobfuscated(MethodEntry obf) { - index.getRelatedMethodImplementations(obf).forEach(this::markMethodAsDeobfuscated); - } - - public void markMethodAsDeobfuscated(MethodEntry obf) { - ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); - classMapping.setMethodName(obf.getName(), obf.getDesc(), obf.getName()); - } - - public void setLocalVariableTreeName(LocalVariableEntry obf, String deobfName) { - MethodEntry obfMethod = obf.getOwnerEntry(); - if (!obf.isParameter()) { - setLocalVariableName(obf, deobfName); - return; - } - - Set implementations = index.getRelatedMethodImplementations(obfMethod); - for (MethodEntry entry : implementations) { - ClassMapping classMapping = mappings.getClassByObf(entry.getOwnerClassEntry()); - if (classMapping != null) { - MethodMapping mapping = classMapping.getMethodByObf(entry.getName(), entry.getDesc()); - // 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 (LocalVariableMapping localVariableMapping : Lists.newArrayList(mapping.arguments())) { - if (localVariableMapping.getIndex() != obf.getIndex()) { - if (mapping.getDeobfLocalVariableName(localVariableMapping.getIndex()).equals(deobfName) - || localVariableMapping.getName().equals(deobfName)) { - throw new IllegalNameException(deobfName, "There is already an argument with that name"); - } - } - } - } - } - } - - for (MethodEntry entry : implementations) { - setLocalVariableName(new LocalVariableEntry(entry, obf.getIndex(), obf.getName(), obf.isParameter()), deobfName); - } - } - - public void setLocalVariableName(LocalVariableEntry obf, String deobfName) { - deobfName = NameValidator.validateArgumentName(deobfName); - ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); - MethodMapping mapping = classMapping.getMethodByObf(obf.getMethodName(), obf.getMethodDesc()); - // 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 (LocalVariableMapping localVariableMapping : Lists.newArrayList(mapping.arguments())) { - if (localVariableMapping.getIndex() != obf.getIndex()) { - if (mapping.getDeobfLocalVariableName(localVariableMapping.getIndex()).equals(deobfName) - || localVariableMapping.getName().equals(deobfName)) { - throw new IllegalNameException(deobfName, "There is already an argument with that name"); - } - } - } - } - - classMapping.setArgumentName(obf.getMethodName(), obf.getMethodDesc(), obf.getIndex(), deobfName); - } - - public void removeLocalVariableMapping(LocalVariableEntry obf) { - ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); - classMapping.removeArgumentName(obf.getMethodName(), obf.getMethodDesc(), obf.getIndex()); - } - - public void markArgumentAsDeobfuscated(LocalVariableEntry obf) { - ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); - classMapping.setArgumentName(obf.getMethodName(), obf.getMethodDesc(), 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.getObfDesc())) { - if (!targetClassMapping.containsDeobfField(fieldMapping.getDeobfName(), fieldMapping.getObfDesc())) { - 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.getObfDesc())) { - if (!targetClassMapping.containsDeobfMethod(methodMapping.getDeobfName(), methodMapping.getObfDesc())) { - targetClassMapping.addMethodMapping(methodMapping); - return true; - } else { - System.err.println("WARNING: deobf method was already there: " + obfClass + "." + methodMapping.getDeobfName() + methodMapping.getObfDesc()); - } - } - 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.getOwnerClassEntry()); - classMapping.setFieldModifier(obEntry.getName(), obEntry.getDesc(), modifier); - } - - public void setMethodModifier(MethodEntry obEntry, Mappings.EntryModifier modifier) { - ClassMapping classMapping = getOrCreateClassMapping(obEntry.getOwnerClassEntry()); - classMapping.setMethodModifier(obEntry.getName(), obEntry.getDesc(), modifier); - } - - public Mappings.EntryModifier getClassModifier(ClassEntry obfEntry) { - ClassMapping classMapping = getOrCreateClassMapping(obfEntry); - return classMapping.getModifier(); - } - - public Mappings.EntryModifier getFieldModifier(FieldEntry obfEntry) { - ClassMapping classMapping = getOrCreateClassMapping(obfEntry.getOwnerClassEntry()); - FieldMapping fieldMapping = classMapping.getFieldByObf(obfEntry); - if (fieldMapping == null) { - return Mappings.EntryModifier.UNCHANGED; - } - return fieldMapping.getModifier(); - } - - public Mappings.EntryModifier getMethodModfifier(MethodEntry obfEntry) { - ClassMapping classMapping = getOrCreateClassMapping(obfEntry.getOwnerClassEntry()); - MethodMapping methodMapping = classMapping.getMethodByObf(obfEntry); - if (methodMapping == null) { - return Mappings.EntryModifier.UNCHANGED; - } - return methodMapping.getModifier(); - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/MappingsSRGWriter.java b/src/main/java/cuchaz/enigma/mapping/MappingsSRGWriter.java deleted file mode 100644 index 32f0ee9..0000000 --- a/src/main/java/cuchaz/enigma/mapping/MappingsSRGWriter.java +++ /dev/null @@ -1,80 +0,0 @@ -package cuchaz.enigma.mapping; - -import com.google.common.base.Charsets; -import cuchaz.enigma.analysis.TranslationIndex; -import cuchaz.enigma.mapping.entry.ReferencedEntryPool; - -import java.io.*; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Created by Mark on 11/08/2016. - */ -public class MappingsSRGWriter { - - public void write(File file, Mappings mappings) throws IOException { - if (file.exists()) { - file.delete(); - } - file.createNewFile(); - - TranslationIndex index = new TranslationIndex(new ReferencedEntryPool()); - - 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.getObfDesc() + " " + innerDeobfClassName + "/" + methodMapping.getDeobfName() + " " + mappings.getTranslator(TranslationDirection.DEOBFUSCATING, index).getTranslatedMethodDesc(methodMapping.getObfDesc())); - } - } - - 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.getObfDesc() + " " + classMapping.getDeobfName() + "/" + methodMapping.getDeobfName() + " " + mappings.getTranslator(TranslationDirection.DEOBFUSCATING, index).getTranslatedMethodDesc(methodMapping.getObfDesc())); - } - } - for (String fd : fieldMappings) { - writer.write(fd); - writer.write(System.lineSeparator()); - } - - for (String md : methodMappings) { - writer.write(md); - writer.write(System.lineSeparator()); - } - - writer.close(); - } - - 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/MappingsTinyReader.java b/src/main/java/cuchaz/enigma/mapping/MappingsTinyReader.java deleted file mode 100644 index 756ac43..0000000 --- a/src/main/java/cuchaz/enigma/mapping/MappingsTinyReader.java +++ /dev/null @@ -1,130 +0,0 @@ -package cuchaz.enigma.mapping; - -import com.google.common.base.Charsets; -import com.google.common.collect.Maps; -import cuchaz.enigma.mapping.entry.ClassEntry; -import cuchaz.enigma.throwables.MappingConflict; -import cuchaz.enigma.throwables.MappingParseException; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -public class MappingsTinyReader { - public ClassMapping readClass(String[] parts) { - // Extract the inner naming of the deob form if it have one - String deobName = parts[2].contains("$") ? parts[2].substring(parts[2].lastIndexOf('$') + 1) : parts[2]; - return new ClassMapping(parts[1], deobName).setDeobfInner(parts[2]); - } - - public FieldMapping readField(String[] parts) { - return new FieldMapping(parts[3], new TypeDescriptor(parts[2]), parts[4], Mappings.EntryModifier.UNCHANGED); - } - - public MethodMapping readMethod(String[] parts) { - return new MethodMapping(parts[3], new MethodDescriptor(parts[2]), parts[4]); - } - - public Mappings read(File file) throws IOException, MappingParseException { - Mappings mappings = new Mappings(Mappings.FormatType.TINY_FILE); - List lines = Files.readAllLines(file.toPath(), Charsets.UTF_8); - Map classMappingMap = Maps.newHashMap(); - lines.remove(0); // TODO: use the header - for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) { - String line = lines.get(lineNumber); - String[] parts = line.split("\t"); - try { - String token = parts[0]; - ClassMapping classMapping; - switch (token) { - case "CLASS": - - // Check for orphan created by field or method entries. It shouldn't be possible but I prefer to handle this case - if (classMappingMap.containsKey(parts[1])) { - classMapping = classMappingMap.get(parts[1]); - - // We have the full deob name, Enigma only support simple class name so we extract it. - String deobName = parts[2].contains("$") ? - parts[2].substring(parts[2].lastIndexOf('$') + 1) : - parts[2]; - - // Add full deob name to the class mapping to handle inner class after this loop - classMappingMap.put(parts[2], classMapping.setDeobfInner(parts[2])); - classMapping.setDeobfName(deobName); - - // Avoid to make the mapping dirty directly at the startup - classMapping.resetDirty(); - } else - classMapping = readClass(parts); - classMappingMap.put(parts[1], classMapping); - break; - case "FIELD": - // We can have missing classes mappings because they don't have a ob name, so we create it and use it - classMapping = classMappingMap.computeIfAbsent(parts[1], k -> new ClassMapping(parts[1])); - classMapping.addFieldMapping(readField(parts)); - break; - case "METHOD": - // We can have missing classes mappings because they don't have a ob name, so we create it and use it - classMapping = classMappingMap.computeIfAbsent(parts[1], k -> new ClassMapping(parts[1])); - classMapping.addMethodMapping(readMethod(parts)); - break; - case "MTH-ARG": - classMapping = classMappingMap.computeIfAbsent(parts[1], k -> new ClassMapping(parts[1])); - classMapping.setArgumentName(parts[3], new MethodDescriptor(parts[2]), Integer.parseInt(parts[4]), parts[5]); - break; - default: - throw new MappingParseException(file, lineNumber, "Unknown token '" + token + "' !"); - } - } catch (ArrayIndexOutOfBoundsException | IllegalArgumentException ex) { - ex.printStackTrace(); - throw new MappingParseException(file, lineNumber, "Malformed line:\n" + line); - } - } - - List toRegister = new ArrayList<>(classMappingMap.values()); - - // After having completely parsed the file, we need to register it to the real mapping - for (ClassMapping classMapping : toRegister) { - ClassEntry obEntry = classMapping.getObfEntry(); - ClassEntry deobEntry = classMapping.getDeObfEntry(); - try { - if (obEntry.isInnerClass()) { - ClassMapping parent = classMappingMap.get(obEntry.getOuterClassName()); - // Inner class can miss their parent... So we create it and add it to the mappings - if (parent == null) { - parent = new ClassMapping(obEntry.getOuterClassName()); // FIXME: WE ACTUALLY DON'T MANAGE INNER CLASS OF INNER CLASS - classMappingMap.put(obEntry.getOuterClassName(), parent); - mappings.addClassMapping(parent); - } - // Add the inner class to the parent - parent.addInnerClassMapping(classMapping); - } - // obf class can become deobf inner classs, manage this case. - else if (deobEntry != null && deobEntry.isInnerClass()) { - String outerClassName = deobEntry.getOuterClassName(); - ClassMapping parent = classMappingMap.get(outerClassName); - - // Only the inner is deob??? Okay - if (parent == null) { - parent = classMappingMap.get(outerClassName); - if (parent == null) { - parent = new ClassMapping(outerClassName); // FIXME: WE ACTUALLY DON'T MANAGE INNER CLASS OF INNER CLASS - classMappingMap.put(outerClassName, parent); - mappings.addClassMapping(parent); - } - } - parent.addInnerClassMapping(classMapping); - } else - mappings.addClassMapping(classMapping); - } catch (MappingConflict e) { - throw new MappingParseException(file, -1, e.getMessage()); - } - } - lines.clear(); - classMappingMap.clear(); - return mappings; - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/MemberMapping.java b/src/main/java/cuchaz/enigma/mapping/MemberMapping.java deleted file mode 100644 index 6effb91..0000000 --- a/src/main/java/cuchaz/enigma/mapping/MemberMapping.java +++ /dev/null @@ -1,21 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * 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 cuchaz.enigma.mapping.entry.ClassEntry; -import cuchaz.enigma.mapping.entry.Entry; - -public interface MemberMapping { - T getObfEntry(ClassEntry classEntry); - - String getObfName(); -} diff --git a/src/main/java/cuchaz/enigma/mapping/MethodDescriptor.java b/src/main/java/cuchaz/enigma/mapping/MethodDescriptor.java deleted file mode 100644 index 0fc0351..0000000 --- a/src/main/java/cuchaz/enigma/mapping/MethodDescriptor.java +++ /dev/null @@ -1,114 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * 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.collect.Lists; -import cuchaz.enigma.mapping.entry.ClassEntry; -import cuchaz.enigma.utils.Utils; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Function; - -public class MethodDescriptor { - - private List argumentDescs; - private TypeDescriptor returnDesc; - - public MethodDescriptor(String desc) { - try { - this.argumentDescs = Lists.newArrayList(); - int i = 0; - while (i < desc.length()) { - char c = desc.charAt(i); - if (c == '(') { - assert (this.argumentDescs.isEmpty()); - assert (this.returnDesc == null); - i++; - } else if (c == ')') { - i++; - break; - } else { - String type = TypeDescriptor.parseFirst(desc.substring(i)); - this.argumentDescs.add(new TypeDescriptor(type)); - i += type.length(); - } - } - this.returnDesc = new TypeDescriptor(TypeDescriptor.parseFirst(desc.substring(i))); - } catch (Exception ex) { - throw new IllegalArgumentException("Unable to parse method descriptor: " + desc, ex); - } - } - - public MethodDescriptor(List argumentDescs, TypeDescriptor returnDesc) { - this.argumentDescs = argumentDescs; - this.returnDesc = returnDesc; - } - - public List getArgumentDescs() { - return this.argumentDescs; - } - - public TypeDescriptor getReturnDesc() { - return this.returnDesc; - } - - @Override - public String toString() { - StringBuilder buf = new StringBuilder(); - buf.append("("); - for (TypeDescriptor desc : this.argumentDescs) { - buf.append(desc); - } - buf.append(")"); - buf.append(this.returnDesc); - return buf.toString(); - } - - public Iterable types() { - List descs = Lists.newArrayList(); - descs.addAll(this.argumentDescs); - descs.add(this.returnDesc); - return descs; - } - - @Override - public boolean equals(Object other) { - return other instanceof MethodDescriptor && equals((MethodDescriptor) other); - } - - public boolean equals(MethodDescriptor other) { - return this.argumentDescs.equals(other.argumentDescs) && this.returnDesc.equals(other.returnDesc); - } - - @Override - public int hashCode() { - return Utils.combineHashesOrdered(this.argumentDescs.hashCode(), this.returnDesc.hashCode()); - } - - public boolean hasClass(ClassEntry classEntry) { - for (TypeDescriptor desc : types()) { - if (desc.containsType() && desc.getTypeEntry().equals(classEntry)) { - return true; - } - } - return false; - } - - public MethodDescriptor remap(Function remapper) { - List argumentDescs = new ArrayList<>(this.argumentDescs.size()); - for (TypeDescriptor desc : this.argumentDescs) { - argumentDescs.add(desc.remap(remapper)); - } - return new MethodDescriptor(argumentDescs, returnDesc.remap(remapper)); - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/MethodMapping.java b/src/main/java/cuchaz/enigma/mapping/MethodMapping.java deleted file mode 100644 index 2f10144..0000000 --- a/src/main/java/cuchaz/enigma/mapping/MethodMapping.java +++ /dev/null @@ -1,210 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * 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.Preconditions; -import com.google.common.collect.Maps; -import cuchaz.enigma.mapping.entry.ClassEntry; -import cuchaz.enigma.mapping.entry.MethodEntry; -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 MethodDescriptor obfDescriptor; - private Map localVariables; - private Mappings.EntryModifier modifier; - - public MethodMapping(String obfName, MethodDescriptor obfDescriptor) { - this(obfName, obfDescriptor, null, Mappings.EntryModifier.UNCHANGED); - } - - public MethodMapping(String obfName, MethodDescriptor obfDescriptor, String deobfName) { - this(obfName, obfDescriptor, deobfName, Mappings.EntryModifier.UNCHANGED); - } - - public MethodMapping(String obfName, MethodDescriptor obfDescriptor, String deobfName, Mappings.EntryModifier modifier) { - Preconditions.checkNotNull(obfName, "Method obf name cannot be null"); - Preconditions.checkNotNull(obfDescriptor, "Method obf desc cannot be null"); - this.obfName = obfName; - this.deobfName = NameValidator.validateMethodName(deobfName); - this.obfDescriptor = obfDescriptor; - this.localVariables = Maps.newTreeMap(); - this.modifier = modifier; - } - - public MethodMapping(MethodMapping other, Translator translator) { - this.obfName = other.obfName; - this.deobfName = other.deobfName; - this.modifier = other.modifier; - this.obfDescriptor = translator.getTranslatedMethodDesc(other.obfDescriptor); - this.localVariables = Maps.newTreeMap(); - for (Map.Entry entry : other.localVariables.entrySet()) { - this.localVariables.put(entry.getKey(), new LocalVariableMapping(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() { - if (deobfName == null) { - return obfName; - } - return this.deobfName; - } - - public void setDeobfName(String val) { - this.deobfName = NameValidator.validateMethodName(val); - } - - public MethodDescriptor getObfDesc() { - return this.obfDescriptor; - } - - public void setObfDescriptor(MethodDescriptor val) { - this.obfDescriptor = val; - } - - public Iterable arguments() { - return this.localVariables.values(); - } - - public void addArgumentMapping(LocalVariableMapping localVariableMapping) throws MappingConflict { - if (this.localVariables.containsKey(localVariableMapping.getIndex())) { - throw new MappingConflict("argument", localVariableMapping.getName(), this.localVariables.get(localVariableMapping.getIndex()).getName()); - } - this.localVariables.put(localVariableMapping.getIndex(), localVariableMapping); - } - - public String getObfLocalVariableName(int index) { - LocalVariableMapping localVariableMapping = this.localVariables.get(index); - if (localVariableMapping != null) { - return localVariableMapping.getName(); - } - - return null; - } - - public String getDeobfLocalVariableName(int index) { - LocalVariableMapping localVariableMapping = this.localVariables.get(index); - if (localVariableMapping != null) { - return localVariableMapping.getName(); - } - - return null; - } - - public void setLocalVariableName(int index, String name) { - LocalVariableMapping localVariableMapping = this.localVariables.get(index); - if (localVariableMapping == null) { - localVariableMapping = new LocalVariableMapping(index, name); - boolean wasAdded = this.localVariables.put(index, localVariableMapping) == null; - assert (wasAdded); - } else { - localVariableMapping.setName(name); - } - } - - public void removeLocalVariableName(int index) { - boolean wasRemoved = this.localVariables.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.obfDescriptor); - buf.append("\n"); - buf.append("\tLocal Variables:\n"); - for (LocalVariableMapping localVariableMapping : this.localVariables.values()) { - buf.append("\t\t"); - buf.append(localVariableMapping.getIndex()); - buf.append(" -> "); - buf.append(localVariableMapping.getName()); - buf.append("\n"); - } - return buf.toString(); - } - - @Override - public int compareTo(MethodMapping other) { - return (this.obfName + this.obfDescriptor).compareTo(other.obfName + other.obfDescriptor); - } - - public boolean containsLocalVariable(String name) { - for (LocalVariableMapping localVariableMapping : this.localVariables.values()) { - if (localVariableMapping.getName().equals(name)) { - return true; - } - } - return false; - } - - public boolean renameObfClass(final String oldObfClassName, final String newObfClassName) { - // rename obf classes in the signature - MethodDescriptor newDescriptor = obfDescriptor.remap(className -> { - if (className.equals(oldObfClassName)) { - return newObfClassName; - } - return className; - }); - - if (!newDescriptor.equals(this.obfDescriptor)) { - this.obfDescriptor = newDescriptor; - return true; - } - return false; - } - - @Override - public MethodEntry getObfEntry(ClassEntry classEntry) { - return new MethodEntry(classEntry, this.obfName, this.obfDescriptor); - } - - public Mappings.EntryModifier getModifier() { - return modifier; - } - - public void setModifier(Mappings.EntryModifier modifier) { - this.modifier = modifier; - } - - public boolean isObfuscated() { - return deobfName == null || deobfName.equals(obfName); - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/NameValidator.java b/src/main/java/cuchaz/enigma/mapping/NameValidator.java deleted file mode 100644 index fca8cfc..0000000 --- a/src/main/java/cuchaz/enigma/mapping/NameValidator.java +++ /dev/null @@ -1,73 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * 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 cuchaz.enigma.mapping.entry.ClassEntry; -import cuchaz.enigma.throwables.IllegalNameException; - -import java.util.Arrays; -import java.util.List; -import java.util.regex.Pattern; - -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" - ); - - 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 && ClassEntry.getPackageName(name) == null) { - throw new IllegalNameException(name, "Class must be in a package"); - } - 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 validateArgumentName(String name) { - return validateFieldName(name); - } - - public static boolean isReserved(String name) { - return ReservedWords.contains(name); - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/Signature.java b/src/main/java/cuchaz/enigma/mapping/Signature.java deleted file mode 100644 index 071e4af..0000000 --- a/src/main/java/cuchaz/enigma/mapping/Signature.java +++ /dev/null @@ -1,82 +0,0 @@ -package cuchaz.enigma.mapping; - -import cuchaz.enigma.bytecode.translators.TranslationSignatureVisitor; -import org.objectweb.asm.signature.SignatureReader; -import org.objectweb.asm.signature.SignatureVisitor; -import org.objectweb.asm.signature.SignatureWriter; - -import java.util.function.Function; -import java.util.regex.Pattern; - -public class Signature { - private static final Pattern OBJECT_PATTERN = Pattern.compile(".*:Ljava/lang/Object;:.*"); - - private final String signature; - private final boolean isType; - - private Signature(String signature, boolean isType) { - if (signature != null && OBJECT_PATTERN.matcher(signature).matches()) { - signature = signature.replaceAll(":Ljava/lang/Object;:", "::"); - } - - this.signature = signature; - this.isType = isType; - } - - public static Signature createTypedSignature(String signature) { - if (signature != null && !signature.isEmpty()) { - return new Signature(signature, true); - } - return new Signature(null, true); - } - - public static Signature createSignature(String signature) { - if (signature != null && !signature.isEmpty()) { - return new Signature(signature, false); - } - return new Signature(null, false); - } - - public String getSignature() { - return signature; - } - - public boolean isType() { - return isType; - } - - public Signature remap(Function remapper) { - if (signature == null) { - return this; - } - SignatureWriter writer = new SignatureWriter(); - SignatureVisitor visitor = new TranslationSignatureVisitor(remapper, writer); - if (isType) { - new SignatureReader(signature).acceptType(visitor); - } else { - new SignatureReader(signature).accept(visitor); - } - return new Signature(writer.toString(), isType); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof Signature) { - Signature other = (Signature) obj; - return (other.signature == null && signature == null || other.signature != null - && signature != null && other.signature.equals(signature)) - && other.isType == this.isType; - } - return false; - } - - @Override - public int hashCode() { - return signature.hashCode() | (isType ? 1 : 0) << 16; - } - - @Override - public String toString() { - return signature; - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/SignatureUpdater.java b/src/main/java/cuchaz/enigma/mapping/SignatureUpdater.java deleted file mode 100644 index ddc5af4..0000000 --- a/src/main/java/cuchaz/enigma/mapping/SignatureUpdater.java +++ /dev/null @@ -1,92 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * 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.collect.Lists; - -import java.io.IOException; -import java.io.StringReader; -import java.util.List; - -public class SignatureUpdater { - - 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; - - // 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); - } - } - - 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); - } - } - } - - return null; - } - - 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 deleted file mode 100644 index 4bbde54..0000000 --- a/src/main/java/cuchaz/enigma/mapping/TranslationDirection.java +++ /dev/null @@ -1,36 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * 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; - -public enum TranslationDirection { - - DEOBFUSCATING { - @Override - public T choose(T deobfChoice, T obfChoice) { - if (deobfChoice == null) { - return obfChoice; - } - return deobfChoice; - } - }, - OBFUSCATING { - @Override - public T choose(T deobfChoice, T obfChoice) { - if (obfChoice == null) { - return deobfChoice; - } - return 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 deleted file mode 100644 index a9ff1cb..0000000 --- a/src/main/java/cuchaz/enigma/mapping/Translator.java +++ /dev/null @@ -1,109 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * 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 cuchaz.enigma.mapping.entry.*; -import org.objectweb.asm.Handle; -import org.objectweb.asm.Type; - -public interface Translator { - ClassEntry getTranslatedClass(ClassEntry entry); - - ClassDefEntry getTranslatedClassDef(ClassDefEntry entry); - - FieldEntry getTranslatedField(FieldEntry entry); - - FieldDefEntry getTranslatedFieldDef(FieldDefEntry entry); - - MethodEntry getTranslatedMethod(MethodEntry entry); - - MethodDefEntry getTranslatedMethodDef(MethodDefEntry entry); - - LocalVariableEntry getTranslatedVariable(LocalVariableEntry entry); - - LocalVariableDefEntry getTranslatedVariableDef(LocalVariableDefEntry entry); - - boolean hasClassMapping(ClassEntry entry); - - boolean hasFieldMapping(FieldEntry entry); - - boolean hasMethodMapping(MethodEntry entry); - - boolean hasLocalVariableMapping(LocalVariableEntry entry); - - TypeDescriptor getTranslatedTypeDesc(TypeDescriptor desc); - - MethodDescriptor getTranslatedMethodDesc(MethodDescriptor descriptor); - - Signature getTranslatedSignature(Signature signature); - - default Type getTranslatedType(Type type) { - String descString = type.getDescriptor(); - switch (type.getSort()) { - case Type.OBJECT: { - ClassEntry classEntry = new ClassEntry(type.getInternalName()); - return Type.getObjectType(getTranslatedClass(classEntry).getName()); - } - case Type.ARRAY: { - TypeDescriptor descriptor = new TypeDescriptor(descString); - return Type.getType(getTranslatedTypeDesc(descriptor).toString()); - } - case Type.METHOD: { - MethodDescriptor descriptor = new MethodDescriptor(descString); - return Type.getMethodType(getTranslatedMethodDesc(descriptor).toString()); - } - } - return type; - } - - default Handle getTranslatedHandle(Handle handle) { - MethodEntry entry = new MethodEntry(new ClassEntry(handle.getOwner()), handle.getName(), new MethodDescriptor(handle.getDesc())); - MethodEntry translatedMethod = getTranslatedMethod(entry); - ClassEntry ownerClass = translatedMethod.getOwnerClassEntry(); - return new Handle(handle.getTag(), ownerClass.getName(), translatedMethod.getName(), translatedMethod.getDesc().toString(), handle.isInterface()); - } - - default Object getTranslatedValue(Object value) { - if (value instanceof Type) { - return this.getTranslatedType((Type) value); - } else if (value instanceof Handle) { - return getTranslatedHandle((Handle) value); - } - return value; - } - - @SuppressWarnings("unchecked") - default T getTranslatedEntry(T entry) { - if (entry instanceof ClassDefEntry) { - return (T) getTranslatedClassDef((ClassDefEntry) entry); - } else if (entry instanceof ClassEntry) { - return (T) getTranslatedClass((ClassEntry) entry); - } else if (entry instanceof FieldDefEntry) { - return (T) getTranslatedFieldDef((FieldDefEntry) entry); - } else if (entry instanceof MethodDefEntry) { - return (T) getTranslatedMethodDef((MethodDefEntry) entry); - } else if (entry instanceof FieldEntry) { - return (T) getTranslatedField((FieldEntry) entry); - } else if (entry instanceof MethodEntry) { - return (T) getTranslatedMethod((MethodEntry) entry); - } else if (entry instanceof LocalVariableDefEntry) { - return (T) getTranslatedVariableDef((LocalVariableDefEntry) entry); - } else if (entry instanceof LocalVariableEntry) { - return (T) getTranslatedVariable((LocalVariableEntry) entry); - } else if (entry instanceof TypeDescriptor) { - return (T) getTranslatedTypeDesc((TypeDescriptor) entry); - } else if (entry instanceof MethodDescriptor) { - return (T) getTranslatedMethodDesc((MethodDescriptor) entry); - } - throw new IllegalArgumentException("Cannot translate unknown entry type"); - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/TypeDescriptor.java b/src/main/java/cuchaz/enigma/mapping/TypeDescriptor.java deleted file mode 100644 index 6e58aa0..0000000 --- a/src/main/java/cuchaz/enigma/mapping/TypeDescriptor.java +++ /dev/null @@ -1,258 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * 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.Preconditions; -import com.google.common.collect.Maps; -import cuchaz.enigma.mapping.entry.ClassEntry; - -import java.util.Map; -import java.util.function.Function; - -public class TypeDescriptor { - - protected final String desc; - - public TypeDescriptor(String desc) { - Preconditions.checkNotNull(desc, "Desc cannot be null"); - - // don't deal with generics - // this is just for raw jvm types - if (desc.charAt(0) == 'T' || desc.indexOf('<') >= 0 || desc.indexOf('>') >= 0) { - throw new IllegalArgumentException("don't use with generic types or templates: " + desc); - } - - this.desc = desc; - } - - public static String parseFirst(String in) { - - if (in == null || in.length() <= 0) { - throw new IllegalArgumentException("No desc to parse, input is empty!"); - } - - // read one desc 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 = TypeDescriptor.parseFirst(in.substring(dim)); - return in.substring(0, dim + arrayType.length()); - } - - throw new IllegalArgumentException("don't know how to parse: " + in); - } - - 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; - } - - public static TypeDescriptor of(String name) { - return new TypeDescriptor("L" + name + ";"); - } - - @Override - public String toString() { - return this.desc; - } - - public boolean isVoid() { - return this.desc.length() == 1 && this.desc.charAt(0) == 'V'; - } - - public boolean isPrimitive() { - return this.desc.length() == 1 && Primitive.get(this.desc.charAt(0)) != null; - } - - public Primitive getPrimitive() { - if (!isPrimitive()) { - throw new IllegalStateException("not a primitive"); - } - return Primitive.get(this.desc.charAt(0)); - } - - public boolean isType() { - return this.desc.charAt(0) == 'L' && this.desc.charAt(this.desc.length() - 1) == ';'; - } - - public ClassEntry getTypeEntry() { - if (isType()) { - String name = this.desc.substring(1, this.desc.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().isType()) { - return getArrayType().getTypeEntry(); - } else { - throw new IllegalStateException("desc doesn't have a class"); - } - } - - public boolean isArray() { - return this.desc.charAt(0) == '['; - } - - public int getArrayDimension() { - if (!isArray()) { - throw new IllegalStateException("not an array"); - } - return countArrayDimension(this.desc); - } - - public TypeDescriptor getArrayType() { - if (!isArray()) { - throw new IllegalStateException("not an array"); - } - return new TypeDescriptor(this.desc.substring(getArrayDimension(), this.desc.length())); - } - - public boolean containsType() { - return isType() || (isArray() && getArrayType().containsType()); - } - - @Override - public boolean equals(Object other) { - return other instanceof TypeDescriptor && equals((TypeDescriptor) other); - } - - public boolean equals(TypeDescriptor other) { - return this.desc.equals(other.desc); - } - - @Override - public int hashCode() { - return this.desc.hashCode(); - } - - public TypeDescriptor remap(Function remapper) { - String desc = this.desc; - if (isType() || (isArray() && containsType())) { - String replacedName = remapper.apply(this.getTypeEntry().getName()); - if (replacedName != null) { - if (this.isType()) { - desc = "L" + replacedName + ";"; - } else { - desc = getArrayPrefix(this.getArrayDimension()) + "L" + replacedName + ";"; - } - } - } - return new TypeDescriptor(desc); - } - - private static String getArrayPrefix(int dimension) { - StringBuilder buf = new StringBuilder(); - for (int i = 0; i < dimension; i++) { - buf.append("["); - } - return buf.toString(); - } - - public int getSize() { - switch (desc.charAt(0)) { - case 'J': - case 'D': - if (desc.length() == 1) { - return 2; - } else { - return 1; - } - default: - return 1; - } - } - - 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/mapping/entry/ClassDefEntry.java b/src/main/java/cuchaz/enigma/mapping/entry/ClassDefEntry.java deleted file mode 100644 index df72e7e..0000000 --- a/src/main/java/cuchaz/enigma/mapping/entry/ClassDefEntry.java +++ /dev/null @@ -1,38 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * 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.entry; - -import com.google.common.base.Preconditions; -import cuchaz.enigma.bytecode.AccessFlags; -import cuchaz.enigma.mapping.Signature; - -public class ClassDefEntry extends ClassEntry implements DefEntry { - private final AccessFlags access; - private final Signature signature; - - public ClassDefEntry(String className, Signature signature, AccessFlags access) { - super(className); - Preconditions.checkNotNull(signature, "Class signature cannot be null"); - Preconditions.checkNotNull(access, "Class access cannot be null"); - this.signature = signature; - this.access = access; - } - - public Signature getSignature() { - return signature; - } - - @Override - public AccessFlags getAccess() { - return access; - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/entry/ClassEntry.java b/src/main/java/cuchaz/enigma/mapping/entry/ClassEntry.java deleted file mode 100644 index c795825..0000000 --- a/src/main/java/cuchaz/enigma/mapping/entry/ClassEntry.java +++ /dev/null @@ -1,175 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * 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.entry; - -import com.google.common.base.Preconditions; -import com.google.common.collect.Lists; - -import java.util.List; - -public class ClassEntry implements Entry { - - private final String name; - - public ClassEntry(String className) { - Preconditions.checkNotNull(className, "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 getOwnerClassEntry() { - return this; - } - - @Override - public ClassEntry updateOwnership(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 isArray() { - return this.name.lastIndexOf('[') >= 0; - } - - 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() { - return getPackageName(this.name); - } - - public String getSimpleName() { - int pos = this.name.lastIndexOf('/'); - if (pos > 0) { - return this.name.substring(pos + 1); - } - return this.name; - } - - public static String getPackageName(String name) { - int pos = name.lastIndexOf('/'); - if (pos > 0) { - return name.substring(0, pos); - } - return null; - } - - 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/entry/DefEntry.java b/src/main/java/cuchaz/enigma/mapping/entry/DefEntry.java deleted file mode 100644 index 43ad027..0000000 --- a/src/main/java/cuchaz/enigma/mapping/entry/DefEntry.java +++ /dev/null @@ -1,7 +0,0 @@ -package cuchaz.enigma.mapping.entry; - -import cuchaz.enigma.bytecode.AccessFlags; - -public interface DefEntry extends Entry { - AccessFlags getAccess(); -} diff --git a/src/main/java/cuchaz/enigma/mapping/entry/Entry.java b/src/main/java/cuchaz/enigma/mapping/entry/Entry.java deleted file mode 100644 index b612140..0000000 --- a/src/main/java/cuchaz/enigma/mapping/entry/Entry.java +++ /dev/null @@ -1,22 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * 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.entry; - -public interface Entry { - String getName(); - - String getClassName(); - - ClassEntry getOwnerClassEntry(); - - Entry updateOwnership(ClassEntry classEntry); -} diff --git a/src/main/java/cuchaz/enigma/mapping/entry/EntryFactory.java b/src/main/java/cuchaz/enigma/mapping/entry/EntryFactory.java deleted file mode 100644 index 5bd159f..0000000 --- a/src/main/java/cuchaz/enigma/mapping/entry/EntryFactory.java +++ /dev/null @@ -1,49 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * 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.entry; - -import cuchaz.enigma.analysis.JarIndex; -import cuchaz.enigma.mapping.ClassMapping; -import cuchaz.enigma.mapping.FieldMapping; -import cuchaz.enigma.mapping.MethodDescriptor; -import cuchaz.enigma.mapping.MethodMapping; - -public class EntryFactory { - 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 FieldEntry getObfFieldEntry(ClassMapping classMapping, FieldMapping fieldMapping) { - return new FieldEntry(getObfClassEntry(classMapping), fieldMapping.getObfName(), fieldMapping.getObfDesc()); - } - - public static MethodEntry getMethodEntry(ClassEntry classEntry, String name, MethodDescriptor desc) { - return new MethodEntry(classEntry, name, desc); - } - - public static MethodEntry getObfMethodEntry(ClassEntry classEntry, MethodMapping methodMapping) { - return getMethodEntry(classEntry, methodMapping.getObfName(), methodMapping.getObfDesc()); - } - - public static MethodEntry getObfMethodEntry(ClassMapping classMapping, MethodMapping methodMapping) { - return getObfMethodEntry(getObfClassEntry(classMapping), methodMapping); - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/entry/FieldDefEntry.java b/src/main/java/cuchaz/enigma/mapping/entry/FieldDefEntry.java deleted file mode 100644 index 223410f..0000000 --- a/src/main/java/cuchaz/enigma/mapping/entry/FieldDefEntry.java +++ /dev/null @@ -1,44 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * 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.entry; - -import com.google.common.base.Preconditions; -import cuchaz.enigma.bytecode.AccessFlags; -import cuchaz.enigma.mapping.Signature; -import cuchaz.enigma.mapping.TypeDescriptor; - -public class FieldDefEntry extends FieldEntry implements DefEntry { - private final AccessFlags access; - private final Signature signature; - - public FieldDefEntry(ClassEntry ownerEntry, String name, TypeDescriptor desc, Signature signature, AccessFlags access) { - super(ownerEntry, name, desc); - Preconditions.checkNotNull(access, "Field access cannot be null"); - Preconditions.checkNotNull(signature, "Field signature cannot be null"); - this.access = access; - this.signature = signature; - } - - @Override - public AccessFlags getAccess() { - return access; - } - - public Signature getSignature() { - return signature; - } - - @Override - public FieldDefEntry updateOwnership(ClassEntry owner) { - return new FieldDefEntry(owner, this.name, this.desc, signature, access); - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/entry/FieldEntry.java b/src/main/java/cuchaz/enigma/mapping/entry/FieldEntry.java deleted file mode 100644 index b6e1554..0000000 --- a/src/main/java/cuchaz/enigma/mapping/entry/FieldEntry.java +++ /dev/null @@ -1,77 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * 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.entry; - -import com.google.common.base.Preconditions; -import cuchaz.enigma.mapping.TypeDescriptor; -import cuchaz.enigma.utils.Utils; - -public class FieldEntry implements Entry { - - protected final ClassEntry ownerEntry; - protected final String name; - protected final TypeDescriptor desc; - - // NOTE: this argument order is important for the MethodReader/MethodWriter - public FieldEntry(ClassEntry ownerEntry, String name, TypeDescriptor desc) { - Preconditions.checkNotNull(ownerEntry, "Owner cannot be null"); - Preconditions.checkNotNull(name, "Field name cannot be null"); - Preconditions.checkNotNull(desc, "Field descriptor cannot be null"); - - this.ownerEntry = ownerEntry; - this.name = name; - this.desc = desc; - } - - @Override - public ClassEntry getOwnerClassEntry() { - return this.ownerEntry; - } - - @Override - public String getName() { - return this.name; - } - - @Override - public String getClassName() { - return this.ownerEntry.getName(); - } - - public TypeDescriptor getDesc() { - return this.desc; - } - - @Override - public FieldEntry updateOwnership(ClassEntry owner) { - return new FieldEntry(owner, this.name, this.desc); - } - - @Override - public int hashCode() { - return Utils.combineHashesOrdered(this.ownerEntry, this.name, this.desc); - } - - @Override - public boolean equals(Object other) { - return other instanceof FieldEntry && equals((FieldEntry) other); - } - - public boolean equals(FieldEntry other) { - return this.ownerEntry.equals(other.ownerEntry) && this.name.equals(other.name) && this.desc.equals(other.desc); - } - - @Override - public String toString() { - return this.ownerEntry.getName() + "." + this.name + ":" + this.desc; - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/entry/LocalVariableDefEntry.java b/src/main/java/cuchaz/enigma/mapping/entry/LocalVariableDefEntry.java deleted file mode 100644 index d186664..0000000 --- a/src/main/java/cuchaz/enigma/mapping/entry/LocalVariableDefEntry.java +++ /dev/null @@ -1,61 +0,0 @@ -package cuchaz.enigma.mapping.entry; - -import com.google.common.base.Preconditions; -import cuchaz.enigma.mapping.TypeDescriptor; -import cuchaz.enigma.utils.Utils; - -/** - * TypeDescriptor... - * Created by Thog - * 19/10/2016 - */ -public class LocalVariableDefEntry extends LocalVariableEntry { - - protected final MethodDefEntry ownerEntry; - protected final TypeDescriptor desc; - - public LocalVariableDefEntry(MethodDefEntry ownerEntry, int index, String name, TypeDescriptor desc) { - this(ownerEntry, index, name, true, desc); - } - - public LocalVariableDefEntry(MethodDefEntry ownerEntry, int index, String name, boolean parameter, TypeDescriptor desc) { - super(ownerEntry, index, name, parameter); - Preconditions.checkNotNull(desc, "Variable desc cannot be null"); - - this.ownerEntry = ownerEntry; - this.desc = desc; - } - - @Override - public MethodDefEntry getOwnerEntry() { - return this.ownerEntry; - } - - public TypeDescriptor getDesc() { - return desc; - } - - @Override - public LocalVariableDefEntry updateOwnership(ClassEntry classEntry) { - return new LocalVariableDefEntry(ownerEntry.updateOwnership(classEntry), index, name, parameter, desc); - } - - @Override - public int hashCode() { - return Utils.combineHashesOrdered(this.ownerEntry, this.desc.hashCode(), this.name.hashCode(), Integer.hashCode(this.index)); - } - - @Override - public boolean equals(Object other) { - return other instanceof LocalVariableDefEntry && equals((LocalVariableDefEntry) other); - } - - public boolean equals(LocalVariableDefEntry other) { - return this.ownerEntry.equals(other.ownerEntry) && this.desc.equals(other.desc) && this.name.equals(other.name) && this.index == other.index; - } - - @Override - public String toString() { - return this.ownerEntry + "(" + this.index + ":" + this.name + ":" + this.desc + ")"; - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/entry/LocalVariableEntry.java b/src/main/java/cuchaz/enigma/mapping/entry/LocalVariableEntry.java deleted file mode 100644 index 3507b25..0000000 --- a/src/main/java/cuchaz/enigma/mapping/entry/LocalVariableEntry.java +++ /dev/null @@ -1,93 +0,0 @@ -package cuchaz.enigma.mapping.entry; - -import com.google.common.base.Preconditions; -import cuchaz.enigma.mapping.MethodDescriptor; -import cuchaz.enigma.utils.Utils; - -/** - * TypeDescriptor... - * Created by Thog - * 19/10/2016 - */ -public class LocalVariableEntry implements Entry { - - protected final MethodEntry ownerEntry; - protected final String name; - protected final int index; - protected final boolean parameter; - - @Deprecated - public LocalVariableEntry(MethodEntry ownerEntry, int index, String name) { - this(ownerEntry, index, name, true); - } - - public LocalVariableEntry(MethodEntry ownerEntry, int index, String name, boolean parameter) { - Preconditions.checkNotNull(ownerEntry, "Variable owner cannot be null"); - Preconditions.checkNotNull(name, "Variable name cannot be null"); - Preconditions.checkArgument(index >= 0, "Index must be positive"); - - this.ownerEntry = ownerEntry; - this.name = name; - this.index = index; - this.parameter = parameter; - } - - public boolean isParameter() { - return this.parameter; - } - - public MethodEntry getOwnerEntry() { - return this.ownerEntry; - } - - public int getIndex() { - return index; - } - - @Override - public String getName() { - return this.name; - } - - @Override - public ClassEntry getOwnerClassEntry() { - return this.ownerEntry.getOwnerClassEntry(); - } - - @Override - public String getClassName() { - return this.ownerEntry.getClassName(); - } - - @Override - public LocalVariableEntry updateOwnership(ClassEntry classEntry) { - return new LocalVariableEntry(ownerEntry.updateOwnership(classEntry), index, name, parameter); - } - - public String getMethodName() { - return this.ownerEntry.getName(); - } - - public MethodDescriptor getMethodDesc() { - return this.ownerEntry.getDesc(); - } - - @Override - public int hashCode() { - return Utils.combineHashesOrdered(this.ownerEntry, 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.ownerEntry.equals(other.ownerEntry) && this.name.equals(other.name) && this.index == other.index; - } - - @Override - public String toString() { - return this.ownerEntry + "(" + this.index + ":" + this.name + ")"; - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/entry/MethodDefEntry.java b/src/main/java/cuchaz/enigma/mapping/entry/MethodDefEntry.java deleted file mode 100644 index fa9e668..0000000 --- a/src/main/java/cuchaz/enigma/mapping/entry/MethodDefEntry.java +++ /dev/null @@ -1,61 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * 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.entry; - -import com.google.common.base.Preconditions; -import cuchaz.enigma.bytecode.AccessFlags; -import cuchaz.enigma.mapping.MethodDescriptor; -import cuchaz.enigma.mapping.Signature; - -public class MethodDefEntry extends MethodEntry implements DefEntry { - - private final AccessFlags access; - private final Signature signature; - - public MethodDefEntry(ClassEntry classEntry, String name, MethodDescriptor descriptor, Signature signature, AccessFlags access) { - super(classEntry, name, descriptor); - Preconditions.checkNotNull(access, "Method access cannot be null"); - Preconditions.checkNotNull(signature, "Method signature cannot be null"); - this.access = access; - this.signature = signature; - } - - @Override - public AccessFlags getAccess() { - return access; - } - - public Signature getSignature() { - return signature; - } - - @Override - public MethodDefEntry updateOwnership(ClassEntry classEntry) { - return new MethodDefEntry(new ClassEntry(classEntry.getName()), name, descriptor, signature, access); - } - - public int getArgumentIndex(ClassDefEntry ownerEntry, int localVariableIndex) { - int argumentIndex = localVariableIndex; - - // Enum constructors have an implicit "name" and "ordinal" parameter as well as "this" - if (ownerEntry.getAccess().isEnum() && getName().startsWith("<")) { - argumentIndex -= 2; - } - - // If we're not static, "this" is bound to index 0 - if (!getAccess().isStatic()) { - argumentIndex -= 1; - } - - return argumentIndex; - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/entry/MethodEntry.java b/src/main/java/cuchaz/enigma/mapping/entry/MethodEntry.java deleted file mode 100644 index 1abc5b1..0000000 --- a/src/main/java/cuchaz/enigma/mapping/entry/MethodEntry.java +++ /dev/null @@ -1,80 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * 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.entry; - -import com.google.common.base.Preconditions; -import cuchaz.enigma.mapping.MethodDescriptor; -import cuchaz.enigma.utils.Utils; - -public class MethodEntry implements Entry { - - protected final ClassEntry classEntry; - protected final String name; - protected final MethodDescriptor descriptor; - - public MethodEntry(ClassEntry classEntry, String name, MethodDescriptor descriptor) { - Preconditions.checkNotNull(classEntry, "Class cannot be null"); - Preconditions.checkNotNull(name, "Method name cannot be null"); - Preconditions.checkNotNull(descriptor, "Method descriptor cannot be null"); - - this.classEntry = classEntry; - this.name = name; - this.descriptor = descriptor; - } - - @Override - public ClassEntry getOwnerClassEntry() { - return this.classEntry; - } - - @Override - public String getName() { - return this.name; - } - - public MethodDescriptor getDesc() { - return this.descriptor; - } - - public boolean isConstructor() { - return name.equals("") || name.equals(""); - } - - @Override - public String getClassName() { - return this.classEntry.getName(); - } - - @Override - public MethodEntry updateOwnership(ClassEntry classEntry) { - return new MethodEntry(new ClassEntry(classEntry.getName()), name, descriptor); - } - - @Override - public int hashCode() { - return Utils.combineHashesOrdered(this.classEntry, this.name, this.descriptor); - } - - @Override - public boolean equals(Object other) { - return other instanceof MethodEntry && equals((MethodEntry) other); - } - - public boolean equals(MethodEntry other) { - return this.classEntry.equals(other.getOwnerClassEntry()) && this.name.equals(other.getName()) && this.descriptor.equals(other.getDesc()); - } - - @Override - public String toString() { - return this.classEntry.getName() + "." + this.name + this.descriptor; - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/entry/ProcyonEntryFactory.java b/src/main/java/cuchaz/enigma/mapping/entry/ProcyonEntryFactory.java deleted file mode 100644 index 73770c5..0000000 --- a/src/main/java/cuchaz/enigma/mapping/entry/ProcyonEntryFactory.java +++ /dev/null @@ -1,48 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * 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.entry; - -import com.strobel.assembler.metadata.FieldDefinition; -import com.strobel.assembler.metadata.MemberReference; -import com.strobel.assembler.metadata.MethodDefinition; -import cuchaz.enigma.bytecode.AccessFlags; -import cuchaz.enigma.mapping.MethodDescriptor; -import cuchaz.enigma.mapping.Signature; -import cuchaz.enigma.mapping.TypeDescriptor; - -public class ProcyonEntryFactory { - private final ReferencedEntryPool entryPool; - - public ProcyonEntryFactory(ReferencedEntryPool entryPool) { - this.entryPool = entryPool; - } - - public FieldEntry getFieldEntry(MemberReference def) { - ClassEntry classEntry = entryPool.getClass(def.getDeclaringType().getInternalName()); - return entryPool.getField(classEntry, def.getName(), def.getErasedSignature()); - } - - public FieldDefEntry getFieldDefEntry(FieldDefinition def) { - ClassEntry classEntry = entryPool.getClass(def.getDeclaringType().getInternalName()); - return new FieldDefEntry(classEntry, def.getName(), new TypeDescriptor(def.getErasedSignature()), Signature.createTypedSignature(def.getSignature()), new AccessFlags(def.getModifiers())); - } - - public MethodEntry getMethodEntry(MemberReference def) { - ClassEntry classEntry = entryPool.getClass(def.getDeclaringType().getInternalName()); - return entryPool.getMethod(classEntry, def.getName(), def.getErasedSignature()); - } - - public MethodDefEntry getMethodDefEntry(MethodDefinition def) { - ClassEntry classEntry = entryPool.getClass(def.getDeclaringType().getInternalName()); - return new MethodDefEntry(classEntry, def.getName(), new MethodDescriptor(def.getErasedSignature()), Signature.createSignature(def.getSignature()), new AccessFlags(def.getModifiers())); - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/entry/ReferencedEntryPool.java b/src/main/java/cuchaz/enigma/mapping/entry/ReferencedEntryPool.java deleted file mode 100644 index 12b3955..0000000 --- a/src/main/java/cuchaz/enigma/mapping/entry/ReferencedEntryPool.java +++ /dev/null @@ -1,59 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * 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.entry; - -import cuchaz.enigma.mapping.MethodDescriptor; -import cuchaz.enigma.mapping.TypeDescriptor; - -import java.util.HashMap; -import java.util.Map; - -public class ReferencedEntryPool { - private final Map classEntries = new HashMap<>(); - private final Map> methodEntries = new HashMap<>(); - private final Map> fieldEntries = new HashMap<>(); - - public ClassEntry getClass(String name) { - // TODO: FIXME - I'm a hack! - if ("[T".equals(name) || "[[T".equals(name) || "[[[T".equals(name)) { - name = name.replaceAll("T", "Ljava/lang/Object;"); - } - - final String computeName = name; - return this.classEntries.computeIfAbsent(name, s -> new ClassEntry(computeName)); - } - - public MethodEntry getMethod(ClassEntry ownerEntry, String name, String desc) { - return getMethod(ownerEntry, name, new MethodDescriptor(desc)); - } - - public MethodEntry getMethod(ClassEntry ownerEntry, String name, MethodDescriptor desc) { - String key = name + desc.toString(); - return getClassMethods(ownerEntry.getName()).computeIfAbsent(key, s -> new MethodEntry(ownerEntry, name, desc)); - } - - public FieldEntry getField(ClassEntry ownerEntry, String name, String desc) { - return getField(ownerEntry, name, new TypeDescriptor(desc)); - } - - public FieldEntry getField(ClassEntry ownerEntry, String name, TypeDescriptor desc) { - return getClassFields(ownerEntry.getName()).computeIfAbsent(name, s -> new FieldEntry(ownerEntry, name, desc)); - } - - private Map getClassMethods(String name) { - return methodEntries.computeIfAbsent(name, s -> new HashMap<>()); - } - - private Map getClassFields(String name) { - return fieldEntries.computeIfAbsent(name, s -> new HashMap<>()); - } -} diff --git a/src/main/java/cuchaz/enigma/translation/MappingTranslator.java b/src/main/java/cuchaz/enigma/translation/MappingTranslator.java new file mode 100644 index 0000000..529d0ed --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/MappingTranslator.java @@ -0,0 +1,24 @@ +package cuchaz.enigma.translation; + +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.mapping.EntryMap; + +public class MappingTranslator implements Translator { + private final EntryMap mappings; + private final EntryResolver resolver; + + public MappingTranslator(EntryMap mappings, EntryResolver resolver) { + this.mappings = mappings; + this.resolver = resolver; + } + + @SuppressWarnings("unchecked") + @Override + public T translate(T translatable) { + if (translatable == null) { + return null; + } + return (T) translatable.translate(this, resolver, mappings); + } +} diff --git a/src/main/java/cuchaz/enigma/translation/SignatureUpdater.java b/src/main/java/cuchaz/enigma/translation/SignatureUpdater.java new file mode 100644 index 0000000..3783053 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/SignatureUpdater.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * 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.translation; + +import com.google.common.collect.Lists; + +import java.io.IOException; +import java.io.StringReader; +import java.util.List; + +public class SignatureUpdater { + + 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; + + // 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); + } + } + + 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); + } + } + } + + return null; + } + + 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/translation/Translatable.java b/src/main/java/cuchaz/enigma/translation/Translatable.java new file mode 100644 index 0000000..0370ef1 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/Translatable.java @@ -0,0 +1,9 @@ +package cuchaz.enigma.translation; + +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.mapping.EntryMap; + +public interface Translatable { + Translatable translate(Translator translator, EntryResolver resolver, EntryMap mappings); +} diff --git a/src/main/java/cuchaz/enigma/translation/TranslationDirection.java b/src/main/java/cuchaz/enigma/translation/TranslationDirection.java new file mode 100644 index 0000000..2ecb30b --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/TranslationDirection.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * 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.translation; + +public enum TranslationDirection { + + DEOBFUSCATING { + @Override + public T choose(T deobfChoice, T obfChoice) { + if (deobfChoice == null) { + return obfChoice; + } + return deobfChoice; + } + }, + OBFUSCATING { + @Override + public T choose(T deobfChoice, T obfChoice) { + if (obfChoice == null) { + return deobfChoice; + } + return obfChoice; + } + }; + + public abstract T choose(T deobfChoice, T obfChoice); +} diff --git a/src/main/java/cuchaz/enigma/translation/Translator.java b/src/main/java/cuchaz/enigma/translation/Translator.java new file mode 100644 index 0000000..de2003e --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/Translator.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * 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.translation; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +public interface Translator { + T translate(T translatable); + + default Collection translate(Collection translatable) { + return translatable.stream() + .map(this::translate) + .collect(Collectors.toList()); + } + + default Map translateKeys(Map translatable) { + Map result = new HashMap<>(translatable.size()); + for (Map.Entry entry : translatable.entrySet()) { + result.put(translate(entry.getKey()), entry.getValue()); + } + return result; + } + + default Map translate(Map translatable) { + Map result = new HashMap<>(translatable.size()); + for (Map.Entry entry : translatable.entrySet()) { + result.put(translate(entry.getKey()), translate(entry.getValue())); + } + return result; + } + + default Multimap translate(Multimap translatable) { + Multimap result = HashMultimap.create(translatable.size(), 1); + for (Map.Entry> entry : translatable.asMap().entrySet()) { + result.putAll(translate(entry.getKey()), translate(entry.getValue())); + } + return result; + } +} diff --git a/src/main/java/cuchaz/enigma/translation/VoidTranslator.java b/src/main/java/cuchaz/enigma/translation/VoidTranslator.java new file mode 100644 index 0000000..c010833 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/VoidTranslator.java @@ -0,0 +1,10 @@ +package cuchaz.enigma.translation; + +public enum VoidTranslator implements Translator { + INSTANCE; + + @Override + public T translate(T translatable) { + return translatable; + } +} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/AccessModifier.java b/src/main/java/cuchaz/enigma/translation/mapping/AccessModifier.java new file mode 100644 index 0000000..5b79b79 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/mapping/AccessModifier.java @@ -0,0 +1,25 @@ +package cuchaz.enigma.translation.mapping; + +import cuchaz.enigma.translation.representation.AccessFlags; + +public enum AccessModifier { + UNCHANGED, PUBLIC, PROTECTED, PRIVATE; + + public String getFormattedName() { + return "ACC:" + super.toString(); + } + + public AccessFlags transform(AccessFlags access) { + switch (this) { + case PUBLIC: + return access.setPublic(); + case PROTECTED: + return access.setProtected(); + case PRIVATE: + return access.setPrivate(); + case UNCHANGED: + default: + return access; + } + } +} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/EntryMap.java b/src/main/java/cuchaz/enigma/translation/mapping/EntryMap.java new file mode 100644 index 0000000..6af4846 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/mapping/EntryMap.java @@ -0,0 +1,24 @@ +package cuchaz.enigma.translation.mapping; + +import cuchaz.enigma.translation.representation.entry.Entry; + +import javax.annotation.Nullable; +import java.util.Collection; + +public interface EntryMap { + void insert(Entry entry, T value); + + @Nullable + T remove(Entry entry); + + @Nullable + T get(Entry entry); + + default boolean contains(Entry entry) { + return get(entry) != null; + } + + Collection> getAllEntries(); + + boolean isEmpty(); +} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java b/src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java new file mode 100644 index 0000000..f11cdef --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java @@ -0,0 +1,30 @@ +package cuchaz.enigma.translation.mapping; + +import javax.annotation.Nonnull; + +public class EntryMapping { + private final String targetName; + private final AccessModifier accessModifier; + + public EntryMapping(@Nonnull String targetName) { + this(targetName, AccessModifier.UNCHANGED); + } + + public EntryMapping(@Nonnull String targetName, AccessModifier accessModifier) { + this.targetName = targetName; + this.accessModifier = accessModifier; + } + + @Nonnull + public String getTargetName() { + return targetName; + } + + @Nonnull + public AccessModifier getAccessModifier() { + if (accessModifier == null) { + return AccessModifier.UNCHANGED; + } + return accessModifier; + } +} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java b/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java new file mode 100644 index 0000000..b7d8d17 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java @@ -0,0 +1,201 @@ +package cuchaz.enigma.translation.mapping; + +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.translation.MappingTranslator; +import cuchaz.enigma.translation.Translatable; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.tree.DeltaTrackingTree; +import cuchaz.enigma.translation.mapping.tree.EntryTree; +import cuchaz.enigma.translation.mapping.tree.EntryTreeNode; +import cuchaz.enigma.translation.mapping.tree.HashEntryTree; +import cuchaz.enigma.translation.representation.entry.Entry; + +import javax.annotation.Nullable; +import java.util.Collection; + +public class EntryRemapper { + private final EntryTree obfToDeobf; + private final DeltaTrackingTree deobfToObf; + + private final JarIndex obfIndex; + + private final EntryResolver obfResolver; + private EntryResolver deobfResolver; + + private final Translator deobfuscator; + private Translator obfuscator; + + private final MappingValidator validator; + + private EntryRemapper(JarIndex jarIndex, EntryTree obfToDeobf, EntryTree deobfToObf) { + this.obfToDeobf = obfToDeobf; + this.deobfToObf = new DeltaTrackingTree<>(deobfToObf); + + this.obfIndex = jarIndex; + this.obfResolver = jarIndex.getEntryResolver(); + + this.deobfuscator = new MappingTranslator(obfToDeobf, obfResolver); + rebuildDeobfIndex(); + + this.validator = new MappingValidator(this.deobfToObf, deobfuscator, obfResolver); + } + + public EntryRemapper(JarIndex jarIndex) { + this(jarIndex, new HashEntryTree<>(), new HashEntryTree<>()); + } + + public EntryRemapper(JarIndex jarIndex, EntryTree deobfuscationTrees) { + this(jarIndex, deobfuscationTrees, inverse(deobfuscationTrees)); + } + + private static EntryTree inverse(EntryTree tree) { + Translator translator = new MappingTranslator(tree, VoidEntryResolver.INSTANCE); + EntryTree inverse = new HashEntryTree<>(); + + // Naive approach, could operate on the nodes of the tree. However, this runs infrequently. + Collection> entries = tree.getAllEntries(); + for (Entry sourceEntry : entries) { + Entry targetEntry = translator.translate(sourceEntry); + inverse.insert(targetEntry, new EntryMapping(sourceEntry.getName())); + } + + return inverse; + } + + private void rebuildDeobfIndex() { + JarIndex deobfIndex = obfIndex.remapped(deobfuscator); + + this.deobfResolver = deobfIndex.getEntryResolver(); + this.obfuscator = new MappingTranslator(deobfToObf, deobfResolver); + } + + public > void mapFromObf(E obfuscatedEntry, @Nullable EntryMapping deobfMapping) { + Collection resolvedEntries = obfResolver.resolveEntry(obfuscatedEntry, ResolutionStrategy.RESOLVE_ROOT); + for (E resolvedEntry : resolvedEntries) { + if (deobfMapping != null) { + validator.validateRename(resolvedEntry, deobfMapping.getTargetName()); + } + + setObfToDeobf(resolvedEntry, deobfMapping); + } + + // Temporary hack, not very performant + rebuildDeobfIndex(); + } + + public > void mapFromDeobf(E deobfuscatedEntry, @Nullable EntryMapping deobfMapping) { + E obfuscatedEntry = obfuscate(deobfuscatedEntry); + mapFromObf(obfuscatedEntry, deobfMapping); + } + + public void removeByObf(Entry obfuscatedEntry) { + mapFromObf(obfuscatedEntry, null); + } + + public void removeByDeobf(Entry deobfuscatedEntry) { + mapFromObf(obfuscate(deobfuscatedEntry), null); + } + + private > void setObfToDeobf(E obfuscatedEntry, @Nullable EntryMapping deobfMapping) { + E prevDeobf = deobfuscate(obfuscatedEntry); + obfToDeobf.insert(obfuscatedEntry, deobfMapping); + + E newDeobf = deobfuscate(obfuscatedEntry); + + // Reconstruct the children of this node in the deobf -> obf tree with our new mapping + // We only need to do this for deobf -> obf because the obf tree is always consistent on the left hand side + // We lookup by obf, and the obf never changes. This is not the case for deobf so we need to update the tree. + + EntryTreeNode node = deobfToObf.findNode(prevDeobf); + if (node != null) { + for (EntryTreeNode child : node.getNodesRecursively()) { + Entry entry = child.getEntry(); + EntryMapping mapping = new EntryMapping(obfuscate(entry).getName()); + + deobfToObf.insert(entry.replaceAncestor(prevDeobf, newDeobf), mapping); + deobfToObf.remove(entry); + } + } else { + deobfToObf.insert(newDeobf, new EntryMapping(obfuscatedEntry.getName())); + } + } + + @Nullable + public EntryMapping getDeobfMapping(Entry entry) { + return obfToDeobf.get(entry); + } + + @Nullable + public EntryMapping getObfMapping(Entry entry) { + return deobfToObf.get(entry); + } + + public boolean hasDeobfMapping(Entry obfEntry) { + return obfToDeobf.contains(obfEntry); + } + + public boolean hasObfMapping(Entry deobfEntry) { + return deobfToObf.contains(deobfEntry); + } + + public T deobfuscate(T translatable) { + return deobfuscator.translate(translatable); + } + + public T obfuscate(T translatable) { + return obfuscator.translate(translatable); + } + + public Translator getDeobfuscator() { + return deobfuscator; + } + + public Translator getObfuscator() { + return obfuscator; + } + + public Collection> getObfEntries() { + return obfToDeobf.getAllEntries(); + } + + public Collection> getObfRootEntries() { + return obfToDeobf.getRootEntries(); + } + + public Collection> getDeobfEntries() { + return deobfToObf.getAllEntries(); + } + + public Collection> getObfChildren(Entry obfuscatedEntry) { + return obfToDeobf.getChildren(obfuscatedEntry); + } + + public Collection> getDeobfChildren(Entry deobfuscatedEntry) { + return deobfToObf.getChildren(deobfuscatedEntry); + } + + public EntryTree getObfToDeobf() { + return obfToDeobf; + } + + public DeltaTrackingTree getDeobfToObf() { + return deobfToObf; + } + + public MappingDelta takeMappingDelta() { + MappingDelta delta = deobfToObf.takeDelta(); + return delta.translate(obfuscator, VoidEntryResolver.INSTANCE, deobfToObf); + } + + public boolean isDirty() { + return deobfToObf.isDirty(); + } + + public EntryResolver getObfResolver() { + return obfResolver; + } + + public EntryResolver getDeobfResolver() { + return deobfResolver; + } +} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/EntryResolver.java b/src/main/java/cuchaz/enigma/translation/mapping/EntryResolver.java new file mode 100644 index 0000000..521f72d --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/mapping/EntryResolver.java @@ -0,0 +1,41 @@ +package cuchaz.enigma.translation.mapping; + +import com.google.common.collect.Streams; +import cuchaz.enigma.analysis.EntryReference; +import cuchaz.enigma.translation.representation.entry.Entry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; + +import java.util.Collection; +import java.util.Set; +import java.util.stream.Collectors; + +public interface EntryResolver { + > Collection resolveEntry(E entry, ResolutionStrategy strategy); + + default > E resolveFirstEntry(E entry, ResolutionStrategy strategy) { + return resolveEntry(entry, strategy).stream().findFirst().orElse(entry); + } + + default , C extends Entry> Collection> resolveReference(EntryReference reference, ResolutionStrategy strategy) { + Collection entry = resolveEntry(reference.entry, strategy); + if (reference.context != null) { + Collection context = resolveEntry(reference.context, strategy); + return Streams.zip(entry.stream(), context.stream(), (e, c) -> new EntryReference<>(e, c, reference)) + .collect(Collectors.toList()); + } else { + return entry.stream() + .map(e -> new EntryReference<>(e, null, reference)) + .collect(Collectors.toList()); + } + } + + default , C extends Entry> EntryReference resolveFirstReference(EntryReference reference, ResolutionStrategy strategy) { + E entry = resolveFirstEntry(reference.entry, strategy); + C context = resolveFirstEntry(reference.context, strategy); + return new EntryReference<>(entry, context, reference); + } + + Set> resolveEquivalentEntries(Entry entry); + + Set resolveEquivalentMethods(MethodEntry methodEntry); +} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/IndexEntryResolver.java b/src/main/java/cuchaz/enigma/translation/mapping/IndexEntryResolver.java new file mode 100644 index 0000000..1f2290a --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/mapping/IndexEntryResolver.java @@ -0,0 +1,225 @@ +package cuchaz.enigma.translation.mapping; + +import com.google.common.collect.Sets; +import cuchaz.enigma.analysis.IndexTreeBuilder; +import cuchaz.enigma.analysis.MethodImplementationsTreeNode; +import cuchaz.enigma.analysis.MethodInheritanceTreeNode; +import cuchaz.enigma.analysis.index.BridgeMethodIndex; +import cuchaz.enigma.analysis.index.EntryIndex; +import cuchaz.enigma.analysis.index.InheritanceIndex; +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.translation.VoidTranslator; +import cuchaz.enigma.translation.representation.AccessFlags; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.Entry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; + +import javax.annotation.Nullable; +import java.util.*; +import java.util.stream.Collectors; + +public class IndexEntryResolver implements EntryResolver { + private final EntryIndex entryIndex; + private final InheritanceIndex inheritanceIndex; + private final BridgeMethodIndex bridgeMethodIndex; + + private final IndexTreeBuilder treeBuilder; + + public IndexEntryResolver(JarIndex index) { + this.entryIndex = index.getEntryIndex(); + this.inheritanceIndex = index.getInheritanceIndex(); + this.bridgeMethodIndex = index.getBridgeMethodIndex(); + + this.treeBuilder = new IndexTreeBuilder(index); + } + + @Override + @SuppressWarnings("unchecked") + public > Collection resolveEntry(E entry, ResolutionStrategy strategy) { + if (entry == null) { + return Collections.emptySet(); + } + + Entry classChild = getClassChild(entry); + if (classChild != null && !(classChild instanceof ClassEntry)) { + AccessFlags access = entryIndex.getEntryAccess(classChild); + + // If we're looking for the closest and this entry exists, we're done looking + if (strategy == ResolutionStrategy.RESOLVE_CLOSEST && access != null) { + return Collections.singleton(entry); + } + + if (access == null || !access.isPrivate()) { + Collection> resolvedChildren = resolveChildEntry(classChild, strategy); + if (!resolvedChildren.isEmpty()) { + return resolvedChildren.stream() + .map(resolvedChild -> (E) entry.replaceAncestor(classChild, resolvedChild)) + .collect(Collectors.toList()); + } + } + } + + return Collections.singleton(entry); + } + + @Nullable + private Entry getClassChild(Entry entry) { + if (entry instanceof ClassEntry) { + return null; + } + + // get the entry in the hierarchy that is the child of a class + List> ancestry = entry.getAncestry(); + for (int i = ancestry.size() - 1; i > 0; i--) { + Entry child = ancestry.get(i); + Entry cast = child.castParent(ClassEntry.class); + if (cast != null && !(cast instanceof ClassEntry)) { + // we found the entry which is a child of a class, we are now able to resolve the owner of this entry + return cast; + } + } + + return null; + } + + private Set> resolveChildEntry(Entry entry, ResolutionStrategy strategy) { + ClassEntry ownerClass = entry.getParent(); + + if (entry instanceof MethodEntry) { + MethodEntry bridgeMethod = bridgeMethodIndex.getBridgeFromAccessed((MethodEntry) entry); + if (bridgeMethod != null && ownerClass.equals(bridgeMethod.getParent())) { + Set> resolvedBridge = resolveChildEntry(bridgeMethod, strategy); + if (!resolvedBridge.isEmpty()) { + return resolvedBridge; + } + } + } + + Set> resolvedEntries = new HashSet<>(); + + for (ClassEntry parentClass : inheritanceIndex.getParents(ownerClass)) { + Entry parentEntry = entry.withParent(parentClass); + + if (strategy == ResolutionStrategy.RESOLVE_ROOT) { + resolvedEntries.addAll(resolveRoot(parentEntry, strategy)); + } else { + resolvedEntries.addAll(resolveClosest(parentEntry, strategy)); + } + } + + return resolvedEntries; + } + + private Collection> resolveRoot(Entry entry, ResolutionStrategy strategy) { + // When resolving root, we want to first look for the lowest entry before returning ourselves + Set> parentResolution = resolveChildEntry(entry, strategy); + + if (parentResolution.isEmpty()) { + AccessFlags parentAccess = entryIndex.getEntryAccess(entry); + if (parentAccess != null && !parentAccess.isPrivate()) { + return Collections.singleton(entry); + } + } + + return parentResolution; + } + + private Collection> resolveClosest(Entry entry, ResolutionStrategy strategy) { + // When resolving closest, we want to first check if we exist before looking further down + AccessFlags parentAccess = entryIndex.getEntryAccess(entry); + if (parentAccess != null && !parentAccess.isPrivate()) { + return Collections.singleton(entry); + } else { + return resolveChildEntry(entry, strategy); + } + } + + @Override + public Set> resolveEquivalentEntries(Entry entry) { + MethodEntry relevantMethod = entry.findAncestor(MethodEntry.class); + if (relevantMethod == null || !entryIndex.hasMethod(relevantMethod)) { + return Collections.singleton(entry); + } + + Set equivalentMethods = resolveEquivalentMethods(relevantMethod); + Set> equivalentEntries = new HashSet<>(equivalentMethods.size()); + + for (MethodEntry equivalentMethod : equivalentMethods) { + Entry equivalentEntry = entry.replaceAncestor(relevantMethod, equivalentMethod); + equivalentEntries.add(equivalentEntry); + } + + return equivalentEntries; + } + + @Override + public Set resolveEquivalentMethods(MethodEntry methodEntry) { + AccessFlags access = entryIndex.getMethodAccess(methodEntry); + if (access == null) { + throw new IllegalArgumentException("Could not find method " + methodEntry); + } + + if (!canInherit(methodEntry, access)) { + return Collections.singleton(methodEntry); + } + + Set methodEntries = Sets.newHashSet(); + resolveEquivalentMethods(methodEntries, treeBuilder.buildMethodInheritance(VoidTranslator.INSTANCE, methodEntry)); + return methodEntries; + } + + private void resolveEquivalentMethods(Set methodEntries, MethodInheritanceTreeNode node) { + MethodEntry methodEntry = node.getMethodEntry(); + if (methodEntries.contains(methodEntry)) { + return; + } + + AccessFlags flags = entryIndex.getMethodAccess(methodEntry); + if (flags != null && canInherit(methodEntry, flags)) { + // collect the entry + methodEntries.add(methodEntry); + } + + // look at bridge methods! + MethodEntry bridgedMethod = bridgeMethodIndex.getBridgeFromAccessed(methodEntry); + while (bridgedMethod != null) { + methodEntries.addAll(resolveEquivalentMethods(bridgedMethod)); + bridgedMethod = bridgeMethodIndex.getBridgeFromAccessed(bridgedMethod); + } + + // look at interface methods too + for (MethodImplementationsTreeNode implementationsNode : treeBuilder.buildMethodImplementations(VoidTranslator.INSTANCE, methodEntry)) { + resolveEquivalentMethods(methodEntries, implementationsNode); + } + + // recurse + for (int i = 0; i < node.getChildCount(); i++) { + resolveEquivalentMethods(methodEntries, (MethodInheritanceTreeNode) node.getChildAt(i)); + } + } + + private void resolveEquivalentMethods(Set methodEntries, MethodImplementationsTreeNode node) { + MethodEntry methodEntry = node.getMethodEntry(); + AccessFlags flags = entryIndex.getMethodAccess(methodEntry); + if (flags != null && !flags.isPrivate() && !flags.isStatic()) { + // collect the entry + methodEntries.add(methodEntry); + } + + // look at bridge methods! + MethodEntry bridgedMethod = bridgeMethodIndex.getBridgeFromAccessed(methodEntry); + while (bridgedMethod != null) { + methodEntries.addAll(resolveEquivalentMethods(bridgedMethod)); + bridgedMethod = bridgeMethodIndex.getBridgeFromAccessed(bridgedMethod); + } + + // recurse + for (int i = 0; i < node.getChildCount(); i++) { + resolveEquivalentMethods(methodEntries, (MethodImplementationsTreeNode) node.getChildAt(i)); + } + } + + private boolean canInherit(MethodEntry entry, AccessFlags access) { + return !entry.isConstructor() && !access.isPrivate() && !access.isStatic() && !access.isFinal(); + } +} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/MappingDelta.java b/src/main/java/cuchaz/enigma/translation/mapping/MappingDelta.java new file mode 100644 index 0000000..4fba49d --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/mapping/MappingDelta.java @@ -0,0 +1,56 @@ +package cuchaz.enigma.translation.mapping; + +import cuchaz.enigma.translation.Translatable; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.tree.HashEntryTree; +import cuchaz.enigma.translation.mapping.tree.EntryTree; +import cuchaz.enigma.translation.representation.entry.Entry; + +public class MappingDelta implements Translatable { + public static final Object PLACEHOLDER = new Object(); + + private final EntryTree additions; + private final EntryTree deletions; + + public MappingDelta(EntryTree additions, EntryTree deletions) { + this.additions = additions; + this.deletions = deletions; + } + + public MappingDelta() { + this(new HashEntryTree<>(), new HashEntryTree<>()); + } + + public static MappingDelta added(EntryTree mappings) { + EntryTree additions = new HashEntryTree<>(); + for (Entry entry : mappings.getAllEntries()) { + additions.insert(entry, PLACEHOLDER); + } + + return new MappingDelta(additions, new HashEntryTree<>()); + } + + public EntryTree getAdditions() { + return additions; + } + + public EntryTree getDeletions() { + return deletions; + } + + @Override + public MappingDelta translate(Translator translator, EntryResolver resolver, EntryMap mappings) { + return new MappingDelta( + translate(translator, additions), + translate(translator, deletions) + ); + } + + private EntryTree translate(Translator translator, EntryTree tree) { + EntryTree translatedTree = new HashEntryTree<>(); + for (Entry entry : tree.getAllEntries()) { + translatedTree.insert(translator.translate(entry), PLACEHOLDER); + } + return translatedTree; + } +} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/MappingPair.java b/src/main/java/cuchaz/enigma/translation/mapping/MappingPair.java new file mode 100644 index 0000000..9ed7e8a --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/mapping/MappingPair.java @@ -0,0 +1,28 @@ +package cuchaz.enigma.translation.mapping; + +import cuchaz.enigma.translation.representation.entry.Entry; + +import javax.annotation.Nullable; + +public class MappingPair, M> { + private final E entry; + private final M mapping; + + public MappingPair(E entry, @Nullable M mapping) { + this.entry = entry; + this.mapping = mapping; + } + + public MappingPair(E entry) { + this(entry, null); + } + + public E getEntry() { + return entry; + } + + @Nullable + public M getMapping() { + return mapping; + } +} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java b/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java new file mode 100644 index 0000000..422bf38 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java @@ -0,0 +1,45 @@ +package cuchaz.enigma.translation.mapping; + +import cuchaz.enigma.throwables.IllegalNameException; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.tree.EntryTree; +import cuchaz.enigma.translation.representation.entry.Entry; + +import java.util.Collection; + +public class MappingValidator { + private final EntryTree deobfToObf; + private final Translator deobfuscator; + private final EntryResolver entryResolver; + + public MappingValidator(EntryTree deobfToObf, Translator deobfuscator, EntryResolver entryResolver) { + this.deobfToObf = deobfToObf; + this.deobfuscator = deobfuscator; + this.entryResolver = entryResolver; + } + + public void validateRename(Entry entry, String name) throws IllegalNameException { + Collection> equivalentEntries = entryResolver.resolveEquivalentEntries(entry); + for (Entry equivalentEntry : equivalentEntries) { + equivalentEntry.validateName(name); + validateUnique(equivalentEntry, name); + } + } + + private void validateUnique(Entry entry, String name) { + Entry translatedEntry = deobfuscator.translate(entry); + Collection> siblings = deobfToObf.getSiblings(translatedEntry); + if (!isUnique(translatedEntry, siblings, name)) { + throw new IllegalNameException(name, "Name is not unique in " + translatedEntry.getParent() + "!"); + } + } + + private boolean isUnique(Entry entry, Collection> siblings, String name) { + for (Entry child : siblings) { + if (entry.canConflictWith(child) && child.getName().equals(name)) { + return false; + } + } + return true; + } +} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java b/src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java new file mode 100644 index 0000000..77d75ec --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java @@ -0,0 +1,91 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * 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.translation.mapping; + +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.translation.mapping.tree.EntryTree; +import cuchaz.enigma.translation.mapping.tree.EntryTreeNode; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.Entry; +import cuchaz.enigma.translation.representation.entry.FieldEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +public class MappingsChecker { + private final JarIndex index; + private final EntryTree mappings; + + public MappingsChecker(JarIndex index, EntryTree mappings) { + this.index = index; + this.mappings = mappings; + } + + public Dropped dropBrokenMappings() { + Dropped dropped = new Dropped(); + + Collection> obfEntries = mappings.getAllEntries(); + for (Entry entry : obfEntries) { + if (entry instanceof ClassEntry || entry instanceof MethodEntry || entry instanceof FieldEntry) { + tryDropEntry(dropped, entry); + } + } + + dropped.apply(mappings); + + return dropped; + } + + private void tryDropEntry(Dropped dropped, Entry entry) { + if (shouldDropEntry(entry)) { + EntryMapping mapping = mappings.get(entry); + if (mapping != null) { + dropped.drop(entry, mapping); + } + } + } + + private boolean shouldDropEntry(Entry entry) { + if (!index.getEntryIndex().hasEntry(entry)) { + return true; + } + Collection> resolvedEntries = index.getEntryResolver().resolveEntry(entry, ResolutionStrategy.RESOLVE_ROOT); + return !resolvedEntries.contains(entry); + } + + public static class Dropped { + private final Map, String> droppedMappings = new HashMap<>(); + + public void drop(Entry entry, EntryMapping mapping) { + droppedMappings.put(entry, mapping.getTargetName()); + } + + void apply(EntryTree mappings) { + for (Entry entry : droppedMappings.keySet()) { + EntryTreeNode node = mappings.findNode(entry); + if (node == null) { + continue; + } + + for (Entry childEntry : node.getChildrenRecursively()) { + mappings.remove(childEntry); + } + } + } + + public Map, String> getDroppedMappings() { + return droppedMappings; + } + } +} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/NameValidator.java b/src/main/java/cuchaz/enigma/translation/mapping/NameValidator.java new file mode 100644 index 0000000..19473ea --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/mapping/NameValidator.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * 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.translation.mapping; + +import cuchaz.enigma.throwables.IllegalNameException; +import cuchaz.enigma.translation.representation.entry.ClassEntry; + +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; + +public class NameValidator { + private static final Pattern IDENTIFIER_PATTERN; + private static final Pattern CLASS_PATTERN; + private static final List ILLEGAL_IDENTIFIERS = 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_>]*"; + IDENTIFIER_PATTERN = Pattern.compile(identifierRegex); + CLASS_PATTERN = Pattern.compile(String.format("^(%s(\\.|/))*(%s)$", identifierRegex, identifierRegex)); + } + + public static void validateClassName(String name, boolean packageRequired) { + if (!CLASS_PATTERN.matcher(name).matches() || ILLEGAL_IDENTIFIERS.contains(name)) { + throw new IllegalNameException(name, "This doesn't look like a legal class name"); + } + if (packageRequired && ClassEntry.getPackageName(name) == null) { + throw new IllegalNameException(name, "Class must be in a package"); + } + } + + public static void validateIdentifier(String name) { + if (!IDENTIFIER_PATTERN.matcher(name).matches() || ILLEGAL_IDENTIFIERS.contains(name)) { + throw new IllegalNameException(name, "This doesn't look like a legal identifier"); + } + } + + public static boolean isReserved(String name) { + return ILLEGAL_IDENTIFIERS.contains(name); + } +} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/ResolutionStrategy.java b/src/main/java/cuchaz/enigma/translation/mapping/ResolutionStrategy.java new file mode 100644 index 0000000..1c28e02 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/mapping/ResolutionStrategy.java @@ -0,0 +1,6 @@ +package cuchaz.enigma.translation.mapping; + +public enum ResolutionStrategy { + RESOLVE_ROOT, + RESOLVE_CLOSEST +} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/VoidEntryResolver.java b/src/main/java/cuchaz/enigma/translation/mapping/VoidEntryResolver.java new file mode 100644 index 0000000..2eab55f --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/mapping/VoidEntryResolver.java @@ -0,0 +1,27 @@ +package cuchaz.enigma.translation.mapping; + +import cuchaz.enigma.translation.representation.entry.Entry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; + +import java.util.Collection; +import java.util.Collections; +import java.util.Set; + +public enum VoidEntryResolver implements EntryResolver { + INSTANCE; + + @Override + public > Collection resolveEntry(E entry, ResolutionStrategy strategy) { + return Collections.singleton(entry); + } + + @Override + public Set> resolveEquivalentEntries(Entry entry) { + return Collections.singleton(entry); + } + + @Override + public Set resolveEquivalentMethods(MethodEntry methodEntry) { + return Collections.singleton(methodEntry); + } +} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsReader.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsReader.java new file mode 100644 index 0000000..d36bc0b --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsReader.java @@ -0,0 +1,260 @@ +package cuchaz.enigma.translation.mapping.serde; + +import com.google.common.base.Charsets; +import cuchaz.enigma.throwables.MappingParseException; +import cuchaz.enigma.translation.mapping.AccessModifier; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.MappingPair; +import cuchaz.enigma.translation.mapping.tree.HashEntryTree; +import cuchaz.enigma.translation.mapping.tree.EntryTree; +import cuchaz.enigma.translation.representation.MethodDescriptor; +import cuchaz.enigma.translation.representation.TypeDescriptor; +import cuchaz.enigma.translation.representation.entry.*; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; + +public enum EnigmaMappingsReader implements MappingsReader { + FILE { + @Override + public EntryTree read(Path path) throws IOException, MappingParseException { + EntryTree mappings = new HashEntryTree<>(); + readFile(path, mappings); + return mappings; + } + }, + DIRECTORY { + @Override + public EntryTree read(Path path) throws IOException, MappingParseException { + EntryTree mappings = new HashEntryTree<>(); + + List files = Files.walk(path) + .filter(f -> !Files.isDirectory(f)) + .filter(f -> f.toString().endsWith(".mapping")) + .collect(Collectors.toList()); + for (Path file : files) { + if (Files.isHidden(file)) { + continue; + } + readFile(file, mappings); + } + + return mappings; + } + }; + + protected void readFile(Path path, EntryTree mappings) throws IOException, MappingParseException { + List lines = Files.readAllLines(path, Charsets.UTF_8); + Deque> mappingStack = new ArrayDeque<>(); + + for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) { + String line = lines.get(lineNumber); + int indentation = countIndentation(line); + + line = formatLine(line); + if (line == null) { + continue; + } + + while (indentation < mappingStack.size()) { + mappingStack.pop(); + } + + try { + MappingPair pair = parseLine(mappingStack.peek(), line); + mappingStack.push(pair.getEntry()); + if (pair.getMapping() != null) { + mappings.insert(pair.getEntry(), pair.getMapping()); + } + } catch (Throwable t) { + t.printStackTrace(); + throw new MappingParseException(path::toString, lineNumber, t.toString()); + } + } + } + + @Nullable + private String formatLine(String line) { + line = stripComment(line); + line = line.trim(); + + if (line.isEmpty()) { + return null; + } + + return line; + } + + private String stripComment(String line) { + int commentPos = line.indexOf('#'); + if (commentPos >= 0) { + return line.substring(0, commentPos); + } + return line; + } + + private int countIndentation(String line) { + int indent = 0; + for (int i = 0; i < line.length(); i++) { + if (line.charAt(i) != '\t') { + break; + } + indent++; + } + return indent; + } + + private MappingPair parseLine(@Nullable Entry parent, String line) { + String[] tokens = line.trim().split("\\s"); + String keyToken = tokens[0].toLowerCase(Locale.ROOT); + + switch (keyToken) { + case "class": + return parseClass(parent, tokens); + case "field": + return parseField(parent, tokens); + case "method": + return parseMethod(parent, tokens); + case "arg": + return parseArgument(parent, tokens); + default: + throw new RuntimeException("Unknown token '" + keyToken + "'"); + } + } + + private MappingPair parseClass(@Nullable Entry parent, String[] tokens) { + String obfuscatedName = ClassEntry.getInnerName(tokens[1]); + ClassEntry obfuscatedEntry; + if (parent instanceof ClassEntry) { + obfuscatedEntry = new ClassEntry((ClassEntry) parent, obfuscatedName); + } else { + obfuscatedEntry = new ClassEntry(obfuscatedName); + } + + String mapping = null; + AccessModifier modifier = AccessModifier.UNCHANGED; + + if (tokens.length == 3) { + AccessModifier parsedModifier = parseModifier(tokens[2]); + if (parsedModifier != null) { + modifier = parsedModifier; + mapping = obfuscatedName; + } else { + mapping = tokens[2]; + } + } else if (tokens.length == 4) { + mapping = tokens[2]; + modifier = parseModifier(tokens[3]); + } + + if (mapping != null) { + return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping, modifier)); + } else { + return new MappingPair<>(obfuscatedEntry); + } + } + + private MappingPair parseField(@Nullable Entry parent, String[] tokens) { + if (!(parent instanceof ClassEntry)) { + throw new RuntimeException("Field must be a child of a class!"); + } + + ClassEntry ownerEntry = (ClassEntry) parent; + + String obfuscatedName = tokens[1]; + String mapping = obfuscatedName; + AccessModifier modifier = AccessModifier.UNCHANGED; + TypeDescriptor descriptor; + + if (tokens.length == 4) { + AccessModifier parsedModifier = parseModifier(tokens[3]); + if (parsedModifier != null) { + descriptor = new TypeDescriptor(tokens[2]); + modifier = parsedModifier; + } else { + mapping = tokens[2]; + descriptor = new TypeDescriptor(tokens[3]); + } + } else if (tokens.length == 5) { + descriptor = new TypeDescriptor(tokens[3]); + mapping = tokens[2]; + modifier = parseModifier(tokens[4]); + } else { + throw new RuntimeException("Invalid method declaration"); + } + + FieldEntry obfuscatedEntry = new FieldEntry(ownerEntry, obfuscatedName, descriptor); + if (mapping != null) { + return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping, modifier)); + } else { + return new MappingPair<>(obfuscatedEntry); + } + } + + private MappingPair parseMethod(@Nullable Entry parent, String[] tokens) { + if (!(parent instanceof ClassEntry)) { + throw new RuntimeException("Method must be a child of a class!"); + } + + ClassEntry ownerEntry = (ClassEntry) parent; + + String obfuscatedName = tokens[1]; + String mapping = null; + AccessModifier modifier = AccessModifier.UNCHANGED; + MethodDescriptor descriptor; + + if (tokens.length == 3) { + descriptor = new MethodDescriptor(tokens[2]); + } else if (tokens.length == 4) { + AccessModifier parsedModifier = parseModifier(tokens[3]); + if (parsedModifier != null) { + modifier = parsedModifier; + mapping = obfuscatedName; + descriptor = new MethodDescriptor(tokens[2]); + } else { + mapping = tokens[2]; + descriptor = new MethodDescriptor(tokens[3]); + } + } else if (tokens.length == 5) { + mapping = tokens[2]; + modifier = parseModifier(tokens[4]); + descriptor = new MethodDescriptor(tokens[3]); + } else { + throw new RuntimeException("Invalid method declaration"); + } + + MethodEntry obfuscatedEntry = new MethodEntry(ownerEntry, obfuscatedName, descriptor); + if (mapping != null) { + return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping, modifier)); + } else { + return new MappingPair<>(obfuscatedEntry); + } + } + + private MappingPair parseArgument(@Nullable Entry parent, String[] tokens) { + if (!(parent instanceof MethodEntry)) { + throw new RuntimeException("Method arg must be a child of a method!"); + } + + MethodEntry ownerEntry = (MethodEntry) parent; + LocalVariableEntry obfuscatedEntry = new LocalVariableEntry(ownerEntry, Integer.parseInt(tokens[1]), "", true); + String mapping = tokens[2]; + + return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping)); + } + + @Nullable + private AccessModifier parseModifier(String token) { + if (token.startsWith("ACC:")) { + return AccessModifier.valueOf(token.substring(4)); + } + return null; + } +} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsWriter.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsWriter.java new file mode 100644 index 0000000..3eef739 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsWriter.java @@ -0,0 +1,260 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * 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.translation.mapping.serde; + +import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.translation.MappingTranslator; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.AccessModifier; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.MappingDelta; +import cuchaz.enigma.translation.mapping.VoidEntryResolver; +import cuchaz.enigma.translation.mapping.tree.EntryTree; +import cuchaz.enigma.translation.mapping.tree.EntryTreeNode; +import cuchaz.enigma.translation.representation.entry.*; + +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +public enum EnigmaMappingsWriter implements MappingsWriter { + FILE { + @Override + public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress) { + Collection classes = mappings.getRootEntries().stream() + .filter(entry -> entry instanceof ClassEntry) + .map(entry -> (ClassEntry) entry) + .collect(Collectors.toList()); + + progress.init(classes.size(), "Writing classes"); + + int steps = 0; + try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(path))) { + for (ClassEntry classEntry : classes) { + progress.step(steps++, classEntry.getFullName()); + writeRoot(writer, mappings, classEntry); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + }, + DIRECTORY { + @Override + public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress) { + applyDeletions(delta.getDeletions(), path); + + Collection classes = delta.getAdditions().getRootEntries().stream() + .filter(entry -> entry instanceof ClassEntry) + .map(entry -> (ClassEntry) entry) + .collect(Collectors.toList()); + + progress.init(classes.size(), "Writing classes"); + + Translator translator = new MappingTranslator(mappings, VoidEntryResolver.INSTANCE); + AtomicInteger steps = new AtomicInteger(); + + classes.parallelStream().forEach(classEntry -> { + progress.step(steps.getAndIncrement(), classEntry.getFullName()); + + try { + Path classPath = resolve(path, translator.translate(classEntry)); + Files.deleteIfExists(classPath); + Files.createDirectories(classPath.getParent()); + + try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(classPath))) { + writeRoot(writer, mappings, classEntry); + } + } catch (Throwable t) { + System.err.println("Failed to write class '" + classEntry.getFullName() + "'"); + t.printStackTrace(); + } + }); + } + + private void applyDeletions(EntryTree deletions, Path root) { + Collection deletedClasses = deletions.getRootEntries().stream() + .filter(e -> e instanceof ClassEntry) + .map(e -> (ClassEntry) e) + .collect(Collectors.toList()); + + for (ClassEntry classEntry : deletedClasses) { + try { + Files.deleteIfExists(resolve(root, classEntry)); + } catch (IOException e) { + System.err.println("Failed to delete deleted class '" + classEntry + "'"); + e.printStackTrace(); + } + } + + for (ClassEntry classEntry : deletedClasses) { + String packageName = classEntry.getPackageName(); + if (packageName != null) { + Path packagePath = Paths.get(packageName); + try { + deleteDeadPackages(root, packagePath); + } catch (IOException e) { + System.err.println("Failed to delete dead package '" + packageName + "'"); + e.printStackTrace(); + } + } + } + } + + private void deleteDeadPackages(Path root, Path packagePath) throws IOException { + for (int i = packagePath.getNameCount() - 1; i >= 0; i--) { + Path subPath = packagePath.subpath(0, i + 1); + Path packagePart = root.resolve(subPath); + if (isEmpty(packagePart)) { + Files.deleteIfExists(packagePart); + } + } + } + + private boolean isEmpty(Path path) { + try (DirectoryStream stream = Files.newDirectoryStream(path)) { + return !stream.iterator().hasNext(); + } catch (IOException e) { + return false; + } + } + + private Path resolve(Path root, ClassEntry classEntry) { + return root.resolve(classEntry.getFullName() + ".mapping"); + } + }; + + protected void writeRoot(PrintWriter writer, EntryTree mappings, ClassEntry classEntry) { + Collection> children = groupChildren(mappings.getChildren(classEntry)); + + writer.println(writeClass(classEntry, mappings.get(classEntry)).trim()); + for (Entry child : children) { + writeEntry(writer, mappings, child, 1); + } + } + + protected void writeEntry(PrintWriter writer, EntryTree mappings, Entry entry, int depth) { + EntryTreeNode node = mappings.findNode(entry); + if (node == null) { + return; + } + + EntryMapping mapping = node.getValue(); + if (entry instanceof ClassEntry) { + String line = writeClass((ClassEntry) entry, mapping); + writer.println(indent(line, depth)); + } else if (entry instanceof MethodEntry) { + String line = writeMethod((MethodEntry) entry, mapping); + writer.println(indent(line, depth)); + } else if (entry instanceof FieldEntry) { + String line = writeField((FieldEntry) entry, mapping); + writer.println(indent(line, depth)); + } else if (entry instanceof LocalVariableEntry) { + String line = writeArgument((LocalVariableEntry) entry, mapping); + writer.println(indent(line, depth)); + } + + Collection> children = groupChildren(node.getChildren()); + for (Entry child : children) { + writeEntry(writer, mappings, child, depth + 1); + } + } + + private Collection> groupChildren(Collection> children) { + Collection> result = new ArrayList<>(children.size()); + + children.stream().filter(e -> e instanceof ClassEntry) + .map(e -> (ClassEntry) e) + .sorted() + .forEach(result::add); + + children.stream().filter(e -> e instanceof FieldEntry) + .map(e -> (FieldEntry) e) + .sorted() + .forEach(result::add); + + children.stream().filter(e -> e instanceof MethodEntry) + .map(e -> (MethodEntry) e) + .sorted() + .forEach(result::add); + + children.stream().filter(e -> e instanceof LocalVariableEntry) + .map(e -> (LocalVariableEntry) e) + .sorted() + .forEach(result::add); + + return result; + } + + protected String writeClass(ClassEntry entry, EntryMapping mapping) { + StringBuilder builder = new StringBuilder("CLASS "); + builder.append(entry.getFullName()).append(' '); + writeMapping(builder, mapping); + + return builder.toString(); + } + + protected String writeMethod(MethodEntry entry, EntryMapping mapping) { + StringBuilder builder = new StringBuilder("METHOD "); + builder.append(entry.getName()).append(' '); + writeMapping(builder, mapping); + + builder.append(entry.getDesc().toString()); + + return builder.toString(); + } + + protected String writeField(FieldEntry entry, EntryMapping mapping) { + StringBuilder builder = new StringBuilder("FIELD "); + builder.append(entry.getName()).append(' '); + writeMapping(builder, mapping); + + builder.append(entry.getDesc().toString()); + + return builder.toString(); + } + + protected String writeArgument(LocalVariableEntry entry, EntryMapping mapping) { + StringBuilder builder = new StringBuilder("ARG "); + builder.append(entry.getIndex()).append(' '); + + String mappedName = mapping != null ? mapping.getTargetName() : entry.getName(); + builder.append(mappedName); + + return builder.toString(); + } + + private void writeMapping(StringBuilder builder, EntryMapping mapping) { + if (mapping != null) { + builder.append(mapping.getTargetName()).append(' '); + if (mapping.getAccessModifier() != AccessModifier.UNCHANGED) { + builder.append(mapping.getAccessModifier().getFormattedName()).append(' '); + } + } + } + + private String indent(String line, int depth) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < depth; i++) { + builder.append("\t"); + } + builder.append(line.trim()); + return builder.toString(); + } +} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java new file mode 100644 index 0000000..4db1645 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java @@ -0,0 +1,54 @@ +package cuchaz.enigma.translation.mapping.serde; + +import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.throwables.MappingParseException; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.MappingDelta; +import cuchaz.enigma.translation.mapping.tree.EntryTree; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.nio.file.Path; + +public enum MappingFormat { + ENIGMA_FILE(EnigmaMappingsWriter.FILE, EnigmaMappingsReader.FILE), + ENIGMA_DIRECTORY(EnigmaMappingsWriter.DIRECTORY, EnigmaMappingsReader.DIRECTORY), + TINY_FILE(null, TinyMappingsReader.INSTANCE), + SRG_FILE(SrgMappingsWriter.INSTANCE, null); + + private final MappingsWriter writer; + private final MappingsReader reader; + + MappingFormat(MappingsWriter writer, MappingsReader reader) { + this.writer = writer; + this.reader = reader; + } + + public void write(EntryTree mappings, Path path, ProgressListener progressListener) { + write(mappings, MappingDelta.added(mappings), path, progressListener); + } + + public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progressListener) { + if (writer == null) { + throw new IllegalStateException(name() + " does not support writing"); + } + writer.write(mappings, delta, path, progressListener); + } + + public EntryTree read(Path path) throws IOException, MappingParseException { + if (reader == null) { + throw new IllegalStateException(name() + " does not support reading"); + } + return reader.read(path); + } + + @Nullable + public MappingsWriter getWriter() { + return writer; + } + + @Nullable + public MappingsReader getReader() { + return reader; + } +} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsReader.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsReader.java new file mode 100644 index 0000000..f239ee6 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsReader.java @@ -0,0 +1,12 @@ +package cuchaz.enigma.translation.mapping.serde; + +import cuchaz.enigma.throwables.MappingParseException; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.tree.EntryTree; + +import java.io.IOException; +import java.nio.file.Path; + +public interface MappingsReader { + EntryTree read(Path path) throws MappingParseException, IOException; +} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsWriter.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsWriter.java new file mode 100644 index 0000000..b519668 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsWriter.java @@ -0,0 +1,12 @@ +package cuchaz.enigma.translation.mapping.serde; + +import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.MappingDelta; +import cuchaz.enigma.translation.mapping.tree.EntryTree; + +import java.nio.file.Path; + +public interface MappingsWriter { + void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress); +} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/SrgMappingsWriter.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/SrgMappingsWriter.java new file mode 100644 index 0000000..15ba4d7 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/mapping/serde/SrgMappingsWriter.java @@ -0,0 +1,115 @@ +package cuchaz.enigma.translation.mapping.serde; + +import com.google.common.collect.Lists; +import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.translation.MappingTranslator; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.MappingDelta; +import cuchaz.enigma.translation.mapping.VoidEntryResolver; +import cuchaz.enigma.translation.mapping.tree.EntryTree; +import cuchaz.enigma.translation.mapping.tree.EntryTreeNode; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.Entry; +import cuchaz.enigma.translation.representation.entry.FieldEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; + +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +public enum SrgMappingsWriter implements MappingsWriter { + INSTANCE; + + @Override + public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress) { + try { + Files.deleteIfExists(path); + Files.createFile(path); + } catch (IOException e) { + e.printStackTrace(); + } + + List classLines = new ArrayList<>(); + List fieldLines = new ArrayList<>(); + List methodLines = new ArrayList<>(); + + Collection> rootEntries = Lists.newArrayList(mappings).stream() + .map(EntryTreeNode::getEntry) + .collect(Collectors.toList()); + progress.init(rootEntries.size(), "Generating mappings"); + + int steps = 0; + for (Entry entry : sorted(rootEntries)) { + progress.step(steps++, entry.getName()); + writeEntry(classLines, fieldLines, methodLines, mappings, entry); + } + + progress.init(3, "Writing mappings"); + try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(path))) { + progress.step(0, "Classes"); + classLines.forEach(writer::println); + progress.step(1, "Fields"); + fieldLines.forEach(writer::println); + progress.step(2, "Methods"); + methodLines.forEach(writer::println); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void writeEntry(List classes, List fields, List methods, EntryTree mappings, Entry entry) { + EntryTreeNode node = mappings.findNode(entry); + if (node == null) { + return; + } + + Translator translator = new MappingTranslator(mappings, VoidEntryResolver.INSTANCE); + if (entry instanceof ClassEntry) { + classes.add(generateClassLine((ClassEntry) entry, translator)); + } else if (entry instanceof FieldEntry) { + fields.add(generateFieldLine((FieldEntry) entry, translator)); + } else if (entry instanceof MethodEntry) { + methods.add(generateMethodLine((MethodEntry) entry, translator)); + } + + for (Entry child : sorted(node.getChildren())) { + writeEntry(classes, fields, methods, mappings, child); + } + } + + private String generateClassLine(ClassEntry sourceEntry, Translator translator) { + ClassEntry targetEntry = translator.translate(sourceEntry); + return "CL: " + sourceEntry.getFullName() + " " + targetEntry.getFullName(); + } + + private String generateMethodLine(MethodEntry sourceEntry, Translator translator) { + MethodEntry targetEntry = translator.translate(sourceEntry); + return "MD: " + describeMethod(sourceEntry) + " " + describeMethod(targetEntry); + } + + private String describeMethod(MethodEntry entry) { + return entry.getParent().getFullName() + "/" + entry.getName() + " " + entry.getDesc(); + } + + private String generateFieldLine(FieldEntry sourceEntry, Translator translator) { + FieldEntry targetEntry = translator.translate(sourceEntry); + return "FD: " + describeField(sourceEntry) + " " + describeField(targetEntry); + } + + private String describeField(FieldEntry entry) { + return entry.getParent().getFullName() + "/" + entry.getName(); + } + + private Collection> sorted(Iterable> iterable) { + ArrayList> sorted = Lists.newArrayList(iterable); + sorted.sort(Comparator.comparing(Entry::getName)); + return sorted; + } +} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsReader.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsReader.java new file mode 100644 index 0000000..e0afc3e --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsReader.java @@ -0,0 +1,100 @@ +package cuchaz.enigma.translation.mapping.serde; + +import com.google.common.base.Charsets; +import cuchaz.enigma.throwables.MappingParseException; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.MappingPair; +import cuchaz.enigma.translation.mapping.tree.HashEntryTree; +import cuchaz.enigma.translation.mapping.tree.EntryTree; +import cuchaz.enigma.translation.representation.MethodDescriptor; +import cuchaz.enigma.translation.representation.TypeDescriptor; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.FieldEntry; +import cuchaz.enigma.translation.representation.entry.LocalVariableEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +public enum TinyMappingsReader implements MappingsReader { + INSTANCE; + + @Override + public EntryTree read(Path path) throws IOException, MappingParseException { + return read(path, Files.readAllLines(path, Charsets.UTF_8)); + } + + private EntryTree read(Path path, List lines) throws MappingParseException { + EntryTree mappings = new HashEntryTree<>(); + lines.remove(0); + + for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) { + String line = lines.get(lineNumber); + + try { + MappingPair mapping = parseLine(line); + mappings.insert(mapping.getEntry(), mapping.getMapping()); + } catch (Throwable t) { + t.printStackTrace(); + throw new MappingParseException(path::toString, lineNumber, t.toString()); + } + } + + return mappings; + } + + private MappingPair parseLine(String line) { + String[] tokens = line.split("\t"); + + String key = tokens[0]; + switch (key) { + case "CLASS": + return parseClass(tokens); + case "FIELD": + return parseField(tokens); + case "METHOD": + return parseMethod(tokens); + case "MTH-ARG": + return parseArgument(tokens); + default: + throw new RuntimeException("Unknown token '" + key + "'!"); + } + } + + private MappingPair parseClass(String[] tokens) { + ClassEntry obfuscatedEntry = new ClassEntry(tokens[1]); + String mapping = tokens[2]; + return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping)); + } + + private MappingPair parseField(String[] tokens) { + ClassEntry ownerClass = new ClassEntry(tokens[1]); + TypeDescriptor descriptor = new TypeDescriptor(tokens[2]); + + FieldEntry obfuscatedEntry = new FieldEntry(ownerClass, tokens[3], descriptor); + String mapping = tokens[4]; + return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping)); + } + + private MappingPair parseMethod(String[] tokens) { + ClassEntry ownerClass = new ClassEntry(tokens[1]); + MethodDescriptor descriptor = new MethodDescriptor(tokens[2]); + + MethodEntry obfuscatedEntry = new MethodEntry(ownerClass, tokens[3], descriptor); + String mapping = tokens[4]; + return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping)); + } + + private MappingPair parseArgument(String[] tokens) { + ClassEntry ownerClass = new ClassEntry(tokens[1]); + MethodDescriptor ownerDescriptor = new MethodDescriptor(tokens[2]); + MethodEntry ownerMethod = new MethodEntry(ownerClass, tokens[3], ownerDescriptor); + int variableIndex = Integer.parseInt(tokens[4]); + + String mapping = tokens[5]; + LocalVariableEntry obfuscatedEntry = new LocalVariableEntry(ownerMethod, variableIndex, "", true); + return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping)); + } +} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/tree/DeltaTrackingTree.java b/src/main/java/cuchaz/enigma/translation/mapping/tree/DeltaTrackingTree.java new file mode 100644 index 0000000..98a01df --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/mapping/tree/DeltaTrackingTree.java @@ -0,0 +1,113 @@ +package cuchaz.enigma.translation.mapping.tree; + +import cuchaz.enigma.translation.mapping.MappingDelta; +import cuchaz.enigma.translation.representation.entry.Entry; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.Iterator; + +public class DeltaTrackingTree implements EntryTree { + private final EntryTree delegate; + + private EntryTree additions = new HashEntryTree<>(); + private EntryTree deletions = new HashEntryTree<>(); + + public DeltaTrackingTree(EntryTree delegate) { + this.delegate = delegate; + } + + public DeltaTrackingTree() { + this(new HashEntryTree<>()); + } + + @Override + public void insert(Entry entry, T value) { + if (value != null) { + trackAddition(entry); + } else { + trackDeletion(entry); + } + delegate.insert(entry, value); + } + + @Nullable + @Override + public T remove(Entry entry) { + T value = delegate.remove(entry); + trackDeletion(entry); + return value; + } + + public void trackAddition(Entry entry) { + deletions.remove(entry); + additions.insert(entry, MappingDelta.PLACEHOLDER); + } + + public void trackDeletion(Entry entry) { + additions.remove(entry); + deletions.insert(entry, MappingDelta.PLACEHOLDER); + } + + @Nullable + @Override + public T get(Entry entry) { + return delegate.get(entry); + } + + @Override + public Collection> getChildren(Entry entry) { + return delegate.getChildren(entry); + } + + @Override + public Collection> getSiblings(Entry entry) { + return delegate.getSiblings(entry); + } + + @Nullable + @Override + public EntryTreeNode findNode(Entry entry) { + return delegate.findNode(entry); + } + + @Override + public Collection> getAllNodes() { + return delegate.getAllNodes(); + } + + @Override + public Collection> getRootEntries() { + return delegate.getRootEntries(); + } + + @Override + public Collection> getAllEntries() { + return delegate.getAllEntries(); + } + + @Override + public boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public Iterator> iterator() { + return delegate.iterator(); + } + + public MappingDelta takeDelta() { + MappingDelta delta = new MappingDelta(additions, deletions); + resetDelta(); + return delta; + } + + private void resetDelta() { + additions = new HashEntryTree<>(); + deletions = new HashEntryTree<>(); + } + + public boolean isDirty() { + return !additions.isEmpty() || !deletions.isEmpty(); + } +} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTree.java b/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTree.java new file mode 100644 index 0000000..73fe12d --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTree.java @@ -0,0 +1,20 @@ +package cuchaz.enigma.translation.mapping.tree; + +import cuchaz.enigma.translation.mapping.EntryMap; +import cuchaz.enigma.translation.representation.entry.Entry; + +import javax.annotation.Nullable; +import java.util.Collection; + +public interface EntryTree extends EntryMap, Iterable> { + Collection> getChildren(Entry entry); + + Collection> getSiblings(Entry entry); + + @Nullable + EntryTreeNode findNode(Entry entry); + + Collection> getAllNodes(); + + Collection> getRootEntries(); +} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTreeNode.java b/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTreeNode.java new file mode 100644 index 0000000..734b60c --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTreeNode.java @@ -0,0 +1,36 @@ +package cuchaz.enigma.translation.mapping.tree; + +import cuchaz.enigma.translation.representation.entry.Entry; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.stream.Collectors; + +public interface EntryTreeNode { + @Nullable + T getValue(); + + Entry getEntry(); + + boolean isEmpty(); + + Collection> getChildren(); + + Collection> getChildNodes(); + + default Collection> getNodesRecursively() { + Collection> nodes = new ArrayList<>(); + nodes.add(this); + for (EntryTreeNode node : getChildNodes()) { + nodes.addAll(node.getNodesRecursively()); + } + return nodes; + } + + default Collection> getChildrenRecursively() { + return getNodesRecursively().stream() + .map(EntryTreeNode::getEntry) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java b/src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java new file mode 100644 index 0000000..ff88bf9 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java @@ -0,0 +1,159 @@ +package cuchaz.enigma.translation.mapping.tree; + +import cuchaz.enigma.translation.representation.entry.Entry; + +import javax.annotation.Nullable; +import java.util.*; +import java.util.stream.Collectors; + +public class HashEntryTree implements EntryTree { + private final Map, HashTreeNode> root = new HashMap<>(); + + @Override + public void insert(Entry entry, T value) { + List> path = computePath(entry); + path.get(path.size() - 1).putValue(value); + if (value == null) { + removeDeadAlong(path); + } + } + + @Override + @Nullable + public T remove(Entry entry) { + List> path = computePath(entry); + T value = path.get(path.size() - 1).removeValue(); + + removeDeadAlong(path); + + return value; + } + + @Override + @Nullable + public T get(Entry entry) { + HashTreeNode node = findNode(entry); + if (node == null) { + return null; + } + return node.getValue(); + } + + @Override + public boolean contains(Entry entry) { + return get(entry) != null; + } + + @Override + public Collection> getChildren(Entry entry) { + HashTreeNode leaf = findNode(entry); + if (leaf == null) { + return Collections.emptyList(); + } + return leaf.getChildren(); + } + + @Override + public Collection> getSiblings(Entry entry) { + List> path = computePath(entry); + if (path.size() <= 1) { + return getSiblings(entry, root.keySet()); + } + HashTreeNode parent = path.get(path.size() - 2); + return getSiblings(entry, parent.getChildren()); + } + + private Collection> getSiblings(Entry entry, Collection> children) { + Set> siblings = new HashSet<>(children); + siblings.remove(entry); + return siblings; + } + + @Override + @Nullable + public HashTreeNode findNode(Entry target) { + List> parentChain = target.getAncestry(); + if (parentChain.isEmpty()) { + return null; + } + + HashTreeNode node = root.get(parentChain.get(0)); + for (int i = 1; i < parentChain.size(); i++) { + if (node == null) { + return null; + } + node = node.getChild(parentChain.get(i), false); + } + + return node; + } + + private List> computePath(Entry target) { + List> ancestry = target.getAncestry(); + if (ancestry.isEmpty()) { + return Collections.emptyList(); + } + + List> path = new ArrayList<>(ancestry.size()); + + Entry rootEntry = ancestry.get(0); + HashTreeNode node = root.computeIfAbsent(rootEntry, HashTreeNode::new); + path.add(node); + + for (int i = 1; i < ancestry.size(); i++) { + node = node.getChild(ancestry.get(i), true); + path.add(node); + } + + return path; + } + + private void removeDeadAlong(List> path) { + for (int i = path.size() - 1; i >= 0; i--) { + HashTreeNode node = path.get(i); + if (node.isEmpty()) { + if (i > 0) { + HashTreeNode parentNode = path.get(i - 1); + parentNode.remove(node.getEntry()); + } else { + root.remove(node.getEntry()); + } + } else { + break; + } + } + } + + @Override + @SuppressWarnings("unchecked") + public Iterator> iterator() { + Collection> values = (Collection) root.values(); + return values.iterator(); + } + + @Override + public Collection> getAllNodes() { + Collection> nodes = new ArrayList<>(); + for (EntryTreeNode node : root.values()) { + nodes.addAll(node.getNodesRecursively()); + } + return nodes; + } + + @Override + public Collection> getAllEntries() { + return getAllNodes().stream() + .map(EntryTreeNode::getEntry) + .collect(Collectors.toList()); + } + + @Override + public Collection> getRootEntries() { + return root.keySet(); + } + + @Override + public boolean isEmpty() { + return root.isEmpty(); + } +} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/tree/HashTreeNode.java b/src/main/java/cuchaz/enigma/translation/mapping/tree/HashTreeNode.java new file mode 100644 index 0000000..90e9164 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/mapping/tree/HashTreeNode.java @@ -0,0 +1,72 @@ +package cuchaz.enigma.translation.mapping.tree; + +import cuchaz.enigma.translation.representation.entry.Entry; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +public class HashTreeNode implements EntryTreeNode, Iterable> { + private final Entry entry; + private final Map, HashTreeNode> children = new HashMap<>(); + private T value; + + HashTreeNode(Entry entry) { + this.entry = entry; + } + + void putValue(T value) { + this.value = value; + } + + T removeValue() { + T value = this.value; + this.value = null; + return value; + } + + HashTreeNode getChild(Entry entry, boolean create) { + if (create) { + return children.computeIfAbsent(entry, HashTreeNode::new); + } else { + return children.get(entry); + } + } + + void remove(Entry entry) { + children.remove(entry); + } + + @Override + @Nullable + public T getValue() { + return value; + } + + @Override + public Entry getEntry() { + return entry; + } + + @Override + public boolean isEmpty() { + return children.isEmpty() && value == null; + } + + @Override + public Collection> getChildren() { + return children.keySet(); + } + + @Override + public Collection> getChildNodes() { + return children.values(); + } + + @Override + public Iterator> iterator() { + return children.values().iterator(); + } +} diff --git a/src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java b/src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java new file mode 100644 index 0000000..0534edd --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java @@ -0,0 +1,112 @@ +package cuchaz.enigma.translation.representation; + +import cuchaz.enigma.analysis.Access; +import org.objectweb.asm.Opcodes; + +import java.lang.reflect.Modifier; + +public class AccessFlags { + public static final AccessFlags PRIVATE = new AccessFlags(Opcodes.ACC_PRIVATE); + public static final AccessFlags PUBLIC = new AccessFlags(Opcodes.ACC_PUBLIC); + + private int flags; + + public AccessFlags(int flags) { + this.flags = flags; + } + + public boolean isPrivate() { + return Modifier.isPrivate(this.flags); + } + + public boolean isProtected() { + return Modifier.isProtected(this.flags); + } + + public boolean isPublic() { + return Modifier.isPublic(this.flags); + } + + public boolean isSynthetic() { + return (this.flags & Opcodes.ACC_SYNTHETIC) != 0; + } + + public boolean isStatic() { + return Modifier.isStatic(this.flags); + } + + public boolean isEnum() { + return (flags & Opcodes.ACC_ENUM) != 0; + } + + public boolean isBridge() { + return (flags & Opcodes.ACC_BRIDGE) != 0; + } + + public boolean isFinal() { + return (flags & Opcodes.ACC_FINAL) != 0; + } + + public AccessFlags setPrivate() { + this.setVisibility(Opcodes.ACC_PRIVATE); + return this; + } + + public AccessFlags setProtected() { + this.setVisibility(Opcodes.ACC_PROTECTED); + return this; + } + + public AccessFlags setPublic() { + this.setVisibility(Opcodes.ACC_PUBLIC); + return this; + } + + public AccessFlags setBridge() { + flags |= Opcodes.ACC_BRIDGE; + return this; + } + + @Deprecated + public AccessFlags setBridged() { + return setBridge(); + } + + public void setVisibility(int visibility) { + this.resetVisibility(); + this.flags |= visibility; + } + + private void resetVisibility() { + this.flags &= ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED | Opcodes.ACC_PUBLIC); + } + + public int getFlags() { + return this.flags; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof AccessFlags && ((AccessFlags) obj).flags == flags; + } + + @Override + public int hashCode() { + return flags; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(Access.get(this).toString().toLowerCase()); + if (isStatic()) { + builder.append(" static"); + } + if (isSynthetic()) { + builder.append(" synthetic"); + } + if (isBridge()) { + builder.append(" bridge"); + } + return builder.toString(); + } +} diff --git a/src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java b/src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java new file mode 100644 index 0000000..c59751f --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java @@ -0,0 +1,132 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * 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.translation.representation; + +import com.google.common.collect.Lists; +import cuchaz.enigma.translation.Translatable; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.mapping.EntryMap; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.utils.Utils; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +public class MethodDescriptor implements Translatable { + + private List argumentDescs; + private TypeDescriptor returnDesc; + + public MethodDescriptor(String desc) { + try { + this.argumentDescs = Lists.newArrayList(); + int i = 0; + while (i < desc.length()) { + char c = desc.charAt(i); + if (c == '(') { + assert (this.argumentDescs.isEmpty()); + assert (this.returnDesc == null); + i++; + } else if (c == ')') { + i++; + break; + } else { + String type = TypeDescriptor.parseFirst(desc.substring(i)); + this.argumentDescs.add(new TypeDescriptor(type)); + i += type.length(); + } + } + this.returnDesc = new TypeDescriptor(TypeDescriptor.parseFirst(desc.substring(i))); + } catch (Exception ex) { + throw new IllegalArgumentException("Unable to parse method descriptor: " + desc, ex); + } + } + + public MethodDescriptor(List argumentDescs, TypeDescriptor returnDesc) { + this.argumentDescs = argumentDescs; + this.returnDesc = returnDesc; + } + + public List getArgumentDescs() { + return this.argumentDescs; + } + + public TypeDescriptor getReturnDesc() { + return this.returnDesc; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append("("); + for (TypeDescriptor desc : this.argumentDescs) { + buf.append(desc); + } + buf.append(")"); + buf.append(this.returnDesc); + return buf.toString(); + } + + public Iterable types() { + List descs = Lists.newArrayList(); + descs.addAll(this.argumentDescs); + descs.add(this.returnDesc); + return descs; + } + + @Override + public boolean equals(Object other) { + return other instanceof MethodDescriptor && equals((MethodDescriptor) other); + } + + public boolean equals(MethodDescriptor other) { + return this.argumentDescs.equals(other.argumentDescs) && this.returnDesc.equals(other.returnDesc); + } + + @Override + public int hashCode() { + return Utils.combineHashesOrdered(this.argumentDescs.hashCode(), this.returnDesc.hashCode()); + } + + public boolean hasClass(ClassEntry classEntry) { + for (TypeDescriptor desc : types()) { + if (desc.containsType() && desc.getTypeEntry().equals(classEntry)) { + return true; + } + } + return false; + } + + public MethodDescriptor remap(Function remapper) { + List argumentDescs = new ArrayList<>(this.argumentDescs.size()); + for (TypeDescriptor desc : this.argumentDescs) { + argumentDescs.add(desc.remap(remapper)); + } + return new MethodDescriptor(argumentDescs, returnDesc.remap(remapper)); + } + + @Override + public Translatable translate(Translator translator, EntryResolver resolver, EntryMap mappings) { + List translatedArguments = new ArrayList<>(argumentDescs.size()); + for (TypeDescriptor argument : argumentDescs) { + translatedArguments.add(translator.translate(argument)); + } + return new MethodDescriptor(translatedArguments, translator.translate(returnDesc)); + } + + public boolean canConflictWith(MethodDescriptor descriptor) { + return descriptor.argumentDescs.equals(argumentDescs); + } +} diff --git a/src/main/java/cuchaz/enigma/translation/representation/ProcyonEntryFactory.java b/src/main/java/cuchaz/enigma/translation/representation/ProcyonEntryFactory.java new file mode 100644 index 0000000..9c9fa3d --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/representation/ProcyonEntryFactory.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * 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.translation.representation; + +import com.strobel.assembler.metadata.FieldDefinition; +import com.strobel.assembler.metadata.MemberReference; +import com.strobel.assembler.metadata.MethodDefinition; +import cuchaz.enigma.translation.representation.entry.*; + +public class ProcyonEntryFactory { + private final ReferencedEntryPool entryPool; + + public ProcyonEntryFactory(ReferencedEntryPool entryPool) { + this.entryPool = entryPool; + } + + public FieldEntry getFieldEntry(MemberReference def) { + ClassEntry classEntry = entryPool.getClass(def.getDeclaringType().getInternalName()); + return entryPool.getField(classEntry, def.getName(), def.getErasedSignature()); + } + + public FieldDefEntry getFieldDefEntry(FieldDefinition def) { + ClassEntry classEntry = entryPool.getClass(def.getDeclaringType().getInternalName()); + return new FieldDefEntry(classEntry, def.getName(), new TypeDescriptor(def.getErasedSignature()), Signature.createTypedSignature(def.getSignature()), new AccessFlags(def.getModifiers())); + } + + public MethodEntry getMethodEntry(MemberReference def) { + ClassEntry classEntry = entryPool.getClass(def.getDeclaringType().getInternalName()); + return entryPool.getMethod(classEntry, def.getName(), def.getErasedSignature()); + } + + public MethodDefEntry getMethodDefEntry(MethodDefinition def) { + ClassEntry classEntry = entryPool.getClass(def.getDeclaringType().getInternalName()); + return new MethodDefEntry(classEntry, def.getName(), new MethodDescriptor(def.getErasedSignature()), Signature.createSignature(def.getSignature()), new AccessFlags(def.getModifiers())); + } +} diff --git a/src/main/java/cuchaz/enigma/translation/representation/ReferencedEntryPool.java b/src/main/java/cuchaz/enigma/translation/representation/ReferencedEntryPool.java new file mode 100644 index 0000000..631b375 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/representation/ReferencedEntryPool.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * 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.translation.representation; + +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.FieldEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; + +import java.util.HashMap; +import java.util.Map; + +public class ReferencedEntryPool { + private final Map classEntries = new HashMap<>(); + private final Map> methodEntries = new HashMap<>(); + private final Map> fieldEntries = new HashMap<>(); + + public ClassEntry getClass(String name) { + // TODO: FIXME - I'm a hack! + if ("[T".equals(name) || "[[T".equals(name) || "[[[T".equals(name)) { + name = name.replaceAll("T", "Ljava/lang/Object;"); + } + + final String computeName = name; + return this.classEntries.computeIfAbsent(name, s -> new ClassEntry(computeName)); + } + + public MethodEntry getMethod(ClassEntry ownerEntry, String name, String desc) { + return getMethod(ownerEntry, name, new MethodDescriptor(desc)); + } + + public MethodEntry getMethod(ClassEntry ownerEntry, String name, MethodDescriptor desc) { + String key = name + desc.toString(); + return getClassMethods(ownerEntry.getFullName()).computeIfAbsent(key, s -> new MethodEntry(ownerEntry, name, desc)); + } + + public FieldEntry getField(ClassEntry ownerEntry, String name, String desc) { + return getField(ownerEntry, name, new TypeDescriptor(desc)); + } + + public FieldEntry getField(ClassEntry ownerEntry, String name, TypeDescriptor desc) { + return getClassFields(ownerEntry.getFullName()).computeIfAbsent(name, s -> new FieldEntry(ownerEntry, name, desc)); + } + + private Map getClassMethods(String name) { + return methodEntries.computeIfAbsent(name, s -> new HashMap<>()); + } + + private Map getClassFields(String name) { + return fieldEntries.computeIfAbsent(name, s -> new HashMap<>()); + } +} diff --git a/src/main/java/cuchaz/enigma/translation/representation/Signature.java b/src/main/java/cuchaz/enigma/translation/representation/Signature.java new file mode 100644 index 0000000..dc241b7 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/representation/Signature.java @@ -0,0 +1,93 @@ +package cuchaz.enigma.translation.representation; + +import cuchaz.enigma.bytecode.translators.TranslationSignatureVisitor; +import cuchaz.enigma.translation.Translatable; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.mapping.EntryMap; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import org.objectweb.asm.signature.SignatureReader; +import org.objectweb.asm.signature.SignatureVisitor; +import org.objectweb.asm.signature.SignatureWriter; + +import java.util.function.Function; +import java.util.regex.Pattern; + +public class Signature implements Translatable { + private static final Pattern OBJECT_PATTERN = Pattern.compile(".*:Ljava/lang/Object;:.*"); + + private final String signature; + private final boolean isType; + + private Signature(String signature, boolean isType) { + if (signature != null && OBJECT_PATTERN.matcher(signature).matches()) { + signature = signature.replaceAll(":Ljava/lang/Object;:", "::"); + } + + this.signature = signature; + this.isType = isType; + } + + public static Signature createTypedSignature(String signature) { + if (signature != null && !signature.isEmpty()) { + return new Signature(signature, true); + } + return new Signature(null, true); + } + + public static Signature createSignature(String signature) { + if (signature != null && !signature.isEmpty()) { + return new Signature(signature, false); + } + return new Signature(null, false); + } + + public String getSignature() { + return signature; + } + + public boolean isType() { + return isType; + } + + public Signature remap(Function remapper) { + if (signature == null) { + return this; + } + SignatureWriter writer = new SignatureWriter(); + SignatureVisitor visitor = new TranslationSignatureVisitor(remapper, writer); + if (isType) { + new SignatureReader(signature).acceptType(visitor); + } else { + new SignatureReader(signature).accept(visitor); + } + return new Signature(writer.toString(), isType); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Signature) { + Signature other = (Signature) obj; + return (other.signature == null && signature == null || other.signature != null + && signature != null && other.signature.equals(signature)) + && other.isType == this.isType; + } + return false; + } + + @Override + public int hashCode() { + return signature.hashCode() | (isType ? 1 : 0) << 16; + } + + @Override + public String toString() { + return signature; + } + + @Override + public Translatable translate(Translator translator, EntryResolver resolver, EntryMap mappings) { + return remap(name -> translator.translate(new ClassEntry(name)).getFullName()); + } +} diff --git a/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java b/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java new file mode 100644 index 0000000..f7ba849 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java @@ -0,0 +1,268 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * 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.translation.representation; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; +import cuchaz.enigma.translation.Translatable; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.mapping.EntryMap; +import cuchaz.enigma.translation.representation.entry.ClassEntry; + +import java.util.Map; +import java.util.function.Function; + +public class TypeDescriptor implements Translatable { + + protected final String desc; + + public TypeDescriptor(String desc) { + Preconditions.checkNotNull(desc, "Desc cannot be null"); + + // don't deal with generics + // this is just for raw jvm types + if (desc.charAt(0) == 'T' || desc.indexOf('<') >= 0 || desc.indexOf('>') >= 0) { + throw new IllegalArgumentException("don't use with generic types or templates: " + desc); + } + + this.desc = desc; + } + + public static String parseFirst(String in) { + + if (in == null || in.length() <= 0) { + throw new IllegalArgumentException("No desc to parse, input is empty!"); + } + + // read one desc 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 = TypeDescriptor.parseFirst(in.substring(dim)); + return in.substring(0, dim + arrayType.length()); + } + + throw new IllegalArgumentException("don't know how to parse: " + in); + } + + 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; + } + + public static TypeDescriptor of(String name) { + return new TypeDescriptor("L" + name + ";"); + } + + @Override + public String toString() { + return this.desc; + } + + public boolean isVoid() { + return this.desc.length() == 1 && this.desc.charAt(0) == 'V'; + } + + public boolean isPrimitive() { + return this.desc.length() == 1 && Primitive.get(this.desc.charAt(0)) != null; + } + + public Primitive getPrimitive() { + if (!isPrimitive()) { + throw new IllegalStateException("not a primitive"); + } + return Primitive.get(this.desc.charAt(0)); + } + + public boolean isType() { + return this.desc.charAt(0) == 'L' && this.desc.charAt(this.desc.length() - 1) == ';'; + } + + public ClassEntry getTypeEntry() { + if (isType()) { + String name = this.desc.substring(1, this.desc.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().isType()) { + return getArrayType().getTypeEntry(); + } else { + throw new IllegalStateException("desc doesn't have a class"); + } + } + + public boolean isArray() { + return this.desc.charAt(0) == '['; + } + + public int getArrayDimension() { + if (!isArray()) { + throw new IllegalStateException("not an array"); + } + return countArrayDimension(this.desc); + } + + public TypeDescriptor getArrayType() { + if (!isArray()) { + throw new IllegalStateException("not an array"); + } + return new TypeDescriptor(this.desc.substring(getArrayDimension())); + } + + public boolean containsType() { + return isType() || (isArray() && getArrayType().containsType()); + } + + @Override + public boolean equals(Object other) { + return other instanceof TypeDescriptor && equals((TypeDescriptor) other); + } + + public boolean equals(TypeDescriptor other) { + return this.desc.equals(other.desc); + } + + @Override + public int hashCode() { + return this.desc.hashCode(); + } + + public TypeDescriptor remap(Function remapper) { + String desc = this.desc; + if (isType() || (isArray() && containsType())) { + String replacedName = remapper.apply(this.getTypeEntry().getFullName()); + if (replacedName != null) { + if (this.isType()) { + desc = "L" + replacedName + ";"; + } else { + desc = getArrayPrefix(this.getArrayDimension()) + "L" + replacedName + ";"; + } + } + } + return new TypeDescriptor(desc); + } + + private static String getArrayPrefix(int dimension) { + StringBuilder buf = new StringBuilder(); + for (int i = 0; i < dimension; i++) { + buf.append("["); + } + return buf.toString(); + } + + public int getSize() { + switch (desc.charAt(0)) { + case 'J': + case 'D': + if (desc.length() == 1) { + return 2; + } else { + return 1; + } + default: + return 1; + } + } + + @Override + public Translatable translate(Translator translator, EntryResolver resolver, EntryMap mappings) { + return remap(name -> translator.translate(new ClassEntry(name)).getFullName()); + } + + 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/translation/representation/entry/ClassDefEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java new file mode 100644 index 0000000..b9391b0 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * 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.translation.representation.entry; + +import com.google.common.base.Preconditions; +import com.strobel.assembler.metadata.TypeDefinition; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.representation.AccessFlags; +import cuchaz.enigma.translation.representation.Signature; + +import javax.annotation.Nullable; +import java.util.Arrays; + +public class ClassDefEntry extends ClassEntry implements DefEntry { + private final AccessFlags access; + private final Signature signature; + private final ClassEntry superClass; + private final ClassEntry[] interfaces; + + public ClassDefEntry(String className, Signature signature, AccessFlags access, @Nullable ClassEntry superClass, ClassEntry[] interfaces) { + this(getOuterClass(className), getInnerName(className), signature, access, superClass, interfaces); + } + + public ClassDefEntry(ClassEntry parent, String className, Signature signature, AccessFlags access, @Nullable ClassEntry superClass, ClassEntry[] interfaces) { + super(parent, className); + Preconditions.checkNotNull(signature, "Class signature cannot be null"); + Preconditions.checkNotNull(access, "Class access cannot be null"); + + this.signature = signature; + this.access = access; + this.superClass = superClass; + this.interfaces = interfaces != null ? interfaces : new ClassEntry[0]; + } + + public static ClassDefEntry parse(int access, String name, String signature, String superName, String[] interfaces) { + ClassEntry superClass = superName != null ? new ClassEntry(superName) : null; + ClassEntry[] interfaceClasses = Arrays.stream(interfaces).map(ClassEntry::new).toArray(ClassEntry[]::new); + return new ClassDefEntry(name, Signature.createSignature(signature), new AccessFlags(access), superClass, interfaceClasses); + } + + public static ClassDefEntry parse(TypeDefinition def) { + String name = def.getInternalName(); + Signature signature = Signature.createSignature(def.getSignature()); + AccessFlags access = new AccessFlags(def.getModifiers()); + ClassEntry superClass = def.getBaseType() != null ? ClassEntry.parse(def.getBaseType()) : null; + ClassEntry[] interfaces = def.getExplicitInterfaces().stream().map(ClassEntry::parse).toArray(ClassEntry[]::new); + return new ClassDefEntry(name, signature, access, superClass, interfaces); + } + + public Signature getSignature() { + return signature; + } + + @Override + public AccessFlags getAccess() { + return access; + } + + @Nullable + public ClassEntry getSuperClass() { + return superClass; + } + + public ClassEntry[] getInterfaces() { + return interfaces; + } + + @Override + public ClassDefEntry translate(Translator translator, @Nullable EntryMapping mapping) { + Signature translatedSignature = translator.translate(signature); + String translatedName = mapping != null ? mapping.getTargetName() : name; + AccessFlags translatedAccess = mapping != null ? mapping.getAccessModifier().transform(access) : access; + ClassEntry translatedSuper = translator.translate(superClass); + ClassEntry[] translatedInterfaces = Arrays.stream(interfaces).map(translator::translate).toArray(ClassEntry[]::new); + return new ClassDefEntry(parent, translatedName, translatedSignature, translatedAccess, translatedSuper, translatedInterfaces); + } + + @Override + public ClassDefEntry withParent(ClassEntry parent) { + return new ClassDefEntry(parent, name, signature, access, superClass, interfaces); + } +} diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java new file mode 100644 index 0000000..dcbb8d9 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java @@ -0,0 +1,180 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * 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.translation.representation.entry; + +import com.strobel.assembler.metadata.TypeReference; +import cuchaz.enigma.throwables.IllegalNameException; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.NameValidator; + +import javax.annotation.Nullable; +import java.util.List; +import java.util.Objects; + +public class ClassEntry extends ParentedEntry implements Comparable { + private final String fullName; + + public ClassEntry(String className) { + this(getOuterClass(className), getInnerName(className)); + } + + public ClassEntry(@Nullable ClassEntry parent, String className) { + super(parent, className); + if (parent != null) { + fullName = parent.getFullName() + "$" + name; + } else { + fullName = name; + } + + if (parent == null && className.indexOf('.') >= 0) { + throw new IllegalArgumentException("Class name must be in JVM format. ie, path/to/package/class$inner : " + className); + } + } + + public static ClassEntry parse(TypeReference typeReference) { + return new ClassEntry(typeReference.getInternalName()); + } + + @Override + public Class getParentType() { + return ClassEntry.class; + } + + @Override + public String getName() { + return this.name; + } + + public String getFullName() { + return fullName; + } + + @Override + public ClassEntry translate(Translator translator, @Nullable EntryMapping mapping) { + String translatedName = mapping != null ? mapping.getTargetName() : name; + return new ClassEntry(parent, translatedName); + } + + @Override + public ClassEntry getContainingClass() { + return this; + } + + @Override + public int hashCode() { + return fullName.hashCode(); + } + + @Override + public boolean equals(Object other) { + return other instanceof ClassEntry && equals((ClassEntry) other); + } + + public boolean equals(ClassEntry other) { + return other != null && Objects.equals(parent, other.parent) && this.name.equals(other.name); + } + + @Override + public boolean canConflictWith(Entry entry) { + return true; + } + + @Override + public void validateName(String name) throws IllegalNameException { + NameValidator.validateClassName(name, !isInnerClass()); + } + + @Override + public ClassEntry withParent(ClassEntry parent) { + return new ClassEntry(parent, name); + } + + @Override + public String toString() { + return getFullName(); + } + + public String getPackageName() { + return getPackageName(this.name); + } + + public String getSimpleName() { + int packagePos = name.lastIndexOf('/'); + if (packagePos > 0) { + return name.substring(packagePos + 1); + } + return name; + } + + public boolean isInnerClass() { + return parent != null; + } + + @Nullable + public ClassEntry getOuterClass() { + return parent; + } + + public ClassEntry buildClassEntry(List classChain) { + assert (classChain.contains(this)); + StringBuilder buf = new StringBuilder(); + for (ClassEntry chainEntry : classChain) { + if (buf.length() == 0) { + buf.append(chainEntry.getFullName()); + } else { + buf.append("$"); + buf.append(chainEntry.getSimpleName()); + } + + if (chainEntry == this) { + break; + } + } + return new ClassEntry(buf.toString()); + } + + public boolean isJre() { + String packageName = getPackageName(); + return packageName != null && (packageName.startsWith("java") || packageName.startsWith("javax")); + } + + public static String getPackageName(String name) { + int pos = name.lastIndexOf('/'); + if (pos > 0) { + return name.substring(0, pos); + } + return null; + } + + @Nullable + public static ClassEntry getOuterClass(String name) { + int index = name.lastIndexOf('$'); + if (index >= 0) { + return new ClassEntry(name.substring(0, index)); + } + return null; + } + + public static String getInnerName(String name) { + int innerClassPos = name.lastIndexOf('$'); + if (innerClassPos > 0) { + return name.substring(innerClassPos + 1); + } + return name; + } + + @Override + public int compareTo(ClassEntry entry) { + return name.compareTo(entry.name); + } +} diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/DefEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/DefEntry.java new file mode 100644 index 0000000..82536c7 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/representation/entry/DefEntry.java @@ -0,0 +1,7 @@ +package cuchaz.enigma.translation.representation.entry; + +import cuchaz.enigma.translation.representation.AccessFlags; + +public interface DefEntry

> extends Entry

{ + AccessFlags getAccess(); +} diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java new file mode 100644 index 0000000..1a2ca78 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * 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.translation.representation.entry; + +import cuchaz.enigma.throwables.IllegalNameException; +import cuchaz.enigma.translation.Translatable; +import cuchaz.enigma.translation.mapping.NameValidator; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; + +public interface Entry

> extends Translatable { + String getName(); + + @Nullable + P getParent(); + + Class

getParentType(); + + Entry

withParent(P parent); + + boolean canConflictWith(Entry entry); + + @Nullable + default ClassEntry getContainingClass() { + P parent = getParent(); + if (parent == null) { + return null; + } + if (parent instanceof ClassEntry) { + return (ClassEntry) parent; + } + return parent.getContainingClass(); + } + + default List> getAncestry() { + P parent = getParent(); + List> entries = new ArrayList<>(); + if (parent != null) { + entries.addAll(parent.getAncestry()); + } + entries.add(this); + return entries; + } + + @Nullable + @SuppressWarnings("unchecked") + default > E findAncestor(Class type) { + List> ancestry = getAncestry(); + for (int i = ancestry.size() - 1; i >= 0; i--) { + Entry ancestor = ancestry.get(i); + if (type.isAssignableFrom(ancestor.getClass())) { + return (E) ancestor; + } + } + return null; + } + + @SuppressWarnings("unchecked") + default > Entry

replaceAncestor(E target, E replacement) { + if (replacement.equals(target)) { + return this; + } + + if (equals(target)) { + return (Entry

) replacement; + } + + P parent = getParent(); + if (parent == null) { + return this; + } + + return withParent((P) parent.replaceAncestor(target, replacement)); + } + + default void validateName(String name) throws IllegalNameException { + NameValidator.validateIdentifier(name); + } + + @SuppressWarnings("unchecked") + @Nullable + default > Entry castParent(Class parentType) { + if (parentType.equals(getParentType())) { + return (Entry) this; + } + return null; + } +} diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java new file mode 100644 index 0000000..d487f71 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * 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.translation.representation.entry; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.representation.AccessFlags; +import cuchaz.enigma.translation.representation.Signature; +import cuchaz.enigma.translation.representation.TypeDescriptor; + +import javax.annotation.Nullable; + +public class FieldDefEntry extends FieldEntry implements DefEntry { + private final AccessFlags access; + private final Signature signature; + + public FieldDefEntry(ClassEntry owner, String name, TypeDescriptor desc, Signature signature, AccessFlags access) { + super(owner, name, desc); + Preconditions.checkNotNull(access, "Field access cannot be null"); + Preconditions.checkNotNull(signature, "Field signature cannot be null"); + this.access = access; + this.signature = signature; + } + + public static FieldDefEntry parse(ClassEntry owner, int access, String name, String desc, String signature) { + return new FieldDefEntry(owner, name, new TypeDescriptor(desc), Signature.createTypedSignature(signature), new AccessFlags(access)); + } + + @Override + public AccessFlags getAccess() { + return access; + } + + public Signature getSignature() { + return signature; + } + + @Override + public FieldDefEntry translate(Translator translator, @Nullable EntryMapping mapping) { + TypeDescriptor translatedDesc = translator.translate(desc); + Signature translatedSignature = translator.translate(signature); + String translatedName = mapping != null ? mapping.getTargetName() : name; + AccessFlags translatedAccess = mapping != null ? mapping.getAccessModifier().transform(access) : access; + return new FieldDefEntry(parent, translatedName, translatedDesc, translatedSignature, translatedAccess); + } + + @Override + public FieldDefEntry withParent(ClassEntry owner) { + return new FieldDefEntry(owner, this.name, this.desc, signature, access); + } +} diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java new file mode 100644 index 0000000..2ec2471 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * 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.translation.representation.entry; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.representation.TypeDescriptor; +import cuchaz.enigma.utils.Utils; + +import javax.annotation.Nullable; + +public class FieldEntry extends ParentedEntry implements Comparable { + protected final TypeDescriptor desc; + + public FieldEntry(ClassEntry parent, String name, TypeDescriptor desc) { + super(parent, name); + + Preconditions.checkNotNull(parent, "Owner cannot be null"); + Preconditions.checkNotNull(desc, "Field descriptor cannot be null"); + + this.desc = desc; + } + + public static FieldEntry parse(String owner, String name, String desc) { + return new FieldEntry(new ClassEntry(owner), name, new TypeDescriptor(desc)); + } + + @Override + public Class getParentType() { + return ClassEntry.class; + } + + public TypeDescriptor getDesc() { + return this.desc; + } + + @Override + public FieldEntry withParent(ClassEntry parent) { + return new FieldEntry(parent, this.name, this.desc); + } + + @Override + protected FieldEntry translate(Translator translator, @Nullable EntryMapping mapping) { + String translatedName = mapping != null ? mapping.getTargetName() : name; + return new FieldEntry(parent, translatedName, translator.translate(desc)); + } + + @Override + public int hashCode() { + return Utils.combineHashesOrdered(this.parent, this.name, this.desc); + } + + @Override + public boolean equals(Object other) { + return other instanceof FieldEntry && equals((FieldEntry) other); + } + + public boolean equals(FieldEntry other) { + return this.parent.equals(other.parent) && name.equals(other.name) && desc.equals(other.desc); + } + + @Override + public boolean canConflictWith(Entry entry) { + return entry instanceof FieldEntry && ((FieldEntry) entry).parent.equals(parent); + } + + @Override + public String toString() { + return this.parent.getFullName() + "." + this.name + ":" + this.desc; + } + + @Override + public int compareTo(FieldEntry entry) { + return (name + desc.toString()).compareTo(entry.name + entry.desc.toString()); + } +} diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java new file mode 100644 index 0000000..86bdf61 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java @@ -0,0 +1,45 @@ +package cuchaz.enigma.translation.representation.entry; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.representation.TypeDescriptor; + +import javax.annotation.Nullable; + +/** + * TypeDescriptor... + * Created by Thog + * 19/10/2016 + */ +public class LocalVariableDefEntry extends LocalVariableEntry { + protected final TypeDescriptor desc; + + public LocalVariableDefEntry(MethodEntry ownerEntry, int index, String name, boolean parameter, TypeDescriptor desc) { + super(ownerEntry, index, name, parameter); + Preconditions.checkNotNull(desc, "Variable desc cannot be null"); + + this.desc = desc; + } + + public TypeDescriptor getDesc() { + return desc; + } + + @Override + public LocalVariableDefEntry translate(Translator translator, @Nullable EntryMapping mapping) { + TypeDescriptor translatedDesc = translator.translate(desc); + String translatedName = mapping != null ? mapping.getTargetName() : name; + return new LocalVariableDefEntry(parent, index, translatedName, parameter, translatedDesc); + } + + @Override + public LocalVariableDefEntry withParent(MethodEntry entry) { + return new LocalVariableDefEntry(entry, index, name, parameter, desc); + } + + @Override + public String toString() { + return this.parent + "(" + this.index + ":" + this.name + ":" + this.desc + ")"; + } +} diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java new file mode 100644 index 0000000..df96b59 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java @@ -0,0 +1,92 @@ +package cuchaz.enigma.translation.representation.entry; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.utils.Utils; + +import javax.annotation.Nullable; + +/** + * TypeDescriptor... + * Created by Thog + * 19/10/2016 + */ +public class LocalVariableEntry extends ParentedEntry implements Comparable { + + protected final int index; + protected final boolean parameter; + + @Deprecated + public LocalVariableEntry(MethodEntry parent, int index, String name) { + this(parent, index, name, true); + } + + public LocalVariableEntry(MethodEntry parent, int index, String name, boolean parameter) { + super(parent, name); + + Preconditions.checkNotNull(parent, "Variable owner cannot be null"); + Preconditions.checkArgument(index >= 0, "Index must be positive"); + + this.index = index; + this.parameter = parameter; + } + + @Override + public Class getParentType() { + return MethodEntry.class; + } + + public boolean isParameter() { + return this.parameter; + } + + public int getIndex() { + return index; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public LocalVariableEntry translate(Translator translator, @Nullable EntryMapping mapping) { + String translatedName = mapping != null ? mapping.getTargetName() : name; + return new LocalVariableEntry(parent, index, translatedName, parameter); + } + + @Override + public LocalVariableEntry withParent(MethodEntry parent) { + return new LocalVariableEntry(parent, index, name, parameter); + } + + @Override + public int hashCode() { + return Utils.combineHashesOrdered(this.parent, this.index); + } + + @Override + public boolean equals(Object other) { + return other instanceof LocalVariableEntry && equals((LocalVariableEntry) other); + } + + public boolean equals(LocalVariableEntry other) { + return this.parent.equals(other.parent) && this.index == other.index; + } + + @Override + public boolean canConflictWith(Entry entry) { + return entry instanceof LocalVariableEntry && ((LocalVariableEntry) entry).parent.equals(parent); + } + + @Override + public String toString() { + return this.parent + "(" + this.index + ":" + this.name + ")"; + } + + @Override + public int compareTo(LocalVariableEntry entry) { + return Integer.compare(index, entry.index); + } +} diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java new file mode 100644 index 0000000..3ecd470 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * 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.translation.representation.entry; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.representation.AccessFlags; +import cuchaz.enigma.translation.representation.MethodDescriptor; +import cuchaz.enigma.translation.representation.Signature; + +import javax.annotation.Nullable; + +public class MethodDefEntry extends MethodEntry implements DefEntry { + private final AccessFlags access; + private final Signature signature; + + public MethodDefEntry(ClassEntry owner, String name, MethodDescriptor descriptor, Signature signature, AccessFlags access) { + super(owner, name, descriptor); + Preconditions.checkNotNull(access, "Method access cannot be null"); + Preconditions.checkNotNull(signature, "Method signature cannot be null"); + this.access = access; + this.signature = signature; + } + + public static MethodDefEntry parse(ClassEntry owner, int access, String name, String desc, String signature) { + return new MethodDefEntry(owner, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access)); + } + + @Override + public AccessFlags getAccess() { + return access; + } + + public Signature getSignature() { + return signature; + } + + @Override + public MethodDefEntry translate(Translator translator, @Nullable EntryMapping mapping) { + MethodDescriptor translatedDesc = translator.translate(descriptor); + Signature translatedSignature = translator.translate(signature); + String translatedName = mapping != null ? mapping.getTargetName() : name; + AccessFlags translatedAccess = mapping != null ? mapping.getAccessModifier().transform(access) : access; + return new MethodDefEntry(parent, translatedName, translatedDesc, translatedSignature, translatedAccess); + } + + @Override + public MethodDefEntry withParent(ClassEntry parent) { + return new MethodDefEntry(new ClassEntry(parent.getFullName()), name, descriptor, signature, access); + } + + public int getArgumentIndex(ClassDefEntry ownerEntry, int localVariableIndex) { + int argumentIndex = localVariableIndex; + + // Enum constructors have an implicit "name" and "ordinal" parameter as well as "this" + if (ownerEntry.getAccess().isEnum() && getName().startsWith("<")) { + argumentIndex -= 2; + } + + // If we're not static, "this" is bound to index 0 + if (!getAccess().isStatic()) { + argumentIndex -= 1; + } + + return argumentIndex; + } +} diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java new file mode 100644 index 0000000..3a1dbb3 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java @@ -0,0 +1,95 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * 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.translation.representation.entry; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.representation.MethodDescriptor; +import cuchaz.enigma.utils.Utils; + +import javax.annotation.Nullable; + +public class MethodEntry extends ParentedEntry implements Comparable { + + protected final MethodDescriptor descriptor; + + public MethodEntry(ClassEntry parent, String name, MethodDescriptor descriptor) { + super(parent, name); + + Preconditions.checkNotNull(parent, "Parent cannot be null"); + Preconditions.checkNotNull(descriptor, "Method descriptor cannot be null"); + + this.descriptor = descriptor; + } + + public static MethodEntry parse(String owner, String name, String desc) { + return new MethodEntry(new ClassEntry(owner), name, new MethodDescriptor(desc)); + } + + @Override + public Class getParentType() { + return ClassEntry.class; + } + + public MethodDescriptor getDesc() { + return this.descriptor; + } + + public boolean isConstructor() { + return name.equals("") || name.equals(""); + } + + @Override + public MethodEntry translate(Translator translator, @Nullable EntryMapping mapping) { + String translatedName = mapping != null ? mapping.getTargetName() : name; + return new MethodEntry(parent, translatedName, translator.translate(descriptor)); + } + + @Override + public MethodEntry withParent(ClassEntry parent) { + return new MethodEntry(new ClassEntry(parent.getFullName()), name, descriptor); + } + + @Override + public int hashCode() { + return Utils.combineHashesOrdered(this.parent, this.name, this.descriptor); + } + + @Override + public boolean equals(Object other) { + return other instanceof MethodEntry && equals((MethodEntry) other); + } + + public boolean equals(MethodEntry other) { + return this.parent.equals(other.getParent()) && this.name.equals(other.getName()) && this.descriptor.equals(other.getDesc()); + } + + @Override + public boolean canConflictWith(Entry entry) { + if (entry instanceof MethodEntry) { + MethodEntry methodEntry = (MethodEntry) entry; + return methodEntry.parent.equals(parent) && methodEntry.descriptor.canConflictWith(descriptor); + } + return false; + } + + @Override + public String toString() { + return this.parent.getFullName() + "." + this.name + this.descriptor; + } + + @Override + public int compareTo(MethodEntry entry) { + return (name + descriptor.toString()).compareTo(entry.name + entry.descriptor.toString()); + } +} diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java new file mode 100644 index 0000000..7ba7c19 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * 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.translation.representation.entry; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.translation.Translatable; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMap; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.mapping.ResolutionStrategy; + +import javax.annotation.Nullable; + +public abstract class ParentedEntry

> implements Entry

{ + protected final P parent; + protected final String name; + + protected ParentedEntry(P parent, String name) { + this.parent = parent; + this.name = name; + + Preconditions.checkNotNull(name, "Name cannot be null"); + } + + @Override + public abstract ParentedEntry

withParent(P parent); + + protected abstract ParentedEntry

translate(Translator translator, @Nullable EntryMapping mapping); + + @Override + public String getName() { + return name; + } + + @Override + @Nullable + public P getParent() { + return parent; + } + + @Override + public Translatable translate(Translator translator, EntryResolver resolver, EntryMap mappings) { + P parent = getParent(); + EntryMapping mapping = resolveMapping(resolver, mappings); + if (parent == null) { + return translate(translator, mapping); + } + P translatedParent = translator.translate(parent); + return withParent(translatedParent).translate(translator, mapping); + } + + private EntryMapping resolveMapping(EntryResolver resolver, EntryMap mappings) { + for (ParentedEntry

entry : resolver.resolveEntry(this, ResolutionStrategy.RESOLVE_ROOT)) { + EntryMapping mapping = mappings.get(entry); + if (mapping != null) { + return mapping; + } + } + return null; + } +} -- cgit v1.2.3