summaryrefslogtreecommitdiff
path: root/src/main/java/cuchaz/enigma
diff options
context:
space:
mode:
authorGravatar Gegy2019-01-24 14:48:32 +0200
committerGravatar Adrian Siekierka2019-01-24 13:48:32 +0100
commit00fcd0550fcdda621c2e4662f6ddd55ce673b931 (patch)
tree6f9e4c24dbcc6d118fceec56adf7bf9d747a485c /src/main/java/cuchaz/enigma
parentmark as 0.13.0-SNAPSHOT for preliminary development (diff)
downloadenigma-fork-00fcd0550fcdda621c2e4662f6ddd55ce673b931.tar.gz
enigma-fork-00fcd0550fcdda621c2e4662f6ddd55ce673b931.tar.xz
enigma-fork-00fcd0550fcdda621c2e4662f6ddd55ce673b931.zip
[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
Diffstat (limited to 'src/main/java/cuchaz/enigma')
-rw-r--r--src/main/java/cuchaz/enigma/CommandMain.java57
-rw-r--r--src/main/java/cuchaz/enigma/Deobfuscator.java483
-rw-r--r--src/main/java/cuchaz/enigma/ITranslatingTypeLoader.java2
-rw-r--r--src/main/java/cuchaz/enigma/Main.java13
-rw-r--r--src/main/java/cuchaz/enigma/ProgressListener.java17
-rw-r--r--src/main/java/cuchaz/enigma/SynchronizedTypeLoader.java2
-rw-r--r--src/main/java/cuchaz/enigma/TranslatingTypeLoader.java55
-rw-r--r--src/main/java/cuchaz/enigma/analysis/Access.java2
-rw-r--r--src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java35
-rw-r--r--src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java32
-rw-r--r--src/main/java/cuchaz/enigma/analysis/ClassReferenceTreeNode.java30
-rw-r--r--src/main/java/cuchaz/enigma/analysis/EntryReference.java34
-rw-r--r--src/main/java/cuchaz/enigma/analysis/EntryRenamer.java167
-rw-r--r--src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java39
-rw-r--r--src/main/java/cuchaz/enigma/analysis/IndexInnerClassVisitor.java29
-rw-r--r--src/main/java/cuchaz/enigma/analysis/IndexReferenceVisitor.java77
-rw-r--r--src/main/java/cuchaz/enigma/analysis/IndexTreeBuilder.java87
-rw-r--r--src/main/java/cuchaz/enigma/analysis/JarIndex.java583
-rw-r--r--src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java48
-rw-r--r--src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java46
-rw-r--r--src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java67
-rw-r--r--src/main/java/cuchaz/enigma/analysis/ParsedJar.java10
-rw-r--r--src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java4
-rw-r--r--src/main/java/cuchaz/enigma/analysis/SourceIndex.java53
-rw-r--r--src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java15
-rw-r--r--src/main/java/cuchaz/enigma/analysis/SourceIndexMethodVisitor.java19
-rw-r--r--src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java8
-rw-r--r--src/main/java/cuchaz/enigma/analysis/TranslationIndex.java275
-rw-r--r--src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java77
-rw-r--r--src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java109
-rw-r--r--src/main/java/cuchaz/enigma/analysis/index/IndexClassVisitor.java (renamed from src/main/java/cuchaz/enigma/analysis/IndexClassVisitor.java)31
-rw-r--r--src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java83
-rw-r--r--src/main/java/cuchaz/enigma/analysis/index/InheritanceIndex.java97
-rw-r--r--src/main/java/cuchaz/enigma/analysis/index/JarIndex.java165
-rw-r--r--src/main/java/cuchaz/enigma/analysis/index/JarIndexer.java24
-rw-r--r--src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java83
-rw-r--r--src/main/java/cuchaz/enigma/analysis/index/RemappableIndex.java9
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/translators/AsmObjectTranslator.java46
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/translators/TranslationAnnotationVisitor.java20
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/translators/TranslationClassVisitor.java74
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/translators/TranslationFieldVisitor.java10
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/translators/TranslationMethodVisitor.java55
-rw-r--r--src/main/java/cuchaz/enigma/gui/ClassSelector.java5
-rw-r--r--src/main/java/cuchaz/enigma/gui/CodeReader.java57
-rw-r--r--src/main/java/cuchaz/enigma/gui/Gui.java80
-rw-r--r--src/main/java/cuchaz/enigma/gui/GuiController.java209
-rw-r--r--src/main/java/cuchaz/enigma/gui/GuiTricks.java42
-rw-r--r--src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java44
-rw-r--r--src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java4
-rw-r--r--src/main/java/cuchaz/enigma/gui/elements/MenuBar.java45
-rw-r--r--src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java2
-rw-r--r--src/main/java/cuchaz/enigma/gui/panels/PanelObf.java6
-rw-r--r--src/main/java/cuchaz/enigma/mapping/ClassMapping.java627
-rw-r--r--src/main/java/cuchaz/enigma/mapping/DirectionalTranslator.java371
-rw-r--r--src/main/java/cuchaz/enigma/mapping/FieldMapping.java100
-rw-r--r--src/main/java/cuchaz/enigma/mapping/LocalVariableMapping.java58
-rw-r--r--src/main/java/cuchaz/enigma/mapping/Mappings.java268
-rw-r--r--src/main/java/cuchaz/enigma/mapping/MappingsChecker.java101
-rw-r--r--src/main/java/cuchaz/enigma/mapping/MappingsEnigmaReader.java186
-rw-r--r--src/main/java/cuchaz/enigma/mapping/MappingsEnigmaWriter.java160
-rw-r--r--src/main/java/cuchaz/enigma/mapping/MappingsRenamer.java365
-rw-r--r--src/main/java/cuchaz/enigma/mapping/MappingsSRGWriter.java80
-rw-r--r--src/main/java/cuchaz/enigma/mapping/MappingsTinyReader.java130
-rw-r--r--src/main/java/cuchaz/enigma/mapping/MemberMapping.java21
-rw-r--r--src/main/java/cuchaz/enigma/mapping/MethodMapping.java210
-rw-r--r--src/main/java/cuchaz/enigma/mapping/Translator.java109
-rw-r--r--src/main/java/cuchaz/enigma/mapping/entry/ClassDefEntry.java38
-rw-r--r--src/main/java/cuchaz/enigma/mapping/entry/ClassEntry.java175
-rw-r--r--src/main/java/cuchaz/enigma/mapping/entry/DefEntry.java7
-rw-r--r--src/main/java/cuchaz/enigma/mapping/entry/Entry.java22
-rw-r--r--src/main/java/cuchaz/enigma/mapping/entry/EntryFactory.java49
-rw-r--r--src/main/java/cuchaz/enigma/mapping/entry/FieldDefEntry.java44
-rw-r--r--src/main/java/cuchaz/enigma/mapping/entry/FieldEntry.java77
-rw-r--r--src/main/java/cuchaz/enigma/mapping/entry/LocalVariableDefEntry.java61
-rw-r--r--src/main/java/cuchaz/enigma/mapping/entry/LocalVariableEntry.java93
-rw-r--r--src/main/java/cuchaz/enigma/mapping/entry/MethodEntry.java80
-rw-r--r--src/main/java/cuchaz/enigma/translation/MappingTranslator.java24
-rw-r--r--src/main/java/cuchaz/enigma/translation/SignatureUpdater.java (renamed from src/main/java/cuchaz/enigma/mapping/SignatureUpdater.java)2
-rw-r--r--src/main/java/cuchaz/enigma/translation/Translatable.java9
-rw-r--r--src/main/java/cuchaz/enigma/translation/TranslationDirection.java (renamed from src/main/java/cuchaz/enigma/mapping/TranslationDirection.java)2
-rw-r--r--src/main/java/cuchaz/enigma/translation/Translator.java54
-rw-r--r--src/main/java/cuchaz/enigma/translation/VoidTranslator.java10
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/AccessModifier.java25
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/EntryMap.java24
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java30
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java201
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/EntryResolver.java41
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/IndexEntryResolver.java225
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/MappingDelta.java56
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/MappingPair.java28
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java45
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java91
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/NameValidator.java (renamed from src/main/java/cuchaz/enigma/mapping/NameValidator.java)41
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/ResolutionStrategy.java6
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/VoidEntryResolver.java27
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsReader.java260
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsWriter.java260
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java54
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsReader.java12
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsWriter.java12
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/SrgMappingsWriter.java115
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsReader.java100
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/tree/DeltaTrackingTree.java113
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTree.java20
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTreeNode.java36
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java159
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/tree/HashTreeNode.java72
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java (renamed from src/main/java/cuchaz/enigma/bytecode/AccessFlags.java)9
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java (renamed from src/main/java/cuchaz/enigma/mapping/MethodDescriptor.java)24
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/ProcyonEntryFactory.java (renamed from src/main/java/cuchaz/enigma/mapping/entry/ProcyonEntryFactory.java)7
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/ReferencedEntryPool.java (renamed from src/main/java/cuchaz/enigma/mapping/entry/ReferencedEntryPool.java)11
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/Signature.java (renamed from src/main/java/cuchaz/enigma/mapping/Signature.java)15
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java (renamed from src/main/java/cuchaz/enigma/mapping/TypeDescriptor.java)20
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java92
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java180
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/entry/DefEntry.java7
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java99
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java61
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java86
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java45
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java92
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java (renamed from src/main/java/cuchaz/enigma/mapping/entry/MethodDefEntry.java)34
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java95
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java71
124 files changed, 4484 insertions, 5716 deletions
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 @@
11 11
12package cuchaz.enigma; 12package cuchaz.enigma;
13 13
14import cuchaz.enigma.Deobfuscator.ProgressListener; 14import cuchaz.enigma.translation.mapping.EntryMapping;
15import cuchaz.enigma.mapping.Mappings; 15import cuchaz.enigma.translation.mapping.serde.MappingFormat;
16import cuchaz.enigma.mapping.MappingsEnigmaReader; 16import cuchaz.enigma.translation.mapping.tree.EntryTree;
17 17
18import java.io.File; 18import java.io.File;
19import java.nio.file.Files;
20import java.nio.file.Path;
21import java.nio.file.Paths;
22import java.util.Locale;
19import java.util.jar.JarFile; 23import java.util.jar.JarFile;
20 24
21public class CommandMain { 25public class CommandMain {
@@ -52,7 +56,7 @@ public class CommandMain {
52 private static void decompile(String[] args) throws Exception { 56 private static void decompile(String[] args) throws Exception {
53 File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true)); 57 File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true));
54 File fileJarOut = getWritableFolder(getArg(args, 2, "out folder", true)); 58 File fileJarOut = getWritableFolder(getArg(args, 2, "out folder", true));
55 File fileMappings = getReadableFile(getArg(args, 3, "mappings file", false)); 59 Path fileMappings = getReadablePath(getArg(args, 3, "mappings file", false));
56 Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn)); 60 Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn));
57 deobfuscator.writeSources(fileJarOut, new ConsoleProgressListener()); 61 deobfuscator.writeSources(fileJarOut, new ConsoleProgressListener());
58 } 62 }
@@ -60,43 +64,47 @@ public class CommandMain {
60 private static void deobfuscate(String[] args) throws Exception { 64 private static void deobfuscate(String[] args) throws Exception {
61 File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true)); 65 File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true));
62 File fileJarOut = getWritableFile(getArg(args, 2, "out jar", true)); 66 File fileJarOut = getWritableFile(getArg(args, 2, "out jar", true));
63 File fileMappings = getReadableFile(getArg(args, 3, "mappings file", false)); 67 Path fileMappings = getReadablePath(getArg(args, 3, "mappings file", false));
64 Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn)); 68 Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn));
65 deobfuscator.writeJar(fileJarOut, new ConsoleProgressListener()); 69 deobfuscator.writeJar(fileJarOut, new ConsoleProgressListener());
66 } 70 }
67 71
68 private static Deobfuscator getDeobfuscator(File fileMappings, JarFile jar) throws Exception { 72 private static Deobfuscator getDeobfuscator(Path fileMappings, JarFile jar) throws Exception {
69 System.out.println("Reading jar..."); 73 System.out.println("Reading jar...");
70 Deobfuscator deobfuscator = new Deobfuscator(jar); 74 Deobfuscator deobfuscator = new Deobfuscator(jar);
71 if (fileMappings != null) { 75 if (fileMappings != null) {
72 System.out.println("Reading mappings..."); 76 System.out.println("Reading mappings...");
73 Mappings mappings = new MappingsEnigmaReader().read(fileMappings); 77 EntryTree<EntryMapping> mappings = chooseEnigmaFormat(fileMappings).read(fileMappings);
74 deobfuscator.setMappings(mappings); 78 deobfuscator.setMappings(mappings);
75 } 79 }
76 return deobfuscator; 80 return deobfuscator;
77 } 81 }
78 82
79 private static void convertMappings(String[] args) throws Exception { 83 private static void convertMappings(String[] args) throws Exception {
80 File fileMappings = getReadableFile(getArg(args, 1, "enigma mapping", true)); 84 Path fileMappings = getReadablePath(getArg(args, 1, "enigma mapping", true));
81 File result = getWritableFile(getArg(args, 2, "enigma mapping", true)); 85 File result = getWritableFile(getArg(args, 2, "enigma mapping", true));
82 String name = getArg(args, 3, "format desc", true); 86 String name = getArg(args, 3, "format desc", true);
83 Mappings.FormatType formatType; 87 MappingFormat saveFormat;
84 try { 88 try {
85 formatType = Mappings.FormatType.valueOf(name.toUpperCase()); 89 saveFormat = MappingFormat.valueOf(name.toUpperCase(Locale.ROOT));
86 } catch (IllegalArgumentException e) { 90 } catch (IllegalArgumentException e) {
87 throw new IllegalArgumentException(name + "is not a valid mapping format!"); 91 throw new IllegalArgumentException(name + "is not a valid mapping format!");
88 } 92 }
89 93
90 System.out.println("Reading mappings..."); 94 System.out.println("Reading mappings...");
91 Mappings mappings = new MappingsEnigmaReader().read(fileMappings); 95
96 MappingFormat readFormat = chooseEnigmaFormat(fileMappings);
97 EntryTree<EntryMapping> mappings = readFormat.read(fileMappings);
92 System.out.println("Saving new mappings..."); 98 System.out.println("Saving new mappings...");
93 switch (formatType) { 99
94 case SRG_FILE: 100 saveFormat.write(mappings, result.toPath(), new ConsoleProgressListener());
95 mappings.saveSRGMappings(result); 101 }
96 break; 102
97 default: 103 private static MappingFormat chooseEnigmaFormat(Path path) {
98 mappings.saveEnigmaMappings(result, Mappings.FormatType.ENIGMA_FILE != formatType); 104 if (Files.isDirectory(path)) {
99 break; 105 return MappingFormat.ENIGMA_DIRECTORY;
106 } else {
107 return MappingFormat.ENIGMA_FILE;
100 } 108 }
101 } 109 }
102 110
@@ -149,6 +157,17 @@ public class CommandMain {
149 return file; 157 return file;
150 } 158 }
151 159
160 private static Path getReadablePath(String path) {
161 if (path == null) {
162 return null;
163 }
164 Path file = Paths.get(path).toAbsolutePath();
165 if (!Files.exists(file)) {
166 throw new IllegalArgumentException("Cannot find file: " + file.toString());
167 }
168 return file;
169 }
170
152 public static class ConsoleProgressListener implements ProgressListener { 171 public static class ConsoleProgressListener implements ProgressListener {
153 172
154 private static final int ReportTime = 5000; // 5s 173 private static final int ReportTime = 5000; // 5s
@@ -166,7 +185,7 @@ public class CommandMain {
166 } 185 }
167 186
168 @Override 187 @Override
169 public void onProgress(int numDone, String message) { 188 public void step(int numDone, String message) {
170 long now = System.currentTimeMillis(); 189 long now = System.currentTimeMillis();
171 boolean isLastUpdate = numDone == this.totalWork; 190 boolean isLastUpdate = numDone == this.totalWork;
172 boolean shouldReport = isLastUpdate || now - this.lastReportTime > ReportTime; 191 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;
13 13
14import com.google.common.base.Stopwatch; 14import com.google.common.base.Stopwatch;
15import com.google.common.collect.Lists; 15import com.google.common.collect.Lists;
16import com.google.common.collect.Maps;
17import com.google.common.collect.Sets;
18import com.strobel.assembler.metadata.ITypeLoader; 16import com.strobel.assembler.metadata.ITypeLoader;
19import com.strobel.assembler.metadata.MetadataSystem; 17import com.strobel.assembler.metadata.MetadataSystem;
20import com.strobel.assembler.metadata.TypeDefinition; 18import com.strobel.assembler.metadata.TypeDefinition;
@@ -28,16 +26,17 @@ import com.strobel.decompiler.languages.java.ast.CompilationUnit;
28import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor; 26import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor;
29import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; 27import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform;
30import cuchaz.enigma.analysis.*; 28import cuchaz.enigma.analysis.*;
29import cuchaz.enigma.analysis.index.JarIndex;
31import cuchaz.enigma.api.EnigmaPlugin; 30import cuchaz.enigma.api.EnigmaPlugin;
32import cuchaz.enigma.mapping.*; 31import cuchaz.enigma.translation.mapping.*;
33import cuchaz.enigma.mapping.entry.*; 32import cuchaz.enigma.translation.mapping.tree.DeltaTrackingTree;
34import cuchaz.enigma.throwables.IllegalNameException; 33import cuchaz.enigma.translation.mapping.tree.EntryTree;
34import cuchaz.enigma.translation.representation.ReferencedEntryPool;
35import cuchaz.enigma.translation.representation.entry.ClassEntry;
36import cuchaz.enigma.translation.representation.entry.Entry;
37import cuchaz.enigma.translation.representation.entry.MethodEntry;
35import cuchaz.enigma.utils.Utils; 38import cuchaz.enigma.utils.Utils;
36import oml.ast.transformers.InvalidIdentifierFix; 39import oml.ast.transformers.*;
37import oml.ast.transformers.Java8Generics;
38import oml.ast.transformers.ObfuscatedEnumSwitchRewriterTransform;
39import oml.ast.transformers.RemoveObjectCasts;
40import oml.ast.transformers.VarargsFixer;
41import org.objectweb.asm.ClassWriter; 40import org.objectweb.asm.ClassWriter;
42import org.objectweb.asm.tree.ClassNode; 41import org.objectweb.asm.tree.ClassNode;
43 42
@@ -49,6 +48,7 @@ import java.util.function.Consumer;
49import java.util.jar.JarEntry; 48import java.util.jar.JarEntry;
50import java.util.jar.JarFile; 49import java.util.jar.JarFile;
51import java.util.jar.JarOutputStream; 50import java.util.jar.JarOutputStream;
51import java.util.stream.Collectors;
52 52
53public class Deobfuscator { 53public class Deobfuscator {
54 54
@@ -57,24 +57,24 @@ public class Deobfuscator {
57 private final ParsedJar parsedJar; 57 private final ParsedJar parsedJar;
58 private final DecompilerSettings settings; 58 private final DecompilerSettings settings;
59 private final JarIndex jarIndex; 59 private final JarIndex jarIndex;
60 private final MappingsRenamer renamer; 60 private final IndexTreeBuilder indexTreeBuilder;
61 private final Map<TranslationDirection, Translator> translatorCache; 61 private EntryRemapper mapper;
62 private Mappings mappings;
63 62
64 public Deobfuscator(ParsedJar jar, Consumer<String> listener) { 63 public Deobfuscator(ParsedJar jar, Consumer<String> listener) {
65 this.parsedJar = jar; 64 this.parsedJar = jar;
66 65
67 // build the jar index 66 // build the jar index
68 listener.accept("Indexing JAR..."); 67 this.jarIndex = JarIndex.empty();
69 this.jarIndex = new JarIndex(entryPool); 68 this.jarIndex.indexJar(this.parsedJar, listener);
70 this.jarIndex.indexJar(this.parsedJar, true);
71 69
72 listener.accept("Initializing plugins..."); 70 listener.accept("Initializing plugins...");
73 for (EnigmaPlugin plugin : getPlugins()) { 71 for (EnigmaPlugin plugin : getPlugins()) {
74 plugin.onClassesLoaded(parsedJar.getClassDataMap(), parsedJar::getClassNode); 72 plugin.onClassesLoaded(parsedJar.getClassDataMap(), parsedJar::getClassNode);
75 } 73 }
76 74
77 listener.accept("Preparing..."); 75 this.indexTreeBuilder = new IndexTreeBuilder(jarIndex);
76
77 listener.accept("Preparing...");
78 // config the decompiler 78 // config the decompiler
79 this.settings = DecompilerSettings.javaDefaults(); 79 this.settings = DecompilerSettings.javaDefaults();
80 this.settings.setMergeVariables(Utils.getSystemPropertyAsBoolean("enigma.mergeVariables", true)); 80 this.settings.setMergeVariables(Utils.getSystemPropertyAsBoolean("enigma.mergeVariables", true));
@@ -85,11 +85,8 @@ public class Deobfuscator {
85 this.settings.setShowDebugLineNumbers(Utils.getSystemPropertyAsBoolean("enigma.showDebugLineNumbers", false)); 85 this.settings.setShowDebugLineNumbers(Utils.getSystemPropertyAsBoolean("enigma.showDebugLineNumbers", false));
86 this.settings.setShowSyntheticMembers(Utils.getSystemPropertyAsBoolean("enigma.showSyntheticMembers", false)); 86 this.settings.setShowSyntheticMembers(Utils.getSystemPropertyAsBoolean("enigma.showSyntheticMembers", false));
87 87
88 // init defaults
89 this.translatorCache = Maps.newTreeMap();
90 this.renamer = new MappingsRenamer(this.jarIndex, null, this.entryPool);
91 // init mappings 88 // init mappings
92 setMappings(new Mappings()); 89 mapper = new EntryRemapper(jarIndex);
93 } 90 }
94 91
95 public Deobfuscator(JarFile jar, Consumer<String> listener) throws IOException { 92 public Deobfuscator(JarFile jar, Consumer<String> listener) throws IOException {
@@ -97,12 +94,14 @@ public class Deobfuscator {
97 } 94 }
98 95
99 public Deobfuscator(ParsedJar jar) throws IOException { 96 public Deobfuscator(ParsedJar jar) throws IOException {
100 this(jar, (msg) -> {}); 97 this(jar, (msg) -> {
101 } 98 });
99 }
102 100
103 public Deobfuscator(JarFile jar) throws IOException { 101 public Deobfuscator(JarFile jar) throws IOException {
104 this(jar, (msg) -> {}); 102 this(jar, (msg) -> {
105 } 103 });
104 }
106 105
107 public ServiceLoader<EnigmaPlugin> getPlugins() { 106 public ServiceLoader<EnigmaPlugin> getPlugins() {
108 return plugins; 107 return plugins;
@@ -116,56 +115,50 @@ public class Deobfuscator {
116 return this.jarIndex; 115 return this.jarIndex;
117 } 116 }
118 117
119 public Mappings getMappings() { 118 public IndexTreeBuilder getIndexTreeBuilder() {
120 return this.mappings; 119 return indexTreeBuilder;
121 } 120 }
122 121
123 public void setMappings(Mappings val) { 122 public EntryRemapper getMapper() {
124 setMappings(val, true); 123 return this.mapper;
125 } 124 }
126 125
127 public void setMappings(Mappings val, boolean warnAboutDrops) { 126 public void setMappings(EntryTree<EntryMapping> mappings) {
128 if (val == null) { 127 if (mappings != null) {
129 val = new Mappings(); 128 Collection<Entry<?>> dropped = dropMappings(mappings);
130 } 129 mapper = new EntryRemapper(jarIndex, mappings);
131 130
132 // drop mappings that don't match the jar 131 DeltaTrackingTree<EntryMapping> deobfToObf = mapper.getDeobfToObf();
133 MappingsChecker checker = new MappingsChecker(this.jarIndex); 132 for (Entry<?> entry : dropped) {
134 checker.dropBrokenMappings(val); 133 deobfToObf.trackDeletion(entry);
135 if (warnAboutDrops) {
136 for (Map.Entry<ClassEntry, ClassMapping> mapping : checker.getDroppedClassMappings().entrySet()) {
137 System.out.println("WARNING: Couldn't find class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped.");
138 }
139 for (Map.Entry<ClassEntry, ClassMapping> mapping : checker.getDroppedInnerClassMappings().entrySet()) {
140 System.out.println("WARNING: Couldn't find inner class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped.");
141 }
142 for (Map.Entry<FieldEntry, FieldMapping> mapping : checker.getDroppedFieldMappings().entrySet()) {
143 System.out.println("WARNING: Couldn't find field entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped.");
144 }
145 for (Map.Entry<MethodEntry, MethodMapping> mapping : checker.getDroppedMethodMappings().entrySet()) {
146 System.out.println("WARNING: Couldn't find behavior entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped.");
147 } 134 }
135 } else {
136 mapper = new EntryRemapper(jarIndex);
148 } 137 }
149
150 this.mappings = val;
151 this.renamer.setMappings(mappings);
152 this.translatorCache.clear();
153 } 138 }
154 139
155 public Translator getTranslator(TranslationDirection direction) { 140 private Collection<Entry<?>> dropMappings(EntryTree<EntryMapping> mappings) {
156 return this.translatorCache.computeIfAbsent(direction, 141 // drop mappings that don't match the jar
157 k -> this.mappings.getTranslator(direction, this.jarIndex.getTranslationIndex())); 142 MappingsChecker checker = new MappingsChecker(jarIndex, mappings);
143 MappingsChecker.Dropped dropped = checker.dropBrokenMappings();
144
145 Map<Entry<?>, String> droppedMappings = dropped.getDroppedMappings();
146 for (Map.Entry<Entry<?>, String> mapping : droppedMappings.entrySet()) {
147 System.out.println("WARNING: Couldn't find " + mapping.getKey() + " (" + mapping.getValue() + ") in jar. Mapping was dropped.");
148 }
149
150 return droppedMappings.keySet();
158 } 151 }
159 152
160 public void getSeparatedClasses(List<ClassEntry> obfClasses, List<ClassEntry> deobfClasses) { 153 public void getSeparatedClasses(List<ClassEntry> obfClasses, List<ClassEntry> deobfClasses) {
161 for (ClassEntry obfClassEntry : this.jarIndex.getObfClassEntries()) { 154 for (ClassEntry obfClassEntry : this.jarIndex.getEntryIndex().getClasses()) {
162 // skip inner classes 155 // skip inner classes
163 if (obfClassEntry.isInnerClass()) { 156 if (obfClassEntry.isInnerClass()) {
164 continue; 157 continue;
165 } 158 }
166 159
167 // separate the classes 160 // separate the classes
168 ClassEntry deobfClassEntry = deobfuscateEntry(obfClassEntry); 161 ClassEntry deobfClassEntry = mapper.deobfuscate(obfClassEntry);
169 if (!deobfClassEntry.equals(obfClassEntry)) { 162 if (!deobfClassEntry.equals(obfClassEntry)) {
170 // if the class has a mapping, clearly it's deobfuscated 163 // if the class has a mapping, clearly it's deobfuscated
171 deobfClasses.add(deobfClassEntry); 164 deobfClasses.add(deobfClassEntry);
@@ -184,8 +177,8 @@ public class Deobfuscator {
184 this.parsedJar, 177 this.parsedJar,
185 this.jarIndex, 178 this.jarIndex,
186 this.entryPool, 179 this.entryPool,
187 getTranslator(TranslationDirection.OBFUSCATING), 180 this.mapper.getObfuscator(),
188 getTranslator(TranslationDirection.DEOBFUSCATING) 181 this.mapper.getDeobfuscator()
189 ); 182 );
190 } 183 }
191 184
@@ -203,14 +196,7 @@ public class Deobfuscator {
203 // we need to tell the decompiler the deobfuscated name so it doesn't get freaked out 196 // we need to tell the decompiler the deobfuscated name so it doesn't get freaked out
204 // the decompiler only sees classes after deobfuscation, so we need to load it by the deobfuscated name if there is one 197 // the decompiler only sees classes after deobfuscation, so we need to load it by the deobfuscated name if there is one
205 198
206 // first, assume class name is deobf 199 String deobfClassName = mapper.deobfuscate(new ClassEntry(className)).getFullName();
207 String deobfClassName = className;
208
209 // if it wasn't actually deobf, then we can find a mapping for it and get the deobf name
210 ClassMapping classMapping = this.mappings.getClassByObf(className);
211 if (classMapping != null && classMapping.getDeobfName() != null) {
212 deobfClassName = classMapping.getDeobfName();
213 }
214 200
215 // set the desc loader 201 // set the desc loader
216 this.settings.setTypeLoader(loader); 202 this.settings.setTypeLoader(loader);
@@ -236,43 +222,23 @@ public class Deobfuscator {
236 } 222 }
237 223
238 public SourceIndex getSourceIndex(CompilationUnit sourceTree, String source) { 224 public SourceIndex getSourceIndex(CompilationUnit sourceTree, String source) {
239 return getSourceIndex(sourceTree, source, null); 225 return getSourceIndex(sourceTree, source, true);
240 } 226 }
241 227
242 public SourceIndex getSourceIndex(CompilationUnit sourceTree, String source, Boolean ignoreBadTokens) { 228 public SourceIndex getSourceIndex(CompilationUnit sourceTree, String source, boolean ignoreBadTokens) {
243 229
244 // build the source index 230 // build the source index
245 SourceIndex index; 231 SourceIndex index = new SourceIndex(source, ignoreBadTokens);
246 if (ignoreBadTokens != null) {
247 index = new SourceIndex(source, ignoreBadTokens);
248 } else {
249 index = new SourceIndex(source);
250 }
251 sourceTree.acceptVisitor(new SourceIndexVisitor(entryPool), index); 232 sourceTree.acceptVisitor(new SourceIndexVisitor(entryPool), index);
252 233
253 // DEBUG 234 EntryResolver resolver = mapper.getDeobfResolver();
254 // sourceTree.acceptVisitor( new TreeDumpVisitor( new File( "tree.txt" ) ), null );
255
256 // resolve all the classes in the source references
257 for (Token token : index.referenceTokens()) {
258 EntryReference<Entry, Entry> deobfReference = index.getDeobfReference(token);
259
260 // get the obfuscated entry
261 Entry obfEntry = obfuscateEntry(deobfReference.entry);
262 235
263 // try to resolve the class 236 Collection<Token> tokens = Lists.newArrayList(index.referenceTokens());
264 ClassEntry resolvedObfClassEntry = this.jarIndex.getTranslationIndex().resolveEntryOwner(obfEntry);
265 if (resolvedObfClassEntry != null && !resolvedObfClassEntry.equals(obfEntry.getOwnerClassEntry())) {
266 // change the class of the entry
267 obfEntry = obfEntry.updateOwnership(resolvedObfClassEntry);
268
269 // save the new deobfuscated reference
270 deobfReference.entry = deobfuscateEntry(obfEntry);
271 index.replaceDeobfReference(token, deobfReference);
272 }
273 237
274 // DEBUG 238 // resolve all the classes in the source references
275 // System.out.println( token + " -> " + reference + " -> " + index.getReferenceToken( reference ) ); 239 for (Token token : tokens) {
240 EntryReference<Entry<?>, Entry<?>> deobfReference = index.getDeobfReference(token);
241 index.replaceDeobfReference(token, resolver.resolveFirstReference(deobfReference, ResolutionStrategy.RESOLVE_CLOSEST));
276 } 242 }
277 243
278 return index; 244 return index;
@@ -288,15 +254,9 @@ public class Deobfuscator {
288 254
289 public void writeSources(File dirOut, ProgressListener progress) { 255 public void writeSources(File dirOut, ProgressListener progress) {
290 // get the classes to decompile 256 // get the classes to decompile
291 Set<ClassEntry> classEntries = Sets.newHashSet(); 257 Set<ClassEntry> classEntries = jarIndex.getEntryIndex().getClasses().stream()
292 for (ClassEntry obfClassEntry : this.jarIndex.getObfClassEntries()) { 258 .filter(classEntry -> !classEntry.isInnerClass())
293 // skip inner classes 259 .collect(Collectors.toSet());
294 if (obfClassEntry.isInnerClass()) {
295 continue;
296 }
297
298 classEntries.add(obfClassEntry);
299 }
300 260
301 if (progress != null) { 261 if (progress != null) {
302 progress.init(classEntries.size(), "Decompiling classes..."); 262 progress.init(classEntries.size(), "Decompiling classes...");
@@ -313,9 +273,9 @@ public class Deobfuscator {
313 Stopwatch stopwatch = Stopwatch.createStarted(); 273 Stopwatch stopwatch = Stopwatch.createStarted();
314 AtomicInteger count = new AtomicInteger(); 274 AtomicInteger count = new AtomicInteger();
315 classEntries.parallelStream().forEach(obfClassEntry -> { 275 classEntries.parallelStream().forEach(obfClassEntry -> {
316 ClassEntry deobfClassEntry = deobfuscateEntry(new ClassEntry(obfClassEntry)); 276 ClassEntry deobfClassEntry = mapper.deobfuscate(obfClassEntry);
317 if (progress != null) { 277 if (progress != null) {
318 progress.onProgress(count.getAndIncrement(), deobfClassEntry.toString()); 278 progress.step(count.getAndIncrement(), deobfClassEntry.toString());
319 } 279 }
320 280
321 try { 281 try {
@@ -332,131 +292,17 @@ public class Deobfuscator {
332 } catch (Throwable t) { 292 } catch (Throwable t) {
333 // don't crash the whole world here, just log the error and keep going 293 // don't crash the whole world here, just log the error and keep going
334 // TODO: set up logback via log4j 294 // TODO: set up logback via log4j
335 System.err.println("Unable to deobfuscate class " + deobfClassEntry + " (" + obfClassEntry + ")"); 295 System.err.println("Unable to decompile class " + deobfClassEntry + " (" + obfClassEntry + ")");
336 t.printStackTrace(System.err); 296 t.printStackTrace(System.err);
337 } 297 }
338 }); 298 });
339 stopwatch.stop(); 299 stopwatch.stop();
340 System.out.println("writeSources Done in : " + stopwatch.toString()); 300 System.out.println("writeSources Done in : " + stopwatch.toString());
341 if (progress != null) { 301 if (progress != null) {
342 progress.onProgress(count.get(), "Done:"); 302 progress.step(count.get(), "Done:");
343 }
344 }
345
346 private void addAllPotentialAncestors(Set<ClassEntry> classEntries, ClassEntry classObfEntry) {
347 for (ClassEntry interfaceEntry : jarIndex.getTranslationIndex().getInterfaces(classObfEntry)) {
348 if (classEntries.add(interfaceEntry)) {
349 addAllPotentialAncestors(classEntries, interfaceEntry);
350 }
351 }
352
353 ClassEntry superClassEntry = jarIndex.getTranslationIndex().getSuperclass(classObfEntry);
354 if (superClassEntry != null && classEntries.add(superClassEntry)) {
355 addAllPotentialAncestors(classEntries, superClassEntry);
356 }
357 }
358
359 public boolean isMethodProvider(MethodEntry methodEntry) {
360 Set<ClassEntry> classEntries = new HashSet<>();
361 addAllPotentialAncestors(classEntries, methodEntry.getOwnerClassEntry());
362
363 for (ClassEntry parentEntry : classEntries) {
364 MethodEntry ancestorMethodEntry = entryPool.getMethod(parentEntry, methodEntry.getName(), methodEntry.getDesc());
365 if (jarIndex.containsObfMethod(ancestorMethodEntry)) {
366 return false;
367 }
368 }
369
370 return true;
371 }
372
373 @Deprecated
374 public boolean isMethodProvider(ClassEntry classObfEntry, MethodEntry methodEntry) {
375 Set<ClassEntry> classEntries = new HashSet<>();
376 addAllPotentialAncestors(classEntries, classObfEntry);
377
378 for (ClassEntry parentEntry : classEntries) {
379 MethodEntry ancestorMethodEntry = entryPool.getMethod(parentEntry, methodEntry.getName(), methodEntry.getDesc());
380 if (jarIndex.containsObfMethod(ancestorMethodEntry)) {
381 return false;
382 }
383 }
384
385 return true;
386 }
387
388 public void rebuildMethodNames(ProgressListener progress) {
389 final AtomicInteger i = new AtomicInteger();
390 Map<ClassMapping, Map<Entry, String>> renameClassMap = new ConcurrentHashMap<>();
391
392 progress.init(getMappings().classes().size() * 3, "Rebuilding method names");
393
394 Lists.newArrayList(getMappings().classes()).parallelStream().forEach(classMapping -> {
395 progress.onProgress(i.getAndIncrement(), classMapping.getDeobfName());
396 rebuildMethodNames(classMapping, renameClassMap);
397 });
398
399
400 renameClassMap.entrySet().stream().forEach(renameClassMapEntry -> {
401 progress.onProgress(i.getAndIncrement(), renameClassMapEntry.getKey().getDeobfName());
402 for (Map.Entry<Entry, String> entry : renameClassMapEntry.getValue().entrySet()) {
403 Entry obfEntry = entry.getKey();
404
405 removeMapping(obfEntry, false);
406 }
407 });
408
409 translatorCache.clear();
410
411 renameClassMap.entrySet().stream().forEach(renameClassMapEntry -> {
412 progress.onProgress(i.getAndIncrement(), renameClassMapEntry.getKey().getDeobfName());
413
414 for (Map.Entry<Entry, String> entry : renameClassMapEntry.getValue().entrySet()) {
415 Entry obfEntry = entry.getKey();
416 String name = entry.getValue();
417
418 if (name != null) {
419 try {
420 rename(obfEntry, name);
421 } catch (IllegalNameException exception) {
422 System.out.println("WARNING: " + exception.getMessage());
423 }
424 }
425 }
426 });
427 }
428
429 private void rebuildMethodNames(ClassMapping classMapping, Map<ClassMapping, Map<Entry, String>> renameClassMap) {
430 Map<Entry, String> renameEntries = new HashMap<>();
431
432 for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) {
433 ClassEntry classObfEntry = classMapping.getObfEntry();
434 MethodEntry obfEntry = methodMapping.getObfEntry(classObfEntry);
435 boolean isProvider = isMethodProvider(obfEntry);
436
437 if (hasDeobfuscatedName(obfEntry)
438 && !(methodMapping.getDeobfName().equals(methodMapping.getObfName()))) {
439 renameEntries.put(obfEntry, isProvider ? methodMapping.getDeobfName() : null);
440 }
441
442 if (isProvider) {
443 for (LocalVariableMapping localVariableMapping : methodMapping.arguments()) {
444 Entry argObfEntry = localVariableMapping.getObfEntry(obfEntry);
445 if (hasDeobfuscatedName(argObfEntry)) {
446 renameEntries.put(argObfEntry, deobfuscateEntry(argObfEntry).getName());
447 }
448 }
449 }
450 }
451
452 classMapping.markDirty();
453 renameClassMap.put(classMapping, renameEntries);
454 for (ClassMapping innerClass : classMapping.innerClasses()) {
455 rebuildMethodNames(innerClass, renameClassMap);
456 } 303 }
457 } 304 }
458 305
459
460 public void writeJar(File out, ProgressListener progress) { 306 public void writeJar(File out, ProgressListener progress) {
461 transformJar(out, progress, createTypeLoader()::transformInto); 307 transformJar(out, progress, createTypeLoader()::transformInto);
462 } 308 }
@@ -470,7 +316,7 @@ public class Deobfuscator {
470 AtomicInteger i = new AtomicInteger(); 316 AtomicInteger i = new AtomicInteger();
471 parsedJar.visitNode(node -> { 317 parsedJar.visitNode(node -> {
472 if (progress != null) { 318 if (progress != null) {
473 progress.onProgress(i.getAndIncrement(), node.name); 319 progress.step(i.getAndIncrement(), node.name);
474 } 320 }
475 321
476 try { 322 try {
@@ -485,50 +331,31 @@ public class Deobfuscator {
485 }); 331 });
486 332
487 if (progress != null) { 333 if (progress != null) {
488 progress.onProgress(i.get(), "Done!"); 334 progress.step(i.get(), "Done!");
489 } 335 }
490 } catch (IOException ex) { 336 } catch (IOException ex) {
491 throw new Error("Unable to write to Jar file!"); 337 throw new Error("Unable to write to Jar file!");
492 } 338 }
493 } 339 }
494 340
495 public <T extends Entry> T obfuscateEntry(T deobfEntry) { 341 public AccessModifier getModifier(Entry<?> entry) {
496 if (deobfEntry == null) { 342 EntryMapping mapping = mapper.getDeobfMapping(entry);
497 return null; 343 if (mapping == null) {
498 } 344 return AccessModifier.UNCHANGED;
499 T translatedEntry = getTranslator(TranslationDirection.OBFUSCATING).getTranslatedEntry(deobfEntry);
500 if (translatedEntry == null) {
501 return deobfEntry;
502 }
503 return translatedEntry;
504 }
505
506 public <T extends Entry> T deobfuscateEntry(T obfEntry) {
507 if (obfEntry == null) {
508 return null;
509 }
510 T translatedEntry = getTranslator(TranslationDirection.DEOBFUSCATING).getTranslatedEntry(obfEntry);
511 if (translatedEntry == null) {
512 return obfEntry;
513 }
514 return translatedEntry;
515 }
516
517 public <E extends Entry, C extends Entry> EntryReference<E, C> obfuscateReference(EntryReference<E, C> deobfReference) {
518 if (deobfReference == null) {
519 return null;
520 } 345 }
521 return new EntryReference<>(obfuscateEntry(deobfReference.entry), obfuscateEntry(deobfReference.context), deobfReference); 346 return mapping.getAccessModifier();
522 } 347 }
523 348
524 public <E extends Entry, C extends Entry> EntryReference<E, C> deobfuscateReference(EntryReference<E, C> obfReference) { 349 public void changeModifier(Entry<?> entry, AccessModifier modifier) {
525 if (obfReference == null) { 350 EntryMapping mapping = mapper.getDeobfMapping(entry);
526 return null; 351 if (mapping != null) {
352 mapper.mapFromObf(entry, new EntryMapping(mapping.getTargetName(), modifier));
353 } else {
354 mapper.mapFromObf(entry, new EntryMapping(entry.getName(), modifier));
527 } 355 }
528 return new EntryReference<>(deobfuscateEntry(obfReference.entry), deobfuscateEntry(obfReference.context), obfReference);
529 } 356 }
530 357
531 public boolean isObfuscatedIdentifier(Entry obfEntry) { 358 public boolean isObfuscatedIdentifier(Entry<?> obfEntry) {
532 if (obfEntry instanceof MethodEntry) { 359 if (obfEntry instanceof MethodEntry) {
533 // HACKHACK: Object methods are not obfuscated identifiers 360 // HACKHACK: Object methods are not obfuscated identifiers
534 MethodEntry obfMethodEntry = (MethodEntry) obfEntry; 361 MethodEntry obfMethodEntry = (MethodEntry) obfEntry;
@@ -559,142 +386,30 @@ public class Deobfuscator {
559 } 386 }
560 } 387 }
561 388
562 return this.jarIndex.containsObfEntry(obfEntry); 389 return this.jarIndex.getEntryIndex().hasEntry(obfEntry);
563 } 390 }
564 391
565 public boolean isRenameable(EntryReference<Entry, Entry> obfReference) { 392 public boolean isRenameable(EntryReference<Entry<?>, Entry<?>> obfReference) {
566 return obfReference.isNamed() && isObfuscatedIdentifier(obfReference.getNameableEntry()); 393 return obfReference.isNamed() && isObfuscatedIdentifier(obfReference.getNameableEntry());
567 } 394 }
568 395
569 public boolean hasDeobfuscatedName(Entry obfEntry) { 396 public boolean hasDeobfuscatedName(Entry<?> obfEntry) {
570 Translator translator = getTranslator(TranslationDirection.DEOBFUSCATING); 397 return mapper.hasDeobfMapping(obfEntry);
571 if (obfEntry instanceof ClassEntry) {
572 ClassEntry obfClass = (ClassEntry) obfEntry;
573 List<ClassMapping> mappingChain = this.mappings.getClassMappingChain(obfClass);
574 ClassMapping classMapping = mappingChain.get(mappingChain.size() - 1);
575 return classMapping != null && classMapping.getDeobfName() != null;
576 } else if (obfEntry instanceof FieldEntry) {
577 return translator.hasFieldMapping((FieldEntry) obfEntry);
578 } else if (obfEntry instanceof MethodEntry) {
579 MethodEntry methodEntry = (MethodEntry) obfEntry;
580 if (methodEntry.isConstructor()) {
581 return false;
582 }
583 return translator.hasMethodMapping(methodEntry);
584 } else if (obfEntry instanceof LocalVariableEntry) {
585 return translator.hasLocalVariableMapping((LocalVariableEntry) obfEntry);
586 } else {
587 throw new Error("Unknown entry desc: " + obfEntry.getClass().getName());
588 }
589 } 398 }
590 399
591 public void rename(Entry obfEntry, String newName) { 400 public void rename(Entry<?> obfEntry, String newName) {
592 rename(obfEntry, newName, true); 401 mapper.mapFromObf(obfEntry, new EntryMapping(newName));
593 } 402 }
594 403
595 // NOTE: these methods are a bit messy... oh well 404 public void removeMapping(Entry<?> obfEntry) {
596 405 mapper.removeByObf(obfEntry);
597 public void rename(Entry obfEntry, String newName, boolean clearCache) {
598 if (obfEntry instanceof ClassEntry) {
599 this.renamer.setClassName((ClassEntry) obfEntry, newName);
600 } else if (obfEntry instanceof FieldEntry) {
601 this.renamer.setFieldName((FieldEntry) obfEntry, newName);
602 } else if (obfEntry instanceof MethodEntry) {
603 if (((MethodEntry) obfEntry).isConstructor()) {
604 throw new IllegalArgumentException("Cannot rename constructors");
605 }
606
607 this.renamer.setMethodTreeName((MethodEntry) obfEntry, newName);
608 } else if (obfEntry instanceof LocalVariableEntry) {
609 // TODO: Discern between arguments (propagate) and local vars (don't)
610 this.renamer.setLocalVariableTreeName((LocalVariableEntry) obfEntry, newName);
611 } else {
612 throw new Error("Unknown entry desc: " + obfEntry.getClass().getName());
613 }
614
615 // clear caches
616 if (clearCache)
617 this.translatorCache.clear();
618 } 406 }
619 407
620 public void removeMapping(Entry obfEntry) { 408 public void markAsDeobfuscated(Entry<?> obfEntry) {
621 removeMapping(obfEntry, true); 409 mapper.mapFromObf(obfEntry, new EntryMapping(mapper.deobfuscate(obfEntry).getName()));
622 } 410 }
623 411
624 public void removeMapping(Entry obfEntry, boolean clearCache) { 412 public static void runCustomTransforms(AstBuilder builder, DecompilerContext context) {
625 if (obfEntry instanceof ClassEntry) {
626 this.renamer.removeClassMapping((ClassEntry) obfEntry);
627 } else if (obfEntry instanceof FieldEntry) {
628 this.renamer.removeFieldMapping((FieldEntry) obfEntry);
629 } else if (obfEntry instanceof MethodEntry) {
630 if (((MethodEntry) obfEntry).isConstructor()) {
631 throw new IllegalArgumentException("Cannot rename constructors");
632 }
633 this.renamer.removeMethodTreeMapping((MethodEntry) obfEntry);
634 } else if (obfEntry instanceof LocalVariableEntry) {
635 this.renamer.removeLocalVariableMapping((LocalVariableEntry) obfEntry);
636 } else {
637 throw new Error("Unknown entry desc: " + obfEntry);
638 }
639
640 // clear caches
641 if (clearCache)
642 this.translatorCache.clear();
643 }
644
645 public void markAsDeobfuscated(Entry obfEntry) {
646 markAsDeobfuscated(obfEntry, true);
647 }
648
649 public void markAsDeobfuscated(Entry obfEntry, boolean clearCache) {
650 if (obfEntry instanceof ClassEntry) {
651 this.renamer.markClassAsDeobfuscated((ClassEntry) obfEntry);
652 } else if (obfEntry instanceof FieldEntry) {
653 this.renamer.markFieldAsDeobfuscated((FieldEntry) obfEntry);
654 } else if (obfEntry instanceof MethodEntry) {
655 MethodEntry methodEntry = (MethodEntry) obfEntry;
656 if (methodEntry.isConstructor()) {
657 throw new IllegalArgumentException("Cannot rename constructors");
658 }
659 this.renamer.markMethodTreeAsDeobfuscated(methodEntry);
660 } else if (obfEntry instanceof LocalVariableEntry) {
661 this.renamer.markArgumentAsDeobfuscated((LocalVariableEntry) obfEntry);
662 } else {
663 throw new Error("Unknown entry desc: " + obfEntry);
664 }
665
666 // clear caches
667 if (clearCache)
668 this.translatorCache.clear();
669 }
670
671 public void changeModifier(Entry entry, Mappings.EntryModifier modifierEntry) {
672 Entry obfEntry = obfuscateEntry(entry);
673 if (obfEntry instanceof ClassEntry)
674 this.renamer.setClassModifier((ClassEntry) obfEntry, modifierEntry);
675 else if (obfEntry instanceof FieldEntry)
676 this.renamer.setFieldModifier((FieldEntry) obfEntry, modifierEntry);
677 else if (obfEntry instanceof MethodEntry)
678 this.renamer.setMethodModifier((MethodEntry) obfEntry, modifierEntry);
679 else
680 throw new Error("Unknown entry desc: " + obfEntry);
681 }
682
683 public Mappings.EntryModifier getModifier(Entry obfEntry) {
684 Entry entry = obfuscateEntry(obfEntry);
685 if (entry != null)
686 obfEntry = entry;
687 if (obfEntry instanceof ClassEntry)
688 return this.renamer.getClassModifier((ClassEntry) obfEntry);
689 else if (obfEntry instanceof FieldEntry)
690 return this.renamer.getFieldModifier((FieldEntry) obfEntry);
691 else if (obfEntry instanceof MethodEntry)
692 return this.renamer.getMethodModfifier((MethodEntry) obfEntry);
693 else
694 throw new Error("Unknown entry desc: " + obfEntry);
695 }
696
697 public static void runCustomTransforms(AstBuilder builder, DecompilerContext context){
698 List<IAstTransform> transformers = Arrays.asList( 413 List<IAstTransform> transformers = Arrays.asList(
699 new ObfuscatedEnumSwitchRewriterTransform(context), 414 new ObfuscatedEnumSwitchRewriterTransform(context),
700 new VarargsFixer(context), 415 new VarargsFixer(context),
@@ -707,12 +422,6 @@ public class Deobfuscator {
707 } 422 }
708 } 423 }
709 424
710 public interface ProgressListener {
711 void init(int totalWork, String title);
712
713 void onProgress(int numDone, String message);
714 }
715
716 public interface ClassTransformer { 425 public interface ClassTransformer {
717 String transform(ClassNode node, ClassWriter writer); 426 String transform(ClassNode node, ClassWriter writer);
718 } 427 }
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 @@
1package cuchaz.enigma; 1package cuchaz.enigma;
2 2
3import com.strobel.assembler.metadata.ITypeLoader; 3import com.strobel.assembler.metadata.ITypeLoader;
4import cuchaz.enigma.mapping.entry.ClassEntry; 4import cuchaz.enigma.translation.representation.entry.ClassEntry;
5import org.objectweb.asm.ClassWriter; 5import org.objectweb.asm.ClassWriter;
6import org.objectweb.asm.tree.ClassNode; 6import org.objectweb.asm.tree.ClassNode;
7 7
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 @@
11 11
12package cuchaz.enigma; 12package cuchaz.enigma;
13 13
14import cuchaz.enigma.config.Config;
15import cuchaz.enigma.config.Themes;
16import cuchaz.enigma.gui.Gui; 14import cuchaz.enigma.gui.Gui;
15import cuchaz.enigma.translation.mapping.serde.MappingFormat;
17 16
18import javax.swing.*;
19import java.io.File; 17import java.io.File;
18import java.nio.file.Files;
19import java.nio.file.Path;
20import java.util.jar.JarFile; 20import java.util.jar.JarFile;
21 21
22public class Main { 22public class Main {
@@ -29,7 +29,12 @@ public class Main {
29 gui.getController().openJar(new JarFile(getFile(args[0]))); 29 gui.getController().openJar(new JarFile(getFile(args[0])));
30 } 30 }
31 if (args.length >= 2) { 31 if (args.length >= 2) {
32 gui.getController().openEnigmaMappings(getFile(args[1])); 32 Path mappingsFile = getFile(args[1]).toPath();
33 if (Files.isDirectory(mappingsFile)) {
34 gui.getController().openMappings(MappingFormat.ENIGMA_DIRECTORY, mappingsFile);
35 } else {
36 gui.getController().openMappings(MappingFormat.ENIGMA_FILE, mappingsFile);
37 }
33 } 38 }
34 39
35 // DEBUG 40 // 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 @@
1package cuchaz.enigma;
2
3public interface ProgressListener {
4 ProgressListener VOID = new ProgressListener() {
5 @Override
6 public void init(int totalWork, String title) {
7 }
8
9 @Override
10 public void step(int numDone, String message) {
11 }
12 };
13
14 void init(int totalWork, String title);
15
16 void step(int numDone, String message);
17}
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 @@
1package cuchaz.enigma; 1package cuchaz.enigma;
2 2
3import com.strobel.assembler.metadata.Buffer; 3import com.strobel.assembler.metadata.Buffer;
4import cuchaz.enigma.mapping.entry.ClassEntry; 4import cuchaz.enigma.translation.representation.entry.ClassEntry;
5import org.objectweb.asm.ClassWriter; 5import org.objectweb.asm.ClassWriter;
6import org.objectweb.asm.tree.ClassNode; 6import org.objectweb.asm.tree.ClassNode;
7 7
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;
14import com.google.common.collect.Lists; 14import com.google.common.collect.Lists;
15import com.strobel.assembler.metadata.Buffer; 15import com.strobel.assembler.metadata.Buffer;
16import com.strobel.assembler.metadata.ITypeLoader; 16import com.strobel.assembler.metadata.ITypeLoader;
17import cuchaz.enigma.analysis.JarIndex; 17import cuchaz.enigma.analysis.index.JarIndex;
18import cuchaz.enigma.analysis.ParsedJar; 18import cuchaz.enigma.analysis.ParsedJar;
19import cuchaz.enigma.bytecode.translators.TranslationClassVisitor; 19import cuchaz.enigma.bytecode.translators.TranslationClassVisitor;
20import cuchaz.enigma.mapping.entry.ClassEntry; 20import cuchaz.enigma.translation.Translator;
21import cuchaz.enigma.mapping.entry.ReferencedEntryPool; 21import cuchaz.enigma.translation.representation.ReferencedEntryPool;
22import cuchaz.enigma.mapping.Translator; 22import cuchaz.enigma.translation.representation.entry.ClassEntry;
23import org.objectweb.asm.ClassWriter; 23import org.objectweb.asm.ClassWriter;
24import org.objectweb.asm.Opcodes; 24import org.objectweb.asm.Opcodes;
25import org.objectweb.asm.tree.AbstractInsnNode; 25import org.objectweb.asm.tree.AbstractInsnNode;
@@ -47,12 +47,12 @@ public class TranslatingTypeLoader extends CachingTypeLoader implements ITransla
47 this.deobfuscatingTranslator = deobfuscatingTranslator; 47 this.deobfuscatingTranslator = deobfuscatingTranslator;
48 } 48 }
49 49
50 protected byte[] doLoad(String className){ 50 protected byte[] doLoad(String className) {
51 byte[] data = loadType(className); 51 byte[] data = loadType(className);
52 if (data == null) { 52 if (data == null) {
53 // chain to default desc loader 53 // chain to default desc loader
54 Buffer parentBuf = new Buffer(); 54 Buffer parentBuf = new Buffer();
55 if (defaultTypeLoader.tryLoadType(className, parentBuf)){ 55 if (defaultTypeLoader.tryLoadType(className, parentBuf)) {
56 return parentBuf.array(); 56 return parentBuf.array();
57 } 57 }
58 return EMPTY_ARRAY;//need to return *something* as null means no store 58 return EMPTY_ARRAY;//need to return *something* as null means no store
@@ -64,21 +64,10 @@ public class TranslatingTypeLoader extends CachingTypeLoader implements ITransla
64 64
65 // NOTE: don't know if class name is obf or deobf 65 // NOTE: don't know if class name is obf or deobf
66 ClassEntry classEntry = new ClassEntry(className); 66 ClassEntry classEntry = new ClassEntry(className);
67 ClassEntry obfClassEntry = this.obfuscatingTranslator.getTranslatedClass(classEntry); 67 ClassEntry obfClassEntry = this.obfuscatingTranslator.translate(classEntry);
68
69 // is this an inner class referenced directly? (ie trying to load b instead of a$b)
70 if (!obfClassEntry.isInnerClass()) {
71 List<ClassEntry> classChain = this.jarIndex.getObfClassChain(obfClassEntry);
72 if (classChain.size() > 1) {
73 System.err.println(String.format("WARNING: no class %s after inner class reconstruction. Try %s",
74 className, obfClassEntry.buildClassEntry(classChain)
75 ));
76 return null;
77 }
78 }
79 68
80 // is this a class we should even know about? 69 // is this a class we should even know about?
81 if (!this.jarIndex.containsObfClass(obfClassEntry)) { 70 if (!jarIndex.getEntryIndex().hasClass(obfClassEntry)) {
82 return null; 71 return null;
83 } 72 }
84 73
@@ -97,15 +86,15 @@ public class TranslatingTypeLoader extends CachingTypeLoader implements ITransla
97 // DUP 86 // DUP
98 // INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class; 87 // INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class;
99 // POP 88 // POP
100 for (MethodNode methodNode : node.methods){ 89 for (MethodNode methodNode : node.methods) {
101 AbstractInsnNode insnNode = methodNode.instructions.getFirst(); 90 AbstractInsnNode insnNode = methodNode.instructions.getFirst();
102 while (insnNode != null){ 91 while (insnNode != null) {
103 if (insnNode instanceof MethodInsnNode && insnNode.getOpcode() == Opcodes.INVOKEVIRTUAL){ 92 if (insnNode instanceof MethodInsnNode && insnNode.getOpcode() == Opcodes.INVOKEVIRTUAL) {
104 MethodInsnNode methodInsnNode = (MethodInsnNode)insnNode; 93 MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode;
105 if (methodInsnNode.name.equals("getClass") && methodInsnNode.owner.equals("java/lang/Object") && methodInsnNode.desc.equals("()Ljava/lang/Class;")){ 94 if (methodInsnNode.name.equals("getClass") && methodInsnNode.owner.equals("java/lang/Object") && methodInsnNode.desc.equals("()Ljava/lang/Class;")) {
106 AbstractInsnNode previous = methodInsnNode.getPrevious(); 95 AbstractInsnNode previous = methodInsnNode.getPrevious();
107 AbstractInsnNode next = methodInsnNode.getNext(); 96 AbstractInsnNode next = methodInsnNode.getNext();
108 if (previous.getOpcode() == Opcodes.DUP && next.getOpcode() == Opcodes.POP){ 97 if (previous.getOpcode() == Opcodes.DUP && next.getOpcode() == Opcodes.POP) {
109 insnNode = previous.getPrevious();//reset the iterator so it gets the new next instruction 98 insnNode = previous.getPrevious();//reset the iterator so it gets the new next instruction
110 methodNode.instructions.remove(previous); 99 methodNode.instructions.remove(previous);
111 methodNode.instructions.remove(methodInsnNode); 100 methodNode.instructions.remove(methodInsnNode);
@@ -140,24 +129,26 @@ public class TranslatingTypeLoader extends CachingTypeLoader implements ITransla
140 129
141 @Override 130 @Override
142 public List<String> getClassNamesToTry(String className) { 131 public List<String> getClassNamesToTry(String className) {
143 return getClassNamesToTry(this.obfuscatingTranslator.getTranslatedClass(new ClassEntry(className))); 132 return getClassNamesToTry(this.obfuscatingTranslator.translate(new ClassEntry(className)));
144 } 133 }
145 134
146 @Override 135 @Override
147 public List<String> getClassNamesToTry(ClassEntry obfClassEntry) { 136 public List<String> getClassNamesToTry(ClassEntry obfClassEntry) {
148 List<String> classNamesToTry = Lists.newArrayList(); 137 List<String> classNamesToTry = Lists.newArrayList();
149 classNamesToTry.add(obfClassEntry.getName()); 138 classNamesToTry.add(obfClassEntry.getFullName());
150 if (obfClassEntry.isInnerClass()) { 139
151 // try just the inner class name 140 ClassEntry outerClass = obfClassEntry.getOuterClass();
152 classNamesToTry.add(obfClassEntry.getInnermostClassName()); 141 if (outerClass != null) {
142 classNamesToTry.addAll(getClassNamesToTry(outerClass));
153 } 143 }
144
154 return classNamesToTry; 145 return classNamesToTry;
155 } 146 }
156 147
157 @Override 148 @Override
158 public String transformInto(ClassNode node, ClassWriter writer) { 149 public String transformInto(ClassNode node, ClassWriter writer) {
159 node.accept(new TranslationClassVisitor(deobfuscatingTranslator, jarIndex, entryPool, Opcodes.ASM5, writer)); 150 node.accept(new TranslationClassVisitor(deobfuscatingTranslator, entryPool, Opcodes.ASM5, writer));
160 return deobfuscatingTranslator.getTranslatedClass(new ClassEntry(node.name)).getName(); 151 return deobfuscatingTranslator.translate(new ClassEntry(node.name)).getFullName();
161 } 152 }
162 153
163} 154}
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 @@
11 11
12package cuchaz.enigma.analysis; 12package cuchaz.enigma.analysis;
13 13
14import cuchaz.enigma.bytecode.AccessFlags; 14import cuchaz.enigma.translation.representation.AccessFlags;
15 15
16import java.lang.reflect.Modifier; 16import java.lang.reflect.Modifier;
17 17
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 @@
12package cuchaz.enigma.analysis; 12package cuchaz.enigma.analysis;
13 13
14import com.google.common.collect.Lists; 14import com.google.common.collect.Lists;
15import cuchaz.enigma.mapping.entry.ClassEntry; 15import cuchaz.enigma.analysis.index.InheritanceIndex;
16import cuchaz.enigma.mapping.entry.MethodEntry; 16import cuchaz.enigma.analysis.index.JarIndex;
17import cuchaz.enigma.mapping.Translator; 17import cuchaz.enigma.translation.Translator;
18import cuchaz.enigma.translation.representation.entry.ClassEntry;
19import cuchaz.enigma.translation.representation.entry.MethodEntry;
18 20
19import javax.swing.tree.DefaultMutableTreeNode; 21import javax.swing.tree.DefaultMutableTreeNode;
22import java.util.Collection;
20import java.util.List; 23import java.util.List;
21 24
22public class ClassImplementationsTreeNode extends DefaultMutableTreeNode { 25public class ClassImplementationsTreeNode extends DefaultMutableTreeNode {
23 26 private final Translator translator;
24 private final Translator deobfuscatingTranslator;
25 private final ClassEntry entry; 27 private final ClassEntry entry;
26 28
27 public ClassImplementationsTreeNode(Translator deobfuscatingTranslator, ClassEntry entry) { 29 public ClassImplementationsTreeNode(Translator translator, ClassEntry entry) {
28 this.deobfuscatingTranslator = deobfuscatingTranslator; 30 this.translator = translator;
29 this.entry = entry; 31 this.entry = entry;
30 } 32 }
31 33
32 public static ClassImplementationsTreeNode findNode(ClassImplementationsTreeNode node, MethodEntry entry) { 34 public static ClassImplementationsTreeNode findNode(ClassImplementationsTreeNode node, MethodEntry entry) {
33 // is this the node? 35 // is this the node?
34 if (node.entry.equals(entry.getOwnerClassEntry())) { 36 if (node.entry.equals(entry.getParent())) {
35 return node; 37 return node;
36 } 38 }
37 39
@@ -49,24 +51,19 @@ public class ClassImplementationsTreeNode extends DefaultMutableTreeNode {
49 return this.entry; 51 return this.entry;
50 } 52 }
51 53
52 public String getDeobfClassName() {
53 return this.deobfuscatingTranslator.getTranslatedClass(entry).getClassName();
54 }
55
56 @Override 54 @Override
57 public String toString() { 55 public String toString() {
58 String className = getDeobfClassName(); 56 return translator.translate(entry).toString();
59 if (className == null) {
60 className = this.entry.getClassName();
61 }
62 return className;
63 } 57 }
64 58
65 public void load(JarIndex index) { 59 public void load(JarIndex index) {
66 // get all method implementations 60 // get all method implementations
67 List<ClassImplementationsTreeNode> nodes = Lists.newArrayList(); 61 List<ClassImplementationsTreeNode> nodes = Lists.newArrayList();
68 for (String implementingClassName : index.getImplementingClasses(this.entry.getClassName())) { 62 InheritanceIndex inheritanceIndex = index.getInheritanceIndex();
69 nodes.add(new ClassImplementationsTreeNode(this.deobfuscatingTranslator, new ClassEntry(implementingClassName))); 63
64 Collection<ClassEntry> inheritors = inheritanceIndex.getChildren(entry);
65 for (ClassEntry inheritor : inheritors) {
66 nodes.add(new ClassImplementationsTreeNode(translator, inheritor));
70 } 67 }
71 68
72 // add them to this node 69 // 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 @@
12package cuchaz.enigma.analysis; 12package cuchaz.enigma.analysis;
13 13
14import com.google.common.collect.Lists; 14import com.google.common.collect.Lists;
15import cuchaz.enigma.mapping.entry.ClassEntry; 15import cuchaz.enigma.analysis.index.InheritanceIndex;
16import cuchaz.enigma.mapping.Translator; 16import cuchaz.enigma.translation.Translator;
17import cuchaz.enigma.translation.representation.entry.ClassEntry;
17 18
18import javax.swing.tree.DefaultMutableTreeNode; 19import javax.swing.tree.DefaultMutableTreeNode;
19import java.util.List; 20import java.util.List;
20 21
21public class ClassInheritanceTreeNode extends DefaultMutableTreeNode { 22public class ClassInheritanceTreeNode extends DefaultMutableTreeNode {
22 23 private final Translator translator;
23 private final Translator deobfuscatingTranslator;
24 private final ClassEntry obfClassEntry; 24 private final ClassEntry obfClassEntry;
25 25
26 public ClassInheritanceTreeNode(Translator deobfuscatingTranslator, String obfClassName) { 26 public ClassInheritanceTreeNode(Translator translator, String obfClassName) {
27 this.deobfuscatingTranslator = deobfuscatingTranslator; 27 this.translator = translator;
28 this.obfClassEntry = new ClassEntry(obfClassName); 28 this.obfClassEntry = new ClassEntry(obfClassName);
29 } 29 }
30 30
31 public static ClassInheritanceTreeNode findNode(ClassInheritanceTreeNode node, ClassEntry entry) { 31 public static ClassInheritanceTreeNode findNode(ClassInheritanceTreeNode node, ClassEntry entry) {
32 // is this the node? 32 // is this the node?
33 if (node.getObfClassName().equals(entry.getName())) { 33 if (node.getObfClassName().equals(entry.getFullName())) {
34 return node; 34 return node;
35 } 35 }
36 36
@@ -45,27 +45,19 @@ public class ClassInheritanceTreeNode extends DefaultMutableTreeNode {
45 } 45 }
46 46
47 public String getObfClassName() { 47 public String getObfClassName() {
48 return this.obfClassEntry.getClassName(); 48 return this.obfClassEntry.getFullName();
49 }
50
51 public String getDeobfClassName() {
52 return this.deobfuscatingTranslator.getTranslatedClass(this.obfClassEntry).getClassName();
53 } 49 }
54 50
55 @Override 51 @Override
56 public String toString() { 52 public String toString() {
57 String deobfClassName = getDeobfClassName(); 53 return translator.translate(obfClassEntry).getFullName();
58 if (deobfClassName != null) {
59 return deobfClassName;
60 }
61 return this.obfClassEntry.getName();
62 } 54 }
63 55
64 public void load(TranslationIndex ancestries, boolean recurse) { 56 public void load(InheritanceIndex ancestries, boolean recurse) {
65 // get all the child nodes 57 // get all the child nodes
66 List<ClassInheritanceTreeNode> nodes = Lists.newArrayList(); 58 List<ClassInheritanceTreeNode> nodes = Lists.newArrayList();
67 for (ClassEntry subclassEntry : ancestries.getSubclass(this.obfClassEntry)) { 59 for (ClassEntry inheritor : ancestries.getChildren(this.obfClassEntry)) {
68 nodes.add(new ClassInheritanceTreeNode(this.deobfuscatingTranslator, subclassEntry.getName())); 60 nodes.add(new ClassInheritanceTreeNode(translator, inheritor.getFullName()));
69 } 61 }
70 62
71 // add them to this node 63 // 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 @@
12package cuchaz.enigma.analysis; 12package cuchaz.enigma.analysis;
13 13
14import com.google.common.collect.Sets; 14import com.google.common.collect.Sets;
15import cuchaz.enigma.bytecode.AccessFlags; 15import cuchaz.enigma.analysis.index.JarIndex;
16import cuchaz.enigma.mapping.Translator; 16import cuchaz.enigma.analysis.index.ReferenceIndex;
17import cuchaz.enigma.mapping.entry.ClassEntry; 17import cuchaz.enigma.translation.Translator;
18import cuchaz.enigma.mapping.entry.Entry; 18import cuchaz.enigma.translation.representation.entry.ClassEntry;
19import cuchaz.enigma.mapping.entry.MethodDefEntry; 19import cuchaz.enigma.translation.representation.entry.Entry;
20import cuchaz.enigma.mapping.entry.MethodEntry; 20import cuchaz.enigma.translation.representation.entry.MethodDefEntry;
21 21
22import javax.swing.tree.DefaultMutableTreeNode; 22import javax.swing.tree.DefaultMutableTreeNode;
23import javax.swing.tree.TreeNode; 23import javax.swing.tree.TreeNode;
@@ -29,7 +29,6 @@ public class ClassReferenceTreeNode extends DefaultMutableTreeNode
29 private Translator deobfuscatingTranslator; 29 private Translator deobfuscatingTranslator;
30 private ClassEntry entry; 30 private ClassEntry entry;
31 private EntryReference<ClassEntry, MethodDefEntry> reference; 31 private EntryReference<ClassEntry, MethodDefEntry> reference;
32 private AccessFlags access;
33 32
34 public ClassReferenceTreeNode(Translator deobfuscatingTranslator, ClassEntry entry) { 33 public ClassReferenceTreeNode(Translator deobfuscatingTranslator, ClassEntry entry) {
35 this.deobfuscatingTranslator = deobfuscatingTranslator; 34 this.deobfuscatingTranslator = deobfuscatingTranslator;
@@ -37,12 +36,10 @@ public class ClassReferenceTreeNode extends DefaultMutableTreeNode
37 this.reference = null; 36 this.reference = null;
38 } 37 }
39 38
40 public ClassReferenceTreeNode(Translator deobfuscatingTranslator, 39 public ClassReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference<ClassEntry, MethodDefEntry> reference) {
41 EntryReference<ClassEntry, MethodDefEntry> reference, AccessFlags access) {
42 this.deobfuscatingTranslator = deobfuscatingTranslator; 40 this.deobfuscatingTranslator = deobfuscatingTranslator;
43 this.entry = reference.entry; 41 this.entry = reference.entry;
44 this.reference = reference; 42 this.reference = reference;
45 this.access = access;
46 } 43 }
47 44
48 @Override 45 @Override
@@ -58,16 +55,17 @@ public class ClassReferenceTreeNode extends DefaultMutableTreeNode
58 @Override 55 @Override
59 public String toString() { 56 public String toString() {
60 if (this.reference != null) { 57 if (this.reference != null) {
61 return String.format("%s (%s)", this.deobfuscatingTranslator.getTranslatedMethodDef(this.reference.context), 58 return String.format("%s", this.deobfuscatingTranslator.translate(this.reference.context));
62 this.access);
63 } 59 }
64 return this.deobfuscatingTranslator.getTranslatedClass(this.entry).getName(); 60 return this.deobfuscatingTranslator.translate(this.entry).getFullName();
65 } 61 }
66 62
67 public void load(JarIndex index, boolean recurse) { 63 public void load(JarIndex index, boolean recurse) {
64 ReferenceIndex referenceIndex = index.getReferenceIndex();
65
68 // get all the child nodes 66 // get all the child nodes
69 for (EntryReference<ClassEntry, MethodDefEntry> reference : index.getMethodsReferencing(this.entry)) { 67 for (EntryReference<ClassEntry, MethodDefEntry> reference : referenceIndex.getReferencesToClass(this.entry)) {
70 add(new ClassReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccessFlags(this.entry))); 68 add(new ClassReferenceTreeNode(this.deobfuscatingTranslator, reference));
71 } 69 }
72 70
73 if (recurse && this.children != null) { 71 if (recurse && this.children != null) {
@@ -76,7 +74,7 @@ public class ClassReferenceTreeNode extends DefaultMutableTreeNode
76 ClassReferenceTreeNode node = (ClassReferenceTreeNode) child; 74 ClassReferenceTreeNode node = (ClassReferenceTreeNode) child;
77 75
78 // don't recurse into ancestor 76 // don't recurse into ancestor
79 Set<Entry> ancestors = Sets.newHashSet(); 77 Set<Entry<?>> ancestors = Sets.newHashSet();
80 TreeNode n = node; 78 TreeNode n = node;
81 while (n.getParent() != null) { 79 while (n.getParent() != null) {
82 n = n.getParent(); 80 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 @@
11 11
12package cuchaz.enigma.analysis; 12package cuchaz.enigma.analysis;
13 13
14import cuchaz.enigma.mapping.entry.ClassEntry; 14import cuchaz.enigma.translation.Translatable;
15import cuchaz.enigma.mapping.entry.Entry; 15import cuchaz.enigma.translation.Translator;
16import cuchaz.enigma.mapping.entry.MethodEntry; 16import cuchaz.enigma.translation.mapping.EntryMapping;
17import cuchaz.enigma.translation.mapping.EntryResolver;
18import cuchaz.enigma.translation.mapping.EntryMap;
19import cuchaz.enigma.translation.representation.entry.ClassEntry;
20import cuchaz.enigma.translation.representation.entry.Entry;
21import cuchaz.enigma.translation.representation.entry.MethodEntry;
17import cuchaz.enigma.utils.Utils; 22import cuchaz.enigma.utils.Utils;
18 23
19import java.util.Arrays; 24import java.util.Arrays;
20import java.util.List; 25import java.util.List;
21 26
22public class EntryReference<E extends Entry, C extends Entry> { 27public class EntryReference<E extends Entry<?>, C extends Entry<?>> implements Translatable {
23 28
24 private static final List<String> CONSTRUCTOR_NON_NAMES = Arrays.asList("this", "super", "static"); 29 private static final List<String> CONSTRUCTOR_NON_NAMES = Arrays.asList("this", "super", "static");
25 public E entry; 30 public E entry;
@@ -53,32 +58,24 @@ public class EntryReference<E extends Entry, C extends Entry> {
53 58
54 public ClassEntry getLocationClassEntry() { 59 public ClassEntry getLocationClassEntry() {
55 if (context != null) { 60 if (context != null) {
56 return context.getOwnerClassEntry(); 61 return context.getContainingClass();
57 } 62 }
58 return entry.getOwnerClassEntry(); 63 return entry.getContainingClass();
59 } 64 }
60 65
61 public boolean isNamed() { 66 public boolean isNamed() {
62 return this.sourceName; 67 return this.sourceName;
63 } 68 }
64 69
65 public Entry getNameableEntry() { 70 public Entry<?> getNameableEntry() {
66 if (entry instanceof MethodEntry && ((MethodEntry) entry).isConstructor()) { 71 if (entry instanceof MethodEntry && ((MethodEntry) entry).isConstructor()) {
67 // renaming a constructor really means renaming the class 72 // renaming a constructor really means renaming the class
68 return entry.getOwnerClassEntry(); 73 return entry.getContainingClass();
69 } 74 }
70 return entry; 75 return entry;
71 } 76 }
72 77
73 public String getNameableName() { 78 public String getNameableName() {
74 if (getNameableEntry() instanceof ClassEntry) {
75 ClassEntry classEntry = (ClassEntry) getNameableEntry();
76 if (classEntry.isInnerClass()) {
77 // make sure we only rename the inner class name
78 return classEntry.getInnermostClassName();
79 }
80 }
81
82 return getNameableEntry().getName(); 79 return getNameableEntry().getName();
83 } 80 }
84 81
@@ -121,4 +118,9 @@ public class EntryReference<E extends Entry, C extends Entry> {
121 } 118 }
122 return buf.toString(); 119 return buf.toString();
123 } 120 }
121
122 @Override
123 public Translatable translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) {
124 return new EntryReference<>(translator.translate(entry), translator.translate(context), this);
125 }
124} 126}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.analysis;
13
14import com.google.common.collect.Lists;
15import com.google.common.collect.Multimap;
16import com.google.common.collect.Sets;
17import cuchaz.enigma.mapping.*;
18import cuchaz.enigma.mapping.entry.*;
19
20import java.util.AbstractMap;
21import java.util.List;
22import java.util.Map;
23import java.util.Set;
24
25public class EntryRenamer {
26
27 public static <T> void renameClassesInSet(Map<String, String> renames, Set<T> set) {
28 List<T> entries = Lists.newArrayList();
29 for (T val : set) {
30 entries.add(renameClassesInThing(renames, val));
31 }
32 set.clear();
33 set.addAll(entries);
34 }
35
36 public static <Key, Val> void renameClassesInMap(Map<String, String> renames, Map<Key, Val> map) {
37 // for each key/value pair...
38 Set<Map.Entry<Key, Val>> entriesToAdd = Sets.newHashSet();
39 for (Map.Entry<Key, Val> entry : map.entrySet()) {
40 entriesToAdd.add(new AbstractMap.SimpleEntry<>(renameClassesInThing(renames, entry.getKey()), renameClassesInThing(renames, entry.getValue())));
41 }
42 map.clear();
43 for (Map.Entry<Key, Val> entry : entriesToAdd) {
44 map.put(entry.getKey(), entry.getValue());
45 }
46 }
47
48 public static <Key, Val> void renameClassesInMultimap(Map<String, String> renames, Multimap<Key, Val> map) {
49 // for each key/value pair...
50 Set<Map.Entry<Key, Val>> entriesToAdd = Sets.newHashSet();
51 for (Map.Entry<Key, Val> entry : map.entries()) {
52 entriesToAdd.add(new AbstractMap.SimpleEntry<>(renameClassesInThing(renames, entry.getKey()), renameClassesInThing(renames, entry.getValue())));
53 }
54 map.clear();
55 for (Map.Entry<Key, Val> entry : entriesToAdd) {
56 map.put(entry.getKey(), entry.getValue());
57 }
58 }
59
60 public static <Key, Val> void renameMethodsInMultimap(Map<MethodEntry, MethodEntry> renames, Multimap<Key, Val> map) {
61 // for each key/value pair...
62 Set<Map.Entry<Key, Val>> entriesToAdd = Sets.newHashSet();
63 for (Map.Entry<Key, Val> entry : map.entries()) {
64 entriesToAdd.add(new AbstractMap.SimpleEntry<>(renameMethodsInThing(renames, entry.getKey()), renameMethodsInThing(renames, entry.getValue())));
65 }
66 map.clear();
67 for (Map.Entry<Key, Val> entry : entriesToAdd) {
68 map.put(entry.getKey(), entry.getValue());
69 }
70 }
71
72 public static <Key, Val> void renameMethodsInMap(Map<MethodEntry, MethodEntry> renames, Map<Key, Val> map) {
73 // for each key/value pair...
74 Set<Map.Entry<Key, Val>> entriesToAdd = Sets.newHashSet();
75 for (Map.Entry<Key, Val> entry : map.entrySet()) {
76 entriesToAdd.add(new AbstractMap.SimpleEntry<>(renameMethodsInThing(renames, entry.getKey()), renameMethodsInThing(renames, entry.getValue())));
77 }
78 map.clear();
79 for (Map.Entry<Key, Val> entry : entriesToAdd) {
80 map.put(entry.getKey(), entry.getValue());
81 }
82 }
83
84 @SuppressWarnings("unchecked")
85 public static <T> T renameMethodsInThing(Map<MethodEntry, MethodEntry> renames, T thing) {
86 if (thing instanceof MethodEntry) {
87 MethodEntry methodEntry = (MethodEntry) thing;
88 MethodEntry newMethodEntry = renames.get(methodEntry);
89 if (newMethodEntry != null) {
90 return (T) new MethodEntry(
91 methodEntry.getOwnerClassEntry(),
92 newMethodEntry.getName(),
93 methodEntry.getDesc()
94 );
95 }
96 return thing;
97 } else if (thing instanceof LocalVariableEntry) {
98 LocalVariableEntry variableEntry = (LocalVariableEntry) thing;
99 return (T) new LocalVariableEntry(
100 renameMethodsInThing(renames, variableEntry.getOwnerEntry()),
101 variableEntry.getIndex(),
102 variableEntry.getName(),
103 variableEntry.isParameter()
104 );
105 } else if (thing instanceof EntryReference) {
106 EntryReference<Entry, Entry> reference = (EntryReference<Entry, Entry>) thing;
107 reference.entry = renameMethodsInThing(renames, reference.entry);
108 reference.context = renameMethodsInThing(renames, reference.context);
109 return thing;
110 }
111 return thing;
112 }
113
114 @SuppressWarnings("unchecked")
115 public static <T> T renameClassesInThing(final Map<String, String> renames, T thing) {
116 if (thing instanceof String) {
117 String stringEntry = (String) thing;
118 if (renames.containsKey(stringEntry)) {
119 return (T) renames.get(stringEntry);
120 }
121 } else if (thing instanceof ClassEntry) {
122 ClassEntry classEntry = (ClassEntry) thing;
123 return (T) new ClassEntry(renameClassesInThing(renames, classEntry.getClassName()));
124 } else if (thing instanceof FieldDefEntry) {
125 FieldDefEntry fieldEntry = (FieldDefEntry) thing;
126 return (T) new FieldDefEntry(
127 renameClassesInThing(renames, fieldEntry.getOwnerClassEntry()),
128 fieldEntry.getName(),
129 renameClassesInThing(renames, fieldEntry.getDesc()),
130 renameClassesInThing(renames, fieldEntry.getSignature()),
131 fieldEntry.getAccess()
132 );
133 } else if (thing instanceof MethodDefEntry) {
134 MethodDefEntry methodEntry = (MethodDefEntry) thing;
135 return (T) new MethodDefEntry(
136 renameClassesInThing(renames, methodEntry.getOwnerClassEntry()),
137 methodEntry.getName(),
138 renameClassesInThing(renames, methodEntry.getDesc()),
139 renameClassesInThing(renames, methodEntry.getSignature()),
140 methodEntry.getAccess()
141 );
142 } else if (thing instanceof MethodEntry) {
143 MethodEntry methodEntry = (MethodEntry) thing;
144 return (T) new MethodEntry(
145 renameClassesInThing(renames, methodEntry.getOwnerClassEntry()),
146 methodEntry.getName(),
147 renameClassesInThing(renames, methodEntry.getDesc())
148 );
149 } else if (thing instanceof LocalVariableEntry) {
150 LocalVariableEntry argumentEntry = (LocalVariableEntry) thing;
151 return (T) new LocalVariableEntry(renameClassesInThing(renames, argumentEntry.getOwnerEntry()), argumentEntry.getIndex(), argumentEntry.getName(), argumentEntry.isParameter());
152 } else if (thing instanceof EntryReference) {
153 EntryReference<Entry, Entry> reference = (EntryReference<Entry, Entry>) thing;
154 reference.entry = renameClassesInThing(renames, reference.entry);
155 reference.context = renameClassesInThing(renames, reference.context);
156 return thing;
157 } else if (thing instanceof MethodDescriptor) {
158 return (T) ((MethodDescriptor) thing).remap(className -> renameClassesInThing(renames, className));
159 } else if (thing instanceof TypeDescriptor) {
160 return (T) ((TypeDescriptor) thing).remap(className -> renameClassesInThing(renames, className));
161 } else if (thing instanceof Signature) {
162 return (T) ((Signature) thing).remap(className -> renameClassesInThing(renames, className));
163 }
164
165 return thing;
166 }
167}
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 @@
11 11
12package cuchaz.enigma.analysis; 12package cuchaz.enigma.analysis;
13 13
14import cuchaz.enigma.bytecode.AccessFlags; 14import cuchaz.enigma.analysis.index.JarIndex;
15import cuchaz.enigma.mapping.*; 15import cuchaz.enigma.analysis.index.ReferenceIndex;
16import cuchaz.enigma.mapping.entry.FieldEntry; 16import cuchaz.enigma.translation.Translator;
17import cuchaz.enigma.mapping.entry.MethodDefEntry; 17import cuchaz.enigma.translation.representation.entry.FieldEntry;
18import cuchaz.enigma.mapping.entry.MethodEntry; 18import cuchaz.enigma.translation.representation.entry.MethodDefEntry;
19import cuchaz.enigma.translation.representation.entry.MethodEntry;
19 20
20import javax.swing.tree.DefaultMutableTreeNode; 21import javax.swing.tree.DefaultMutableTreeNode;
21 22
22public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode<FieldEntry, MethodDefEntry> { 23public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode<FieldEntry, MethodDefEntry> {
23 24
24 private Translator deobfuscatingTranslator; 25 private final Translator translator;
25 private FieldEntry entry; 26 private FieldEntry entry;
26 private EntryReference<FieldEntry, MethodDefEntry> reference; 27 private EntryReference<FieldEntry, MethodDefEntry> reference;
27 private AccessFlags access;
28 28
29 public FieldReferenceTreeNode(Translator deobfuscatingTranslator, FieldEntry entry) { 29 public FieldReferenceTreeNode(Translator translator, FieldEntry entry) {
30 this.deobfuscatingTranslator = deobfuscatingTranslator; 30 this.translator = translator;
31 this.entry = entry; 31 this.entry = entry;
32 this.reference = null; 32 this.reference = null;
33 } 33 }
34 34
35 private FieldReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference<FieldEntry, MethodDefEntry> reference, AccessFlags access) { 35 private FieldReferenceTreeNode(Translator translator, EntryReference<FieldEntry, MethodDefEntry> reference) {
36 this.deobfuscatingTranslator = deobfuscatingTranslator; 36 this.translator = translator;
37 this.entry = reference.entry; 37 this.entry = reference.entry;
38 this.reference = reference; 38 this.reference = reference;
39 this.access = access;
40 } 39 }
41 40
42 @Override 41 @Override
@@ -52,27 +51,29 @@ public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements Re
52 @Override 51 @Override
53 public String toString() { 52 public String toString() {
54 if (this.reference != null) { 53 if (this.reference != null) {
55 return String.format("%s (%s)", this.deobfuscatingTranslator.getTranslatedMethodDef(this.reference.context), this.access); 54 return String.format("%s", translator.translate(this.reference.context));
56 } 55 }
57 return deobfuscatingTranslator.getTranslatedField(entry).getName(); 56 return translator.translate(entry).toString();
58 } 57 }
59 58
60 public void load(JarIndex index, boolean recurse) { 59 public void load(JarIndex index, boolean recurse) {
60 ReferenceIndex referenceIndex = index.getReferenceIndex();
61
61 // get all the child nodes 62 // get all the child nodes
62 if (this.reference == null) { 63 if (this.reference == null) {
63 for (EntryReference<FieldEntry, MethodDefEntry> reference : index.getFieldReferences(this.entry)) { 64 for (EntryReference<FieldEntry, MethodDefEntry> reference : referenceIndex.getReferencesToField(this.entry)) {
64 add(new FieldReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccessFlags(this.entry))); 65 add(new FieldReferenceTreeNode(translator, reference));
65 } 66 }
66 } else { 67 } else {
67 for (EntryReference<MethodEntry, MethodDefEntry> reference : index.getMethodsReferencing(this.reference.context)) { 68 for (EntryReference<MethodEntry, MethodDefEntry> reference : referenceIndex.getReferencesToMethod(this.reference.context)) {
68 add(new MethodReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccessFlags(this.reference.context))); 69 add(new MethodReferenceTreeNode(translator, reference));
69 } 70 }
70 } 71 }
71 72
72 if (recurse && children != null) { 73 if (recurse && children != null) {
73 for (Object node : children) { 74 for (Object node : children) {
74 if (node instanceof MethodReferenceTreeNode) { 75 if (node instanceof MethodReferenceTreeNode) {
75 ((MethodReferenceTreeNode) node).load(index, true); 76 ((MethodReferenceTreeNode) node).load(index, true, false);
76 } else if (node instanceof FieldReferenceTreeNode) { 77 } else if (node instanceof FieldReferenceTreeNode) {
77 ((FieldReferenceTreeNode) node).load(index, true); 78 ((FieldReferenceTreeNode) node).load(index, true);
78 } 79 }
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 @@
1package cuchaz.enigma.analysis;
2
3import cuchaz.enigma.mapping.entry.ClassEntry;
4import org.objectweb.asm.ClassVisitor;
5
6public class IndexInnerClassVisitor extends ClassVisitor {
7 private final JarIndex index;
8
9 public IndexInnerClassVisitor(JarIndex index, int api) {
10 super(api);
11 this.index = index;
12 }
13
14 public IndexInnerClassVisitor(JarIndex index, int api, ClassVisitor cv) {
15 super(api, cv);
16 this.index = index;
17 }
18
19 @Override
20 public void visitInnerClass(String name, String outerName, String innerName, int access) {
21 ClassEntry entry = new ClassEntry(name);
22 // Ignore anonymous classes
23 if (innerName != null && outerName != null) {
24 ClassEntry outerEntry = new ClassEntry(outerName);
25 index.indexInnerClass(entry, outerEntry);
26 }
27 super.visitInnerClass(name, outerName, innerName, access);
28 }
29}
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 @@
1package cuchaz.enigma.analysis;
2
3import cuchaz.enigma.bytecode.AccessFlags;
4import cuchaz.enigma.mapping.MethodDescriptor;
5import cuchaz.enigma.mapping.Signature;
6import cuchaz.enigma.mapping.entry.ClassEntry;
7import cuchaz.enigma.mapping.entry.MethodDefEntry;
8import org.objectweb.asm.ClassVisitor;
9import org.objectweb.asm.Handle;
10import org.objectweb.asm.MethodVisitor;
11import org.objectweb.asm.Opcodes;
12
13public class IndexReferenceVisitor extends ClassVisitor {
14 private final JarIndex index;
15 private ClassEntry classEntry;
16
17 public IndexReferenceVisitor(JarIndex index, int api) {
18 super(api);
19 this.index = index;
20 }
21
22 @Override
23 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
24 this.classEntry = new ClassEntry(name);
25 }
26
27 @Override
28 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
29 MethodDefEntry entry = new MethodDefEntry(classEntry, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access));
30 return new Method(this.index, entry, this.api);
31 }
32
33 private class Method extends MethodVisitor {
34 private final JarIndex index;
35 private final MethodDefEntry callerEntry;
36
37 public Method(JarIndex index, MethodDefEntry callerEntry, int api) {
38 super(api);
39 this.index = index;
40 this.callerEntry = callerEntry;
41 }
42
43 @Override
44 public void visitFieldInsn(int opcode, String owner, String name, String desc) {
45 this.index.indexFieldAccess(callerEntry, owner, name, desc);
46 }
47
48 @Override
49 public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
50 this.index.indexMethodCall(callerEntry, owner, name, desc);
51 }
52
53 @Override
54 public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
55 for (Object bsmArg : bsmArgs){
56 if (bsmArg instanceof Handle){
57 Handle handle = (Handle)bsmArg;
58 switch (handle.getTag()){
59 case Opcodes.H_GETFIELD:
60 case Opcodes.H_GETSTATIC:
61 case Opcodes.H_PUTFIELD:
62 case Opcodes.H_PUTSTATIC:
63 this.index.indexFieldAccess(callerEntry, handle.getOwner(), handle.getName(), handle.getDesc());
64 break;
65 case Opcodes.H_INVOKEINTERFACE:
66 case Opcodes.H_INVOKESPECIAL:
67 case Opcodes.H_INVOKESTATIC:
68 case Opcodes.H_INVOKEVIRTUAL:
69 case Opcodes.H_NEWINVOKESPECIAL:
70 this.index.indexMethodCall(callerEntry, handle.getOwner(), handle.getName(), handle.getDesc());
71 break;
72 }
73 }
74 }
75 }
76 }
77}
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 @@
1package cuchaz.enigma.analysis;
2
3import com.google.common.collect.Lists;
4import cuchaz.enigma.analysis.index.EntryIndex;
5import cuchaz.enigma.analysis.index.JarIndex;
6import cuchaz.enigma.translation.Translator;
7import cuchaz.enigma.translation.mapping.ResolutionStrategy;
8import cuchaz.enigma.translation.representation.entry.ClassEntry;
9import cuchaz.enigma.translation.representation.entry.MethodEntry;
10
11import java.util.List;
12
13public class IndexTreeBuilder {
14 private final JarIndex index;
15
16 public IndexTreeBuilder(JarIndex index) {
17 this.index = index;
18 }
19
20 public ClassInheritanceTreeNode buildClassInheritance(Translator translator, ClassEntry obfClassEntry) {
21 // get the root node
22 List<String> ancestry = Lists.newArrayList();
23 ancestry.add(obfClassEntry.getFullName());
24 for (ClassEntry classEntry : index.getInheritanceIndex().getAncestors(obfClassEntry)) {
25 ancestry.add(classEntry.getFullName());
26 }
27
28 ClassInheritanceTreeNode rootNode = new ClassInheritanceTreeNode(translator, ancestry.get(ancestry.size() - 1));
29
30 // expand all children recursively
31 rootNode.load(index.getInheritanceIndex(), true);
32
33 return rootNode;
34 }
35
36 public ClassImplementationsTreeNode buildClassImplementations(Translator translator, ClassEntry obfClassEntry) {
37 if (index.getInheritanceIndex().isParent(obfClassEntry)) {
38 ClassImplementationsTreeNode node = new ClassImplementationsTreeNode(translator, obfClassEntry);
39 node.load(index);
40 return node;
41 }
42 return null;
43 }
44
45 public MethodInheritanceTreeNode buildMethodInheritance(Translator translator, MethodEntry obfMethodEntry) {
46 MethodEntry resolvedEntry = index.getEntryResolver().resolveFirstEntry(obfMethodEntry, ResolutionStrategy.RESOLVE_ROOT);
47
48 // make a root node at the base
49 MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode(
50 translator, resolvedEntry,
51 index.getEntryIndex().hasMethod(resolvedEntry)
52 );
53
54 // expand the full tree
55 rootNode.load(index, true);
56
57 return rootNode;
58 }
59
60 public List<MethodImplementationsTreeNode> buildMethodImplementations(Translator translator, MethodEntry obfMethodEntry) {
61 EntryIndex entryIndex = index.getEntryIndex();
62
63 List<MethodEntry> ancestorMethodEntries = Lists.newArrayList();
64
65 if (entryIndex.hasMethod(obfMethodEntry)) {
66 ancestorMethodEntries.add(obfMethodEntry);
67 }
68
69 for (ClassEntry ancestorEntry : index.getInheritanceIndex().getAncestors(obfMethodEntry.getParent())) {
70 MethodEntry ancestorMethod = obfMethodEntry.withParent(ancestorEntry);
71 if (entryIndex.hasMethod(ancestorMethod)) {
72 ancestorMethodEntries.add(ancestorMethod);
73 }
74 }
75
76 List<MethodImplementationsTreeNode> nodes = Lists.newArrayList();
77 if (!ancestorMethodEntries.isEmpty()) {
78 for (MethodEntry interfaceMethodEntry : ancestorMethodEntries) {
79 MethodImplementationsTreeNode node = new MethodImplementationsTreeNode(translator, interfaceMethodEntry);
80 node.load(index);
81 nodes.add(node);
82 }
83 }
84
85 return nodes;
86 }
87}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.analysis;
13
14import com.google.common.collect.*;
15import cuchaz.enigma.bytecode.AccessFlags;
16import cuchaz.enigma.mapping.*;
17import cuchaz.enigma.mapping.entry.*;
18import org.objectweb.asm.ClassReader;
19import org.objectweb.asm.ClassVisitor;
20import org.objectweb.asm.Opcodes;
21
22import java.util.*;
23
24public class JarIndex {
25
26 private final ReferencedEntryPool entryPool;
27
28 private Set<ClassEntry> obfClassEntries;
29 private TranslationIndex translationIndex;
30 private Map<Entry, AccessFlags> access;
31 private Multimap<ClassEntry, FieldDefEntry> fields;
32 private Multimap<ClassEntry, MethodDefEntry> methods;
33 private Multimap<String, MethodDefEntry> methodImplementations;
34 private Multimap<MethodEntry, EntryReference<MethodEntry, MethodDefEntry>> methodsReferencing;
35 private Multimap<ClassEntry, EntryReference<ClassEntry, MethodDefEntry>> methodsReferencingClasses;
36 private Multimap<MethodEntry, MethodEntry> methodReferences;
37 private Multimap<FieldEntry, EntryReference<FieldEntry, MethodDefEntry>> fieldReferences;
38 private Multimap<ClassEntry, ClassEntry> innerClassesByOuter;
39 private Map<ClassEntry, ClassEntry> outerClassesByInner;
40 private Map<MethodEntry, MethodEntry> bridgedMethods;
41 private Set<MethodEntry> syntheticMethods;
42
43 public JarIndex(ReferencedEntryPool entryPool) {
44 this.entryPool = entryPool;
45 this.obfClassEntries = Sets.newHashSet();
46 this.translationIndex = new TranslationIndex(entryPool);
47 this.access = Maps.newHashMap();
48 this.fields = HashMultimap.create();
49 this.methods = HashMultimap.create();
50 this.methodImplementations = HashMultimap.create();
51 this.methodsReferencingClasses = HashMultimap.create();
52 this.methodsReferencing = HashMultimap.create();
53 this.methodReferences = HashMultimap.create();
54 this.fieldReferences = HashMultimap.create();
55 this.innerClassesByOuter = HashMultimap.create();
56 this.outerClassesByInner = Maps.newHashMap();
57 this.bridgedMethods = Maps.newHashMap();
58 this.syntheticMethods = Sets.newHashSet();
59 }
60
61 public void indexJar(ParsedJar jar, boolean buildInnerClasses) {
62
63 // step 1: read the class names
64 obfClassEntries.addAll(jar.getClassEntries());
65
66 // step 2: index classes, fields, methods, interfaces
67 if (buildInnerClasses) {
68 // + step 5: index inner classes
69 jar.visitReader(name -> new IndexClassVisitor(this, Opcodes.ASM5, new IndexInnerClassVisitor(this, Opcodes.ASM5)), ClassReader.SKIP_CODE);
70 } else {
71 jar.visitReader(name -> new IndexClassVisitor(this, Opcodes.ASM5), ClassReader.SKIP_CODE);
72 }
73
74 // step 3: index field, method, constructor references
75 jar.visitReader(name -> new IndexReferenceVisitor(this, Opcodes.ASM5), ClassReader.SKIP_FRAMES);
76
77 // step 4: index access and bridged methods
78 for (MethodDefEntry methodEntry : methods.values()) {
79 // look for access and bridged methods
80 MethodEntry accessedMethod = findAccessMethod(methodEntry);
81 if (accessedMethod != null) {
82 if (isBridgedMethod(accessedMethod, methodEntry)) {
83 this.bridgedMethods.put(methodEntry, accessedMethod);
84 }
85 }
86 }
87
88 if (buildInnerClasses) {
89 // step 6: update other indices with inner class info
90 Map<String, String> renames = Maps.newHashMap();
91 for (ClassEntry innerClassEntry : this.innerClassesByOuter.values()) {
92 String newName = innerClassEntry.buildClassEntry(getObfClassChain(innerClassEntry)).getName();
93 if (!innerClassEntry.getName().equals(newName)) {
94 // DEBUG
95 //System.out.println("REPLACE: " + innerClassEntry.getName() + " WITH " + newName);
96 renames.put(innerClassEntry.getName(), newName);
97 }
98 }
99 EntryRenamer.renameClassesInSet(renames, this.obfClassEntries);
100 this.translationIndex.renameClasses(renames);
101 EntryRenamer.renameClassesInMultimap(renames, this.methodImplementations);
102 EntryRenamer.renameClassesInMultimap(renames, this.methodsReferencingClasses);
103 EntryRenamer.renameClassesInMultimap(renames, this.methodsReferencing);
104 EntryRenamer.renameClassesInMultimap(renames, this.methodReferences);
105 EntryRenamer.renameClassesInMultimap(renames, this.fieldReferences);
106 EntryRenamer.renameClassesInMap(renames, this.access);
107 }
108 }
109
110 protected ClassDefEntry indexClass(int access, String name, String signature, String superName, String[] interfaces) {
111 for (String interfaceName : interfaces) {
112 if (name.equals(interfaceName)) {
113 throw new IllegalArgumentException("Class cannot be its own interface! " + name);
114 }
115 }
116 ClassDefEntry entry = this.translationIndex.indexClass(access, name, signature, superName, interfaces);
117 this.access.put(entry, entry.getAccess());
118 return entry;
119 }
120
121 protected void indexField(ClassDefEntry owner, int access, String name, String desc, String signature) {
122 FieldDefEntry fieldEntry = new FieldDefEntry(owner, name, new TypeDescriptor(desc), Signature.createTypedSignature(signature), new AccessFlags(access));
123 this.translationIndex.indexField(fieldEntry);
124 this.access.put(fieldEntry, fieldEntry.getAccess());
125 this.fields.put(fieldEntry.getOwnerClassEntry(), fieldEntry);
126 }
127
128 protected void indexMethod(ClassDefEntry owner, int access, String name, String desc, String signature) {
129 MethodDefEntry methodEntry = new MethodDefEntry(owner, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access));
130 this.translationIndex.indexMethod(methodEntry);
131 this.access.put(methodEntry, methodEntry.getAccess());
132 this.methods.put(methodEntry.getOwnerClassEntry(), methodEntry);
133
134 if (new AccessFlags(access).isSynthetic()) {
135 syntheticMethods.add(methodEntry);
136 }
137
138 // we don't care about constructors here
139 if (!methodEntry.isConstructor()) {
140 // index implementation
141 this.methodImplementations.put(methodEntry.getClassName(), methodEntry);
142 }
143 }
144
145 protected void indexMethodCall(MethodDefEntry callerEntry, String owner, String name, String desc) {
146 ClassEntry referencedClass = entryPool.getClass(owner);
147 MethodEntry referencedMethod = new MethodEntry(referencedClass, name, new MethodDescriptor(desc));
148 ClassEntry resolvedClassEntry = translationIndex.resolveEntryOwner(referencedMethod);
149 if (resolvedClassEntry != null && !resolvedClassEntry.equals(referencedMethod.getOwnerClassEntry())) {
150 referencedMethod = referencedMethod.updateOwnership(resolvedClassEntry);
151 }
152 methodsReferencing.put(referencedMethod, new EntryReference<>(referencedMethod, referencedMethod.getName(), callerEntry));
153 if (referencedMethod.isConstructor()) {
154 methodsReferencingClasses.put(referencedClass, new EntryReference<>(referencedClass, referencedMethod.getName(), callerEntry));
155 }
156 methodReferences.put(callerEntry, referencedMethod);
157 }
158
159 protected void indexFieldAccess(MethodDefEntry callerEntry, String owner, String name, String desc) {
160 FieldEntry referencedField = new FieldEntry(entryPool.getClass(owner), name, new TypeDescriptor(desc));
161 ClassEntry resolvedClassEntry = translationIndex.resolveEntryOwner(referencedField);
162 if (resolvedClassEntry != null && !resolvedClassEntry.equals(referencedField.getOwnerClassEntry())) {
163 referencedField = referencedField.updateOwnership(resolvedClassEntry);
164 }
165 fieldReferences.put(referencedField, new EntryReference<>(referencedField, referencedField.getName(), callerEntry));
166 }
167
168 public void indexInnerClass(ClassEntry innerEntry, ClassEntry outerEntry) {
169 this.innerClassesByOuter.put(outerEntry, innerEntry);
170 this.outerClassesByInner.putIfAbsent(innerEntry, outerEntry);
171 }
172
173 private MethodEntry findAccessMethod(MethodDefEntry method) {
174
175 // we want to find all compiler-added methods that directly call another with no processing
176
177 // skip non-synthetic methods
178 if (!method.getAccess().isSynthetic()) {
179 return null;
180 }
181
182 // get all the methods that we call
183 final Collection<MethodEntry> referencedMethods = methodReferences.get(method);
184
185 // is there just one?
186 if (referencedMethods.size() != 1) {
187 return null;
188 }
189
190 return referencedMethods.stream().findFirst().orElse(null);
191 }
192
193 private boolean isBridgedMethod(MethodEntry called, MethodEntry access) {
194 // Bridged methods will always have the same name as the method they are calling
195 // They will also have the same amount of parameters (though equal descriptors cannot be guaranteed)
196 if (!called.getName().equals(access.getName()) || called.getDesc().getArgumentDescs().size() != access.getDesc().getArgumentDescs().size()) {
197 return false;
198 }
199
200 TypeDescriptor accessReturn = access.getDesc().getReturnDesc();
201 TypeDescriptor calledReturn = called.getDesc().getReturnDesc();
202 if (calledReturn.isVoid() || calledReturn.isPrimitive() || accessReturn.isVoid() || accessReturn.isPrimitive()) {
203 return false;
204 }
205
206 // Bridged methods will never have the same type as what they are calling
207 if (accessReturn.equals(calledReturn)) {
208 return false;
209 }
210
211 String accessType = accessReturn.toString();
212
213 // If we're casting down from generic type to type-erased Object we're a bridge method
214 if (accessType.equals("Ljava/lang/Object;")) {
215 return true;
216 }
217
218 // Now we need to detect cases where we are being casted down to a higher type bound
219 List<ClassEntry> calledAncestry = translationIndex.getAncestry(calledReturn.getTypeEntry());
220 return calledAncestry.contains(accessReturn.getTypeEntry());
221 }
222
223 public Set<ClassEntry> getObfClassEntries() {
224 return this.obfClassEntries;
225 }
226
227 public Collection<FieldDefEntry> getObfFieldEntries() {
228 return this.fields.values();
229 }
230
231 public Collection<FieldDefEntry> getObfFieldEntries(ClassEntry classEntry) {
232 return this.fields.get(classEntry);
233 }
234
235 public Collection<MethodDefEntry> getObfBehaviorEntries() {
236 return this.methods.values();
237 }
238
239 public Collection<MethodDefEntry> getObfBehaviorEntries(ClassEntry classEntry) {
240 return this.methods.get(classEntry);
241 }
242
243 public TranslationIndex getTranslationIndex() {
244 return this.translationIndex;
245 }
246
247 @Deprecated
248 public Access getAccess(Entry entry) {
249 AccessFlags flags = getAccessFlags(entry);
250 return flags != null ? Access.get(flags) : null;
251 }
252
253 public AccessFlags getAccessFlags(Entry entry) {
254 return this.access.get(entry);
255 }
256
257 public ClassInheritanceTreeNode getClassInheritance(Translator deobfuscatingTranslator, ClassEntry obfClassEntry) {
258
259 // get the root node
260 List<String> ancestry = Lists.newArrayList();
261 ancestry.add(obfClassEntry.getName());
262 for (ClassEntry classEntry : this.translationIndex.getAncestry(obfClassEntry)) {
263 if (containsObfClass(classEntry)) {
264 ancestry.add(classEntry.getName());
265 }
266 }
267 ClassInheritanceTreeNode rootNode = new ClassInheritanceTreeNode(
268 deobfuscatingTranslator,
269 ancestry.get(ancestry.size() - 1)
270 );
271
272 // expand all children recursively
273 rootNode.load(this.translationIndex, true);
274
275 return rootNode;
276 }
277
278 public ClassImplementationsTreeNode getClassImplementations(Translator deobfuscatingTranslator, ClassEntry obfClassEntry) {
279
280 // is this even an interface?
281 if (isInterface(obfClassEntry.getClassName())) {
282 ClassImplementationsTreeNode node = new ClassImplementationsTreeNode(deobfuscatingTranslator, obfClassEntry);
283 node.load(this);
284 return node;
285 }
286 return null;
287 }
288
289 public MethodInheritanceTreeNode getMethodInheritance(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) {
290 // travel to the ancestor implementation
291 LinkedList<ClassEntry> entries = new LinkedList<>();
292 entries.add(obfMethodEntry.getOwnerClassEntry());
293
294 // TODO: This could be optimized to not go through interfaces repeatedly...
295
296 ClassEntry baseImplementationClassEntry = obfMethodEntry.getOwnerClassEntry();
297
298 for (ClassEntry itf : getInterfaces(obfMethodEntry.getOwnerClassEntry().getClassName())) {
299 MethodEntry itfMethodEntry = entryPool.getMethod(itf, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString());
300 if (itfMethodEntry != null && containsObfMethod(itfMethodEntry)) {
301 baseImplementationClassEntry = itf;
302 }
303 }
304
305 for (ClassEntry ancestorClassEntry : this.translationIndex.getAncestry(entries.remove())) {
306 MethodEntry ancestorMethodEntry = entryPool.getMethod(ancestorClassEntry, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString());
307 if (ancestorMethodEntry != null) {
308 if (containsObfMethod(ancestorMethodEntry)) {
309 baseImplementationClassEntry = ancestorClassEntry;
310 }
311
312 for (ClassEntry itf : getInterfaces(ancestorClassEntry.getClassName())) {
313 MethodEntry itfMethodEntry = entryPool.getMethod(itf, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString());
314 if (itfMethodEntry != null && containsObfMethod(itfMethodEntry)) {
315 baseImplementationClassEntry = itf;
316 }
317 }
318 }
319 }
320
321 // make a root node at the base
322 MethodEntry methodEntry = entryPool.getMethod(baseImplementationClassEntry, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString());
323 MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode(
324 deobfuscatingTranslator,
325 methodEntry,
326 containsObfMethod(methodEntry)
327 );
328
329 // expand the full tree
330 rootNode.load(this, true);
331
332 return rootNode;
333 }
334
335 public List<MethodImplementationsTreeNode> getMethodImplementations(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) {
336
337 List<MethodEntry> interfaceMethodEntries = Lists.newArrayList();
338
339 // is this method on an interface?
340 if (isInterface(obfMethodEntry.getClassName())) {
341 interfaceMethodEntries.add(obfMethodEntry);
342 } else {
343 // get the interface class
344 for (ClassEntry interfaceEntry : getInterfaces(obfMethodEntry.getClassName())) {
345
346 // is this method defined in this interface?
347 MethodEntry methodInterface = entryPool.getMethod(interfaceEntry, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString());
348 if (methodInterface != null && containsObfMethod(methodInterface)) {
349 interfaceMethodEntries.add(methodInterface);
350 }
351 }
352 }
353
354 List<MethodImplementationsTreeNode> nodes = Lists.newArrayList();
355 if (!interfaceMethodEntries.isEmpty()) {
356 for (MethodEntry interfaceMethodEntry : interfaceMethodEntries) {
357 MethodImplementationsTreeNode node = new MethodImplementationsTreeNode(deobfuscatingTranslator, interfaceMethodEntry);
358 node.load(this);
359 nodes.add(node);
360 }
361 }
362 return nodes;
363 }
364
365 public Set<MethodEntry> getRelatedMethodImplementations(MethodEntry obfMethodEntry) {
366 AccessFlags flags = getAccessFlags(obfMethodEntry);
367 if (flags.isPrivate() || flags.isStatic()) {
368 return Collections.singleton(obfMethodEntry);
369 }
370
371 Set<MethodEntry> methodEntries = Sets.newHashSet();
372 getRelatedMethodImplementations(methodEntries, getMethodInheritance(new DirectionalTranslator(entryPool), obfMethodEntry));
373 return methodEntries;
374 }
375
376 private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodInheritanceTreeNode node) {
377 MethodEntry methodEntry = node.getMethodEntry();
378 if (methodEntries.contains(methodEntry)) {
379 return;
380 }
381
382 if (containsObfMethod(methodEntry)) {
383 AccessFlags flags = getAccessFlags(methodEntry);
384 if (!flags.isPrivate() && !flags.isStatic()) {
385 // collect the entry
386 methodEntries.add(methodEntry);
387 }
388 }
389
390 // look at bridge methods!
391 MethodEntry bridgedMethod = getBridgedMethod(methodEntry);
392 while (bridgedMethod != null) {
393 methodEntries.addAll(getRelatedMethodImplementations(bridgedMethod));
394 bridgedMethod = getBridgedMethod(bridgedMethod);
395 }
396
397 // look at interface methods too
398 for (MethodImplementationsTreeNode implementationsNode : getMethodImplementations(new DirectionalTranslator(entryPool), methodEntry)) {
399 getRelatedMethodImplementations(methodEntries, implementationsNode);
400 }
401
402 // recurse
403 for (int i = 0; i < node.getChildCount(); i++) {
404 getRelatedMethodImplementations(methodEntries, (MethodInheritanceTreeNode) node.getChildAt(i));
405 }
406 }
407
408 private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodImplementationsTreeNode node) {
409 MethodEntry methodEntry = node.getMethodEntry();
410 if (containsObfMethod(methodEntry)) {
411 AccessFlags flags = getAccessFlags(methodEntry);
412 if (!flags.isPrivate() && !flags.isStatic()) {
413 // collect the entry
414 methodEntries.add(methodEntry);
415 }
416 }
417
418 // look at bridge methods!
419 MethodEntry bridgedMethod = getBridgedMethod(methodEntry);
420 while (bridgedMethod != null) {
421 methodEntries.addAll(getRelatedMethodImplementations(bridgedMethod));
422 bridgedMethod = getBridgedMethod(bridgedMethod);
423 }
424
425 // recurse
426 for (int i = 0; i < node.getChildCount(); i++) {
427 getRelatedMethodImplementations(methodEntries, (MethodImplementationsTreeNode) node.getChildAt(i));
428 }
429 }
430
431 public Collection<EntryReference<FieldEntry, MethodDefEntry>> getFieldReferences(FieldEntry fieldEntry) {
432 return this.fieldReferences.get(fieldEntry);
433 }
434
435 public Collection<FieldEntry> getReferencedFields(MethodDefEntry methodEntry) {
436 // linear search is fast enough for now
437 Set<FieldEntry> fieldEntries = Sets.newHashSet();
438 for (EntryReference<FieldEntry, MethodDefEntry> reference : this.fieldReferences.values()) {
439 if (reference.context == methodEntry) {
440 fieldEntries.add(reference.entry);
441 }
442 }
443 return fieldEntries;
444 }
445
446 public Collection<EntryReference<ClassEntry, MethodDefEntry>> getMethodsReferencing(ClassEntry classEntry) {
447 return this.methodsReferencingClasses.get(classEntry);
448 }
449
450 @Deprecated
451 public Collection<EntryReference<MethodEntry, MethodDefEntry>> getMethodsReferencing(MethodEntry methodEntry) {
452 return getMethodsReferencing(methodEntry, false);
453 }
454
455 public Collection<EntryReference<MethodEntry, MethodDefEntry>> getMethodsReferencing(MethodEntry methodEntry, boolean recurse) {
456 if (!recurse) {
457 return this.methodsReferencing.get(methodEntry);
458 }
459
460 List<EntryReference<MethodEntry, MethodDefEntry>> references = new ArrayList<>();
461 Set<MethodEntry> methodEntries = getRelatedMethodImplementations(methodEntry);
462 for (MethodEntry entry : methodEntries) {
463 references.addAll(getMethodsReferencing(entry, false));
464 }
465 return references;
466 }
467
468 public Collection<MethodEntry> getReferencedMethods(MethodDefEntry methodEntry) {
469 return this.methodReferences.get(methodEntry);
470 }
471
472 public Collection<ClassEntry> getInnerClasses(ClassEntry obfOuterClassEntry) {
473 return this.innerClassesByOuter.get(obfOuterClassEntry);
474 }
475
476 public ClassEntry getOuterClass(ClassEntry obfInnerClassEntry) {
477 return this.outerClassesByInner.get(obfInnerClassEntry);
478 }
479
480 public boolean isSyntheticMethod(MethodEntry methodEntry) {
481 return this.syntheticMethods.contains(methodEntry);
482 }
483
484 public Set<ClassEntry> getInterfaces(String className) {
485 ClassEntry classEntry = entryPool.getClass(className);
486 Set<ClassEntry> interfaces = new HashSet<>(this.translationIndex.getInterfaces(classEntry));
487 for (ClassEntry ancestor : this.translationIndex.getAncestry(classEntry)) {
488 interfaces.addAll(this.translationIndex.getInterfaces(ancestor));
489 }
490 return interfaces;
491 }
492
493 public Set<String> getImplementingClasses(String targetInterfaceName) {
494
495 // linear search is fast enough for now
496 Set<String> classNames = Sets.newHashSet();
497 for (Map.Entry<ClassEntry, ClassEntry> entry : this.translationIndex.getClassInterfaces()) {
498 ClassEntry classEntry = entry.getKey();
499 ClassEntry interfaceEntry = entry.getValue();
500 if (interfaceEntry.getName().equals(targetInterfaceName)) {
501 String className = classEntry.getClassName();
502 classNames.add(className);
503 if (isInterface(className)) {
504 classNames.addAll(getImplementingClasses(className));
505 }
506
507 this.translationIndex.getSubclassNamesRecursively(classNames, classEntry);
508 }
509 }
510 return classNames;
511 }
512
513 public boolean isInterface(String className) {
514 return this.translationIndex.isInterface(entryPool.getClass(className));
515 }
516
517 public boolean containsObfClass(ClassEntry obfClassEntry) {
518 return this.obfClassEntries.contains(obfClassEntry);
519 }
520
521 public boolean containsObfField(FieldEntry obfFieldEntry) {
522 return this.access.containsKey(obfFieldEntry);
523 }
524
525 public boolean containsObfMethod(MethodEntry obfMethodEntry) {
526 return this.access.containsKey(obfMethodEntry);
527 }
528
529 public boolean containsEntryWithSameName(Entry entry) {
530 for (Entry target : this.access.keySet())
531 if (target.getName().equals(entry.getName()) && entry.getClass().isInstance(target.getClass()))
532 return true;
533 return false;
534 }
535
536 public boolean containsObfVariable(LocalVariableEntry obfVariableEntry) {
537 // check the behavior
538 if (!containsObfMethod(obfVariableEntry.getOwnerEntry())) {
539 return false;
540 }
541
542 return true;
543 }
544
545 public boolean containsObfEntry(Entry obfEntry) {
546 if (obfEntry instanceof ClassEntry) {
547 return containsObfClass((ClassEntry) obfEntry);
548 } else if (obfEntry instanceof FieldEntry) {
549 return containsObfField((FieldEntry) obfEntry);
550 } else if (obfEntry instanceof MethodEntry) {
551 return containsObfMethod((MethodEntry) obfEntry);
552 } else if (obfEntry instanceof LocalVariableEntry) {
553 return containsObfVariable((LocalVariableEntry) obfEntry);
554 } else {
555 throw new Error("Entry desc not supported: " + obfEntry.getClass().getName());
556 }
557 }
558
559 public MethodEntry getBridgedMethod(MethodEntry bridgeMethodEntry) {
560 return this.bridgedMethods.get(bridgeMethodEntry);
561 }
562
563 public List<ClassEntry> getObfClassChain(ClassEntry obfClassEntry) {
564
565 // build class chain in inner-to-outer order
566 List<ClassEntry> obfClassChain = Lists.newArrayList(obfClassEntry);
567 ClassEntry checkClassEntry = obfClassEntry;
568 while (true) {
569 ClassEntry obfOuterClassEntry = getOuterClass(checkClassEntry);
570 if (obfOuterClassEntry != null) {
571 obfClassChain.add(obfOuterClassEntry);
572 checkClassEntry = obfOuterClassEntry;
573 } else {
574 break;
575 }
576 }
577
578 // switch to outer-to-inner order
579 Collections.reverse(obfClassChain);
580
581 return obfClassChain;
582 }
583}
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 @@
12package cuchaz.enigma.analysis; 12package cuchaz.enigma.analysis;
13 13
14import com.google.common.collect.Lists; 14import com.google.common.collect.Lists;
15import cuchaz.enigma.mapping.entry.ClassEntry; 15import cuchaz.enigma.analysis.index.EntryIndex;
16import cuchaz.enigma.mapping.entry.MethodEntry; 16import cuchaz.enigma.analysis.index.InheritanceIndex;
17import cuchaz.enigma.mapping.Translator; 17import cuchaz.enigma.analysis.index.JarIndex;
18import cuchaz.enigma.translation.Translator;
19import cuchaz.enigma.translation.representation.entry.ClassEntry;
20import cuchaz.enigma.translation.representation.entry.MethodEntry;
18 21
19import javax.swing.tree.DefaultMutableTreeNode; 22import javax.swing.tree.DefaultMutableTreeNode;
23import java.util.Collection;
20import java.util.List; 24import java.util.List;
21 25
22public class MethodImplementationsTreeNode extends DefaultMutableTreeNode { 26public class MethodImplementationsTreeNode extends DefaultMutableTreeNode {
23 27
24 private Translator deobfuscatingTranslator; 28 private final Translator translator;
25 private MethodEntry entry; 29 private MethodEntry entry;
26 30
27 public MethodImplementationsTreeNode(Translator deobfuscatingTranslator, MethodEntry entry) { 31 public MethodImplementationsTreeNode(Translator translator, MethodEntry entry) {
32 this.translator = translator;
28 if (entry == null) { 33 if (entry == null) {
29 throw new IllegalArgumentException("Entry cannot be null!"); 34 throw new IllegalArgumentException("Entry cannot be null!");
30 } 35 }
31 36
32 this.deobfuscatingTranslator = deobfuscatingTranslator;
33 this.entry = entry; 37 this.entry = entry;
34 } 38 }
35 39
@@ -53,35 +57,25 @@ public class MethodImplementationsTreeNode extends DefaultMutableTreeNode {
53 return this.entry; 57 return this.entry;
54 } 58 }
55 59
56 public String getDeobfClassName() {
57 return this.deobfuscatingTranslator.getTranslatedClass(this.entry.getOwnerClassEntry()).getClassName();
58 }
59
60 public String getDeobfMethodName() {
61 return this.deobfuscatingTranslator.getTranslatedMethod(this.entry).getName();
62 }
63
64 @Override 60 @Override
65 public String toString() { 61 public String toString() {
66 String className = getDeobfClassName(); 62 MethodEntry translatedEntry = translator.translate(entry);
67 if (className == null) { 63 String className = translatedEntry.getParent().getFullName();
68 className = this.entry.getClassName(); 64 String methodName = translatedEntry.getName();
69 }
70
71 String methodName = getDeobfMethodName();
72 if (methodName == null) {
73 methodName = this.entry.getName();
74 }
75 return className + "." + methodName + "()"; 65 return className + "." + methodName + "()";
76 } 66 }
77 67
78 public void load(JarIndex index) { 68 public void load(JarIndex index) {
79 // get all method implementations 69 // get all method implementations
80 List<MethodImplementationsTreeNode> nodes = Lists.newArrayList(); 70 List<MethodImplementationsTreeNode> nodes = Lists.newArrayList();
81 for (String implementingClassName : index.getImplementingClasses(this.entry.getClassName())) { 71 EntryIndex entryIndex = index.getEntryIndex();
82 MethodEntry methodEntry = new MethodEntry(new ClassEntry(implementingClassName), this.entry.getName(), this.entry.getDesc()); 72 InheritanceIndex inheritanceIndex = index.getInheritanceIndex();
83 if (index.containsObfMethod(methodEntry)) { 73
84 nodes.add(new MethodImplementationsTreeNode(this.deobfuscatingTranslator, methodEntry)); 74 Collection<ClassEntry> inheritors = inheritanceIndex.getChildren(entry.getParent());
75 for (ClassEntry inheritor : inheritors) {
76 MethodEntry methodEntry = entry.withParent(inheritor);
77 if (entryIndex.hasMethod(methodEntry)) {
78 nodes.add(new MethodImplementationsTreeNode(translator, methodEntry));
85 } 79 }
86 } 80 }
87 81
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 @@
12package cuchaz.enigma.analysis; 12package cuchaz.enigma.analysis;
13 13
14import com.google.common.collect.Lists; 14import com.google.common.collect.Lists;
15import cuchaz.enigma.mapping.entry.ClassEntry; 15import cuchaz.enigma.analysis.index.EntryIndex;
16import cuchaz.enigma.mapping.entry.MethodEntry; 16import cuchaz.enigma.analysis.index.InheritanceIndex;
17import cuchaz.enigma.mapping.Translator; 17import cuchaz.enigma.analysis.index.JarIndex;
18import cuchaz.enigma.translation.Translator;
19import cuchaz.enigma.translation.representation.entry.ClassEntry;
20import cuchaz.enigma.translation.representation.entry.MethodEntry;
18 21
19import javax.swing.tree.DefaultMutableTreeNode; 22import javax.swing.tree.DefaultMutableTreeNode;
20import java.util.List; 23import java.util.List;
21 24
22public class MethodInheritanceTreeNode extends DefaultMutableTreeNode { 25public class MethodInheritanceTreeNode extends DefaultMutableTreeNode {
23 26
24 private Translator deobfuscatingTranslator; 27 private final Translator translator;
25 private MethodEntry entry; 28 private MethodEntry entry;
26 private boolean isImplemented; 29 private boolean isImplemented;
27 30
28 public MethodInheritanceTreeNode(Translator deobfuscatingTranslator, MethodEntry entry, boolean isImplemented) { 31 public MethodInheritanceTreeNode(Translator translator, MethodEntry entry, boolean isImplemented) {
29 this.deobfuscatingTranslator = deobfuscatingTranslator; 32 this.translator = translator;
30 this.entry = entry; 33 this.entry = entry;
31 this.isImplemented = isImplemented; 34 this.isImplemented = isImplemented;
32 } 35 }
@@ -51,32 +54,19 @@ public class MethodInheritanceTreeNode extends DefaultMutableTreeNode {
51 return this.entry; 54 return this.entry;
52 } 55 }
53 56
54 public String getDeobfClassName() {
55 return this.deobfuscatingTranslator.getTranslatedClass(this.entry.getOwnerClassEntry()).getName();
56 }
57
58 public String getDeobfMethodName() {
59 return this.deobfuscatingTranslator.getTranslatedMethod(this.entry).getName();
60 }
61
62 public boolean isImplemented() { 57 public boolean isImplemented() {
63 return this.isImplemented; 58 return this.isImplemented;
64 } 59 }
65 60
66 @Override 61 @Override
67 public String toString() { 62 public String toString() {
68 String className = getDeobfClassName(); 63 MethodEntry translatedEntry = translator.translate(entry);
69 if (className == null) { 64 String className = translatedEntry.getContainingClass().getFullName();
70 className = this.entry.getClassName();
71 }
72 65
73 if (!this.isImplemented) { 66 if (!this.isImplemented) {
74 return className; 67 return className;
75 } else { 68 } else {
76 String methodName = getDeobfMethodName(); 69 String methodName = translatedEntry.getName();
77 if (methodName == null) {
78 methodName = this.entry.getName();
79 }
80 return className + "." + methodName + "()"; 70 return className + "." + methodName + "()";
81 } 71 }
82 } 72 }
@@ -84,14 +74,12 @@ public class MethodInheritanceTreeNode extends DefaultMutableTreeNode {
84 public void load(JarIndex index, boolean recurse) { 74 public void load(JarIndex index, boolean recurse) {
85 // get all the child nodes 75 // get all the child nodes
86 List<MethodInheritanceTreeNode> nodes = Lists.newArrayList(); 76 List<MethodInheritanceTreeNode> nodes = Lists.newArrayList();
87 for (ClassEntry subclassEntry : index.getTranslationIndex().getSubclass(this.entry.getOwnerClassEntry())) { 77 EntryIndex entryIndex = index.getEntryIndex();
88 MethodEntry methodEntry = new MethodEntry(subclassEntry, this.entry.getName(), this.entry.getDesc()); 78 InheritanceIndex inheritanceIndex = index.getInheritanceIndex();
89 nodes.add(new MethodInheritanceTreeNode(this.deobfuscatingTranslator, methodEntry, index.containsObfMethod(methodEntry)));
90 }
91 79
92 for (ClassEntry subclassEntry : index.getTranslationIndex().getImplementers(this.entry.getOwnerClassEntry())) { 80 for (ClassEntry inheritorEntry : inheritanceIndex.getChildren(this.entry.getParent())) {
93 MethodEntry methodEntry = new MethodEntry(subclassEntry, this.entry.getName(), this.entry.getDesc()); 81 MethodEntry methodEntry = new MethodEntry(inheritorEntry, this.entry.getName(), this.entry.getDesc());
94 nodes.add(new MethodInheritanceTreeNode(this.deobfuscatingTranslator, methodEntry, index.containsObfMethod(methodEntry))); 82 nodes.add(new MethodInheritanceTreeNode(translator, methodEntry, entryIndex.hasMethod(methodEntry)));
95 } 83 }
96 84
97 // add them to this node 85 // 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 @@
12package cuchaz.enigma.analysis; 12package cuchaz.enigma.analysis;
13 13
14import com.google.common.collect.Sets; 14import com.google.common.collect.Sets;
15import cuchaz.enigma.bytecode.AccessFlags; 15import cuchaz.enigma.analysis.index.JarIndex;
16import cuchaz.enigma.mapping.*; 16import cuchaz.enigma.analysis.index.ReferenceIndex;
17import cuchaz.enigma.mapping.entry.Entry; 17import cuchaz.enigma.translation.Translator;
18import cuchaz.enigma.mapping.entry.MethodDefEntry; 18import cuchaz.enigma.translation.mapping.EntryResolver;
19import cuchaz.enigma.mapping.entry.MethodEntry; 19import cuchaz.enigma.translation.representation.entry.Entry;
20import cuchaz.enigma.translation.representation.entry.MethodDefEntry;
21import cuchaz.enigma.translation.representation.entry.MethodEntry;
20 22
21import javax.swing.tree.DefaultMutableTreeNode; 23import javax.swing.tree.DefaultMutableTreeNode;
22import javax.swing.tree.TreeNode; 24import javax.swing.tree.TreeNode;
25import java.util.ArrayList;
26import java.util.Collection;
23import java.util.Set; 27import java.util.Set;
24 28
25public class MethodReferenceTreeNode extends DefaultMutableTreeNode 29public class MethodReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode<MethodEntry, MethodDefEntry> {
26 implements ReferenceTreeNode<MethodEntry, MethodDefEntry> {
27 30
28 private Translator deobfuscatingTranslator; 31 private final Translator translator;
29 private MethodEntry entry; 32 private MethodEntry entry;
30 private EntryReference<MethodEntry, MethodDefEntry> reference; 33 private EntryReference<MethodEntry, MethodDefEntry> reference;
31 private AccessFlags access;
32 34
33 public MethodReferenceTreeNode(Translator deobfuscatingTranslator, MethodEntry entry) { 35 public MethodReferenceTreeNode(Translator translator, MethodEntry entry) {
34 this.deobfuscatingTranslator = deobfuscatingTranslator; 36 this.translator = translator;
35 this.entry = entry; 37 this.entry = entry;
36 this.reference = null; 38 this.reference = null;
37 } 39 }
38 40
39 public MethodReferenceTreeNode(Translator deobfuscatingTranslator, 41 public MethodReferenceTreeNode(Translator translator, EntryReference<MethodEntry, MethodDefEntry> reference) {
40 EntryReference<MethodEntry, MethodDefEntry> reference, AccessFlags access) { 42 this.translator = translator;
41 this.deobfuscatingTranslator = deobfuscatingTranslator;
42 this.entry = reference.entry; 43 this.entry = reference.entry;
43 this.reference = reference; 44 this.reference = reference;
44 this.access = access;
45 } 45 }
46 46
47 @Override 47 @Override
@@ -57,21 +57,17 @@ public class MethodReferenceTreeNode extends DefaultMutableTreeNode
57 @Override 57 @Override
58 public String toString() { 58 public String toString() {
59 if (this.reference != null) { 59 if (this.reference != null) {
60 return String.format("%s (%s)", this.deobfuscatingTranslator.getTranslatedMethodDef(this.reference.context), 60 return String.format("%s", translator.translate(this.reference.context));
61 this.access);
62 } 61 }
63 return this.deobfuscatingTranslator.getTranslatedMethod(this.entry).getName(); 62 return translator.translate(this.entry).getName();
64 }
65
66 @Deprecated
67 public void load(JarIndex index, boolean recurse) {
68 load(index, recurse, false);
69 } 63 }
70 64
71 public void load(JarIndex index, boolean recurse, boolean recurseMethod) { 65 public void load(JarIndex index, boolean recurse, boolean recurseMethod) {
72 // get all the child nodes 66 // get all the child nodes
73 for (EntryReference<MethodEntry, MethodDefEntry> reference : index.getMethodsReferencing(this.entry, recurseMethod)) { 67 Collection<EntryReference<MethodEntry, MethodDefEntry>> references = getReferences(index, recurseMethod);
74 add(new MethodReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccessFlags(this.entry))); 68
69 for (EntryReference<MethodEntry, MethodDefEntry> reference : references) {
70 add(new MethodReferenceTreeNode(translator, reference));
75 } 71 }
76 72
77 if (recurse && this.children != null) { 73 if (recurse && this.children != null) {
@@ -80,7 +76,7 @@ public class MethodReferenceTreeNode extends DefaultMutableTreeNode
80 MethodReferenceTreeNode node = (MethodReferenceTreeNode) child; 76 MethodReferenceTreeNode node = (MethodReferenceTreeNode) child;
81 77
82 // don't recurse into ancestor 78 // don't recurse into ancestor
83 Set<Entry> ancestors = Sets.newHashSet(); 79 Set<Entry<?>> ancestors = Sets.newHashSet();
84 TreeNode n = node; 80 TreeNode n = node;
85 while (n.getParent() != null) { 81 while (n.getParent() != null) {
86 n = n.getParent(); 82 n = n.getParent();
@@ -92,9 +88,26 @@ public class MethodReferenceTreeNode extends DefaultMutableTreeNode
92 continue; 88 continue;
93 } 89 }
94 90
95 node.load(index, true); 91 node.load(index, true, false);
96 } 92 }
97 } 93 }
98 } 94 }
99 } 95 }
96
97 private Collection<EntryReference<MethodEntry, MethodDefEntry>> getReferences(JarIndex index, boolean recurseMethod) {
98 ReferenceIndex referenceIndex = index.getReferenceIndex();
99
100 if (recurseMethod) {
101 Collection<EntryReference<MethodEntry, MethodDefEntry>> references = new ArrayList<>();
102
103 EntryResolver entryResolver = index.getEntryResolver();
104 for (MethodEntry methodEntry : entryResolver.resolveEquivalentMethods(entry)) {
105 references.addAll(referenceIndex.getReferencesToMethod(methodEntry));
106 }
107
108 return references;
109 } else {
110 return referenceIndex.getReferencesToMethod(entry);
111 }
112 }
100} 113}
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 @@
12package cuchaz.enigma.analysis; 12package cuchaz.enigma.analysis;
13 13
14import com.google.common.io.ByteStreams; 14import com.google.common.io.ByteStreams;
15import cuchaz.enigma.mapping.entry.ClassEntry; 15import cuchaz.enigma.translation.representation.entry.ClassEntry;
16import org.objectweb.asm.ClassReader; 16import org.objectweb.asm.ClassReader;
17import org.objectweb.asm.ClassVisitor; 17import org.objectweb.asm.ClassVisitor;
18import org.objectweb.asm.tree.ClassNode; 18import org.objectweb.asm.tree.ClassNode;
19 19
20import javax.annotation.Nullable;
20import java.io.BufferedInputStream; 21import java.io.BufferedInputStream;
21import java.io.IOException; 22import java.io.IOException;
22import java.io.InputStream; 23import java.io.InputStream;
@@ -100,9 +101,14 @@ public class ParsedJar {
100 return entries; 101 return entries;
101 } 102 }
102 103
104 @Nullable
103 public ClassNode getClassNode(String name) { 105 public ClassNode getClassNode(String name) {
104 return nodeCache.computeIfAbsent(name, (n) -> { 106 return nodeCache.computeIfAbsent(name, (n) -> {
105 ClassReader reader = new ClassReader(classBytes.get(name)); 107 byte[] bytes = classBytes.get(name);
108 if (bytes == null) {
109 return null;
110 }
111 ClassReader reader = new ClassReader(bytes);
106 ClassNode node = new ClassNode(); 112 ClassNode node = new ClassNode();
107 reader.accept(node, 0); 113 reader.accept(node, 0);
108 return node; 114 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 @@
11 11
12package cuchaz.enigma.analysis; 12package cuchaz.enigma.analysis;
13 13
14import cuchaz.enigma.mapping.entry.Entry; 14import cuchaz.enigma.translation.representation.entry.Entry;
15 15
16public interface ReferenceTreeNode<E extends Entry, C extends Entry> { 16public interface ReferenceTreeNode<E extends Entry<?>, C extends Entry<?>> {
17 E getEntry(); 17 E getEntry();
18 18
19 EntryReference<E, C> getReference(); 19 EntryReference<E, C> 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 @@
11 11
12package cuchaz.enigma.analysis; 12package cuchaz.enigma.analysis;
13 13
14import com.google.common.collect.HashMultimap; 14import com.google.common.collect.*;
15import com.google.common.collect.Lists;
16import com.google.common.collect.Maps;
17import com.google.common.collect.Multimap;
18import com.strobel.decompiler.languages.Region; 15import com.strobel.decompiler.languages.Region;
19import com.strobel.decompiler.languages.java.ast.AstNode; 16import com.strobel.decompiler.languages.java.ast.AstNode;
20import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration; 17import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration;
21import com.strobel.decompiler.languages.java.ast.Identifier; 18import com.strobel.decompiler.languages.java.ast.Identifier;
22import com.strobel.decompiler.languages.java.ast.TypeDeclaration; 19import com.strobel.decompiler.languages.java.ast.TypeDeclaration;
23import cuchaz.enigma.mapping.entry.Entry; 20import cuchaz.enigma.translation.representation.entry.Entry;
24 21
22import javax.annotation.Nullable;
25import java.util.*; 23import java.util.*;
26import java.util.regex.Pattern; 24import java.util.regex.Pattern;
27 25
@@ -29,9 +27,9 @@ public class SourceIndex {
29 private static Pattern ANONYMOUS_INNER = Pattern.compile("\\$\\d+$"); 27 private static Pattern ANONYMOUS_INNER = Pattern.compile("\\$\\d+$");
30 28
31 private String source; 29 private String source;
32 private TreeMap<Token, EntryReference<Entry, Entry>> tokenToReference; 30 private TreeMap<Token, EntryReference<Entry<?>, Entry<?>>> tokenToReference;
33 private Multimap<EntryReference<Entry, Entry>, Token> referenceToTokens; 31 private Multimap<EntryReference<Entry<?>, Entry<?>>, Token> referenceToTokens;
34 private Map<Entry, Token> declarationToToken; 32 private Map<Entry<?>, Token> declarationToToken;
35 private List<Integer> lineOffsets; 33 private List<Integer> lineOffsets;
36 private boolean ignoreBadTokens; 34 private boolean ignoreBadTokens;
37 35
@@ -42,7 +40,7 @@ public class SourceIndex {
42 public SourceIndex(String source, boolean ignoreBadTokens) { 40 public SourceIndex(String source, boolean ignoreBadTokens) {
43 this.source = source; 41 this.source = source;
44 this.ignoreBadTokens = ignoreBadTokens; 42 this.ignoreBadTokens = ignoreBadTokens;
45 this.tokenToReference = Maps.newTreeMap(); 43 this.tokenToReference = new TreeMap<>();
46 this.referenceToTokens = HashMultimap.create(); 44 this.referenceToTokens = HashMultimap.create();
47 this.declarationToToken = Maps.newHashMap(); 45 this.declarationToToken = Maps.newHashMap();
48 calculateLineOffsets(); 46 calculateLineOffsets();
@@ -63,12 +61,12 @@ public class SourceIndex {
63 this.source = source; 61 this.source = source;
64 calculateLineOffsets(); 62 calculateLineOffsets();
65 63
66 for (Entry entry : Lists.newArrayList(declarationToToken.keySet())) { 64 for (Entry<?> entry : Lists.newArrayList(declarationToToken.keySet())) {
67 Token token = declarationToToken.get(entry); 65 Token token = declarationToToken.get(entry);
68 declarationToToken.put(entry, tokenMap.getOrDefault(token, token)); 66 declarationToToken.put(entry, tokenMap.getOrDefault(token, token));
69 } 67 }
70 68
71 for (EntryReference<Entry, Entry> ref : referenceToTokens.keySet()) { 69 for (EntryReference<Entry<?>, Entry<?>> ref : referenceToTokens.keySet()) {
72 Collection<Token> oldTokens = referenceToTokens.get(ref); 70 Collection<Token> oldTokens = referenceToTokens.get(ref);
73 List<Token> newTokens = new ArrayList<>(oldTokens.size()); 71 List<Token> newTokens = new ArrayList<>(oldTokens.size());
74 72
@@ -79,7 +77,8 @@ public class SourceIndex {
79 referenceToTokens.replaceValues(ref, newTokens); 77 referenceToTokens.replaceValues(ref, newTokens);
80 } 78 }
81 79
82 Map<Token, EntryReference<Entry, Entry>> tokenToReferenceCopy = Maps.newHashMap(tokenToReference); 80 TreeMap<Token, EntryReference<Entry<?>, Entry<?>>> tokenToReferenceCopy = new TreeMap<>(tokenToReference);
81
83 tokenToReference.clear(); 82 tokenToReference.clear();
84 for (Token token : tokenToReferenceCopy.keySet()) { 83 for (Token token : tokenToReferenceCopy.keySet()) {
85 tokenToReference.put(tokenMap.getOrDefault(token, token), tokenToReferenceCopy.get(token)); 84 tokenToReference.put(tokenMap.getOrDefault(token, token), tokenToReferenceCopy.get(token));
@@ -112,9 +111,9 @@ public class SourceIndex {
112 return null; 111 return null;
113 } 112 }
114 113
115 if (node instanceof Identifier && name.indexOf('$') >=0 && node.getParent() instanceof ConstructorDeclaration && name.lastIndexOf('$') >= 0 && !ANONYMOUS_INNER.matcher(name).matches()){ 114 if (node instanceof Identifier && name.indexOf('$') >= 0 && node.getParent() instanceof ConstructorDeclaration && name.lastIndexOf('$') >= 0 && !ANONYMOUS_INNER.matcher(name).matches()) {
116 TypeDeclaration type = node.getParent().getParent() instanceof TypeDeclaration ? (TypeDeclaration) node.getParent().getParent() : null; 115 TypeDeclaration type = node.getParent().getParent() instanceof TypeDeclaration ? (TypeDeclaration) node.getParent().getParent() : null;
117 if (type != null){ 116 if (type != null) {
118 name = type.getName(); 117 name = type.getName();
119 token.end = token.start + name.length(); 118 token.end = token.start + name.length();
120 } 119 }
@@ -133,19 +132,19 @@ public class SourceIndex {
133 return token; 132 return token;
134 } 133 }
135 134
136 public void addReference(AstNode node, Entry deobfEntry, Entry deobfContext) { 135 public void addReference(AstNode node, Entry<?> deobfEntry, Entry<?> deobfContext) {
137 Token token = getToken(node); 136 Token token = getToken(node);
138 if (token != null) { 137 if (token != null) {
139 EntryReference<Entry, Entry> deobfReference = new EntryReference<>(deobfEntry, token.text, deobfContext); 138 EntryReference<Entry<?>, Entry<?>> deobfReference = new EntryReference<>(deobfEntry, token.text, deobfContext);
140 this.tokenToReference.put(token, deobfReference); 139 this.tokenToReference.put(token, deobfReference);
141 this.referenceToTokens.put(deobfReference, token); 140 this.referenceToTokens.put(deobfReference, token);
142 } 141 }
143 } 142 }
144 143
145 public void addDeclaration(AstNode node, Entry deobfEntry) { 144 public void addDeclaration(AstNode node, Entry<?> deobfEntry) {
146 Token token = getToken(node); 145 Token token = getToken(node);
147 if (token != null) { 146 if (token != null) {
148 EntryReference<Entry, Entry> reference = new EntryReference<>(deobfEntry, token.text); 147 EntryReference<Entry<?>, Entry<?>> reference = new EntryReference<>(deobfEntry, token.text);
149 this.tokenToReference.put(token, reference); 148 this.tokenToReference.put(token, reference);
150 this.referenceToTokens.put(reference, token); 149 this.referenceToTokens.put(reference, token);
151 this.declarationToToken.put(deobfEntry, token); 150 this.declarationToToken.put(deobfEntry, token);
@@ -160,22 +159,22 @@ public class SourceIndex {
160 return null; 159 return null;
161 } 160 }
162 161
163 public Collection<Token> getReferenceTokens(EntryReference<Entry, Entry> deobfReference) { 162 public Collection<Token> getReferenceTokens(EntryReference<Entry<?>, Entry<?>> deobfReference) {
164 return this.referenceToTokens.get(deobfReference); 163 return this.referenceToTokens.get(deobfReference);
165 } 164 }
166 165
167 public EntryReference<Entry, Entry> getDeobfReference(Token token) { 166 @Nullable
167 public EntryReference<Entry<?>, Entry<?>> getDeobfReference(Token token) {
168 if (token == null) { 168 if (token == null) {
169 return null; 169 return null;
170 } 170 }
171 return this.tokenToReference.get(token); 171 return this.tokenToReference.get(token);
172 } 172 }
173 173
174 public void replaceDeobfReference(Token token, EntryReference<Entry, Entry> newDeobfReference) { 174 public void replaceDeobfReference(Token token, EntryReference<Entry<?>, Entry<?>> newDeobfReference) {
175 EntryReference<Entry, Entry> oldDeobfReference = this.tokenToReference.get(token); 175 EntryReference<Entry<?>, Entry<?>> oldDeobfReferences = this.tokenToReference.replace(token, newDeobfReference);
176 this.tokenToReference.put(token, newDeobfReference); 176
177 Collection<Token> tokens = this.referenceToTokens.get(oldDeobfReference); 177 Collection<Token> tokens = this.referenceToTokens.removeAll(oldDeobfReferences);
178 this.referenceToTokens.removeAll(oldDeobfReference);
179 this.referenceToTokens.putAll(newDeobfReference, tokens); 178 this.referenceToTokens.putAll(newDeobfReference, tokens);
180 } 179 }
181 180
@@ -187,11 +186,11 @@ public class SourceIndex {
187 return this.declarationToToken.values(); 186 return this.declarationToToken.values();
188 } 187 }
189 188
190 public Iterable<Entry> declarations() { 189 public Iterable<Entry<?>> declarations() {
191 return this.declarationToToken.keySet(); 190 return this.declarationToToken.keySet();
192 } 191 }
193 192
194 public Token getDeclarationToken(Entry deobfEntry) { 193 public Token getDeclarationToken(Entry<?> deobfEntry) {
195 return this.declarationToToken.get(deobfEntry); 194 return this.declarationToToken.get(deobfEntry);
196 } 195 }
197 196
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;
17import com.strobel.assembler.metadata.TypeReference; 17import com.strobel.assembler.metadata.TypeReference;
18import com.strobel.decompiler.languages.TextLocation; 18import com.strobel.decompiler.languages.TextLocation;
19import com.strobel.decompiler.languages.java.ast.*; 19import com.strobel.decompiler.languages.java.ast.*;
20import cuchaz.enigma.bytecode.AccessFlags; 20import cuchaz.enigma.translation.representation.ProcyonEntryFactory;
21import cuchaz.enigma.mapping.Signature; 21import cuchaz.enigma.translation.representation.ReferencedEntryPool;
22import cuchaz.enigma.mapping.entry.*; 22import cuchaz.enigma.translation.representation.entry.ClassDefEntry;
23import cuchaz.enigma.translation.representation.entry.ClassEntry;
24import cuchaz.enigma.translation.representation.entry.FieldDefEntry;
25import cuchaz.enigma.translation.representation.entry.MethodDefEntry;
23 26
24public class SourceIndexClassVisitor extends SourceIndexVisitor { 27public class SourceIndexClassVisitor extends SourceIndexVisitor {
25 private final ReferencedEntryPool entryPool; 28 private final ReferencedEntryPool entryPool;
@@ -37,7 +40,7 @@ public class SourceIndexClassVisitor extends SourceIndexVisitor {
37 public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) { 40 public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) {
38 // is this this class, or a subtype? 41 // is this this class, or a subtype?
39 TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION); 42 TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION);
40 ClassDefEntry classEntry = new ClassDefEntry(def.getInternalName(), Signature.createSignature(def.getSignature()), new AccessFlags(def.getModifiers())); 43 ClassDefEntry classEntry = ClassDefEntry.parse(def);
41 if (!classEntry.equals(this.classEntry)) { 44 if (!classEntry.equals(this.classEntry)) {
42 // it's a subtype, recurse 45 // it's a subtype, recurse
43 index.addDeclaration(node.getNameToken(), classEntry); 46 index.addDeclaration(node.getNameToken(), classEntry);
@@ -68,7 +71,7 @@ public class SourceIndexClassVisitor extends SourceIndexVisitor {
68 tokenNode = node.getModifiers().firstOrNullObject(); 71 tokenNode = node.getModifiers().firstOrNullObject();
69 } 72 }
70 index.addDeclaration(tokenNode, methodEntry); 73 index.addDeclaration(tokenNode, methodEntry);
71 return node.acceptVisitor(new SourceIndexMethodVisitor(entryPool, classEntry, methodEntry), index); 74 return node.acceptVisitor(new SourceIndexMethodVisitor(entryPool, methodEntry), index);
72 } 75 }
73 76
74 @Override 77 @Override
@@ -76,7 +79,7 @@ public class SourceIndexClassVisitor extends SourceIndexVisitor {
76 MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION); 79 MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION);
77 MethodDefEntry methodEntry = entryFactory.getMethodDefEntry(def); 80 MethodDefEntry methodEntry = entryFactory.getMethodDefEntry(def);
78 index.addDeclaration(node.getNameToken(), methodEntry); 81 index.addDeclaration(node.getNameToken(), methodEntry);
79 return node.acceptVisitor(new SourceIndexMethodVisitor(entryPool, classEntry, methodEntry), index); 82 return node.acceptVisitor(new SourceIndexMethodVisitor(entryPool, methodEntry), index);
80 } 83 }
81 84
82 @Override 85 @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.*;
17import com.strobel.decompiler.ast.Variable; 17import com.strobel.decompiler.ast.Variable;
18import com.strobel.decompiler.languages.TextLocation; 18import com.strobel.decompiler.languages.TextLocation;
19import com.strobel.decompiler.languages.java.ast.*; 19import com.strobel.decompiler.languages.java.ast.*;
20import cuchaz.enigma.mapping.TypeDescriptor; 20import cuchaz.enigma.translation.representation.TypeDescriptor;
21import cuchaz.enigma.mapping.entry.*; 21import cuchaz.enigma.translation.representation.*;
22import cuchaz.enigma.translation.representation.entry.*;
22 23
23import java.lang.Error; 24import java.lang.Error;
24import java.util.HashMap; 25import java.util.HashMap;
@@ -26,19 +27,15 @@ import java.util.Map;
26 27
27public class SourceIndexMethodVisitor extends SourceIndexVisitor { 28public class SourceIndexMethodVisitor extends SourceIndexVisitor {
28 private final ReferencedEntryPool entryPool; 29 private final ReferencedEntryPool entryPool;
29 private final ProcyonEntryFactory entryFactory;
30 30
31 private final ClassDefEntry ownerEntry;
32 private final MethodDefEntry methodEntry; 31 private final MethodDefEntry methodEntry;
33 32
34 private Multimap<String, Identifier> unmatchedIdentifier = HashMultimap.create(); 33 private Multimap<String, Identifier> unmatchedIdentifier = HashMultimap.create();
35 private Map<String, Entry> identifierEntryCache = new HashMap<>(); 34 private Map<String, Entry<?>> identifierEntryCache = new HashMap<>();
36 35
37 public SourceIndexMethodVisitor(ReferencedEntryPool entryPool, ClassDefEntry ownerEntry, MethodDefEntry methodEntry) { 36 public SourceIndexMethodVisitor(ReferencedEntryPool entryPool, MethodDefEntry methodEntry) {
38 super(entryPool); 37 super(entryPool);
39 this.entryPool = entryPool; 38 this.entryPool = entryPool;
40 this.entryFactory = new ProcyonEntryFactory(entryPool);
41 this.ownerEntry = ownerEntry;
42 this.methodEntry = methodEntry; 39 this.methodEntry = methodEntry;
43 } 40 }
44 41
@@ -86,7 +83,7 @@ public class SourceIndexMethodVisitor extends SourceIndexVisitor {
86 ClassEntry classEntry = entryPool.getClass(ref.getDeclaringType().getInternalName()); 83 ClassEntry classEntry = entryPool.getClass(ref.getDeclaringType().getInternalName());
87 FieldEntry fieldEntry = entryPool.getField(classEntry, ref.getName(), new TypeDescriptor(erasedSignature)); 84 FieldEntry fieldEntry = entryPool.getField(classEntry, ref.getName(), new TypeDescriptor(erasedSignature));
88 if (fieldEntry == null) { 85 if (fieldEntry == null) {
89 throw new Error("Failed to find field " + ref.getName() + " on " + classEntry.getName()); 86 throw new Error("Failed to find field " + ref.getName() + " on " + classEntry.getFullName());
90 } 87 }
91 index.addReference(node.getMemberNameToken(), fieldEntry, this.methodEntry); 88 index.addReference(node.getMemberNameToken(), fieldEntry, this.methodEntry);
92 } 89 }
@@ -128,7 +125,7 @@ public class SourceIndexMethodVisitor extends SourceIndexVisitor {
128 ClassEntry classEntry = entryPool.getClass(ref.getDeclaringType().getInternalName()); 125 ClassEntry classEntry = entryPool.getClass(ref.getDeclaringType().getInternalName());
129 FieldEntry fieldEntry = entryPool.getField(classEntry, ref.getName(), new TypeDescriptor(ref.getErasedSignature())); 126 FieldEntry fieldEntry = entryPool.getField(classEntry, ref.getName(), new TypeDescriptor(ref.getErasedSignature()));
130 if (fieldEntry == null) { 127 if (fieldEntry == null) {
131 throw new Error("Failed to find field " + ref.getName() + " on " + classEntry.getName()); 128 throw new Error("Failed to find field " + ref.getName() + " on " + classEntry.getFullName());
132 } 129 }
133 index.addReference(node.getIdentifierToken(), fieldEntry, this.methodEntry); 130 index.addReference(node.getIdentifierToken(), fieldEntry, this.methodEntry);
134 } else 131 } else
@@ -144,7 +141,7 @@ public class SourceIndexMethodVisitor extends SourceIndexVisitor {
144 } 141 }
145 142
146 private void addDeclarationToUnmatched(String key, SourceIndex index) { 143 private void addDeclarationToUnmatched(String key, SourceIndex index) {
147 Entry entry = identifierEntryCache.get(key); 144 Entry<?> entry = identifierEntryCache.get(key);
148 145
149 // This cannot happened in theory 146 // This cannot happened in theory
150 if (entry == null) 147 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;
14import com.strobel.assembler.metadata.TypeDefinition; 14import com.strobel.assembler.metadata.TypeDefinition;
15import com.strobel.decompiler.languages.java.ast.*; 15import com.strobel.decompiler.languages.java.ast.*;
16import com.strobel.decompiler.patterns.Pattern; 16import com.strobel.decompiler.patterns.Pattern;
17import cuchaz.enigma.bytecode.AccessFlags; 17import cuchaz.enigma.translation.representation.ReferencedEntryPool;
18import cuchaz.enigma.mapping.Signature; 18import cuchaz.enigma.translation.representation.entry.ClassDefEntry;
19import cuchaz.enigma.mapping.entry.ClassDefEntry;
20import cuchaz.enigma.mapping.entry.ReferencedEntryPool;
21 19
22public class SourceIndexVisitor implements IAstVisitor<SourceIndex, Void> { 20public class SourceIndexVisitor implements IAstVisitor<SourceIndex, Void> {
23 private final ReferencedEntryPool entryPool; 21 private final ReferencedEntryPool entryPool;
@@ -29,7 +27,7 @@ public class SourceIndexVisitor implements IAstVisitor<SourceIndex, Void> {
29 @Override 27 @Override
30 public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) { 28 public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) {
31 TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION); 29 TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION);
32 ClassDefEntry classEntry = new ClassDefEntry(def.getInternalName(), Signature.createSignature(def.getSignature()), new AccessFlags(def.getModifiers())); 30 ClassDefEntry classEntry = ClassDefEntry.parse(def);
33 index.addDeclaration(node.getNameToken(), classEntry); 31 index.addDeclaration(node.getNameToken(), classEntry);
34 32
35 return node.acceptVisitor(new SourceIndexClassVisitor(entryPool, classEntry), index); 33 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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.analysis;
13
14import com.google.common.cache.Cache;
15import com.google.common.cache.CacheBuilder;
16import com.google.common.collect.HashMultimap;
17import com.google.common.collect.Lists;
18import com.google.common.collect.Maps;
19import com.google.common.collect.Multimap;
20import cuchaz.enigma.bytecode.AccessFlags;
21import cuchaz.enigma.mapping.*;
22import cuchaz.enigma.mapping.entry.*;
23
24import java.util.*;
25
26public class TranslationIndex {
27
28 private final ReferencedEntryPool entryPool;
29 private Map<ClassEntry, ClassEntry> superclasses;
30 private Map<Entry, DefEntry> defEntries = new HashMap<>();
31 private Multimap<ClassEntry, FieldDefEntry> fieldEntries;
32 private Multimap<ClassEntry, MethodDefEntry> methodEntries;
33 private Multimap<ClassEntry, ClassEntry> interfaces;
34
35 public TranslationIndex(ReferencedEntryPool entryPool) {
36 this.entryPool = entryPool;
37 this.superclasses = Maps.newHashMap();
38 this.fieldEntries = HashMultimap.create();
39 this.methodEntries = HashMultimap.create();
40 this.interfaces = HashMultimap.create();
41
42 for (FieldDefEntry entry : fieldEntries.values()) {
43 defEntries.put(entry, entry);
44 }
45
46 for (MethodDefEntry entry : methodEntries.values()) {
47 defEntries.put(entry, entry);
48 }
49 }
50
51 public TranslationIndex(TranslationIndex other, Translator translator) {
52 this.entryPool = other.entryPool;
53
54 // translate the superclasses
55 this.superclasses = Maps.newHashMap();
56 for (Map.Entry<ClassEntry, ClassEntry> mapEntry : other.superclasses.entrySet()) {
57 this.superclasses.put(translator.getTranslatedClass(mapEntry.getKey()), translator.getTranslatedClass(mapEntry.getValue()));
58 }
59
60 // translate the interfaces
61 this.interfaces = HashMultimap.create();
62 for (Map.Entry<ClassEntry, ClassEntry> mapEntry : other.interfaces.entries()) {
63 this.interfaces.put(
64 translator.getTranslatedClass(mapEntry.getKey()),
65 translator.getTranslatedClass(mapEntry.getValue())
66 );
67 }
68
69 // translate the fields
70 this.fieldEntries = HashMultimap.create();
71 for (Map.Entry<ClassEntry, FieldDefEntry> mapEntry : other.fieldEntries.entries()) {
72 this.fieldEntries.put(
73 translator.getTranslatedClass(mapEntry.getKey()),
74 translator.getTranslatedFieldDef(mapEntry.getValue())
75 );
76 }
77
78 this.methodEntries = HashMultimap.create();
79 for (Map.Entry<ClassEntry, MethodDefEntry> mapEntry : other.methodEntries.entries()) {
80 this.methodEntries.put(
81 translator.getTranslatedClass(mapEntry.getKey()),
82 translator.getTranslatedMethodDef(mapEntry.getValue())
83 );
84 }
85
86 for (FieldDefEntry entry : fieldEntries.values()) {
87 defEntries.put(entry, entry);
88 }
89
90 for (MethodDefEntry entry : methodEntries.values()) {
91 defEntries.put(entry, entry);
92 }
93 }
94
95 protected ClassDefEntry indexClass(int access, String name, String signature, String superName, String[] interfaces) {
96 ClassDefEntry classEntry = new ClassDefEntry(name, Signature.createSignature(signature), new AccessFlags(access));
97 if (isJre(classEntry)) {
98 return null;
99 }
100
101 // add the superclass
102 ClassEntry superclassEntry = entryPool.getClass(superName);
103 if (superclassEntry != null) {
104 this.superclasses.put(classEntry, superclassEntry);
105 }
106
107 // add the interfaces
108 for (String interfaceClassName : interfaces) {
109 ClassEntry interfaceClassEntry = entryPool.getClass(interfaceClassName);
110 if (!isJre(interfaceClassEntry)) {
111 this.interfaces.put(classEntry, interfaceClassEntry);
112 }
113 }
114
115 return classEntry;
116 }
117
118 protected void indexField(FieldDefEntry fieldEntry) {
119 this.fieldEntries.put(fieldEntry.getOwnerClassEntry(), fieldEntry);
120 this.defEntries.put(fieldEntry, fieldEntry);
121 }
122
123 protected void indexMethod(MethodDefEntry methodEntry) {
124 this.methodEntries.put(methodEntry.getOwnerClassEntry(), methodEntry);
125 this.defEntries.put(methodEntry, methodEntry);
126 }
127
128 public void renameClasses(Map<String, String> renames) {
129 EntryRenamer.renameClassesInMap(renames, this.superclasses);
130 EntryRenamer.renameClassesInMultimap(renames, this.fieldEntries);
131 EntryRenamer.renameClassesInMultimap(renames, this.methodEntries);
132
133 this.defEntries.clear();
134 for (FieldDefEntry entry : fieldEntries.values()) {
135 defEntries.put(entry, entry);
136 }
137
138 for (MethodDefEntry entry : methodEntries.values()) {
139 defEntries.put(entry, entry);
140 }
141 }
142
143 public ClassEntry getSuperclass(ClassEntry classEntry) {
144 return this.superclasses.get(classEntry);
145 }
146
147 public List<ClassEntry> getAncestry(ClassEntry classEntry) {
148 List<ClassEntry> ancestors = Lists.newArrayList();
149 while (classEntry != null) {
150 classEntry = getSuperclass(classEntry);
151 if (classEntry != null) {
152 ancestors.add(classEntry);
153 }
154 }
155 return ancestors;
156 }
157
158 public List<ClassEntry> getImplementers(ClassEntry classEntry) {
159 // linear search is fast enough for now
160 List<ClassEntry> implementers = Lists.newArrayList();
161 for (ClassEntry itf : this.interfaces.keySet()) {
162 if (this.interfaces.containsEntry(itf, classEntry)) {
163 implementers.add(itf);
164 }
165 }
166 return implementers;
167 }
168
169 public List<ClassEntry> getSubclass(ClassEntry classEntry) {
170 // linear search is fast enough for now
171 List<ClassEntry> subclasses = Lists.newArrayList();
172 for (Map.Entry<ClassEntry, ClassEntry> entry : this.superclasses.entrySet()) {
173 ClassEntry subclass = entry.getKey();
174 ClassEntry superclass = entry.getValue();
175 if (classEntry.equals(superclass)) {
176 subclasses.add(subclass);
177 }
178 }
179 return subclasses;
180 }
181
182 public void getSubclassesRecursively(Set<ClassEntry> out, ClassEntry classEntry) {
183 for (ClassEntry subclassEntry : getSubclass(classEntry)) {
184 out.add(subclassEntry);
185 getSubclassesRecursively(out, subclassEntry);
186 }
187 }
188
189 public void getSubclassNamesRecursively(Set<String> out, ClassEntry classEntry) {
190 for (ClassEntry subclassEntry : getSubclass(classEntry)) {
191 out.add(subclassEntry.getName());
192 getSubclassNamesRecursively(out, subclassEntry);
193 }
194 }
195
196 public Collection<Map.Entry<ClassEntry, ClassEntry>> getClassInterfaces() {
197 return this.interfaces.entries();
198 }
199
200 public Collection<ClassEntry> getInterfaces(ClassEntry classEntry) {
201 return this.interfaces.get(classEntry);
202 }
203
204 public boolean isInterface(ClassEntry classEntry) {
205 return this.interfaces.containsValue(classEntry);
206 }
207
208 public boolean entryExists(Entry entry) {
209 if (entry == null) {
210 return false;
211 }
212
213 if (entry instanceof FieldEntry) {
214 return fieldExists((FieldEntry) entry);
215 } else if (entry instanceof MethodEntry) {
216 return methodExists((MethodEntry) entry);
217 } else if (entry instanceof LocalVariableEntry) {
218 return methodExists(((LocalVariableEntry) entry).getOwnerEntry());
219 }
220 throw new IllegalArgumentException("Cannot check existence for " + entry.getClass());
221 }
222
223 public boolean fieldExists(FieldEntry fieldEntry) {
224 return this.fieldEntries.containsEntry(fieldEntry.getOwnerClassEntry(), fieldEntry);
225 }
226
227 public boolean methodExists(MethodEntry methodEntry) {
228 return this.methodEntries.containsEntry(methodEntry.getOwnerClassEntry(), methodEntry);
229 }
230
231 public ClassEntry resolveEntryOwner(Entry entry) {
232 if (entry instanceof ClassEntry) {
233 return (ClassEntry) entry;
234 }
235
236 if (entryExists(entry)) {
237 return entry.getOwnerClassEntry();
238 }
239
240 DefEntry def = defEntries.get(entry);
241 if (def != null && (def.getAccess().isPrivate())) {
242 return null;
243 }
244
245 // if we're protected/public/non-static, chances are we're somewhere down
246 LinkedList<ClassEntry> classEntries = new LinkedList<>();
247 classEntries.add(entry.getOwnerClassEntry());
248 while (!classEntries.isEmpty()) {
249 ClassEntry c = classEntries.remove();
250 Entry cEntry = entry.updateOwnership(c);
251
252 if (entryExists(cEntry)) {
253 def = defEntries.get(cEntry);
254 if (def == null || (!def.getAccess().isPrivate())) {
255 return cEntry.getOwnerClassEntry();
256 }
257 }
258
259 ClassEntry superC = getSuperclass(c);
260 if (superC != null) {
261 classEntries.add(superC);
262 }
263 if (entry instanceof MethodEntry) {
264 classEntries.addAll(getInterfaces(c));
265 }
266 }
267
268 return null;
269 }
270
271 private boolean isJre(ClassEntry classEntry) {
272 String packageName = classEntry.getPackageName();
273 return packageName != null && (packageName.startsWith("java") || packageName.startsWith("javax"));
274 }
275}
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 @@
1package cuchaz.enigma.analysis.index;
2
3import com.google.common.collect.Maps;
4import cuchaz.enigma.translation.Translator;
5import cuchaz.enigma.translation.mapping.EntryResolver;
6import cuchaz.enigma.translation.representation.AccessFlags;
7import cuchaz.enigma.translation.representation.entry.MethodEntry;
8
9import javax.annotation.Nullable;
10import java.util.Collection;
11import java.util.Map;
12
13public class BridgeMethodIndex implements JarIndexer, RemappableIndex {
14 private final EntryIndex entryIndex;
15 private final ReferenceIndex referenceIndex;
16
17 private Map<MethodEntry, MethodEntry> accessedToBridge = Maps.newHashMap();
18
19 public BridgeMethodIndex(EntryIndex entryIndex, ReferenceIndex referenceIndex) {
20 this.entryIndex = entryIndex;
21 this.referenceIndex = referenceIndex;
22 }
23
24 @Override
25 public void remap(Translator translator) {
26 accessedToBridge = translator.translate(accessedToBridge);
27 }
28
29 @Override
30 public BridgeMethodIndex remapped(Translator translator) {
31 BridgeMethodIndex index = new BridgeMethodIndex(entryIndex, referenceIndex);
32 index.accessedToBridge = translator.translate(accessedToBridge);
33
34 return index;
35 }
36
37 @Override
38 public void processIndex(EntryResolver resolver) {
39 // look for access and bridged methods
40 for (MethodEntry methodEntry : entryIndex.getMethods()) {
41 AccessFlags access = entryIndex.getMethodAccess(methodEntry);
42 if (access == null || !access.isSynthetic()) {
43 continue;
44 }
45
46 indexSyntheticMethod(methodEntry, access);
47 }
48 }
49
50 private void indexSyntheticMethod(MethodEntry syntheticMethod, AccessFlags access) {
51 if (access.isBridge()) {
52 MethodEntry accessedMethod = findAccessMethod(syntheticMethod);
53 if (accessedMethod != null) {
54 accessedToBridge.put(accessedMethod, syntheticMethod);
55 }
56 }
57 }
58
59 private MethodEntry findAccessMethod(MethodEntry method) {
60 // we want to find all compiler-added methods that directly call another with no processing
61
62 // get all the methods that we call
63 final Collection<MethodEntry> referencedMethods = referenceIndex.getMethodsReferencedBy(method);
64
65 // is there just one?
66 if (referencedMethods.size() != 1) {
67 return null;
68 }
69
70 return referencedMethods.stream().findFirst().orElse(null);
71 }
72
73 @Nullable
74 public MethodEntry getBridgeFromAccessed(MethodEntry entry) {
75 return accessedToBridge.get(entry);
76 }
77}
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 @@
1package cuchaz.enigma.analysis.index;
2
3import cuchaz.enigma.translation.Translator;
4import cuchaz.enigma.translation.representation.AccessFlags;
5import cuchaz.enigma.translation.representation.entry.*;
6
7import javax.annotation.Nullable;
8import java.util.Collection;
9import java.util.HashMap;
10import java.util.Map;
11
12public class EntryIndex implements JarIndexer, RemappableIndex {
13 private Map<ClassEntry, AccessFlags> classes = new HashMap<>();
14 private Map<FieldEntry, AccessFlags> fields = new HashMap<>();
15 private Map<MethodEntry, AccessFlags> methods = new HashMap<>();
16
17 @Override
18 public void remap(Translator translator) {
19 classes = translator.translateKeys(classes);
20 fields = translator.translateKeys(fields);
21 methods = translator.translateKeys(methods);
22 }
23
24 @Override
25 public EntryIndex remapped(Translator translator) {
26 EntryIndex index = new EntryIndex();
27 index.classes = translator.translateKeys(classes);
28 index.fields = translator.translateKeys(fields);
29 index.methods = translator.translateKeys(methods);
30
31 return index;
32 }
33
34 @Override
35 public void indexClass(ClassDefEntry classEntry) {
36 classes.put(classEntry, classEntry.getAccess());
37 }
38
39 @Override
40 public void indexMethod(MethodDefEntry methodEntry) {
41 methods.put(methodEntry, methodEntry.getAccess());
42 }
43
44 @Override
45 public void indexField(FieldDefEntry fieldEntry) {
46 fields.put(fieldEntry, fieldEntry.getAccess());
47 }
48
49 public boolean hasClass(ClassEntry entry) {
50 return classes.containsKey(entry);
51 }
52
53 public boolean hasMethod(MethodEntry entry) {
54 return methods.containsKey(entry);
55 }
56
57 public boolean hasField(FieldEntry entry) {
58 return fields.containsKey(entry);
59 }
60
61 public boolean hasEntry(Entry<?> entry) {
62 if (entry instanceof ClassEntry) {
63 return hasClass((ClassEntry) entry);
64 } else if (entry instanceof MethodEntry) {
65 return hasMethod((MethodEntry) entry);
66 } else if (entry instanceof FieldEntry) {
67 return hasField((FieldEntry) entry);
68 } else if (entry instanceof LocalVariableEntry) {
69 return hasMethod(((LocalVariableEntry) entry).getParent());
70 }
71
72 return false;
73 }
74
75 @Nullable
76 public AccessFlags getMethodAccess(MethodEntry entry) {
77 return methods.get(entry);
78 }
79
80 @Nullable
81 public AccessFlags getFieldAccess(FieldEntry entry) {
82 return fields.get(entry);
83 }
84
85 @Nullable
86 public AccessFlags getEntryAccess(Entry<?> entry) {
87 if (entry instanceof MethodEntry) {
88 return getMethodAccess((MethodEntry) entry);
89 } else if (entry instanceof FieldEntry) {
90 return getFieldAccess((FieldEntry) entry);
91 } else if (entry instanceof LocalVariableEntry) {
92 return getMethodAccess(((LocalVariableEntry) entry).getParent());
93 }
94
95 return null;
96 }
97
98 public Collection<ClassEntry> getClasses() {
99 return classes.keySet();
100 }
101
102 public Collection<MethodEntry> getMethods() {
103 return methods.keySet();
104 }
105
106 public Collection<FieldEntry> getFields() {
107 return fields.keySet();
108 }
109}
diff --git a/src/main/java/cuchaz/enigma/analysis/IndexClassVisitor.java b/src/main/java/cuchaz/enigma/analysis/index/IndexClassVisitor.java
index 4d5e803..f9cb23c 100644
--- a/src/main/java/cuchaz/enigma/analysis/IndexClassVisitor.java
+++ b/src/main/java/cuchaz/enigma/analysis/index/IndexClassVisitor.java
@@ -1,43 +1,40 @@
1package cuchaz.enigma.analysis; 1package cuchaz.enigma.analysis.index;
2 2
3import cuchaz.enigma.mapping.entry.ClassDefEntry; 3import cuchaz.enigma.translation.representation.entry.ClassDefEntry;
4import cuchaz.enigma.translation.representation.entry.FieldDefEntry;
5import cuchaz.enigma.translation.representation.entry.MethodDefEntry;
4import org.objectweb.asm.ClassVisitor; 6import org.objectweb.asm.ClassVisitor;
5import org.objectweb.asm.FieldVisitor; 7import org.objectweb.asm.FieldVisitor;
6import org.objectweb.asm.MethodVisitor; 8import org.objectweb.asm.MethodVisitor;
7 9
8public class IndexClassVisitor extends ClassVisitor { 10public class IndexClassVisitor extends ClassVisitor {
9 private final JarIndex index; 11 private final JarIndexer indexer;
10 private ClassDefEntry classEntry; 12 private ClassDefEntry classEntry;
11 13
12 public IndexClassVisitor(JarIndex index, int api) { 14 public IndexClassVisitor(JarIndex indexer, int api) {
13 super(api); 15 super(api);
14 this.index = index; 16 this.indexer = indexer;
15 }
16
17 public IndexClassVisitor(JarIndex index, int api, ClassVisitor cv) {
18 super(api, cv);
19 this.index = index;
20 } 17 }
21 18
22 @Override 19 @Override
23 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { 20 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
24 this.classEntry = this.index.indexClass(access, name, signature, superName, interfaces); 21 classEntry = ClassDefEntry.parse(access, name, signature, superName, interfaces);
22 indexer.indexClass(classEntry);
23
25 super.visit(version, access, name, signature, superName, interfaces); 24 super.visit(version, access, name, signature, superName, interfaces);
26 } 25 }
27 26
28 @Override 27 @Override
29 public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { 28 public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
30 if (this.classEntry != null) { 29 indexer.indexField(FieldDefEntry.parse(classEntry, access, name, desc, signature));
31 this.index.indexField(this.classEntry, access, name, desc, signature); 30
32 }
33 return super.visitField(access, name, desc, signature, value); 31 return super.visitField(access, name, desc, signature, value);
34 } 32 }
35 33
36 @Override 34 @Override
37 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { 35 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
38 if (this.classEntry != null) { 36 indexer.indexMethod(MethodDefEntry.parse(classEntry, access, name, desc, signature));
39 this.index.indexMethod(this.classEntry, access, name, desc, signature); 37
40 }
41 return super.visitMethod(access, name, desc, signature, exceptions); 38 return super.visitMethod(access, name, desc, signature, exceptions);
42 } 39 }
43} 40}
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 @@
1package cuchaz.enigma.analysis.index;
2
3import cuchaz.enigma.translation.representation.AccessFlags;
4import cuchaz.enigma.translation.representation.MethodDescriptor;
5import cuchaz.enigma.translation.representation.Signature;
6import cuchaz.enigma.translation.representation.entry.ClassEntry;
7import cuchaz.enigma.translation.representation.entry.FieldEntry;
8import cuchaz.enigma.translation.representation.entry.MethodDefEntry;
9import cuchaz.enigma.translation.representation.entry.MethodEntry;
10import org.objectweb.asm.ClassVisitor;
11import org.objectweb.asm.Handle;
12import org.objectweb.asm.MethodVisitor;
13import org.objectweb.asm.Opcodes;
14
15public class IndexReferenceVisitor extends ClassVisitor {
16 private final JarIndexer indexer;
17 private ClassEntry classEntry;
18
19 public IndexReferenceVisitor(JarIndexer indexer, int api) {
20 super(api);
21 this.indexer = indexer;
22 }
23
24 @Override
25 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
26 this.classEntry = new ClassEntry(name);
27 }
28
29 @Override
30 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
31 MethodDefEntry entry = new MethodDefEntry(classEntry, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access));
32 return new Method(this.indexer, entry, this.api);
33 }
34
35 private static class Method extends MethodVisitor {
36 private final JarIndexer indexer;
37 private final MethodDefEntry callerEntry;
38
39 public Method(JarIndexer indexer, MethodDefEntry callerEntry, int api) {
40 super(api);
41 this.indexer = indexer;
42 this.callerEntry = callerEntry;
43 }
44
45 @Override
46 public void visitFieldInsn(int opcode, String owner, String name, String desc) {
47 FieldEntry fieldEntry = FieldEntry.parse(owner, name, desc);
48 this.indexer.indexFieldReference(callerEntry, fieldEntry);
49 }
50
51 @Override
52 public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
53 MethodEntry methodEntry = MethodEntry.parse(owner, name, desc);
54 this.indexer.indexMethodReference(callerEntry, methodEntry);
55 }
56
57 @Override
58 public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
59 for (Object bsmArg : bsmArgs) {
60 if (bsmArg instanceof Handle) {
61 Handle handle = (Handle) bsmArg;
62 switch (handle.getTag()) {
63 case Opcodes.H_GETFIELD:
64 case Opcodes.H_GETSTATIC:
65 case Opcodes.H_PUTFIELD:
66 case Opcodes.H_PUTSTATIC:
67 FieldEntry fieldEntry = FieldEntry.parse(handle.getOwner(), handle.getName(), handle.getDesc());
68 this.indexer.indexFieldReference(callerEntry, fieldEntry);
69 break;
70 case Opcodes.H_INVOKEINTERFACE:
71 case Opcodes.H_INVOKESPECIAL:
72 case Opcodes.H_INVOKESTATIC:
73 case Opcodes.H_INVOKEVIRTUAL:
74 case Opcodes.H_NEWINVOKESPECIAL:
75 MethodEntry methodEntry = MethodEntry.parse(handle.getOwner(), handle.getName(), handle.getDesc());
76 this.indexer.indexMethodReference(callerEntry, methodEntry);
77 break;
78 }
79 }
80 }
81 }
82 }
83}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.analysis.index;
13
14import com.google.common.collect.HashMultimap;
15import com.google.common.collect.Multimap;
16import com.google.common.collect.Sets;
17import cuchaz.enigma.translation.Translator;
18import cuchaz.enigma.translation.representation.entry.ClassDefEntry;
19import cuchaz.enigma.translation.representation.entry.ClassEntry;
20
21import java.util.Collection;
22import java.util.LinkedList;
23import java.util.Set;
24
25public class InheritanceIndex implements JarIndexer, RemappableIndex {
26 private Multimap<ClassEntry, ClassEntry> classParents = HashMultimap.create();
27 private Multimap<ClassEntry, ClassEntry> classChildren = HashMultimap.create();
28
29 @Override
30 public void remap(Translator translator) {
31 classChildren = translator.translate(classChildren);
32 classParents = translator.translate(classParents);
33 }
34
35 @Override
36 public InheritanceIndex remapped(Translator translator) {
37 InheritanceIndex index = new InheritanceIndex();
38 index.classParents = translator.translate(classParents);
39 index.classChildren = translator.translate(classChildren);
40
41 return index;
42 }
43
44 @Override
45 public void indexClass(ClassDefEntry classEntry) {
46 ClassEntry superClass = classEntry.getSuperClass();
47 if (superClass != null) {
48 indexParent(classEntry, superClass);
49 }
50
51 for (ClassEntry interfaceEntry : classEntry.getInterfaces()) {
52 indexParent(classEntry, interfaceEntry);
53 }
54 }
55
56 private void indexParent(ClassEntry childEntry, ClassEntry parentEntry) {
57 if (childEntry.isJre() || parentEntry.isJre()) {
58 return;
59 }
60 classParents.put(childEntry, parentEntry);
61 classChildren.put(parentEntry, childEntry);
62 }
63
64 public Collection<ClassEntry> getParents(ClassEntry classEntry) {
65 return classParents.get(classEntry);
66 }
67
68 public Collection<ClassEntry> getChildren(ClassEntry classEntry) {
69 return classChildren.get(classEntry);
70 }
71
72 public Set<ClassEntry> getAncestors(ClassEntry classEntry) {
73 Set<ClassEntry> ancestors = Sets.newHashSet();
74
75 LinkedList<ClassEntry> ancestorQueue = new LinkedList<>();
76 ancestorQueue.push(classEntry);
77
78 while (!ancestorQueue.isEmpty()) {
79 ClassEntry ancestor = ancestorQueue.pop();
80 Collection<ClassEntry> parents = getParents(ancestor);
81
82 parents.forEach(ancestorQueue::push);
83 ancestors.addAll(parents);
84 }
85
86 return ancestors;
87 }
88
89 public boolean isParent(ClassEntry classEntry) {
90 return classChildren.containsKey(classEntry);
91 }
92
93 public boolean hasParents(ClassEntry classEntry) {
94 Collection<ClassEntry> parents = classParents.get(classEntry);
95 return parents != null && !parents.isEmpty();
96 }
97}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.analysis.index;
13
14import com.google.common.collect.HashMultimap;
15import com.google.common.collect.Multimap;
16import cuchaz.enigma.analysis.ParsedJar;
17import cuchaz.enigma.translation.Translator;
18import cuchaz.enigma.translation.mapping.EntryResolver;
19import cuchaz.enigma.translation.mapping.IndexEntryResolver;
20import cuchaz.enigma.translation.representation.entry.*;
21import org.objectweb.asm.ClassReader;
22import org.objectweb.asm.Opcodes;
23
24import java.util.Arrays;
25import java.util.Collection;
26import java.util.function.Consumer;
27
28public class JarIndex implements JarIndexer, RemappableIndex {
29 private final EntryIndex entryIndex;
30 private final InheritanceIndex inheritanceIndex;
31 private final ReferenceIndex referenceIndex;
32 private final BridgeMethodIndex bridgeMethodIndex;
33 private final EntryResolver entryResolver;
34
35 private final Collection<JarIndexer> indexers;
36
37 private final Multimap<String, MethodDefEntry> methodImplementations = HashMultimap.create();
38
39 public JarIndex(EntryIndex entryIndex, InheritanceIndex inheritanceIndex, ReferenceIndex referenceIndex, BridgeMethodIndex bridgeMethodIndex) {
40 this.entryIndex = entryIndex;
41 this.inheritanceIndex = inheritanceIndex;
42 this.referenceIndex = referenceIndex;
43 this.bridgeMethodIndex = bridgeMethodIndex;
44 this.indexers = Arrays.asList(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex);
45 this.entryResolver = new IndexEntryResolver(this);
46 }
47
48 public static JarIndex empty() {
49 EntryIndex entryIndex = new EntryIndex();
50 InheritanceIndex inheritanceIndex = new InheritanceIndex();
51 ReferenceIndex referenceIndex = new ReferenceIndex();
52 BridgeMethodIndex bridgeMethodIndex = new BridgeMethodIndex(entryIndex, referenceIndex);
53 return new JarIndex(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex);
54 }
55
56 @Override
57 public void remap(Translator translator) {
58 entryIndex.remap(translator);
59 inheritanceIndex.remap(translator);
60 bridgeMethodIndex.remap(translator);
61 }
62
63 @Override
64 public JarIndex remapped(Translator translator) {
65 EntryIndex entryIndex = this.entryIndex.remapped(translator);
66 InheritanceIndex inheritanceIndex = this.inheritanceIndex.remapped(translator);
67 BridgeMethodIndex bridgeMethodIndex = this.bridgeMethodIndex.remapped(translator);
68
69 JarIndex remappedIndex = new JarIndex(entryIndex, inheritanceIndex, this.referenceIndex, bridgeMethodIndex);
70 remappedIndex.methodImplementations.putAll(methodImplementations);
71
72 return remappedIndex;
73 }
74
75 public void indexJar(ParsedJar jar, Consumer<String> progress) {
76 progress.accept("Indexing entries (1/3)");
77 jar.visitReader(name -> new IndexClassVisitor(this, Opcodes.ASM5), ClassReader.SKIP_CODE);
78
79 progress.accept("Indexing entry references (2/3)");
80 jar.visitReader(name -> new IndexReferenceVisitor(this, Opcodes.ASM5), ClassReader.SKIP_FRAMES);
81
82 progress.accept("Processing index (3/3)");
83 processIndex(entryResolver);
84 }
85
86 @Override
87 public void processIndex(EntryResolver resolver) {
88 indexers.forEach(indexer -> indexer.processIndex(entryResolver));
89 }
90
91 @Override
92 public void indexClass(ClassDefEntry classEntry) {
93 if (classEntry.isJre()) {
94 return;
95 }
96
97 for (ClassEntry interfaceEntry : classEntry.getInterfaces()) {
98 if (classEntry.equals(interfaceEntry)) {
99 throw new IllegalArgumentException("Class cannot be its own interface! " + classEntry);
100 }
101 }
102
103 indexers.forEach(indexer -> indexer.indexClass(classEntry));
104 }
105
106 @Override
107 public void indexField(FieldDefEntry fieldEntry) {
108 if (fieldEntry.getParent().isJre()) {
109 return;
110 }
111
112 indexers.forEach(indexer -> indexer.indexField(fieldEntry));
113 }
114
115 @Override
116 public void indexMethod(MethodDefEntry methodEntry) {
117 if (methodEntry.getParent().isJre()) {
118 return;
119 }
120
121 indexers.forEach(indexer -> indexer.indexMethod(methodEntry));
122
123 if (!methodEntry.isConstructor()) {
124 methodImplementations.put(methodEntry.getParent().getFullName(), methodEntry);
125 }
126 }
127
128 @Override
129 public void indexMethodReference(MethodDefEntry callerEntry, MethodEntry referencedEntry) {
130 if (callerEntry.getParent().isJre()) {
131 return;
132 }
133
134 indexers.forEach(indexer -> indexer.indexMethodReference(callerEntry, referencedEntry));
135 }
136
137 @Override
138 public void indexFieldReference(MethodDefEntry callerEntry, FieldEntry referencedEntry) {
139 if (callerEntry.getParent().isJre()) {
140 return;
141 }
142
143 indexers.forEach(indexer -> indexer.indexFieldReference(callerEntry, referencedEntry));
144 }
145
146 public EntryIndex getEntryIndex() {
147 return entryIndex;
148 }
149
150 public InheritanceIndex getInheritanceIndex() {
151 return this.inheritanceIndex;
152 }
153
154 public ReferenceIndex getReferenceIndex() {
155 return referenceIndex;
156 }
157
158 public BridgeMethodIndex getBridgeMethodIndex() {
159 return bridgeMethodIndex;
160 }
161
162 public EntryResolver getEntryResolver() {
163 return entryResolver;
164 }
165}
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 @@
1package cuchaz.enigma.analysis.index;
2
3import cuchaz.enigma.translation.mapping.EntryResolver;
4import cuchaz.enigma.translation.representation.entry.*;
5
6public interface JarIndexer {
7 default void indexClass(ClassDefEntry classEntry) {
8 }
9
10 default void indexField(FieldDefEntry fieldEntry) {
11 }
12
13 default void indexMethod(MethodDefEntry methodEntry) {
14 }
15
16 default void indexMethodReference(MethodDefEntry callerEntry, MethodEntry referencedEntry) {
17 }
18
19 default void indexFieldReference(MethodDefEntry callerEntry, FieldEntry referencedEntry) {
20 }
21
22 default void processIndex(EntryResolver resolver) {
23 }
24}
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 @@
1package cuchaz.enigma.analysis.index;
2
3import com.google.common.collect.HashMultimap;
4import com.google.common.collect.Multimap;
5import cuchaz.enigma.analysis.EntryReference;
6import cuchaz.enigma.translation.mapping.EntryResolver;
7import cuchaz.enigma.translation.mapping.ResolutionStrategy;
8import cuchaz.enigma.translation.representation.entry.*;
9
10import java.util.Collection;
11import java.util.Map;
12
13public class ReferenceIndex implements JarIndexer {
14 private Multimap<MethodEntry, MethodEntry> methodReferences = HashMultimap.create();
15
16 private Multimap<MethodEntry, EntryReference<MethodEntry, MethodDefEntry>> referencesToMethods = HashMultimap.create();
17 private Multimap<ClassEntry, EntryReference<ClassEntry, MethodDefEntry>> referencesToClasses = HashMultimap.create();
18 private Multimap<FieldEntry, EntryReference<FieldEntry, MethodDefEntry>> referencesToFields = HashMultimap.create();
19
20 @Override
21 public void indexMethodReference(MethodDefEntry callerEntry, MethodEntry referencedEntry) {
22 referencesToMethods.put(referencedEntry, new EntryReference<>(referencedEntry, referencedEntry.getName(), callerEntry));
23 methodReferences.put(callerEntry, referencedEntry);
24
25 if (referencedEntry.isConstructor()) {
26 ClassEntry referencedClass = referencedEntry.getParent();
27 referencesToClasses.put(referencedClass, new EntryReference<>(referencedClass, referencedEntry.getName(), callerEntry));
28 }
29 }
30
31 @Override
32 public void indexFieldReference(MethodDefEntry callerEntry, FieldEntry referencedEntry) {
33 referencesToFields.put(referencedEntry, new EntryReference<>(referencedEntry, referencedEntry.getName(), callerEntry));
34 }
35
36 @Override
37 public void processIndex(EntryResolver resolver) {
38 methodReferences = resolveReferences(resolver, methodReferences);
39 referencesToMethods = resolveReferencesTo(resolver, referencesToMethods);
40 referencesToClasses = resolveReferencesTo(resolver, referencesToClasses);
41 referencesToFields = resolveReferencesTo(resolver, referencesToFields);
42 }
43
44 private <K extends Entry<?>, V extends Entry<?>> Multimap<K, V> resolveReferences(EntryResolver resolver, Multimap<K, V> multimap) {
45 Multimap<K, V> resolved = HashMultimap.create();
46 for (Map.Entry<K, V> entry : multimap.entries()) {
47 resolved.put(resolve(resolver, entry.getKey()), resolve(resolver, entry.getValue()));
48 }
49 return resolved;
50 }
51
52 private <E extends Entry<?>, C extends Entry<?>> Multimap<E, EntryReference<E, C>> resolveReferencesTo(EntryResolver resolver, Multimap<E, EntryReference<E, C>> multimap) {
53 Multimap<E, EntryReference<E, C>> resolved = HashMultimap.create();
54 for (Map.Entry<E, EntryReference<E, C>> entry : multimap.entries()) {
55 resolved.put(resolve(resolver, entry.getKey()), resolve(resolver, entry.getValue()));
56 }
57 return resolved;
58 }
59
60 private <E extends Entry<?>> E resolve(EntryResolver resolver, E entry) {
61 return resolver.resolveFirstEntry(entry, ResolutionStrategy.RESOLVE_CLOSEST);
62 }
63
64 private <E extends Entry<?>, C extends Entry<?>> EntryReference<E, C> resolve(EntryResolver resolver, EntryReference<E, C> reference) {
65 return resolver.resolveFirstReference(reference, ResolutionStrategy.RESOLVE_CLOSEST);
66 }
67
68 public Collection<MethodEntry> getMethodsReferencedBy(MethodEntry entry) {
69 return methodReferences.get(entry);
70 }
71
72 public Collection<EntryReference<FieldEntry, MethodDefEntry>> getReferencesToField(FieldEntry entry) {
73 return referencesToFields.get(entry);
74 }
75
76 public Collection<EntryReference<ClassEntry, MethodDefEntry>> getReferencesToClass(ClassEntry entry) {
77 return referencesToClasses.get(entry);
78 }
79
80 public Collection<EntryReference<MethodEntry, MethodDefEntry>> getReferencesToMethod(MethodEntry entry) {
81 return referencesToMethods.get(entry);
82 }
83}
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 @@
1package cuchaz.enigma.analysis.index;
2
3import cuchaz.enigma.translation.Translator;
4
5public interface RemappableIndex {
6 void remap(Translator translator);
7
8 RemappableIndex remapped(Translator translator);
9}
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 @@
1package cuchaz.enigma.bytecode.translators;
2
3import cuchaz.enigma.translation.Translator;
4import cuchaz.enigma.translation.representation.MethodDescriptor;
5import cuchaz.enigma.translation.representation.TypeDescriptor;
6import cuchaz.enigma.translation.representation.entry.ClassEntry;
7import cuchaz.enigma.translation.representation.entry.MethodEntry;
8import org.objectweb.asm.Handle;
9import org.objectweb.asm.Type;
10
11public class AsmObjectTranslator {
12 public static Type translateType(Translator translator, Type type) {
13 String descString = type.getDescriptor();
14 switch (type.getSort()) {
15 case Type.OBJECT: {
16 ClassEntry classEntry = new ClassEntry(type.getInternalName());
17 return Type.getObjectType(translator.translate(classEntry).getFullName());
18 }
19 case Type.ARRAY: {
20 TypeDescriptor descriptor = new TypeDescriptor(descString);
21 return Type.getType(translator.translate(descriptor).toString());
22 }
23 case Type.METHOD: {
24 MethodDescriptor descriptor = new MethodDescriptor(descString);
25 return Type.getMethodType(translator.translate(descriptor).toString());
26 }
27 }
28 return type;
29 }
30
31 public static Handle translateHandle(Translator translator, Handle handle) {
32 MethodEntry entry = new MethodEntry(new ClassEntry(handle.getOwner()), handle.getName(), new MethodDescriptor(handle.getDesc()));
33 MethodEntry translatedMethod = translator.translate(entry);
34 ClassEntry ownerClass = translatedMethod.getParent();
35 return new Handle(handle.getTag(), ownerClass.getFullName(), translatedMethod.getName(), translatedMethod.getDesc().toString(), handle.isInterface());
36 }
37
38 public static Object translateValue(Translator translator, Object value) {
39 if (value instanceof Type) {
40 return translateType(translator, (Type) value);
41 } else if (value instanceof Handle) {
42 return translateHandle(translator, (Handle) value);
43 }
44 return value;
45 }
46}
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 @@
1package cuchaz.enigma.bytecode.translators; 1package cuchaz.enigma.bytecode.translators;
2 2
3import cuchaz.enigma.mapping.Translator; 3import cuchaz.enigma.translation.Translator;
4import cuchaz.enigma.mapping.TypeDescriptor; 4import cuchaz.enigma.translation.representation.TypeDescriptor;
5import cuchaz.enigma.mapping.entry.ClassEntry; 5import cuchaz.enigma.translation.representation.entry.ClassEntry;
6import cuchaz.enigma.mapping.entry.FieldEntry; 6import cuchaz.enigma.translation.representation.entry.FieldEntry;
7import org.objectweb.asm.AnnotationVisitor; 7import org.objectweb.asm.AnnotationVisitor;
8 8
9public class TranslationAnnotationVisitor extends AnnotationVisitor { 9public class TranslationAnnotationVisitor extends AnnotationVisitor {
@@ -18,7 +18,7 @@ public class TranslationAnnotationVisitor extends AnnotationVisitor {
18 18
19 @Override 19 @Override
20 public void visit(String name, Object value) { 20 public void visit(String name, Object value) {
21 super.visit(name, translator.getTranslatedValue(value)); 21 super.visit(name, AsmObjectTranslator.translateValue(translator, value));
22 } 22 }
23 23
24 @Override 24 @Override
@@ -30,22 +30,22 @@ public class TranslationAnnotationVisitor extends AnnotationVisitor {
30 public AnnotationVisitor visitAnnotation(String name, String desc) { 30 public AnnotationVisitor visitAnnotation(String name, String desc) {
31 TypeDescriptor type = new TypeDescriptor(desc); 31 TypeDescriptor type = new TypeDescriptor(desc);
32 if (name != null) { 32 if (name != null) {
33 FieldEntry annotationField = translator.getTranslatedField(new FieldEntry(annotationEntry, name, type)); 33 FieldEntry annotationField = translator.translate(new FieldEntry(annotationEntry, name, type));
34 return super.visitAnnotation(annotationField.getName(), annotationField.getDesc().toString()); 34 return super.visitAnnotation(annotationField.getName(), annotationField.getDesc().toString());
35 } else { 35 } else {
36 return super.visitAnnotation(null, translator.getTranslatedTypeDesc(type).toString()); 36 return super.visitAnnotation(null, translator.translate(type).toString());
37 } 37 }
38 } 38 }
39 39
40 @Override 40 @Override
41 public void visitEnum(String name, String desc, String value) { 41 public void visitEnum(String name, String desc, String value) {
42 TypeDescriptor type = new TypeDescriptor(desc); 42 TypeDescriptor type = new TypeDescriptor(desc);
43 FieldEntry enumField = translator.getTranslatedField(new FieldEntry(type.getTypeEntry(), value, type)); 43 FieldEntry enumField = translator.translate(new FieldEntry(type.getTypeEntry(), value, type));
44 if (name != null) { 44 if (name != null) {
45 FieldEntry annotationField = translator.getTranslatedField(new FieldEntry(annotationEntry, name, type)); 45 FieldEntry annotationField = translator.translate(new FieldEntry(annotationEntry, name, type));
46 super.visitEnum(annotationField.getName(), annotationField.getDesc().toString(), enumField.getName()); 46 super.visitEnum(annotationField.getName(), annotationField.getDesc().toString(), enumField.getName());
47 } else { 47 } else {
48 super.visitEnum(null, translator.getTranslatedTypeDesc(type).toString(), enumField.getName()); 48 super.visitEnum(null, translator.translate(type).toString(), enumField.getName());
49 } 49 }
50 } 50 }
51} 51}
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 @@
11 11
12package cuchaz.enigma.bytecode.translators; 12package cuchaz.enigma.bytecode.translators;
13 13
14import cuchaz.enigma.analysis.JarIndex; 14import cuchaz.enigma.translation.Translator;
15import cuchaz.enigma.bytecode.AccessFlags; 15import cuchaz.enigma.translation.representation.MethodDescriptor;
16import cuchaz.enigma.mapping.MethodDescriptor; 16import cuchaz.enigma.translation.representation.ReferencedEntryPool;
17import cuchaz.enigma.mapping.Signature; 17import cuchaz.enigma.translation.representation.TypeDescriptor;
18import cuchaz.enigma.mapping.Translator; 18import cuchaz.enigma.translation.representation.entry.*;
19import cuchaz.enigma.mapping.TypeDescriptor;
20import cuchaz.enigma.mapping.entry.*;
21import org.objectweb.asm.*; 19import org.objectweb.asm.*;
22 20
21import java.util.Arrays;
22
23public class TranslationClassVisitor extends ClassVisitor { 23public class TranslationClassVisitor extends ClassVisitor {
24 private final Translator translator; 24 private final Translator translator;
25 private final JarIndex jarIndex;
26 private final ReferencedEntryPool entryPool; 25 private final ReferencedEntryPool entryPool;
27 26
28 private ClassDefEntry obfClassEntry; 27 private ClassDefEntry obfClassEntry;
29 private Signature obfSignature;
30 28
31 public TranslationClassVisitor(Translator translator, JarIndex jarIndex, ReferencedEntryPool entryPool, int api, ClassVisitor cv) { 29 public TranslationClassVisitor(Translator translator, ReferencedEntryPool entryPool, int api, ClassVisitor cv) {
32 super(api, cv); 30 super(api, cv);
33 this.translator = translator; 31 this.translator = translator;
34 this.jarIndex = jarIndex;
35 this.entryPool = entryPool; 32 this.entryPool = entryPool;
36 } 33 }
37 34
38 @Override 35 @Override
39 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { 36 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
40 obfSignature = Signature.createSignature(signature); 37 obfClassEntry = ClassDefEntry.parse(access, name, signature, superName, interfaces);
41 obfClassEntry = new ClassDefEntry(name, obfSignature, new AccessFlags(access)); 38
42 ClassDefEntry translatedEntry = translator.getTranslatedClassDef(obfClassEntry); 39 ClassDefEntry translatedEntry = translator.translate(obfClassEntry);
43 ClassEntry superEntry = translator.getTranslatedClass(entryPool.getClass(superName)); 40 String translatedSuper = translatedEntry.getSuperClass() != null ? translatedEntry.getSuperClass().getFullName() : null;
44 String[] translatedInterfaces = new String[interfaces.length]; 41 String[] translatedInterfaces = Arrays.stream(translatedEntry.getInterfaces()).map(ClassEntry::getFullName).toArray(String[]::new);
45 for (int i = 0; i < interfaces.length; i++) { 42
46 translatedInterfaces[i] = translator.getTranslatedClass(entryPool.getClass(interfaces[i])).getName(); 43 super.visit(version, translatedEntry.getAccess().getFlags(), translatedEntry.getFullName(), translatedEntry.getSignature().toString(), translatedSuper, translatedInterfaces);
47 }
48 super.visit(version, translatedEntry.getAccess().getFlags(), translatedEntry.getName(), translatedEntry.getSignature().toString(), superEntry.getName(), translatedInterfaces);
49 } 44 }
50 45
51 @Override 46 @Override
52 public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { 47 public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
53 FieldDefEntry entry = new FieldDefEntry(obfClassEntry, name, new TypeDescriptor(desc), Signature.createTypedSignature(signature), new AccessFlags(access)); 48 FieldDefEntry entry = FieldDefEntry.parse(obfClassEntry, access, name, desc, signature);
54 FieldDefEntry translatedEntry = translator.getTranslatedFieldDef(entry); 49 FieldDefEntry translatedEntry = translator.translate(entry);
55 FieldVisitor fv = super.visitField(translatedEntry.getAccess().getFlags(), translatedEntry.getName(), translatedEntry.getDesc().toString(), translatedEntry.getSignature().toString(), value); 50 FieldVisitor fv = super.visitField(translatedEntry.getAccess().getFlags(), translatedEntry.getName(), translatedEntry.getDesc().toString(), translatedEntry.getSignature().toString(), value);
56 return new TranslationFieldVisitor(translator, translatedEntry, api, fv); 51 return new TranslationFieldVisitor(translator, translatedEntry, api, fv);
57 } 52 }
58 53
59 @Override 54 @Override
60 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { 55 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
61 MethodDefEntry entry = new MethodDefEntry(obfClassEntry, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access)); 56 MethodDefEntry entry = MethodDefEntry.parse(obfClassEntry, access, name, desc, signature);
62 MethodDefEntry translatedEntry = translator.getTranslatedMethodDef(entry); 57 MethodDefEntry translatedEntry = translator.translate(entry);
63 if (jarIndex.getBridgedMethod(entry) != null) {
64 translatedEntry.getAccess().setBridge();
65 }
66 String[] translatedExceptions = new String[exceptions.length]; 58 String[] translatedExceptions = new String[exceptions.length];
67 for (int i = 0; i < exceptions.length; i++) { 59 for (int i = 0; i < exceptions.length; i++) {
68 translatedExceptions[i] = translator.getTranslatedClass(entryPool.getClass(exceptions[i])).getName(); 60 translatedExceptions[i] = translator.translate(entryPool.getClass(exceptions[i])).getFullName();
69 } 61 }
70 MethodVisitor mv = super.visitMethod(translatedEntry.getAccess().getFlags(), translatedEntry.getName(), translatedEntry.getDesc().toString(), translatedEntry.getSignature().toString(), translatedExceptions); 62 MethodVisitor mv = super.visitMethod(translatedEntry.getAccess().getFlags(), translatedEntry.getName(), translatedEntry.getDesc().toString(), translatedEntry.getSignature().toString(), translatedExceptions);
71 return new TranslationMethodVisitor(translator, obfClassEntry, entry, api, mv); 63 return new TranslationMethodVisitor(translator, obfClassEntry, entry, api, mv);
@@ -73,25 +65,25 @@ public class TranslationClassVisitor extends ClassVisitor {
73 65
74 @Override 66 @Override
75 public void visitInnerClass(String name, String outerName, String innerName, int access) { 67 public void visitInnerClass(String name, String outerName, String innerName, int access) {
76 ClassDefEntry translatedEntry = translator.getTranslatedClassDef(new ClassDefEntry(name, obfSignature, new AccessFlags(access))); 68 ClassDefEntry classEntry = ClassDefEntry.parse(access, name, obfClassEntry.getSignature().toString(), null, new String[0]);
77 String translatedName = translatedEntry.getName(); 69 ClassDefEntry translatedEntry = translator.translate(classEntry);
78 int separatorIndex = translatedName.lastIndexOf("$"); 70 ClassEntry translatedOuterClass = translatedEntry.getOuterClass();
79 String parentName = translatedName.substring(0, separatorIndex); 71 if (translatedOuterClass == null) {
80 String childName = translatedName.substring(separatorIndex + 1); 72 throw new IllegalStateException("Translated inner class did not have outer class");
81 73 }
82 ClassEntry outerEntry = translator.getTranslatedClass(entryPool.getClass(parentName));
83 74
84 // 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 75 // 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
85 String translatedOuterName = outerName != null ? outerEntry.getName() : null; 76 String translatedName = translatedEntry.getFullName();
86 String translatedInnerName = innerName != null ? childName : null; 77 String translatedOuterName = outerName != null ? translatedOuterClass.getFullName() : null;
78 String translatedInnerName = innerName != null ? translatedEntry.getName() : null;
87 super.visitInnerClass(translatedName, translatedOuterName, translatedInnerName, translatedEntry.getAccess().getFlags()); 79 super.visitInnerClass(translatedName, translatedOuterName, translatedInnerName, translatedEntry.getAccess().getFlags());
88 } 80 }
89 81
90 @Override 82 @Override
91 public void visitOuterClass(String owner, String name, String desc) { 83 public void visitOuterClass(String owner, String name, String desc) {
92 if (desc != null) { 84 if (desc != null) {
93 MethodEntry translatedEntry = translator.getTranslatedMethod(new MethodEntry(new ClassEntry(owner), name, new MethodDescriptor(desc))); 85 MethodEntry translatedEntry = translator.translate(new MethodEntry(new ClassEntry(owner), name, new MethodDescriptor(desc)));
94 super.visitOuterClass(translatedEntry.getClassName(), translatedEntry.getName(), translatedEntry.getDesc().toString()); 86 super.visitOuterClass(translatedEntry.getParent().getFullName(), translatedEntry.getName(), translatedEntry.getDesc().toString());
95 } else { 87 } else {
96 super.visitOuterClass(owner, name, desc); 88 super.visitOuterClass(owner, name, desc);
97 } 89 }
@@ -99,14 +91,14 @@ public class TranslationClassVisitor extends ClassVisitor {
99 91
100 @Override 92 @Override
101 public AnnotationVisitor visitAnnotation(String desc, boolean visible) { 93 public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
102 TypeDescriptor translatedDesc = translator.getTranslatedTypeDesc(new TypeDescriptor(desc)); 94 TypeDescriptor translatedDesc = translator.translate(new TypeDescriptor(desc));
103 AnnotationVisitor av = super.visitAnnotation(translatedDesc.toString(), visible); 95 AnnotationVisitor av = super.visitAnnotation(translatedDesc.toString(), visible);
104 return new TranslationAnnotationVisitor(translator, translatedDesc.getTypeEntry(), api, av); 96 return new TranslationAnnotationVisitor(translator, translatedDesc.getTypeEntry(), api, av);
105 } 97 }
106 98
107 @Override 99 @Override
108 public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { 100 public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {
109 TypeDescriptor translatedDesc = translator.getTranslatedTypeDesc(new TypeDescriptor(desc)); 101 TypeDescriptor translatedDesc = translator.translate(new TypeDescriptor(desc));
110 AnnotationVisitor av = super.visitTypeAnnotation(typeRef, typePath, translatedDesc.toString(), visible); 102 AnnotationVisitor av = super.visitTypeAnnotation(typeRef, typePath, translatedDesc.toString(), visible);
111 return new TranslationAnnotationVisitor(translator, translatedDesc.getTypeEntry(), api, av); 103 return new TranslationAnnotationVisitor(translator, translatedDesc.getTypeEntry(), api, av);
112 } 104 }
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 @@
1package cuchaz.enigma.bytecode.translators; 1package cuchaz.enigma.bytecode.translators;
2 2
3import cuchaz.enigma.mapping.Translator; 3import cuchaz.enigma.translation.Translator;
4import cuchaz.enigma.mapping.TypeDescriptor; 4import cuchaz.enigma.translation.representation.TypeDescriptor;
5import cuchaz.enigma.mapping.entry.FieldDefEntry; 5import cuchaz.enigma.translation.representation.entry.FieldDefEntry;
6import org.objectweb.asm.AnnotationVisitor; 6import org.objectweb.asm.AnnotationVisitor;
7import org.objectweb.asm.FieldVisitor; 7import org.objectweb.asm.FieldVisitor;
8import org.objectweb.asm.TypePath; 8import org.objectweb.asm.TypePath;
@@ -19,14 +19,14 @@ public class TranslationFieldVisitor extends FieldVisitor {
19 19
20 @Override 20 @Override
21 public AnnotationVisitor visitAnnotation(String desc, boolean visible) { 21 public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
22 TypeDescriptor typeDesc = translator.getTranslatedTypeDesc(new TypeDescriptor(desc)); 22 TypeDescriptor typeDesc = translator.translate(new TypeDescriptor(desc));
23 AnnotationVisitor av = super.visitAnnotation(typeDesc.toString(), visible); 23 AnnotationVisitor av = super.visitAnnotation(typeDesc.toString(), visible);
24 return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av); 24 return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av);
25 } 25 }
26 26
27 @Override 27 @Override
28 public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { 28 public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {
29 TypeDescriptor typeDesc = translator.getTranslatedTypeDesc(new TypeDescriptor(desc)); 29 TypeDescriptor typeDesc = translator.translate(new TypeDescriptor(desc));
30 AnnotationVisitor av = super.visitAnnotation(typeDesc.toString(), visible); 30 AnnotationVisitor av = super.visitAnnotation(typeDesc.toString(), visible);
31 return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av); 31 return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av);
32 } 32 }
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 @@
1package cuchaz.enigma.bytecode.translators; 1package cuchaz.enigma.bytecode.translators;
2 2
3import cuchaz.enigma.mapping.*; 3import cuchaz.enigma.translation.Translator;
4import cuchaz.enigma.mapping.entry.*; 4import cuchaz.enigma.translation.mapping.NameValidator;
5import cuchaz.enigma.translation.representation.MethodDescriptor;
6import cuchaz.enigma.translation.representation.Signature;
7import cuchaz.enigma.translation.representation.TypeDescriptor;
8import cuchaz.enigma.translation.representation.entry.*;
5import org.objectweb.asm.*; 9import org.objectweb.asm.*;
6 10
7import java.util.Collection; 11import java.util.Collection;
@@ -26,15 +30,15 @@ public class TranslationMethodVisitor extends MethodVisitor {
26 @Override 30 @Override
27 public void visitFieldInsn(int opcode, String owner, String name, String desc) { 31 public void visitFieldInsn(int opcode, String owner, String name, String desc) {
28 FieldEntry entry = new FieldEntry(new ClassEntry(owner), name, new TypeDescriptor(desc)); 32 FieldEntry entry = new FieldEntry(new ClassEntry(owner), name, new TypeDescriptor(desc));
29 FieldEntry translatedEntry = translator.getTranslatedField(entry); 33 FieldEntry translatedEntry = translator.translate(entry);
30 super.visitFieldInsn(opcode, translatedEntry.getClassName(), translatedEntry.getName(), translatedEntry.getDesc().toString()); 34 super.visitFieldInsn(opcode, translatedEntry.getParent().getFullName(), translatedEntry.getName(), translatedEntry.getDesc().toString());
31 } 35 }
32 36
33 @Override 37 @Override
34 public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { 38 public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
35 MethodEntry entry = new MethodEntry(new ClassEntry(owner), name, new MethodDescriptor(desc)); 39 MethodEntry entry = new MethodEntry(new ClassEntry(owner), name, new MethodDescriptor(desc));
36 MethodEntry translatedEntry = translator.getTranslatedMethod(entry); 40 MethodEntry translatedEntry = translator.translate(entry);
37 super.visitMethodInsn(opcode, translatedEntry.getClassName(), translatedEntry.getName(), translatedEntry.getDesc().toString(), itf); 41 super.visitMethodInsn(opcode, translatedEntry.getParent().getFullName(), translatedEntry.getName(), translatedEntry.getDesc().toString(), itf);
38 } 42 }
39 43
40 @Override 44 @Override
@@ -52,7 +56,7 @@ public class TranslationMethodVisitor extends MethodVisitor {
52 Object object = array[i]; 56 Object object = array[i];
53 if (object instanceof String) { 57 if (object instanceof String) {
54 String type = (String) object; 58 String type = (String) object;
55 array[i] = translator.getTranslatedClass(new ClassEntry(type)).getName(); 59 array[i] = translator.translate(new ClassEntry(type)).getFullName();
56 } 60 }
57 } 61 }
58 return array; 62 return array;
@@ -60,21 +64,21 @@ public class TranslationMethodVisitor extends MethodVisitor {
60 64
61 @Override 65 @Override
62 public AnnotationVisitor visitAnnotation(String desc, boolean visible) { 66 public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
63 TypeDescriptor typeDesc = translator.getTranslatedTypeDesc(new TypeDescriptor(desc)); 67 TypeDescriptor typeDesc = translator.translate(new TypeDescriptor(desc));
64 AnnotationVisitor av = super.visitAnnotation(typeDesc.toString(), visible); 68 AnnotationVisitor av = super.visitAnnotation(typeDesc.toString(), visible);
65 return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av); 69 return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av);
66 } 70 }
67 71
68 @Override 72 @Override
69 public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { 73 public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
70 TypeDescriptor typeDesc = translator.getTranslatedTypeDesc(new TypeDescriptor(desc)); 74 TypeDescriptor typeDesc = translator.translate(new TypeDescriptor(desc));
71 AnnotationVisitor av = super.visitParameterAnnotation(parameter, typeDesc.toString(), visible); 75 AnnotationVisitor av = super.visitParameterAnnotation(parameter, typeDesc.toString(), visible);
72 return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av); 76 return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av);
73 } 77 }
74 78
75 @Override 79 @Override
76 public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { 80 public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {
77 TypeDescriptor typeDesc = translator.getTranslatedTypeDesc(new TypeDescriptor(desc)); 81 TypeDescriptor typeDesc = translator.translate(new TypeDescriptor(desc));
78 AnnotationVisitor av = super.visitTypeAnnotation(typeRef, typePath, typeDesc.toString(), visible); 82 AnnotationVisitor av = super.visitTypeAnnotation(typeRef, typePath, typeDesc.toString(), visible);
79 return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av); 83 return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av);
80 } 84 }
@@ -83,19 +87,18 @@ public class TranslationMethodVisitor extends MethodVisitor {
83 public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { 87 public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
84 hasParameterMeta = true; 88 hasParameterMeta = true;
85 89
86 String translatedSignature = translator.getTranslatedSignature(Signature.createTypedSignature(signature)).toString(); 90 String translatedSignature = translator.translate(Signature.createTypedSignature(signature)).toString();
87 int argumentIndex = methodEntry.getArgumentIndex(ownerEntry, index); 91 int argumentIndex = methodEntry.getArgumentIndex(ownerEntry, index);
88 92
89 if (argumentIndex >= 0) { 93 if (argumentIndex >= 0) {
90 LocalVariableDefEntry entry = new LocalVariableDefEntry(methodEntry, index, name, new TypeDescriptor(desc)); 94 LocalVariableDefEntry entry = new LocalVariableDefEntry(methodEntry, index, name, true, new TypeDescriptor(desc));
91 LocalVariableDefEntry translatedEntry = translator.getTranslatedVariableDef(entry); 95 LocalVariableDefEntry translatedEntry = translator.translate(entry);
92 String translatedName = translatedEntry.getName(); 96 String translatedName = translatedEntry.getName();
93 97
94 // TODO: Better name inference
95 if (translatedName.equals(entry.getName())) { 98 if (translatedName.equals(entry.getName())) {
96 List<TypeDescriptor> arguments = methodEntry.getDesc().getArgumentDescs(); 99 List<TypeDescriptor> arguments = methodEntry.getDesc().getArgumentDescs();
97 List<TypeDescriptor> translatedArguments = arguments.stream() 100 List<TypeDescriptor> translatedArguments = arguments.stream()
98 .map(translator::getTranslatedTypeDesc) 101 .map(translator::translate)
99 .collect(Collectors.toList()); 102 .collect(Collectors.toList());
100 103
101 boolean argument = argumentIndex < arguments.size(); 104 boolean argument = argumentIndex < arguments.size();
@@ -109,42 +112,42 @@ public class TranslationMethodVisitor extends MethodVisitor {
109 super.visitLocalVariable(translatedName, translatedEntry.getDesc().toString(), translatedSignature, start, end, index); 112 super.visitLocalVariable(translatedName, translatedEntry.getDesc().toString(), translatedSignature, start, end, index);
110 } else { 113 } else {
111 // Handle "this" variable 114 // Handle "this" variable
112 TypeDescriptor translatedDesc = translator.getTranslatedTypeDesc(new TypeDescriptor(desc)); 115 TypeDescriptor translatedDesc = translator.translate(new TypeDescriptor(desc));
113 super.visitLocalVariable(name, translatedDesc.toString(), translatedSignature, start, end, index); 116 super.visitLocalVariable(name, translatedDesc.toString(), translatedSignature, start, end, index);
114 } 117 }
115 } 118 }
116 119
117 @Override 120 @Override
118 public void visitTypeInsn(int opcode, String type) { 121 public void visitTypeInsn(int opcode, String type) {
119 ClassEntry translatedEntry = translator.getTranslatedClass(new ClassEntry(type)); 122 ClassEntry translatedEntry = translator.translate(new ClassEntry(type));
120 super.visitTypeInsn(opcode, translatedEntry.getName()); 123 super.visitTypeInsn(opcode, translatedEntry.getFullName());
121 } 124 }
122 125
123 @Override 126 @Override
124 public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { 127 public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
125 MethodDescriptor translatedMethodDesc = translator.getTranslatedMethodDesc(new MethodDescriptor(desc)); 128 MethodDescriptor translatedMethodDesc = translator.translate(new MethodDescriptor(desc));
126 Object[] translatedBsmArgs = new Object[bsmArgs.length]; 129 Object[] translatedBsmArgs = new Object[bsmArgs.length];
127 for (int i = 0; i < bsmArgs.length; i++) { 130 for (int i = 0; i < bsmArgs.length; i++) {
128 translatedBsmArgs[i] = translator.getTranslatedValue(bsmArgs[i]); 131 translatedBsmArgs[i] = AsmObjectTranslator.translateValue(translator, bsmArgs[i]);
129 } 132 }
130 super.visitInvokeDynamicInsn(name, translatedMethodDesc.toString(), translator.getTranslatedHandle(bsm), translatedBsmArgs); 133 super.visitInvokeDynamicInsn(name, translatedMethodDesc.toString(), AsmObjectTranslator.translateHandle(translator, bsm), translatedBsmArgs);
131 } 134 }
132 135
133 @Override 136 @Override
134 public void visitLdcInsn(Object cst) { 137 public void visitLdcInsn(Object cst) {
135 super.visitLdcInsn(translator.getTranslatedValue(cst)); 138 super.visitLdcInsn(AsmObjectTranslator.translateValue(translator, cst));
136 } 139 }
137 140
138 @Override 141 @Override
139 public void visitMultiANewArrayInsn(String desc, int dims) { 142 public void visitMultiANewArrayInsn(String desc, int dims) {
140 super.visitMultiANewArrayInsn(translator.getTranslatedTypeDesc(new TypeDescriptor(desc)).toString(), dims); 143 super.visitMultiANewArrayInsn(translator.translate(new TypeDescriptor(desc)).toString(), dims);
141 } 144 }
142 145
143 @Override 146 @Override
144 public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { 147 public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
145 if (type != null) { 148 if (type != null) {
146 ClassEntry translatedEntry = translator.getTranslatedClass(new ClassEntry(type)); 149 ClassEntry translatedEntry = translator.translate(new ClassEntry(type));
147 super.visitTryCatchBlock(start, end, handler, translatedEntry.getName()); 150 super.visitTryCatchBlock(start, end, handler, translatedEntry.getFullName());
148 } else { 151 } else {
149 super.visitTryCatchBlock(start, end, handler, type); 152 super.visitTryCatchBlock(start, end, handler, type);
150 } 153 }
@@ -159,7 +162,7 @@ public class TranslationMethodVisitor extends MethodVisitor {
159 162
160 for (int argumentIndex = 0; argumentIndex < arguments.size(); argumentIndex++) { 163 for (int argumentIndex = 0; argumentIndex < arguments.size(); argumentIndex++) {
161 LocalVariableEntry entry = new LocalVariableEntry(methodEntry, offset, "", true); 164 LocalVariableEntry entry = new LocalVariableEntry(methodEntry, offset, "", true);
162 LocalVariableEntry translatedEntry = translator.getTranslatedVariable(entry); 165 LocalVariableEntry translatedEntry = translator.translate(entry);
163 String translatedName = translatedEntry.getName(); 166 String translatedName = translatedEntry.getName();
164 if (translatedName.equals(entry.getName())) { 167 if (translatedName.equals(entry.getName())) {
165 super.visitParameter(inferArgumentName(argumentIndex, arguments.get(argumentIndex), arguments), 0); 168 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;
17import com.google.common.collect.Multimap; 17import com.google.common.collect.Multimap;
18import cuchaz.enigma.gui.node.ClassSelectorClassNode; 18import cuchaz.enigma.gui.node.ClassSelectorClassNode;
19import cuchaz.enigma.gui.node.ClassSelectorPackageNode; 19import cuchaz.enigma.gui.node.ClassSelectorPackageNode;
20import cuchaz.enigma.mapping.entry.ClassEntry; 20import cuchaz.enigma.translation.representation.entry.ClassEntry;
21import cuchaz.enigma.throwables.IllegalNameException; 21import cuchaz.enigma.throwables.IllegalNameException;
22 22
23import javax.swing.*; 23import javax.swing.*;
24import javax.swing.event.CellEditorListener; 24import javax.swing.event.CellEditorListener;
25import javax.swing.event.ChangeEvent; 25import javax.swing.event.ChangeEvent;
26import javax.swing.tree.*; 26import javax.swing.tree.*;
27import java.awt.*;
28import java.awt.event.MouseAdapter; 27import java.awt.event.MouseAdapter;
29import java.awt.event.MouseEvent; 28import java.awt.event.MouseEvent;
30import java.util.*; 29import java.util.*;
@@ -32,7 +31,7 @@ import java.util.List;
32 31
33public class ClassSelector extends JTree { 32public class ClassSelector extends JTree {
34 33
35 public static final Comparator<ClassEntry> DEOBF_CLASS_COMPARATOR = Comparator.comparing(ClassEntry::getName); 34 public static final Comparator<ClassEntry> DEOBF_CLASS_COMPARATOR = Comparator.comparing(ClassEntry::getFullName);
36 private DefaultMutableTreeNode rootNodes; 35 private DefaultMutableTreeNode rootNodes;
37 private ClassSelectionListener selectionListener; 36 private ClassSelectionListener selectionListener;
38 private RenameSelectionListener renameSelectionListener; 37 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;
16import cuchaz.enigma.analysis.EntryReference; 16import cuchaz.enigma.analysis.EntryReference;
17import cuchaz.enigma.analysis.SourceIndex; 17import cuchaz.enigma.analysis.SourceIndex;
18import cuchaz.enigma.analysis.Token; 18import cuchaz.enigma.analysis.Token;
19import cuchaz.enigma.gui.highlight.SelectionHighlightPainter; 19import cuchaz.enigma.translation.representation.entry.ClassEntry;
20import cuchaz.enigma.mapping.entry.ClassEntry; 20import cuchaz.enigma.translation.representation.entry.Entry;
21import cuchaz.enigma.mapping.entry.Entry;
22import de.sciss.syntaxpane.DefaultSyntaxKit; 21import de.sciss.syntaxpane.DefaultSyntaxKit;
23 22
24import javax.swing.*; 23import javax.swing.*;
@@ -33,7 +32,6 @@ public class CodeReader extends JEditorPane {
33 private static final long serialVersionUID = 3673180950485748810L; 32 private static final long serialVersionUID = 3673180950485748810L;
34 33
35 private static final Object lock = new Object(); 34 private static final Object lock = new Object();
36 private SelectionHighlightPainter selectionHighlightPainter;
37 private SourceIndex sourceIndex; 35 private SourceIndex sourceIndex;
38 private SelectionListener selectionListener; 36 private SelectionListener selectionListener;
39 37
@@ -58,8 +56,6 @@ public class CodeReader extends JEditorPane {
58 } 56 }
59 } 57 }
60 }); 58 });
61
62 selectionHighlightPainter = new SelectionHighlightPainter();
63 } 59 }
64 60
65 // HACKHACK: someday we can update the main GUI to use this code reader 61 // HACKHACK: someday we can update the main GUI to use this code reader
@@ -144,7 +140,7 @@ public class CodeReader extends JEditorPane {
144 140
145 // decompile it 141 // decompile it
146 142
147 CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getOutermostClassName()); 143 CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getName());
148 String source = deobfuscator.getSource(sourceTree); 144 String source = deobfuscator.getSource(sourceTree);
149 setCode(source); 145 setCode(source);
150 sourceIndex = deobfuscator.getSourceIndex(sourceTree, source, ignoreBadTokens); 146 sourceIndex = deobfuscator.getSourceIndex(sourceTree, source, ignoreBadTokens);
@@ -155,52 +151,7 @@ public class CodeReader extends JEditorPane {
155 }).start(); 151 }).start();
156 } 152 }
157 153
158 public void navigateToClassDeclaration(ClassEntry classEntry) {
159
160 // navigate to the class declaration
161 Token token = sourceIndex.getDeclarationToken(classEntry);
162 if (token == null) {
163 // couldn't find the class declaration token, might be an anonymous class
164 // look for any declaration in that class instead
165 for (Entry entry : sourceIndex.declarations()) {
166 if (entry.getOwnerClassEntry().equals(classEntry)) {
167 token = sourceIndex.getDeclarationToken(entry);
168 break;
169 }
170 }
171 }
172
173 if (token != null) {
174 navigateToToken(token);
175 } else {
176 // couldn't find anything =(
177 System.out.println("Unable to find declaration in source for " + classEntry);
178 }
179 }
180
181 public void navigateToToken(final Token token) {
182 navigateToToken(this, token, selectionHighlightPainter);
183 }
184
185 public void setHighlightedTokens(Iterable<Token> tokens, HighlightPainter painter) {
186 for (Token token : tokens) {
187 setHighlightedToken(token, painter);
188 }
189 }
190
191 public void setHighlightedToken(Token token, HighlightPainter painter) {
192 try {
193 getHighlighter().addHighlight(token.start, token.end, painter);
194 } catch (BadLocationException ex) {
195 throw new IllegalArgumentException(ex);
196 }
197 }
198
199 public void clearHighlights() {
200 getHighlighter().removeAllHighlights();
201 }
202
203 public interface SelectionListener { 154 public interface SelectionListener {
204 void onSelect(EntryReference<Entry, Entry> reference); 155 void onSelect(EntryReference<Entry<?>, Entry<?>> reference);
205 } 156 }
206} 157}
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;
29import cuchaz.enigma.gui.panels.PanelEditor; 29import cuchaz.enigma.gui.panels.PanelEditor;
30import cuchaz.enigma.gui.panels.PanelIdentifier; 30import cuchaz.enigma.gui.panels.PanelIdentifier;
31import cuchaz.enigma.gui.panels.PanelObf; 31import cuchaz.enigma.gui.panels.PanelObf;
32import cuchaz.enigma.mapping.*;
33import cuchaz.enigma.mapping.entry.*;
34import cuchaz.enigma.throwables.IllegalNameException; 32import cuchaz.enigma.throwables.IllegalNameException;
33import cuchaz.enigma.translation.mapping.AccessModifier;
34import cuchaz.enigma.translation.representation.entry.*;
35import cuchaz.enigma.utils.Utils; 35import cuchaz.enigma.utils.Utils;
36import de.sciss.syntaxpane.DefaultSyntaxKit; 36import de.sciss.syntaxpane.DefaultSyntaxKit;
37 37
@@ -44,8 +44,8 @@ import javax.swing.tree.TreeNode;
44import javax.swing.tree.TreePath; 44import javax.swing.tree.TreePath;
45import java.awt.*; 45import java.awt.*;
46import java.awt.event.*; 46import java.awt.event.*;
47import java.io.File;
48import java.io.IOException; 47import java.io.IOException;
48import java.nio.file.Path;
49import java.util.*; 49import java.util.*;
50import java.util.List; 50import java.util.List;
51import java.util.function.Function; 51import java.util.function.Function;
@@ -58,7 +58,7 @@ public class Gui {
58 58
59 private final MenuBar menuBar; 59 private final MenuBar menuBar;
60 // state 60 // state
61 public EntryReference<Entry, Entry> reference; 61 public EntryReference<Entry<?>, Entry<?>> reference;
62 public FileDialog jarFileChooser; 62 public FileDialog jarFileChooser;
63 public FileDialog tinyMappingsFileChooser; 63 public FileDialog tinyMappingsFileChooser;
64 public JFileChooser enigmaMappingsFileChooser; 64 public JFileChooser enigmaMappingsFileChooser;
@@ -222,7 +222,7 @@ public class Gui {
222 222
223 Object node = path.getLastPathComponent(); 223 Object node = path.getLastPathComponent();
224 if (node instanceof ReferenceTreeNode) { 224 if (node instanceof ReferenceTreeNode) {
225 ReferenceTreeNode<Entry, Entry> referenceNode = ((ReferenceTreeNode<Entry, Entry>) node); 225 ReferenceTreeNode<Entry<?>, Entry<?>> referenceNode = ((ReferenceTreeNode<Entry<?>, Entry<?>>) node);
226 if (referenceNode.getReference() != null) { 226 if (referenceNode.getReference() != null) {
227 navigateTo(referenceNode.getReference()); 227 navigateTo(referenceNode.getReference());
228 } else { 228 } else {
@@ -250,10 +250,10 @@ public class Gui {
250 tokens.setPreferredSize(new Dimension(0, 200)); 250 tokens.setPreferredSize(new Dimension(0, 200));
251 tokens.setMinimumSize(new Dimension(0, 200)); 251 tokens.setMinimumSize(new Dimension(0, 200));
252 JSplitPane callPanel = new JSplitPane( 252 JSplitPane callPanel = new JSplitPane(
253 JSplitPane.VERTICAL_SPLIT, 253 JSplitPane.VERTICAL_SPLIT,
254 true, 254 true,
255 new JScrollPane(callsTree), 255 new JScrollPane(callsTree),
256 new JScrollPane(tokens) 256 new JScrollPane(tokens)
257 ); 257 );
258 callPanel.setResizeWeight(1); // let the top side take all the slack 258 callPanel.setResizeWeight(1); // let the top side take all the slack
259 callPanel.resetToPreferredSizes(); 259 callPanel.resetToPreferredSizes();
@@ -368,9 +368,9 @@ public class Gui {
368 this.deobfPanel.deobfClasses.setClasses(deobfClasses); 368 this.deobfPanel.deobfClasses.setClasses(deobfClasses);
369 } 369 }
370 370
371 public void setMappingsFile(File file) { 371 public void setMappingsFile(Path path) {
372 this.enigmaMappingsFileChooser.setSelectedFile(file); 372 this.enigmaMappingsFileChooser.setSelectedFile(path != null ? path.toFile() : null);
373 this.menuBar.saveMappingsMenu.setEnabled(file != null); 373 this.menuBar.saveMappingsMenu.setEnabled(path != null);
374 } 374 }
375 375
376 public void setSource(String source) { 376 public void setSource(String source) {
@@ -427,7 +427,7 @@ public class Gui {
427 } 427 }
428 } 428 }
429 429
430 private void showReference(EntryReference<Entry, Entry> reference) { 430 private void showReference(EntryReference<Entry<?>, Entry<?>> reference) {
431 if (reference == null) { 431 if (reference == null) {
432 infoPanel.clearReference(); 432 infoPanel.clearReference();
433 return; 433 return;
@@ -453,29 +453,29 @@ public class Gui {
453 453
454 private void showLocalVariableEntry(LocalVariableEntry entry) { 454 private void showLocalVariableEntry(LocalVariableEntry entry) {
455 addNameValue(infoPanel, "Variable", entry.getName()); 455 addNameValue(infoPanel, "Variable", entry.getName());
456 addNameValue(infoPanel, "Class", entry.getOwnerClassEntry().getName()); 456 addNameValue(infoPanel, "Class", entry.getContainingClass().getFullName());
457 addNameValue(infoPanel, "Method", entry.getOwnerEntry().getName()); 457 addNameValue(infoPanel, "Method", entry.getParent().getName());
458 addNameValue(infoPanel, "Index", Integer.toString(entry.getIndex())); 458 addNameValue(infoPanel, "Index", Integer.toString(entry.getIndex()));
459 } 459 }
460 460
461 private void showClassEntry(ClassEntry entry) { 461 private void showClassEntry(ClassEntry entry) {
462 addNameValue(infoPanel, "Class", entry.getName()); 462 addNameValue(infoPanel, "Class", entry.getFullName());
463 addModifierComboBox(infoPanel, "Modifier", entry); 463 addModifierComboBox(infoPanel, "Modifier", entry);
464 } 464 }
465 465
466 private void showFieldEntry(FieldEntry entry) { 466 private void showFieldEntry(FieldEntry entry) {
467 addNameValue(infoPanel, "Field", entry.getName()); 467 addNameValue(infoPanel, "Field", entry.getName());
468 addNameValue(infoPanel, "Class", entry.getOwnerClassEntry().getName()); 468 addNameValue(infoPanel, "Class", entry.getParent().getFullName());
469 addNameValue(infoPanel, "TypeDescriptor", entry.getDesc().toString()); 469 addNameValue(infoPanel, "TypeDescriptor", entry.getDesc().toString());
470 addModifierComboBox(infoPanel, "Modifier", entry); 470 addModifierComboBox(infoPanel, "Modifier", entry);
471 } 471 }
472 472
473 private void showMethodEntry(MethodEntry entry) { 473 private void showMethodEntry(MethodEntry entry) {
474 if (entry.isConstructor()) { 474 if (entry.isConstructor()) {
475 addNameValue(infoPanel, "Constructor", entry.getOwnerClassEntry().getName()); 475 addNameValue(infoPanel, "Constructor", entry.getParent().getFullName());
476 } else { 476 } else {
477 addNameValue(infoPanel, "Method", entry.getName()); 477 addNameValue(infoPanel, "Method", entry.getName());
478 addNameValue(infoPanel, "Class", entry.getOwnerClassEntry().getName()); 478 addNameValue(infoPanel, "Class", entry.getParent().getFullName());
479 } 479 }
480 addNameValue(infoPanel, "MethodDescriptor", entry.getDesc().toString()); 480 addNameValue(infoPanel, "MethodDescriptor", entry.getDesc().toString());
481 addModifierComboBox(infoPanel, "Modifier", entry); 481 addModifierComboBox(infoPanel, "Modifier", entry);
@@ -494,7 +494,7 @@ public class Gui {
494 container.add(panel); 494 container.add(panel);
495 } 495 }
496 496
497 private JComboBox<Mappings.EntryModifier> addModifierComboBox(JPanel container, String name, Entry entry) { 497 private JComboBox<AccessModifier> addModifierComboBox(JPanel container, String name, Entry entry) {
498 if (!getController().entryIsInJar(entry)) 498 if (!getController().entryIsInJar(entry))
499 return null; 499 return null;
500 JPanel panel = new JPanel(); 500 JPanel panel = new JPanel();
@@ -502,7 +502,7 @@ public class Gui {
502 JLabel label = new JLabel(name + ":", JLabel.RIGHT); 502 JLabel label = new JLabel(name + ":", JLabel.RIGHT);
503 label.setPreferredSize(new Dimension(100, label.getPreferredSize().height)); 503 label.setPreferredSize(new Dimension(100, label.getPreferredSize().height));
504 panel.add(label); 504 panel.add(label);
505 JComboBox<Mappings.EntryModifier> combo = new JComboBox<>(Mappings.EntryModifier.values()); 505 JComboBox<AccessModifier> combo = new JComboBox<>(AccessModifier.values());
506 ((JLabel) combo.getRenderer()).setHorizontalAlignment(JLabel.LEFT); 506 ((JLabel) combo.getRenderer()).setHorizontalAlignment(JLabel.LEFT);
507 combo.setPreferredSize(new Dimension(100, label.getPreferredSize().height)); 507 combo.setPreferredSize(new Dimension(100, label.getPreferredSize().height));
508 combo.setSelectedIndex(getController().getDeobfuscator().getModifier(entry).ordinal()); 508 combo.setSelectedIndex(getController().getDeobfuscator().getModifier(entry).ordinal());
@@ -520,11 +520,13 @@ public class Gui {
520 boolean isToken = token != null; 520 boolean isToken = token != null;
521 521
522 reference = this.controller.getDeobfReference(token); 522 reference = this.controller.getDeobfReference(token);
523 boolean isClassEntry = isToken && reference.entry instanceof ClassEntry; 523
524 boolean isFieldEntry = isToken && reference.entry instanceof FieldEntry; 524 Entry<?> referenceEntry = reference != null ? reference.entry : null;
525 boolean isMethodEntry = isToken && reference.entry instanceof MethodEntry && !((MethodEntry) reference.entry).isConstructor(); 525 boolean isClassEntry = isToken && referenceEntry instanceof ClassEntry;
526 boolean isConstructorEntry = isToken && reference.entry instanceof MethodEntry && ((MethodEntry) reference.entry).isConstructor(); 526 boolean isFieldEntry = isToken && referenceEntry instanceof FieldEntry;
527 boolean isInJar = isToken && this.controller.entryIsInJar(reference.entry); 527 boolean isMethodEntry = isToken && referenceEntry instanceof MethodEntry && !((MethodEntry) referenceEntry).isConstructor();
528 boolean isConstructorEntry = isToken && referenceEntry instanceof MethodEntry && ((MethodEntry) referenceEntry).isConstructor();
529 boolean isInJar = isToken && this.controller.entryIsInJar(referenceEntry);
528 boolean isRenameable = isToken && this.controller.referenceIsRenameable(reference); 530 boolean isRenameable = isToken && this.controller.referenceIsRenameable(reference);
529 531
530 if (isToken) { 532 if (isToken) {
@@ -542,14 +544,14 @@ public class Gui {
542 this.popupMenu.openPreviousMenu.setEnabled(this.controller.hasPreviousLocation()); 544 this.popupMenu.openPreviousMenu.setEnabled(this.controller.hasPreviousLocation());
543 this.popupMenu.toggleMappingMenu.setEnabled(isRenameable); 545 this.popupMenu.toggleMappingMenu.setEnabled(isRenameable);
544 546
545 if (isToken && this.controller.entryHasDeobfuscatedName(reference.entry)) { 547 if (isToken && this.controller.entryHasDeobfuscatedName(referenceEntry)) {
546 this.popupMenu.toggleMappingMenu.setText("Reset to obfuscated"); 548 this.popupMenu.toggleMappingMenu.setText("Reset to obfuscated");
547 } else { 549 } else {
548 this.popupMenu.toggleMappingMenu.setText("Mark as deobfuscated"); 550 this.popupMenu.toggleMappingMenu.setText("Mark as deobfuscated");
549 } 551 }
550 } 552 }
551 553
552 public void navigateTo(Entry entry) { 554 public void navigateTo(Entry<?> entry) {
553 if (!this.controller.entryIsInJar(entry)) { 555 if (!this.controller.entryIsInJar(entry)) {
554 // entry is not in the jar. Ignore it 556 // entry is not in the jar. Ignore it
555 return; 557 return;
@@ -560,7 +562,7 @@ public class Gui {
560 this.controller.openDeclaration(entry); 562 this.controller.openDeclaration(entry);
561 } 563 }
562 564
563 private void navigateTo(EntryReference<Entry, Entry> reference) { 565 private void navigateTo(EntryReference<Entry<?>, Entry<?>> reference) {
564 if (!this.controller.entryIsInJar(reference.getLocationClassEntry())) { 566 if (!this.controller.entryIsInJar(reference.getLocationClassEntry())) {
565 return; 567 return;
566 } 568 }
@@ -613,7 +615,7 @@ public class Gui {
613 String newName = text.getText(); 615 String newName = text.getText();
614 if (saveName && newName != null && !newName.isEmpty()) { 616 if (saveName && newName != null && !newName.isEmpty()) {
615 try { 617 try {
616 this.controller.rename(reference, newName); 618 this.controller.rename(reference, newName, true);
617 } catch (IllegalNameException ex) { 619 } catch (IllegalNameException ex) {
618 text.setBorder(BorderFactory.createLineBorder(Color.red, 1)); 620 text.setBorder(BorderFactory.createLineBorder(Color.red, 1));
619 text.setToolTipText(ex.getReason()); 621 text.setToolTipText(ex.getReason());
@@ -737,13 +739,13 @@ public class Gui {
737 739
738 public void showDiscardDiag(Function<Integer, Void> callback, String... options) { 740 public void showDiscardDiag(Function<Integer, Void> callback, String... options) {
739 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, 741 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,
740 JOptionPane.QUESTION_MESSAGE, null, options, options[2]); 742 JOptionPane.QUESTION_MESSAGE, null, options, options[2]);
741 callback.apply(response); 743 callback.apply(response);
742 } 744 }
743 745
744 public void saveMapping() throws IOException { 746 public void saveMapping() throws IOException {
745 if (this.enigmaMappingsFileChooser.getSelectedFile() != null || this.enigmaMappingsFileChooser.showSaveDialog(this.frame) == JFileChooser.APPROVE_OPTION) 747 if (this.enigmaMappingsFileChooser.getSelectedFile() != null || this.enigmaMappingsFileChooser.showSaveDialog(this.frame) == JFileChooser.APPROVE_OPTION)
746 this.controller.saveMappings(this.enigmaMappingsFileChooser.getSelectedFile()); 748 this.controller.saveMappings(this.enigmaMappingsFileChooser.getSelectedFile().toPath());
747 } 749 }
748 750
749 public void close() { 751 public void close() {
@@ -782,7 +784,7 @@ public class Gui {
782 DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) node.getChildAt(i); 784 DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) node.getChildAt(i);
783 ClassEntry prevDataChild = (ClassEntry) childNode.getUserObject(); 785 ClassEntry prevDataChild = (ClassEntry) childNode.getUserObject();
784 ClassEntry dataChild = new ClassEntry(data + "/" + prevDataChild.getSimpleName()); 786 ClassEntry dataChild = new ClassEntry(data + "/" + prevDataChild.getSimpleName());
785 this.controller.rename(new EntryReference<>(prevDataChild, prevDataChild.getName()), dataChild.getName(), false, i + 1 == node.getChildCount()); 787 this.controller.rename(new EntryReference<>(prevDataChild, prevDataChild.getFullName()), dataChild.getFullName(), false);
786 childNode.setUserObject(dataChild); 788 childNode.setUserObject(dataChild);
787 } 789 }
788 node.setUserObject(data); 790 node.setUserObject(data);
@@ -791,19 +793,19 @@ public class Gui {
791 } 793 }
792 // class rename 794 // class rename
793 else if (data instanceof ClassEntry) 795 else if (data instanceof ClassEntry)
794 this.controller.rename(new EntryReference<>((ClassEntry) prevData, ((ClassEntry) prevData).getName()), ((ClassEntry) data).getName(), false, true); 796 this.controller.rename(new EntryReference<>((ClassEntry) prevData, ((ClassEntry) prevData).getFullName()), ((ClassEntry) data).getFullName(), false);
795 } 797 }
796 798
797 public void moveClassTree(EntryReference<Entry, Entry> deobfReference, String newName) { 799 public void moveClassTree(EntryReference<Entry<?>, Entry<?>> deobfReference, String newName) {
798 String oldEntry = deobfReference.entry.getOwnerClassEntry().getPackageName(); 800 String oldEntry = deobfReference.entry.getContainingClass().getPackageName();
799 String newEntry = new ClassEntry(newName).getPackageName(); 801 String newEntry = new ClassEntry(newName).getPackageName();
800 moveClassTree(deobfReference, newName, oldEntry == null, 802 moveClassTree(deobfReference, newName, oldEntry == null,
801 newEntry == null); 803 newEntry == null);
802 } 804 }
803 805
804 // TODO: getExpansionState will *not* actually update itself based on name changes! 806 // TODO: getExpansionState will *not* actually update itself based on name changes!
805 public void moveClassTree(EntryReference<Entry, Entry> deobfReference, String newName, boolean isOldOb, boolean isNewOb) { 807 public void moveClassTree(EntryReference<Entry<?>, Entry<?>> deobfReference, String newName, boolean isOldOb, boolean isNewOb) {
806 ClassEntry oldEntry = deobfReference.entry.getOwnerClassEntry(); 808 ClassEntry oldEntry = deobfReference.entry.getContainingClass();
807 ClassEntry newEntry = new ClassEntry(newName); 809 ClassEntry newEntry = new ClassEntry(newName);
808 810
809 // Ob -> deob 811 // 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.*;
20import cuchaz.enigma.api.EnigmaPlugin; 20import cuchaz.enigma.api.EnigmaPlugin;
21import cuchaz.enigma.config.Config; 21import cuchaz.enigma.config.Config;
22import cuchaz.enigma.gui.dialog.ProgressDialog; 22import cuchaz.enigma.gui.dialog.ProgressDialog;
23import cuchaz.enigma.mapping.*;
24import cuchaz.enigma.mapping.entry.ClassEntry;
25import cuchaz.enigma.mapping.entry.Entry;
26import cuchaz.enigma.mapping.entry.FieldEntry;
27import cuchaz.enigma.mapping.entry.MethodEntry;
28import cuchaz.enigma.throwables.MappingParseException; 23import cuchaz.enigma.throwables.MappingParseException;
24import cuchaz.enigma.translation.Translator;
25import cuchaz.enigma.translation.mapping.*;
26import cuchaz.enigma.translation.mapping.serde.MappingFormat;
27import cuchaz.enigma.translation.mapping.tree.EntryTree;
28import cuchaz.enigma.translation.representation.entry.ClassEntry;
29import cuchaz.enigma.translation.representation.entry.Entry;
30import cuchaz.enigma.translation.representation.entry.FieldEntry;
31import cuchaz.enigma.translation.representation.entry.MethodEntry;
29import cuchaz.enigma.utils.ReadableToken; 32import cuchaz.enigma.utils.ReadableToken;
30 33
34import javax.annotation.Nullable;
31import java.awt.event.ItemEvent; 35import java.awt.event.ItemEvent;
32import java.io.File; 36import java.io.File;
33import java.io.IOException; 37import java.io.IOException;
38import java.nio.file.Path;
34import java.util.*; 39import java.util.*;
35import java.util.jar.JarFile; 40import java.util.jar.JarFile;
41import java.util.stream.Collectors;
36 42
37public class GuiController { 43public class GuiController {
38 44
@@ -40,27 +46,26 @@ public class GuiController {
40 private Gui gui; 46 private Gui gui;
41 private SourceIndex index; 47 private SourceIndex index;
42 private ClassEntry currentObfClass; 48 private ClassEntry currentObfClass;
43 private boolean isDirty; 49 private Deque<EntryReference<Entry<?>, Entry<?>>> referenceStack;
44 private Deque<EntryReference<Entry, Entry>> referenceStack; 50
51 private Path loadedMappingPath;
52 private MappingFormat loadedMappingFormat;
45 53
46 public GuiController(Gui gui) { 54 public GuiController(Gui gui) {
47 this.gui = gui; 55 this.gui = gui;
48 this.deobfuscator = null; 56 this.deobfuscator = null;
49 this.index = null; 57 this.index = null;
50 this.currentObfClass = null; 58 this.currentObfClass = null;
51 this.isDirty = false;
52 this.referenceStack = Queues.newArrayDeque(); 59 this.referenceStack = Queues.newArrayDeque();
53 } 60 }
54 61
55 public boolean isDirty() { 62 public boolean isDirty() {
56 return this.isDirty; 63 return deobfuscator.getMapper().isDirty();
57 } 64 }
58 65
59 public void openJar(final JarFile jar) throws IOException { 66 public void openJar(final JarFile jar) throws IOException {
60 this.gui.onStartOpenJar("Loading JAR..."); 67 this.gui.onStartOpenJar("Loading JAR...");
61 this.deobfuscator = new Deobfuscator(jar, (msg) -> { 68 this.deobfuscator = new Deobfuscator(jar, this.gui::onStartOpenJar);
62 this.gui.onStartOpenJar(msg);
63 });
64 this.gui.onFinishOpenJar(jar.getName()); 69 this.gui.onFinishOpenJar(jar.getName());
65 refreshClasses(); 70 refreshClasses();
66 } 71 }
@@ -70,43 +75,37 @@ public class GuiController {
70 this.gui.onCloseJar(); 75 this.gui.onCloseJar();
71 } 76 }
72 77
73 public void openEnigmaMappings(File file) throws IOException, MappingParseException { 78 public void openMappings(MappingFormat format, Path path) throws IOException, MappingParseException {
74 this.deobfuscator.setMappings(new MappingsEnigmaReader().read(file)); 79 EntryTree<EntryMapping> mappings = format.read(path);
75 this.isDirty = false; 80 deobfuscator.setMappings(mappings);
76 this.gui.setMappingsFile(file); 81
82 gui.setMappingsFile(path);
83 loadedMappingFormat = format;
84
77 refreshClasses(); 85 refreshClasses();
78 refreshCurrentClass(); 86 refreshCurrentClass();
79 } 87 }
80 88
81 public void openTinyMappings(File file) throws IOException, MappingParseException { 89 public void saveMappings(Path path) {
82 this.deobfuscator.setMappings(new MappingsTinyReader().read(file)); 90 saveMappings(loadedMappingFormat, path);
83 this.isDirty = false;
84 this.gui.setMappingsFile(file);
85 refreshClasses();
86 refreshCurrentClass();
87 } 91 }
88 92
89 public void saveMappings(File file) throws IOException { 93 public void saveMappings(MappingFormat format, Path path) {
90 Mappings mappings = this.deobfuscator.getMappings(); 94 EntryRemapper mapper = deobfuscator.getMapper();
91 switch (mappings.getOriginMappingFormat()) {
92 case SRG_FILE:
93 saveSRGMappings(file);
94 break;
95 default:
96 saveEnigmaMappings(file, Mappings.FormatType.ENIGMA_FILE != mappings.getOriginMappingFormat());
97 break;
98 }
99 95
100 } 96 MappingDelta delta = mapper.takeMappingDelta();
97 boolean saveAll = !path.equals(loadedMappingPath);
101 98
102 public void saveEnigmaMappings(File file, boolean isDirectoryFormat) throws IOException { 99 ProgressDialog.runInThread(this.gui.getFrame(), progress -> {
103 this.deobfuscator.getMappings().saveEnigmaMappings(file, isDirectoryFormat); 100 if (saveAll) {
104 this.isDirty = false; 101 format.write(mapper.getObfToDeobf(), path, progress);
105 } 102 } else {
103 format.write(mapper.getObfToDeobf(), delta, path, progress);
104 }
105 });
106 106
107 public void saveSRGMappings(File file) throws IOException { 107 loadedMappingFormat = format;
108 this.deobfuscator.getMappings().saveSRGMappings(file); 108 loadedMappingPath = path;
109 this.isDirty = false;
110 } 109 }
111 110
112 public void closeMappings() { 111 public void closeMappings() {
@@ -116,11 +115,6 @@ public class GuiController {
116 refreshCurrentClass(); 115 refreshCurrentClass();
117 } 116 }
118 117
119 public void rebuildMethodNames() {
120 ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.rebuildMethodNames(progress));
121 this.isDirty = true;
122 }
123
124 public void exportSource(final File dirOut) { 118 public void exportSource(final File dirOut) {
125 ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.writeSources(dirOut, progress)); 119 ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.writeSources(dirOut, progress));
126 } 120 }
@@ -136,7 +130,8 @@ public class GuiController {
136 return this.index.getReferenceToken(pos); 130 return this.index.getReferenceToken(pos);
137 } 131 }
138 132
139 public EntryReference<Entry, Entry> getDeobfReference(Token token) { 133 @Nullable
134 public EntryReference<Entry<?>, Entry<?>> getDeobfReference(Token token) {
140 if (this.index == null) { 135 if (this.index == null) {
141 return null; 136 return null;
142 } 137 }
@@ -148,44 +143,52 @@ public class GuiController {
148 return null; 143 return null;
149 } 144 }
150 return new ReadableToken( 145 return new ReadableToken(
151 this.index.getLineNumber(token.start), 146 this.index.getLineNumber(token.start),
152 this.index.getColumnNumber(token.start), 147 this.index.getColumnNumber(token.start),
153 this.index.getColumnNumber(token.end) 148 this.index.getColumnNumber(token.end)
154 ); 149 );
155 } 150 }
156 151
157 public boolean entryHasDeobfuscatedName(Entry deobfEntry) { 152 public boolean entryHasDeobfuscatedName(Entry<?> deobfEntry) {
158 return this.deobfuscator.hasDeobfuscatedName(this.deobfuscator.obfuscateEntry(deobfEntry)); 153 EntryResolver resolver = this.deobfuscator.getMapper().getDeobfResolver();
154 Entry<?> resolvedEntry = resolver.resolveFirstEntry(deobfEntry, ResolutionStrategy.RESOLVE_ROOT);
155 return this.deobfuscator.hasDeobfuscatedName(this.deobfuscator.getMapper().obfuscate(resolvedEntry));
159 } 156 }
160 157
161 public boolean entryIsInJar(Entry deobfEntry) { 158 public boolean entryIsInJar(Entry<?> deobfEntry) {
162 return this.deobfuscator.isObfuscatedIdentifier(this.deobfuscator.obfuscateEntry(deobfEntry)); 159 if (deobfEntry == null) return false;
160 return this.deobfuscator.isObfuscatedIdentifier(this.deobfuscator.getMapper().obfuscate(deobfEntry));
163 } 161 }
164 162
165 public boolean referenceIsRenameable(EntryReference<Entry, Entry> deobfReference) { 163 public boolean referenceIsRenameable(EntryReference<Entry<?>, Entry<?>> deobfReference) {
166 return this.deobfuscator.isRenameable(this.deobfuscator.obfuscateReference(deobfReference)); 164 if (deobfReference == null) return false;
165 return this.deobfuscator.isRenameable(this.deobfuscator.getMapper().obfuscate(deobfReference));
167 } 166 }
168 167
169 public ClassInheritanceTreeNode getClassInheritance(ClassEntry deobfClassEntry) { 168 public ClassInheritanceTreeNode getClassInheritance(ClassEntry deobfClassEntry) {
170 ClassEntry obfClassEntry = this.deobfuscator.obfuscateEntry(deobfClassEntry); 169 ClassEntry obfClassEntry = this.deobfuscator.getMapper().obfuscate(deobfClassEntry);
171 ClassInheritanceTreeNode rootNode = this.deobfuscator.getJarIndex().getClassInheritance(this.deobfuscator.getTranslator(TranslationDirection.DEOBFUSCATING), obfClassEntry); 170 Translator translator = this.deobfuscator.getMapper().getDeobfuscator();
171 ClassInheritanceTreeNode rootNode = this.deobfuscator.getIndexTreeBuilder().buildClassInheritance(translator, obfClassEntry);
172 return ClassInheritanceTreeNode.findNode(rootNode, obfClassEntry); 172 return ClassInheritanceTreeNode.findNode(rootNode, obfClassEntry);
173 } 173 }
174 174
175 public ClassImplementationsTreeNode getClassImplementations(ClassEntry deobfClassEntry) { 175 public ClassImplementationsTreeNode getClassImplementations(ClassEntry deobfClassEntry) {
176 ClassEntry obfClassEntry = this.deobfuscator.obfuscateEntry(deobfClassEntry); 176 ClassEntry obfClassEntry = this.deobfuscator.getMapper().obfuscate(deobfClassEntry);
177 return this.deobfuscator.getJarIndex().getClassImplementations(this.deobfuscator.getTranslator(TranslationDirection.DEOBFUSCATING), obfClassEntry); 177 Translator translator = this.deobfuscator.getMapper().getDeobfuscator();
178 return this.deobfuscator.getIndexTreeBuilder().buildClassImplementations(translator, obfClassEntry);
178 } 179 }
179 180
180 public MethodInheritanceTreeNode getMethodInheritance(MethodEntry deobfMethodEntry) { 181 public MethodInheritanceTreeNode getMethodInheritance(MethodEntry deobfMethodEntry) {
181 MethodEntry obfMethodEntry = this.deobfuscator.obfuscateEntry(deobfMethodEntry); 182 MethodEntry obfMethodEntry = this.deobfuscator.getMapper().obfuscate(deobfMethodEntry);
182 MethodInheritanceTreeNode rootNode = this.deobfuscator.getJarIndex().getMethodInheritance(this.deobfuscator.getTranslator(TranslationDirection.DEOBFUSCATING), obfMethodEntry); 183 Translator translator = this.deobfuscator.getMapper().getDeobfuscator();
184 MethodInheritanceTreeNode rootNode = this.deobfuscator.getIndexTreeBuilder().buildMethodInheritance(translator, obfMethodEntry);
183 return MethodInheritanceTreeNode.findNode(rootNode, obfMethodEntry); 185 return MethodInheritanceTreeNode.findNode(rootNode, obfMethodEntry);
184 } 186 }
185 187
186 public MethodImplementationsTreeNode getMethodImplementations(MethodEntry deobfMethodEntry) { 188 public MethodImplementationsTreeNode getMethodImplementations(MethodEntry deobfMethodEntry) {
187 MethodEntry obfMethodEntry = this.deobfuscator.obfuscateEntry(deobfMethodEntry); 189 MethodEntry obfMethodEntry = this.deobfuscator.getMapper().obfuscate(deobfMethodEntry);
188 List<MethodImplementationsTreeNode> rootNodes = this.deobfuscator.getJarIndex().getMethodImplementations(this.deobfuscator.getTranslator(TranslationDirection.DEOBFUSCATING), obfMethodEntry); 190 Translator translator = this.deobfuscator.getMapper().getDeobfuscator();
191 List<MethodImplementationsTreeNode> rootNodes = this.deobfuscator.getIndexTreeBuilder().buildMethodImplementations(translator, obfMethodEntry);
189 if (rootNodes.isEmpty()) { 192 if (rootNodes.isEmpty()) {
190 return null; 193 return null;
191 } 194 }
@@ -196,34 +199,32 @@ public class GuiController {
196 } 199 }
197 200
198 public ClassReferenceTreeNode getClassReferences(ClassEntry deobfClassEntry) { 201 public ClassReferenceTreeNode getClassReferences(ClassEntry deobfClassEntry) {
199 ClassEntry obfClassEntry = this.deobfuscator.obfuscateEntry(deobfClassEntry); 202 ClassEntry obfClassEntry = this.deobfuscator.getMapper().obfuscate(deobfClassEntry);
200 ClassReferenceTreeNode rootNode = new ClassReferenceTreeNode(this.deobfuscator.getTranslator(TranslationDirection.DEOBFUSCATING), obfClassEntry); 203 Translator deobfuscator = this.deobfuscator.getMapper().getDeobfuscator();
204 ClassReferenceTreeNode rootNode = new ClassReferenceTreeNode(deobfuscator, obfClassEntry);
201 rootNode.load(this.deobfuscator.getJarIndex(), true); 205 rootNode.load(this.deobfuscator.getJarIndex(), true);
202 return rootNode; 206 return rootNode;
203 } 207 }
204 208
205 public FieldReferenceTreeNode getFieldReferences(FieldEntry deobfFieldEntry) { 209 public FieldReferenceTreeNode getFieldReferences(FieldEntry deobfFieldEntry) {
206 FieldEntry obfFieldEntry = this.deobfuscator.obfuscateEntry(deobfFieldEntry); 210 FieldEntry obfFieldEntry = this.deobfuscator.getMapper().obfuscate(deobfFieldEntry);
207 FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(this.deobfuscator.getTranslator(TranslationDirection.DEOBFUSCATING), obfFieldEntry); 211 Translator translator = this.deobfuscator.getMapper().getDeobfuscator();
212 FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(translator, obfFieldEntry);
208 rootNode.load(this.deobfuscator.getJarIndex(), true); 213 rootNode.load(this.deobfuscator.getJarIndex(), true);
209 return rootNode; 214 return rootNode;
210 } 215 }
211 216
212 public MethodReferenceTreeNode getMethodReferences(MethodEntry deobfMethodEntry, boolean recursive) { 217 public MethodReferenceTreeNode getMethodReferences(MethodEntry deobfMethodEntry, boolean recursive) {
213 MethodEntry obfMethodEntry = this.deobfuscator.obfuscateEntry(deobfMethodEntry); 218 MethodEntry obfMethodEntry = this.deobfuscator.getMapper().obfuscate(deobfMethodEntry);
214 MethodReferenceTreeNode rootNode = new MethodReferenceTreeNode(this.deobfuscator.getTranslator(TranslationDirection.DEOBFUSCATING), obfMethodEntry); 219 Translator translator = this.deobfuscator.getMapper().getDeobfuscator();
220 MethodReferenceTreeNode rootNode = new MethodReferenceTreeNode(translator, obfMethodEntry);
215 rootNode.load(this.deobfuscator.getJarIndex(), true, recursive); 221 rootNode.load(this.deobfuscator.getJarIndex(), true, recursive);
216 return rootNode; 222 return rootNode;
217 } 223 }
218 224
219 public void rename(EntryReference<Entry, Entry> deobfReference, String newName) { 225 public void rename(EntryReference<Entry<?>, Entry<?>> deobfReference, String newName, boolean refreshClassTree) {
220 rename(deobfReference, newName, true, true); 226 EntryReference<Entry<?>, Entry<?>> obfReference = this.deobfuscator.getMapper().obfuscate(deobfReference);
221 } 227 this.deobfuscator.rename(obfReference.getNameableEntry(), newName);
222
223 public void rename(EntryReference<Entry, Entry> deobfReference, String newName, boolean refreshClassTree, boolean clearTranslationCache) {
224 EntryReference<Entry, Entry> obfReference = this.deobfuscator.obfuscateReference(deobfReference);
225 this.deobfuscator.rename(obfReference.getNameableEntry(), newName, clearTranslationCache);
226 this.isDirty = true;
227 228
228 if (refreshClassTree && deobfReference.entry instanceof ClassEntry && !((ClassEntry) deobfReference.entry).isInnerClass()) 229 if (refreshClassTree && deobfReference.entry instanceof ClassEntry && !((ClassEntry) deobfReference.entry).isInnerClass())
229 this.gui.moveClassTree(deobfReference, newName); 230 this.gui.moveClassTree(deobfReference, newName);
@@ -231,39 +232,37 @@ public class GuiController {
231 232
232 } 233 }
233 234
234 public void removeMapping(EntryReference<Entry, Entry> deobfReference) { 235 public void removeMapping(EntryReference<Entry<?>, Entry<?>> deobfReference) {
235 EntryReference<Entry, Entry> obfReference = this.deobfuscator.obfuscateReference(deobfReference); 236 EntryReference<Entry<?>, Entry<?>> obfReference = this.deobfuscator.getMapper().obfuscate(deobfReference);
236 this.deobfuscator.removeMapping(obfReference.getNameableEntry()); 237 this.deobfuscator.removeMapping(obfReference.getNameableEntry());
237 this.isDirty = true;
238 if (deobfReference.entry instanceof ClassEntry) 238 if (deobfReference.entry instanceof ClassEntry)
239 this.gui.moveClassTree(deobfReference, obfReference.entry.getName(), false, true); 239 this.gui.moveClassTree(deobfReference, obfReference.entry.getName(), false, true);
240 refreshCurrentClass(obfReference); 240 refreshCurrentClass(obfReference);
241 } 241 }
242 242
243 public void markAsDeobfuscated(EntryReference<Entry, Entry> deobfReference) { 243 public void markAsDeobfuscated(EntryReference<Entry<?>, Entry<?>> deobfReference) {
244 EntryReference<Entry, Entry> obfReference = this.deobfuscator.obfuscateReference(deobfReference); 244 EntryReference<Entry<?>, Entry<?>> obfReference = this.deobfuscator.getMapper().obfuscate(deobfReference);
245 this.deobfuscator.markAsDeobfuscated(obfReference.getNameableEntry()); 245 this.deobfuscator.markAsDeobfuscated(obfReference.getNameableEntry());
246 this.isDirty = true;
247 if (deobfReference.entry instanceof ClassEntry && !((ClassEntry) deobfReference.entry).isInnerClass()) 246 if (deobfReference.entry instanceof ClassEntry && !((ClassEntry) deobfReference.entry).isInnerClass())
248 this.gui.moveClassTree(deobfReference, obfReference.entry.getName(), true, false); 247 this.gui.moveClassTree(deobfReference, obfReference.entry.getName(), true, false);
249 refreshCurrentClass(obfReference); 248 refreshCurrentClass(obfReference);
250 } 249 }
251 250
252 public void openDeclaration(Entry deobfEntry) { 251 public void openDeclaration(Entry<?> deobfEntry) {
253 if (deobfEntry == null) { 252 if (deobfEntry == null) {
254 throw new IllegalArgumentException("Entry cannot be null!"); 253 throw new IllegalArgumentException("Entry cannot be null!");
255 } 254 }
256 openReference(new EntryReference<>(deobfEntry, deobfEntry.getName())); 255 openReference(new EntryReference<>(deobfEntry, deobfEntry.getName()));
257 } 256 }
258 257
259 public void openReference(EntryReference<Entry, Entry> deobfReference) { 258 public void openReference(EntryReference<Entry<?>, Entry<?>> deobfReference) {
260 if (deobfReference == null) { 259 if (deobfReference == null) {
261 throw new IllegalArgumentException("Reference cannot be null!"); 260 throw new IllegalArgumentException("Reference cannot be null!");
262 } 261 }
263 262
264 // get the reference target class 263 // get the reference target class
265 EntryReference<Entry, Entry> obfReference = this.deobfuscator.obfuscateReference(deobfReference); 264 EntryReference<Entry<?>, Entry<?>> obfReference = this.deobfuscator.getMapper().obfuscate(deobfReference);
266 ClassEntry obfClassEntry = obfReference.getLocationClassEntry().getOutermostClassEntry(); 265 ClassEntry obfClassEntry = obfReference.getLocationClassEntry();
267 if (!this.deobfuscator.isObfuscatedIdentifier(obfClassEntry)) { 266 if (!this.deobfuscator.isObfuscatedIdentifier(obfClassEntry)) {
268 throw new IllegalArgumentException("Obfuscated class " + obfClassEntry + " was not found in the jar!"); 267 throw new IllegalArgumentException("Obfuscated class " + obfClassEntry + " was not found in the jar!");
269 } 268 }
@@ -276,24 +275,30 @@ public class GuiController {
276 } 275 }
277 } 276 }
278 277
279 private void showReference(EntryReference<Entry, Entry> obfReference) { 278 private void showReference(EntryReference<Entry<?>, Entry<?>> obfReference) {
280 EntryReference<Entry, Entry> deobfReference = this.deobfuscator.deobfuscateReference(obfReference); 279 EntryRemapper mapper = this.deobfuscator.getMapper();
281 Collection<Token> tokens = this.index.getReferenceTokens(deobfReference); 280
281 Collection<Token> tokens = mapper.getObfResolver().resolveReference(obfReference, ResolutionStrategy.RESOLVE_ROOT)
282 .stream()
283 .map(mapper::deobfuscate)
284 .flatMap(reference -> index.getReferenceTokens(reference).stream())
285 .collect(Collectors.toList());
286
282 if (tokens.isEmpty()) { 287 if (tokens.isEmpty()) {
283 // DEBUG 288 // DEBUG
284 System.err.println(String.format("WARNING: no tokens found for %s in %s", deobfReference, this.currentObfClass)); 289 System.err.println(String.format("WARNING: no tokens found for %s in %s", tokens, this.currentObfClass));
285 } else { 290 } else {
286 this.gui.showTokens(tokens); 291 this.gui.showTokens(tokens);
287 } 292 }
288 } 293 }
289 294
290 public void savePreviousReference(EntryReference<Entry, Entry> deobfReference) { 295 public void savePreviousReference(EntryReference<Entry<?>, Entry<?>> deobfReference) {
291 this.referenceStack.push(this.deobfuscator.obfuscateReference(deobfReference)); 296 this.referenceStack.push(this.deobfuscator.getMapper().obfuscate(deobfReference));
292 } 297 }
293 298
294 public void openPreviousReference() { 299 public void openPreviousReference() {
295 if (hasPreviousLocation()) { 300 if (hasPreviousLocation()) {
296 openReference(this.deobfuscator.deobfuscateReference(this.referenceStack.pop())); 301 openReference(this.deobfuscator.getMapper().deobfuscate(this.referenceStack.pop()));
297 } 302 }
298 } 303 }
299 304
@@ -313,13 +318,13 @@ public class GuiController {
313 refreshCurrentClass(null); 318 refreshCurrentClass(null);
314 } 319 }
315 320
316 private void refreshCurrentClass(EntryReference<Entry, Entry> obfReference) { 321 private void refreshCurrentClass(EntryReference<Entry<?>, Entry<?>> obfReference) {
317 if (this.currentObfClass != null) { 322 if (this.currentObfClass != null) {
318 deobfuscate(this.currentObfClass, obfReference); 323 deobfuscate(this.currentObfClass, obfReference);
319 } 324 }
320 } 325 }
321 326
322 private void deobfuscate(final ClassEntry classEntry, final EntryReference<Entry, Entry> obfReference) { 327 private void deobfuscate(final ClassEntry classEntry, final EntryReference<Entry<?>, Entry<?>> obfReference) {
323 328
324 this.gui.setSource("(deobfuscating...)"); 329 this.gui.setSource("(deobfuscating...)");
325 330
@@ -327,7 +332,7 @@ public class GuiController {
327 new Thread(() -> 332 new Thread(() ->
328 { 333 {
329 // decompile,deobfuscate the bytecode 334 // decompile,deobfuscate the bytecode
330 CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getClassName()); 335 CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getFullName());
331 if (sourceTree == null) { 336 if (sourceTree == null) {
332 // decompilation of this class is not supported 337 // decompilation of this class is not supported
333 gui.setSource("Unable to find class: " + classEntry); 338 gui.setSource("Unable to find class: " + classEntry);
@@ -349,17 +354,18 @@ public class GuiController {
349 boolean remapped = false; 354 boolean remapped = false;
350 355
351 for (Token inToken : index.referenceTokens()) { 356 for (Token inToken : index.referenceTokens()) {
352 EntryReference<Entry, Entry> reference = index.getDeobfReference(inToken);
353 Token token = inToken.move(offset); 357 Token token = inToken.move(offset);
354 358
359 EntryReference<Entry<?>, Entry<?>> reference = index.getDeobfReference(inToken);
355 if (referenceIsRenameable(reference)) { 360 if (referenceIsRenameable(reference)) {
356 boolean added = false; 361 boolean added = false;
357 362
358 if (!entryHasDeobfuscatedName(reference.getNameableEntry())) { 363 if (!entryHasDeobfuscatedName(reference.getNameableEntry())) {
359 Entry obfEntry = deobfuscator.obfuscateEntry(reference.getNameableEntry()); 364 Entry<?> obfEntry = deobfuscator.getMapper().obfuscate(reference.getNameableEntry());
360 if (obfEntry instanceof FieldEntry) { 365 if (obfEntry instanceof FieldEntry) {
361 for (EnigmaPlugin plugin : deobfuscator.getPlugins()) { 366 for (EnigmaPlugin plugin : deobfuscator.getPlugins()) {
362 String proposal = plugin.proposeFieldName(obfEntry.getClassName(), obfEntry.getName(), ((FieldEntry) obfEntry).getDesc().toString()); 367 String owner = obfEntry.getContainingClass().getFullName();
368 String proposal = plugin.proposeFieldName(owner, obfEntry.getName(), ((FieldEntry) obfEntry).getDesc().toString());
363 if (proposal != null) { 369 if (proposal != null) {
364 proposedTokens.add(token); 370 proposedTokens.add(token);
365 offset += token.getRenameOffset(proposal); 371 offset += token.getRenameOffset(proposal);
@@ -411,8 +417,7 @@ public class GuiController {
411 417
412 public void modifierChange(ItemEvent event) { 418 public void modifierChange(ItemEvent event) {
413 if (event.getStateChange() == ItemEvent.SELECTED) { 419 if (event.getStateChange() == ItemEvent.SELECTED) {
414 deobfuscator.changeModifier(gui.reference.entry, (Mappings.EntryModifier) event.getItem()); 420 deobfuscator.changeModifier(gui.reference.entry, (AccessModifier) event.getItem());
415 this.isDirty = true;
416 refreshCurrentClass(); 421 refreshCurrentClass();
417 } 422 }
418 } 423 }
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.gui;
13
14import javax.swing.*;
15import java.awt.*;
16import java.awt.event.ActionListener;
17
18public class GuiTricks {
19
20 public static JLabel unboldLabel(JLabel label) {
21 Font font = label.getFont();
22 label.setFont(font.deriveFont(font.getStyle() & ~Font.BOLD));
23 return label;
24 }
25
26 public static void deactivateButton(JButton button) {
27 button.setEnabled(false);
28 button.setText("");
29 for (ActionListener listener : button.getActionListeners()) {
30 button.removeActionListener(listener);
31 }
32 }
33
34 public static void activateButton(JButton button, String text, ActionListener newListener) {
35 button.setText(text);
36 button.setEnabled(true);
37 for (ActionListener listener : button.getActionListeners()) {
38 button.removeActionListener(listener);
39 }
40 button.addActionListener(newListener);
41 }
42}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.gui;
13
14import cuchaz.enigma.mapping.entry.ClassEntry;
15
16public class ScoredClassEntry extends ClassEntry {
17
18 private static final long serialVersionUID = -8798725308554217105L;
19
20 private float score;
21
22 public ScoredClassEntry(ClassEntry other, float score) {
23 super(other);
24 this.score = score;
25 }
26
27 public float getScore() {
28 return score;
29 }
30
31 @Override
32 public int hashCode() {
33 return Float.hashCode(score) + super.hashCode();
34 }
35
36 @Override
37 public boolean equals(Object other) {
38 return super.equals(other) && other instanceof ScoredClassEntry && equals((ScoredClassEntry) other);
39 }
40
41 public boolean equals(ScoredClassEntry other) {
42 return other != null && Float.compare(score, other.score) == 0;
43 }
44}
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 @@
12package cuchaz.enigma.gui.dialog; 12package cuchaz.enigma.gui.dialog;
13 13
14import cuchaz.enigma.Constants; 14import cuchaz.enigma.Constants;
15import cuchaz.enigma.Deobfuscator.ProgressListener; 15import cuchaz.enigma.ProgressListener;
16import cuchaz.enigma.utils.Utils; 16import cuchaz.enigma.utils.Utils;
17 17
18import javax.swing.*; 18import javax.swing.*;
@@ -81,7 +81,7 @@ public class ProgressDialog implements ProgressListener, AutoCloseable {
81 } 81 }
82 82
83 @Override 83 @Override
84 public void onProgress(int numDone, String message) { 84 public void step(int numDone, String message) {
85 this.labelText.setText(message); 85 this.labelText.setText(message);
86 this.progress.setValue(numDone); 86 this.progress.setValue(numDone);
87 87
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;
5import cuchaz.enigma.gui.Gui; 5import cuchaz.enigma.gui.Gui;
6import cuchaz.enigma.gui.dialog.AboutDialog; 6import cuchaz.enigma.gui.dialog.AboutDialog;
7import cuchaz.enigma.throwables.MappingParseException; 7import cuchaz.enigma.throwables.MappingParseException;
8import cuchaz.enigma.translation.mapping.serde.MappingFormat;
8 9
9import javax.swing.*; 10import javax.swing.*;
10import java.awt.event.InputEvent; 11import java.awt.event.InputEvent;
@@ -23,7 +24,6 @@ public class MenuBar extends JMenuBar {
23 public final JMenuItem saveMappingEnigmaDirectoryMenu; 24 public final JMenuItem saveMappingEnigmaDirectoryMenu;
24 public final JMenuItem saveMappingsSrgMenu; 25 public final JMenuItem saveMappingsSrgMenu;
25 public final JMenuItem closeMappingsMenu; 26 public final JMenuItem closeMappingsMenu;
26 public final JMenuItem rebuildMethodNamesMenu;
27 public final JMenuItem exportSourceMenu; 27 public final JMenuItem exportSourceMenu;
28 public final JMenuItem exportJarMenu; 28 public final JMenuItem exportJarMenu;
29 private final Gui gui; 29 private final Gui gui;
@@ -68,7 +68,9 @@ public class MenuBar extends JMenuBar {
68 item.addActionListener(event -> { 68 item.addActionListener(event -> {
69 if (this.gui.enigmaMappingsFileChooser.showOpenDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { 69 if (this.gui.enigmaMappingsFileChooser.showOpenDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) {
70 try { 70 try {
71 this.gui.getController().openEnigmaMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile()); 71 File selectedFile = this.gui.enigmaMappingsFileChooser.getSelectedFile();
72 MappingFormat format = selectedFile.isDirectory() ? MappingFormat.ENIGMA_DIRECTORY : MappingFormat.ENIGMA_FILE;
73 this.gui.getController().openMappings(format, selectedFile.toPath());
72 } catch (IOException ex) { 74 } catch (IOException ex) {
73 throw new Error(ex); 75 throw new Error(ex);
74 } catch (MappingParseException ex) { 76 } catch (MappingParseException ex) {
@@ -85,7 +87,7 @@ public class MenuBar extends JMenuBar {
85 File file = new File(this.gui.tinyMappingsFileChooser.getDirectory() + File.separator + this.gui.tinyMappingsFileChooser.getFile()); 87 File file = new File(this.gui.tinyMappingsFileChooser.getDirectory() + File.separator + this.gui.tinyMappingsFileChooser.getFile());
86 if (file.exists()) { 88 if (file.exists()) {
87 try { 89 try {
88 this.gui.getController().openTinyMappings(file); 90 this.gui.getController().openMappings(MappingFormat.TINY_FILE, file.toPath());
89 } catch (IOException ex) { 91 } catch (IOException ex) {
90 throw new Error(ex); 92 throw new Error(ex);
91 } catch (MappingParseException ex) { 93 } catch (MappingParseException ex) {
@@ -99,11 +101,7 @@ public class MenuBar extends JMenuBar {
99 JMenuItem item = new JMenuItem("Save Mappings"); 101 JMenuItem item = new JMenuItem("Save Mappings");
100 menu.add(item); 102 menu.add(item);
101 item.addActionListener(event -> { 103 item.addActionListener(event -> {
102 try { 104 this.gui.getController().saveMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath());
103 this.gui.getController().saveMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile());
104 } catch (IOException ex) {
105 throw new Error(ex);
106 }
107 }); 105 });
108 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK)); 106 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK));
109 this.saveMappingsMenu = item; 107 this.saveMappingsMenu = item;
@@ -116,12 +114,8 @@ public class MenuBar extends JMenuBar {
116 item.addActionListener(event -> { 114 item.addActionListener(event -> {
117 // TODO: Use a specific file chooser for it 115 // TODO: Use a specific file chooser for it
118 if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { 116 if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) {
119 try { 117 this.gui.getController().saveMappings(MappingFormat.ENIGMA_FILE, this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath());
120 this.gui.getController().saveEnigmaMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile(), false); 118 this.saveMappingsMenu.setEnabled(true);
121 this.saveMappingsMenu.setEnabled(true);
122 } catch (IOException ex) {
123 throw new Error(ex);
124 }
125 } 119 }
126 }); 120 });
127 this.saveMappingEnigmaFileMenu = item; 121 this.saveMappingEnigmaFileMenu = item;
@@ -132,12 +126,8 @@ public class MenuBar extends JMenuBar {
132 item.addActionListener(event -> { 126 item.addActionListener(event -> {
133 // TODO: Use a specific file chooser for it 127 // TODO: Use a specific file chooser for it
134 if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { 128 if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) {
135 try { 129 this.gui.getController().saveMappings(MappingFormat.ENIGMA_DIRECTORY, this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath());
136 this.gui.getController().saveEnigmaMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile(), true); 130 this.saveMappingsMenu.setEnabled(true);
137 this.saveMappingsMenu.setEnabled(true);
138 } catch (IOException ex) {
139 throw new Error(ex);
140 }
141 } 131 }
142 }); 132 });
143 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK)); 133 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK));
@@ -149,12 +139,8 @@ public class MenuBar extends JMenuBar {
149 item.addActionListener(event -> { 139 item.addActionListener(event -> {
150 // TODO: Use a specific file chooser for it 140 // TODO: Use a specific file chooser for it
151 if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { 141 if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) {
152 try { 142 this.gui.getController().saveMappings(MappingFormat.SRG_FILE, this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath());
153 this.gui.getController().saveSRGMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile()); 143 this.saveMappingsMenu.setEnabled(true);
154 this.saveMappingsMenu.setEnabled(true);
155 } catch (IOException ex) {
156 throw new Error(ex);
157 }
158 } 144 }
159 }); 145 });
160 this.saveMappingsSrgMenu = item; 146 this.saveMappingsSrgMenu = item;
@@ -184,13 +170,6 @@ public class MenuBar extends JMenuBar {
184 } 170 }
185 menu.addSeparator(); 171 menu.addSeparator();
186 { 172 {
187 JMenuItem item = new JMenuItem("Rebuild Method Names");
188 menu.add(item);
189 item.addActionListener(event -> this.gui.getController().rebuildMethodNames());
190 this.rebuildMethodNamesMenu = item;
191 }
192 menu.addSeparator();
193 {
194 JMenuItem item = new JMenuItem("Export Source..."); 173 JMenuItem item = new JMenuItem("Export Source...");
195 menu.add(item); 174 menu.add(item);
196 item.addActionListener(event -> { 175 item.addActionListener(event -> {
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 @@
11 11
12package cuchaz.enigma.gui.node; 12package cuchaz.enigma.gui.node;
13 13
14import cuchaz.enigma.mapping.entry.ClassEntry; 14import cuchaz.enigma.translation.representation.entry.ClassEntry;
15 15
16import javax.swing.tree.DefaultMutableTreeNode; 16import javax.swing.tree.DefaultMutableTreeNode;
17 17
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;
2 2
3import cuchaz.enigma.gui.ClassSelector; 3import cuchaz.enigma.gui.ClassSelector;
4import cuchaz.enigma.gui.Gui; 4import cuchaz.enigma.gui.Gui;
5import cuchaz.enigma.mapping.entry.ClassEntry; 5import cuchaz.enigma.translation.representation.entry.ClassEntry;
6 6
7import javax.swing.*; 7import javax.swing.*;
8import java.awt.*; 8import java.awt.*;
@@ -17,8 +17,8 @@ public class PanelObf extends JPanel {
17 this.gui = gui; 17 this.gui = gui;
18 18
19 Comparator<ClassEntry> obfClassComparator = (a, b) -> { 19 Comparator<ClassEntry> obfClassComparator = (a, b) -> {
20 String aname = a.getName(); 20 String aname = a.getFullName();
21 String bname = b.getName(); 21 String bname = b.getFullName();
22 if (aname.length() != bname.length()) { 22 if (aname.length() != bname.length()) {
23 return aname.length() - bname.length(); 23 return aname.length() - bname.length();
24 } 24 }
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.mapping;
13
14import com.google.common.collect.Maps;
15import cuchaz.enigma.mapping.entry.ClassEntry;
16import cuchaz.enigma.mapping.entry.FieldEntry;
17import cuchaz.enigma.mapping.entry.MethodEntry;
18import cuchaz.enigma.throwables.MappingConflict;
19
20import java.util.ArrayList;
21import java.util.Map;
22
23// FIXME: Enigma doesn't support inner classes of inner class????!
24public class ClassMapping implements Comparable<ClassMapping> {
25
26 private String obfFullName;
27 private String obfSimpleName;
28 private String deobfName;
29 private String deobfFullName;
30 private String previousDeobfName;
31 private Map<String, ClassMapping> innerClassesByObfSimple;
32 private Map<String, ClassMapping> innerClassesByObfFull;
33 private Map<String, ClassMapping> innerClassesByDeobf;
34 private Map<String, FieldMapping> fieldsByObf;
35 private Map<String, FieldMapping> fieldsByDeobf;
36 private Map<String, MethodMapping> methodsByObf;
37 private Map<String, MethodMapping> methodsByDeobf;
38 private boolean isDirty;
39 private Mappings.EntryModifier modifier;
40
41 public ClassMapping(String obfFullName) {
42 this(obfFullName, null, Mappings.EntryModifier.UNCHANGED);
43 }
44
45 public ClassMapping(String obfFullName, String deobfName) {
46 this(obfFullName, deobfName, Mappings.EntryModifier.UNCHANGED);
47 }
48
49 public ClassMapping(String obfFullName, String deobfName, Mappings.EntryModifier modifier) {
50 this.obfFullName = obfFullName;
51 ClassEntry classEntry = new ClassEntry(obfFullName);
52 obfSimpleName = classEntry.isInnerClass() ? classEntry.getInnermostClassName() : classEntry.getSimpleName();
53 previousDeobfName = null;
54 this.deobfName = NameValidator.validateClassName(deobfName, false);
55 innerClassesByObfSimple = Maps.newHashMap();
56 innerClassesByObfFull = Maps.newHashMap();
57 innerClassesByDeobf = Maps.newHashMap();
58 fieldsByObf = Maps.newHashMap();
59 fieldsByDeobf = Maps.newHashMap();
60 methodsByObf = Maps.newHashMap();
61 methodsByDeobf = Maps.newHashMap();
62 isDirty = true;
63 this.modifier = modifier;
64 }
65
66 public static boolean isSimpleClassName(String name) {
67 return name.indexOf('/') < 0 && name.indexOf('$') < 0;
68 }
69
70 public String getObfFullName() {
71 return obfFullName;
72 }
73
74 public String getObfSimpleName() {
75 return obfSimpleName;
76 }
77
78 public String getPreviousDeobfName() {
79 return previousDeobfName;
80 }
81
82 public String getDeobfName() {
83 return deobfName;
84 }
85
86 public String getTranslatedName(TranslationDirection direction) {
87 return direction.choose(deobfName, obfFullName);
88 }
89
90 //// INNER CLASSES ////////
91
92 public void setDeobfName(String val) {
93 previousDeobfName = deobfName;
94 deobfName = NameValidator.validateClassName(val, false);
95 this.isDirty = true;
96 }
97
98 public Iterable<ClassMapping> innerClasses() {
99 assert (innerClassesByObfSimple.size() >= innerClassesByDeobf.size());
100 return innerClassesByObfSimple.values();
101 }
102
103 public void addInnerClassMapping(ClassMapping classMapping) throws MappingConflict {
104 // FIXME: dirty hack, that can get into issues, but it's a temp fix!
105 if (this.innerClassesByObfFull.containsKey(classMapping.getObfSimpleName())) {
106 throw new MappingConflict("classes", classMapping.getObfSimpleName(), this.innerClassesByObfSimple.get(classMapping.getObfSimpleName()).getObfSimpleName());
107 }
108 innerClassesByObfFull.put(classMapping.getObfFullName(), classMapping);
109 innerClassesByObfSimple.put(classMapping.getObfSimpleName(), classMapping);
110
111 if (classMapping.getDeobfName() != null) {
112 if (this.innerClassesByDeobf.containsKey(classMapping.getDeobfName())) {
113 throw new MappingConflict("classes", classMapping.getDeobfName(), this.innerClassesByDeobf.get(classMapping.getDeobfName()).getDeobfName());
114 }
115 innerClassesByDeobf.put(classMapping.getDeobfName(), classMapping);
116 }
117 this.isDirty = true;
118 }
119
120 public void removeInnerClassMapping(ClassMapping classMapping) {
121 innerClassesByObfFull.remove(classMapping.getObfFullName());
122 boolean obfWasRemoved = innerClassesByObfSimple.remove(classMapping.getObfSimpleName()) != null;
123 assert (obfWasRemoved);
124 if (classMapping.getDeobfName() != null) {
125 boolean deobfWasRemoved = innerClassesByDeobf.remove(classMapping.getDeobfName()) != null;
126 assert (deobfWasRemoved);
127 }
128 this.isDirty = true;
129 }
130
131 public ClassMapping getOrCreateInnerClass(ClassEntry obfInnerClass) {
132 ClassMapping classMapping = innerClassesByObfSimple.get(obfInnerClass.getInnermostClassName());
133 if (classMapping == null) {
134 classMapping = new ClassMapping(obfInnerClass.getName());
135 innerClassesByObfFull.put(classMapping.getObfFullName(), classMapping);
136 boolean wasAdded = innerClassesByObfSimple.put(classMapping.getObfSimpleName(), classMapping) == null;
137 assert (wasAdded);
138 this.isDirty = true;
139 }
140 return classMapping;
141 }
142
143 public ClassMapping getInnerClassByObfSimple(String obfSimpleName) {
144 assert (isSimpleClassName(obfSimpleName));
145 return innerClassesByObfSimple.get(obfSimpleName);
146 }
147
148 public ClassMapping getInnerClassByDeobf(String deobfName) {
149 assert (isSimpleClassName(deobfName));
150 return innerClassesByDeobf.get(deobfName);
151 }
152
153 public ClassMapping getInnerClassByDeobfThenObfSimple(String name) {
154 ClassMapping classMapping = getInnerClassByDeobf(name);
155 if (classMapping == null) {
156 classMapping = getInnerClassByObfSimple(name);
157 }
158 return classMapping;
159 }
160
161 public String getDeobfInnerClassName(String obfSimpleName) {
162 assert (isSimpleClassName(obfSimpleName));
163 ClassMapping classMapping = innerClassesByObfSimple.get(obfSimpleName);
164 if (classMapping != null) {
165 return classMapping.getDeobfName();
166 }
167 return null;
168 }
169
170 public void setInnerClassName(ClassEntry obfInnerClass, String deobfName) {
171 ClassMapping classMapping = getOrCreateInnerClass(obfInnerClass);
172 if (classMapping.getDeobfName() != null) {
173 boolean wasRemoved = innerClassesByDeobf.remove(classMapping.getDeobfName()) != null;
174 assert (wasRemoved);
175 }
176 classMapping.setDeobfName(deobfName);
177 if (deobfName != null) {
178 assert (isSimpleClassName(deobfName));
179 boolean wasAdded = innerClassesByDeobf.put(deobfName, classMapping) == null;
180 assert (wasAdded);
181 }
182 this.isDirty = true;
183 }
184
185 public boolean hasInnerClassByObfSimple(String obfSimpleName) {
186 return innerClassesByObfSimple.containsKey(obfSimpleName);
187 }
188
189 //// FIELDS ////////
190
191 public boolean hasInnerClassByDeobf(String deobfName) {
192 return innerClassesByDeobf.containsKey(deobfName);
193 }
194
195 public Iterable<FieldMapping> fields() {
196 assert (fieldsByObf.size() == fieldsByDeobf.size());
197 return fieldsByObf.values();
198 }
199
200 public boolean containsObfField(String obfName, TypeDescriptor obfDesc) {
201 return fieldsByObf.containsKey(getFieldKey(obfName, obfDesc));
202 }
203
204 public boolean containsDeobfField(String deobfName, TypeDescriptor deobfDesc) {
205 return fieldsByDeobf.containsKey(getFieldKey(deobfName, deobfDesc));
206 }
207
208 public void addFieldMapping(FieldMapping fieldMapping) {
209 String obfKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfDesc());
210 if (fieldsByObf.containsKey(obfKey)) {
211 throw new Error("Already have mapping for " + obfFullName + "." + obfKey);
212 }
213 if (fieldMapping.getDeobfName() != null) {
214 String deobfKey = getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfDesc());
215 if (fieldsByDeobf.containsKey(deobfKey)) {
216 throw new Error("Already have mapping for " + deobfName + "." + deobfKey);
217 }
218 boolean deobfWasAdded = fieldsByDeobf.put(deobfKey, fieldMapping) == null;
219 assert (deobfWasAdded);
220 }
221 boolean obfWasAdded = fieldsByObf.put(obfKey, fieldMapping) == null;
222 assert (obfWasAdded);
223 this.isDirty = true;
224 }
225
226 public void removeFieldMapping(FieldMapping fieldMapping) {
227 boolean obfWasRemoved = fieldsByObf.remove(getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfDesc())) != null;
228 assert (obfWasRemoved);
229 if (fieldMapping.getDeobfName() != null) {
230 boolean deobfWasRemoved = fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfDesc())) != null;
231 assert (deobfWasRemoved);
232 }
233 this.isDirty = true;
234 }
235
236 public FieldMapping getFieldByObf(String obfName, TypeDescriptor obfDesc) {
237 return fieldsByObf.get(getFieldKey(obfName, obfDesc));
238 }
239
240 public FieldMapping getFieldByObf(FieldEntry field) {
241 return getFieldByObf(field.getName(), field.getDesc());
242 }
243
244 public FieldMapping getFieldByDeobf(String deobfName, TypeDescriptor obfDesc) {
245 return fieldsByDeobf.get(getFieldKey(deobfName, obfDesc));
246 }
247
248 public String getObfFieldName(String deobfName, TypeDescriptor obfDesc) {
249 FieldMapping fieldMapping = fieldsByDeobf.get(getFieldKey(deobfName, obfDesc));
250 if (fieldMapping != null) {
251 return fieldMapping.getObfName();
252 }
253 return null;
254 }
255
256 public String getDeobfFieldName(String obfName, TypeDescriptor obfDesc) {
257 FieldMapping fieldMapping = fieldsByObf.get(getFieldKey(obfName, obfDesc));
258 if (fieldMapping != null) {
259 return fieldMapping.getDeobfName();
260 }
261 return null;
262 }
263
264 private String getFieldKey(String name, TypeDescriptor desc) {
265 if (name == null) {
266 throw new IllegalArgumentException("name cannot be null!");
267 }
268 if (desc == null) {
269 throw new IllegalArgumentException("desc cannot be null!");
270 }
271 return name + ":" + desc;
272 }
273
274 public void setFieldName(String obfName, TypeDescriptor obfDesc, String deobfName) {
275 assert (deobfName != null);
276 FieldMapping fieldMapping = fieldsByObf.get(getFieldKey(obfName, obfDesc));
277 if (fieldMapping == null) {
278 fieldMapping = new FieldMapping(obfName, obfDesc, deobfName, Mappings.EntryModifier.UNCHANGED);
279 boolean obfWasAdded = fieldsByObf.put(getFieldKey(obfName, obfDesc), fieldMapping) == null;
280 assert (obfWasAdded);
281 } else {
282 boolean wasRemoved = fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), obfDesc)) != null;
283 assert (wasRemoved);
284 }
285 fieldMapping.setDeobfName(deobfName);
286 if (deobfName != null) {
287 boolean wasAdded = fieldsByDeobf.put(getFieldKey(deobfName, obfDesc), fieldMapping) == null;
288 assert (wasAdded);
289 }
290 this.isDirty = true;
291 }
292
293 //// METHODS ////////
294
295 public void setFieldObfNameAndType(String oldObfName, TypeDescriptor obfDesc, String newObfName, TypeDescriptor newObfDesc) {
296 assert (newObfName != null);
297 FieldMapping fieldMapping = fieldsByObf.remove(getFieldKey(oldObfName, obfDesc));
298 assert (fieldMapping != null);
299 fieldMapping.setObfName(newObfName);
300 fieldMapping.setObfDesc(newObfDesc);
301 boolean obfWasAdded = fieldsByObf.put(getFieldKey(newObfName, newObfDesc), fieldMapping) == null;
302 assert (obfWasAdded);
303 this.isDirty = true;
304 }
305
306 public Iterable<MethodMapping> methods() {
307 assert (methodsByObf.size() >= methodsByDeobf.size());
308 return methodsByObf.values();
309 }
310
311 public boolean containsObfMethod(String obfName, MethodDescriptor obfDescriptor) {
312 return methodsByObf.containsKey(getMethodKey(obfName, obfDescriptor));
313 }
314
315 public boolean containsDeobfMethod(String deobfName, MethodDescriptor obfDescriptor) {
316 return methodsByDeobf.containsKey(getMethodKey(deobfName, obfDescriptor));
317 }
318
319 public void addMethodMapping(MethodMapping methodMapping) {
320 String obfKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfDesc());
321 if (methodsByObf.containsKey(obfKey)) {
322 throw new Error("Already have mapping for " + obfFullName + "." + obfKey);
323 }
324 boolean wasAdded = methodsByObf.put(obfKey, methodMapping) == null;
325 assert (wasAdded);
326 if (!methodMapping.isObfuscated()) {
327 String deobfKey = getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfDesc());
328 if (methodsByDeobf.containsKey(deobfKey)) {
329 throw new Error("Already have mapping for " + deobfName + "." + deobfKey);
330 }
331 boolean deobfWasAdded = methodsByDeobf.put(deobfKey, methodMapping) == null;
332 assert (deobfWasAdded);
333 }
334 this.isDirty = true;
335 assert (methodsByObf.size() >= methodsByDeobf.size());
336 }
337
338 public void removeMethodMapping(MethodMapping methodMapping) {
339 boolean obfWasRemoved = methodsByObf.remove(getMethodKey(methodMapping.getObfName(), methodMapping.getObfDesc())) != null;
340 assert (obfWasRemoved);
341 if (!methodMapping.isObfuscated()) {
342 boolean deobfWasRemoved = methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfDesc())) != null;
343 assert (deobfWasRemoved);
344 }
345 this.isDirty = true;
346 }
347
348 public MethodMapping getMethodByObf(String obfName, MethodDescriptor obfDescriptor) {
349 return methodsByObf.get(getMethodKey(obfName, obfDescriptor));
350 }
351
352 public MethodMapping getMethodByObf(MethodEntry method) {
353 return getMethodByObf(method.getName(), method.getDesc());
354 }
355
356 public MethodMapping getMethodByDeobf(String deobfName, MethodDescriptor obfDescriptor) {
357 return methodsByDeobf.get(getMethodKey(deobfName, obfDescriptor));
358 }
359
360 private String getMethodKey(String name, MethodDescriptor descriptor) {
361 if (name == null) {
362 throw new IllegalArgumentException("name cannot be null!");
363 }
364 if (descriptor == null) {
365 throw new IllegalArgumentException("descriptor cannot be null!");
366 }
367 return name + descriptor;
368 }
369
370 public void setMethodName(String obfName, MethodDescriptor obfDescriptor, String deobfName) {
371 MethodMapping methodMapping = methodsByObf.get(getMethodKey(obfName, obfDescriptor));
372 if (methodMapping == null) {
373 methodMapping = createMethodMapping(obfName, obfDescriptor);
374 } else if (!methodMapping.isObfuscated()) {
375 boolean wasRemoved = methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfDesc())) != null;
376 assert (wasRemoved);
377 }
378 methodMapping.setDeobfName(deobfName);
379 if (deobfName != null) {
380 boolean wasAdded = methodsByDeobf.put(getMethodKey(deobfName, obfDescriptor), methodMapping) == null;
381 assert (wasAdded);
382 }
383 this.isDirty = true;
384 }
385
386 //// ARGUMENTS ////////
387
388 public void setMethodObfNameAndSignature(String oldObfName, MethodDescriptor obfDescriptor, String newObfName, MethodDescriptor newObfDescriptor) {
389 assert (newObfName != null);
390 MethodMapping methodMapping = methodsByObf.remove(getMethodKey(oldObfName, obfDescriptor));
391 assert (methodMapping != null);
392 methodMapping.setObfName(newObfName);
393 methodMapping.setObfDescriptor(newObfDescriptor);
394 boolean obfWasAdded = methodsByObf.put(getMethodKey(newObfName, newObfDescriptor), methodMapping) == null;
395 assert (obfWasAdded);
396 this.isDirty = true;
397 }
398
399 public void setArgumentName(String obfMethodName, MethodDescriptor obfMethodDescriptor, int argumentIndex, String argumentName) {
400 assert (argumentName != null);
401 MethodMapping methodMapping = methodsByObf.get(getMethodKey(obfMethodName, obfMethodDescriptor));
402 if (methodMapping == null) {
403 methodMapping = createMethodMapping(obfMethodName, obfMethodDescriptor);
404 }
405 methodMapping.setLocalVariableName(argumentIndex, argumentName);
406 this.isDirty = true;
407 }
408
409 public void removeArgumentName(String obfMethodName, MethodDescriptor obfMethodDescriptor, int argumentIndex) {
410 methodsByObf.get(getMethodKey(obfMethodName, obfMethodDescriptor)).removeLocalVariableName(argumentIndex);
411 this.isDirty = true;
412 }
413
414 private MethodMapping createMethodMapping(String obfName, MethodDescriptor obfDescriptor) {
415 MethodMapping methodMapping = new MethodMapping(obfName, obfDescriptor);
416 boolean wasAdded = methodsByObf.put(getMethodKey(obfName, obfDescriptor), methodMapping) == null;
417 assert (wasAdded);
418 this.isDirty = true;
419 return methodMapping;
420 }
421
422 @Override
423 public String toString() {
424 StringBuilder buf = new StringBuilder();
425 buf.append(obfFullName);
426 buf.append(" <-> ");
427 buf.append(deobfName);
428 buf.append("\n");
429 buf.append("Fields:\n");
430 for (FieldMapping fieldMapping : fields()) {
431 buf.append("\t");
432 buf.append(fieldMapping.getObfName());
433 buf.append(" <-> ");
434 buf.append(fieldMapping.getDeobfName());
435 buf.append("\n");
436 }
437 buf.append("Methods:\n");
438 for (MethodMapping methodMapping : methodsByObf.values()) {
439 buf.append(methodMapping);
440 buf.append("\n");
441 }
442 buf.append("Inner Classes:\n");
443 for (ClassMapping classMapping : innerClassesByObfSimple.values()) {
444 buf.append("\t");
445 buf.append(classMapping.getObfSimpleName());
446 buf.append(" <-> ");
447 buf.append(classMapping.getDeobfName());
448 buf.append("\n");
449 }
450 return buf.toString();
451 }
452
453 @Override
454 public int compareTo(ClassMapping other) {
455 // sort by a, b, c, ... aa, ab, etc
456 if (obfFullName.length() != other.obfFullName.length()) {
457 return obfFullName.length() - other.obfFullName.length();
458 }
459 return obfFullName.compareTo(other.obfFullName);
460 }
461
462 public boolean renameObfClass(String oldObfClassName, String newObfClassName) {
463
464 // rename inner classes
465 for (ClassMapping innerClassMapping : new ArrayList<>(innerClassesByObfSimple.values())) {
466 if (innerClassMapping.renameObfClass(oldObfClassName, newObfClassName)) {
467 boolean wasRemoved = innerClassesByObfSimple.remove(oldObfClassName) != null;
468 assert (wasRemoved);
469 boolean wasAdded = innerClassesByObfSimple.put(newObfClassName, innerClassMapping) == null;
470 assert (wasAdded);
471 }
472 }
473
474 // rename field types
475 for (FieldMapping fieldMapping : new ArrayList<>(fieldsByObf.values())) {
476 String oldFieldKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfDesc());
477 if (fieldMapping.renameObfClass(oldObfClassName, newObfClassName)) {
478 boolean wasRemoved = fieldsByObf.remove(oldFieldKey) != null;
479 assert (wasRemoved);
480 boolean wasAdded = fieldsByObf
481 .put(getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfDesc()), fieldMapping) == null;
482 assert (wasAdded);
483 }
484 }
485
486 // rename method signatures
487 for (MethodMapping methodMapping : new ArrayList<>(methodsByObf.values())) {
488 String oldMethodKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfDesc());
489 if (methodMapping.renameObfClass(oldObfClassName, newObfClassName)) {
490 boolean wasRemoved = methodsByObf.remove(oldMethodKey) != null;
491 assert (wasRemoved);
492 boolean wasAdded = methodsByObf
493 .put(getMethodKey(methodMapping.getObfName(), methodMapping.getObfDesc()), methodMapping) == null;
494 assert (wasAdded);
495 }
496 }
497
498 if (obfFullName.equals(oldObfClassName)) {
499 // rename this class
500 obfFullName = newObfClassName;
501 return true;
502 }
503 this.isDirty = true;
504 return false;
505 }
506
507 public boolean containsArgument(MethodEntry obfMethodEntry, String name) {
508 MethodMapping methodMapping = methodsByObf.get(getMethodKey(obfMethodEntry.getName(), obfMethodEntry.getDesc()));
509 return methodMapping != null && methodMapping.containsLocalVariable(name);
510 }
511
512 public ClassEntry getObfEntry() {
513 return new ClassEntry(obfFullName);
514 }
515
516 public ClassEntry getDeObfEntry() {
517 return deobfFullName != null ? new ClassEntry(deobfFullName) : null;
518 }
519
520 public boolean isObfuscated() {
521 return this.deobfName == null || this.deobfName.equals(this.obfFullName);
522 }
523
524 public String getSaveName() {
525 return this.isObfuscated() ? this.obfFullName : this.deobfName;
526 }
527
528 public boolean isDirty() {
529 return isDirty || areInnersDirty();
530 }
531
532 private boolean areInnersDirty(){
533 for (ClassMapping c : this.innerClasses()){
534 if (c.isDirty()){
535 return true;
536 }
537 }
538 return false;
539 }
540
541 public void resetDirty() {
542 this.isDirty = false;
543 }
544
545 public void markDirty() {
546 this.isDirty = true;
547 }
548
549 public Mappings.EntryModifier getModifier() {
550 return modifier;
551 }
552
553 public void setModifier(Mappings.EntryModifier modifier) {
554 if (this.modifier != modifier)
555 this.isDirty = true;
556 this.modifier = modifier;
557 }
558
559 public void setFieldModifier(String obfName, TypeDescriptor obfDesc, Mappings.EntryModifier modifier) {
560 FieldMapping fieldMapping = fieldsByObf.computeIfAbsent(getFieldKey(obfName, obfDesc),
561 k -> new FieldMapping(obfName, obfDesc, null, Mappings.EntryModifier.UNCHANGED));
562
563 if (fieldMapping.getModifier() != modifier) {
564 fieldMapping.setModifier(modifier);
565 this.isDirty = true;
566 }
567 }
568
569 public void setMethodModifier(String obfName, MethodDescriptor sig, Mappings.EntryModifier modifier) {
570 MethodMapping methodMapping = methodsByObf.computeIfAbsent(getMethodKey(obfName, sig),
571 k -> new MethodMapping(obfName, sig, null, Mappings.EntryModifier.UNCHANGED));
572
573 if (methodMapping.getModifier() != modifier) {
574 methodMapping.setModifier(modifier);
575 this.isDirty = true;
576 }
577 }
578
579 // Used for tiny parsing to keep track of deobfuscate inner classes
580 public ClassMapping setDeobfInner(String deobName) {
581 this.deobfFullName = deobName;
582 return this;
583 }
584
585 public ClassMapping copy() {
586 ClassMapping copied = new ClassMapping(this.obfFullName);
587 copied.obfSimpleName= this.obfSimpleName;
588 copied.modifier = this.modifier;
589 copied.deobfFullName = this.deobfFullName;
590 copied.deobfName = this.deobfName;
591 copied.innerClassesByDeobf = this.innerClassesByDeobf;
592 copied.innerClassesByObfFull = this.innerClassesByObfFull;
593 copied.innerClassesByObfSimple = this.innerClassesByObfSimple;
594 copied.fieldsByObf = this.fieldsByObf;
595 copied.fieldsByDeobf = this.fieldsByDeobf;
596 copied.methodsByObf = this.methodsByObf;
597 copied.methodsByDeobf = this.methodsByDeobf;
598 return copied;
599 }
600
601 @Override
602 public int hashCode() {
603 return this.obfFullName.hashCode();
604 }
605
606 @Override
607 public boolean equals(Object obj) {
608 return obj instanceof ClassMapping && ((ClassMapping) obj).obfFullName.equals(this.obfFullName);
609 }
610
611 public boolean isEmpty() {
612 if (fieldsByDeobf.isEmpty() && methodsByDeobf.isEmpty() && deobfFullName == null && deobfName == null
613 && innerClassesByObfSimple.values().stream().allMatch(ClassMapping::isEmpty)) {
614
615 // check args
616 for (MethodMapping mapping : methodsByObf.values()) {
617 if (mapping.arguments().iterator().hasNext()) {
618 return false;
619 }
620 }
621
622 return true;
623 }
624
625 return false;
626 }
627}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.mapping;
13
14import com.google.common.collect.Lists;
15import com.google.common.collect.Maps;
16import cuchaz.enigma.analysis.TranslationIndex;
17import cuchaz.enigma.bytecode.AccessFlags;
18import cuchaz.enigma.mapping.entry.*;
19
20import java.util.ArrayList;
21import java.util.List;
22import java.util.Map;
23import java.util.ServiceLoader;
24
25public class DirectionalTranslator implements Translator {
26 private final TranslationDirection direction;
27 private final Map<String, ClassMapping> classes;
28 private final TranslationIndex index;
29
30 public DirectionalTranslator(ReferencedEntryPool entryPool) {
31 this.direction = null;
32 this.classes = Maps.newHashMap();
33 this.index = new TranslationIndex(entryPool);
34 }
35
36 public DirectionalTranslator(TranslationDirection direction, Map<String, ClassMapping> classes, TranslationIndex index) {
37 this.direction = direction;
38 this.classes = classes;
39 this.index = index;
40 }
41
42 public TranslationDirection getDirection() {
43 return direction;
44 }
45
46 public TranslationIndex getTranslationIndex() {
47 return index;
48 }
49
50 @Override
51 public ClassEntry getTranslatedClass(ClassEntry entry) {
52 String className;
53 if (entry.isArray()) {
54 className = this.getTranslatedTypeDesc(new TypeDescriptor(entry.getName())).toString();
55 } else {
56 className = entry.isInnerClass() ? translateInnerClassName(entry) : translateClassName(entry);
57 }
58 return new ClassEntry(className);
59 }
60
61 @Override
62 public ClassDefEntry getTranslatedClassDef(ClassDefEntry entry) {
63 String className;
64 if (entry.isArray()) {
65 className = this.getTranslatedTypeDesc(new TypeDescriptor(entry.getName())).toString();
66 } else {
67 className = entry.isInnerClass() ? translateInnerClassName(entry) : translateClassName(entry);
68 }
69 Signature translatedSignature = this.getTranslatedSignature(entry.getSignature());
70 return new ClassDefEntry(className, translatedSignature, getClassModifier(entry).transform(entry.getAccess()));
71 }
72
73 private String translateClassName(ClassEntry entry) {
74 // normal classes are easy
75 ClassMapping classMapping = this.classes.get(entry.getName());
76 if (classMapping == null) {
77 return entry.getName();
78 }
79 return classMapping.getTranslatedName(direction);
80 }
81
82 private String translateInnerClassName(ClassEntry entry) {
83 // translate as much of the class chain as we can
84 List<ClassMapping> mappingsChain = getClassMappingChain(entry);
85 String[] obfClassNames = entry.getName().split("\\$");
86 StringBuilder buf = new StringBuilder();
87 for (int i = 0; i < obfClassNames.length; i++) {
88 boolean isFirstClass = buf.length() == 0;
89 String className = null;
90 ClassMapping classMapping = mappingsChain.get(i);
91 if (classMapping != null) {
92 className = this.direction.choose(
93 classMapping.getDeobfName(),
94 isFirstClass ? classMapping.getObfFullName() : classMapping.getObfSimpleName()
95 );
96 }
97 if (className == null) {
98 className = obfClassNames[i];
99 }
100 if (!isFirstClass) {
101 buf.append("$");
102 }
103 buf.append(className);
104 }
105 return buf.toString();
106 }
107
108 @Override
109 public FieldDefEntry getTranslatedFieldDef(FieldDefEntry entry) {
110 String translatedName = translateFieldName(entry);
111 if (translatedName == null) {
112 translatedName = entry.getName();
113 }
114 ClassEntry translatedOwner = getTranslatedClass(entry.getOwnerClassEntry());
115 TypeDescriptor translatedDesc = getTranslatedTypeDesc(entry.getDesc());
116 Signature translatedSignature = getTranslatedSignature(entry.getSignature());
117 AccessFlags translatedAccess = getFieldModifier(entry).transform(entry.getAccess());
118 return new FieldDefEntry(translatedOwner, translatedName, translatedDesc, translatedSignature, translatedAccess);
119 }
120
121 @Override
122 public FieldEntry getTranslatedField(FieldEntry entry) {
123 String translatedName = translateFieldName(entry);
124 if (translatedName == null) {
125 translatedName = entry.getName();
126 }
127 ClassEntry translatedOwner = getTranslatedClass(entry.getOwnerClassEntry());
128 TypeDescriptor translatedDesc = getTranslatedTypeDesc(entry.getDesc());
129 return new FieldEntry(translatedOwner, translatedName, translatedDesc);
130 }
131
132 private String translateFieldName(FieldEntry entry) {
133 // resolve the class entry
134 ClassEntry resolvedClassEntry = this.index.resolveEntryOwner(entry);
135 if (resolvedClassEntry != null) {
136 // look for the class
137 ClassMapping classMapping = findClassMapping(resolvedClassEntry);
138 if (classMapping != null) {
139 // look for the field
140 FieldMapping mapping = this.direction.choose(
141 classMapping.getFieldByObf(entry.getName(), entry.getDesc()),
142 classMapping.getFieldByDeobf(entry.getName(), getTranslatedTypeDesc(entry.getDesc()))
143 );
144 if (mapping != null) {
145 return this.direction.choose(mapping.getDeobfName(), mapping.getObfName());
146 }
147 }
148 }
149 return null;
150 }
151
152 @Override
153 public MethodDefEntry getTranslatedMethodDef(MethodDefEntry entry) {
154 String translatedName = translateMethodName(entry);
155 if (translatedName == null) {
156 translatedName = entry.getName();
157 }
158 ClassEntry translatedOwner = getTranslatedClass(entry.getOwnerClassEntry());
159 MethodDescriptor translatedDesc = getTranslatedMethodDesc(entry.getDesc());
160 Signature translatedSignature = getTranslatedSignature(entry.getSignature());
161 AccessFlags access = getMethodModifier(entry).transform(entry.getAccess());
162 return new MethodDefEntry(translatedOwner, translatedName, translatedDesc, translatedSignature, access);
163 }
164
165 @Override
166 public MethodEntry getTranslatedMethod(MethodEntry entry) {
167 String translatedName = translateMethodName(entry);
168 if (translatedName == null) {
169 translatedName = entry.getName();
170 }
171 ClassEntry translatedOwner = getTranslatedClass(entry.getOwnerClassEntry());
172 MethodDescriptor translatedDesc = getTranslatedMethodDesc(entry.getDesc());
173 return new MethodEntry(translatedOwner, translatedName, translatedDesc);
174 }
175
176 private String translateMethodName(MethodEntry entry) {
177 // resolve the class entry
178 ClassEntry resolvedOwner = this.index.resolveEntryOwner(entry);
179 if (resolvedOwner != null) {
180 // look for class
181 ClassMapping classMapping = findClassMapping(resolvedOwner);
182 if (classMapping != null) {
183 // look for the method
184 MethodMapping mapping = this.direction.choose(
185 classMapping.getMethodByObf(entry.getName(), entry.getDesc()),
186 classMapping.getMethodByDeobf(entry.getName(), getTranslatedMethodDesc(entry.getDesc()))
187 );
188 if (mapping != null) {
189 return this.direction.choose(mapping.getDeobfName(), mapping.getObfName());
190 }
191 }
192 }
193 return null;
194 }
195
196 @Override
197 public LocalVariableEntry getTranslatedVariable(LocalVariableEntry entry) {
198 String translatedArgumentName = translateLocalVariableName(entry);
199 if (translatedArgumentName == null) {
200 translatedArgumentName = inheritLocalVariableName(entry);
201 }
202 if (translatedArgumentName == null) {
203 translatedArgumentName = entry.getName();
204 }
205 // TODO: Translating arguments calls method translation.. Can we refactor the code in such a way that we don't need this?
206 MethodEntry translatedOwner = getTranslatedMethod(entry.getOwnerEntry());
207 return new LocalVariableEntry(translatedOwner, entry.getIndex(), translatedArgumentName, entry.isParameter());
208 }
209
210 @Override
211 public LocalVariableDefEntry getTranslatedVariableDef(LocalVariableDefEntry entry) {
212 String translatedArgumentName = translateLocalVariableName(entry);
213 if (translatedArgumentName == null) {
214 translatedArgumentName = inheritLocalVariableName(entry);
215 }
216 // TODO: Translating arguments calls method translation.. Can we refactor the code in such a way that we don't need this?
217 MethodDefEntry translatedOwner = getTranslatedMethodDef(entry.getOwnerEntry());
218 TypeDescriptor translatedTypeDesc = getTranslatedTypeDesc(entry.getDesc());
219 return new LocalVariableDefEntry(translatedOwner, entry.getIndex(), translatedArgumentName != null ? translatedArgumentName : entry.getName(), entry.isParameter(), translatedTypeDesc);
220 }
221
222 @Override
223 public boolean hasClassMapping(ClassEntry entry) {
224 return classes.containsKey(entry.getName());
225 }
226
227 @Override
228 public boolean hasFieldMapping(FieldEntry entry) {
229 return translateFieldName(entry) != null;
230 }
231
232 @Override
233 public boolean hasMethodMapping(MethodEntry entry) {
234 return translateMethodName(entry) != null;
235 }
236
237 @Override
238 public boolean hasLocalVariableMapping(LocalVariableEntry entry) {
239 return translateLocalVariableName(entry) != null || inheritLocalVariableName(entry) != null;
240 }
241
242 // TODO: support not identical behavior (specific to constructor)
243 private String translateLocalVariableName(LocalVariableEntry entry) {
244 // look for identical behavior in superclasses
245 ClassEntry ownerEntry = entry.getOwnerClassEntry();
246 if (ownerEntry != null) {
247 // look for the class
248 ClassMapping classMapping = findClassMapping(ownerEntry);
249 if (classMapping != null) {
250 // look for the method
251 MethodMapping methodMapping = this.direction.choose(
252 classMapping.getMethodByObf(entry.getMethodName(), entry.getMethodDesc()),
253 classMapping.getMethodByDeobf(entry.getMethodName(), getTranslatedMethodDesc(entry.getMethodDesc()))
254 );
255 if (methodMapping != null) {
256 int index = entry.getIndex();
257 return this.direction.choose(
258 methodMapping.getDeobfLocalVariableName(index),
259 methodMapping.getObfLocalVariableName(index)
260 );
261 }
262 }
263 }
264 return null;
265 }
266
267 private String inheritLocalVariableName(LocalVariableEntry entry) {
268 List<ClassEntry> ancestry = this.index.getAncestry(entry.getOwnerClassEntry());
269 // Check in mother class for the arg
270 for (ClassEntry ancestorEntry : ancestry) {
271 LocalVariableEntry motherArg = entry.updateOwnership(ancestorEntry);
272 if (this.index.entryExists(motherArg)) {
273 String result = translateLocalVariableName(motherArg);
274 if (result != null) {
275 return result;
276 }
277 }
278 }
279 return null;
280 }
281
282 @Override
283 public TypeDescriptor getTranslatedTypeDesc(TypeDescriptor desc) {
284 return desc.remap(this::remapClass);
285 }
286
287 @Override
288 public MethodDescriptor getTranslatedMethodDesc(MethodDescriptor descriptor) {
289 List<TypeDescriptor> arguments = descriptor.getArgumentDescs();
290 List<TypeDescriptor> translatedArguments = new ArrayList<>(arguments.size());
291 for (TypeDescriptor argument : arguments) {
292 translatedArguments.add(getTranslatedTypeDesc(argument));
293 }
294 return new MethodDescriptor(translatedArguments, getTranslatedTypeDesc(descriptor.getReturnDesc()));
295 }
296
297 @Override
298 public Signature getTranslatedSignature(Signature signature) {
299 if (signature == null) {
300 return null;
301 }
302 return signature.remap(this::remapClass);
303 }
304
305 private ClassMapping findClassMapping(ClassEntry entry) {
306 List<ClassMapping> mappingChain = getClassMappingChain(entry);
307 return mappingChain.get(mappingChain.size() - 1);
308 }
309
310 private List<ClassMapping> getClassMappingChain(ClassEntry entry) {
311
312 // get a list of all the classes in the hierarchy
313 String[] parts = entry.getName().split("\\$");
314 List<ClassMapping> mappingsChain = Lists.newArrayList();
315
316 // get mappings for the outer class
317 ClassMapping outerClassMapping = this.classes.get(parts[0]);
318 mappingsChain.add(outerClassMapping);
319
320 for (int i = 1; i < parts.length; i++) {
321
322 // get mappings for the inner class
323 ClassMapping innerClassMapping = null;
324 if (outerClassMapping != null) {
325 innerClassMapping = this.direction.choose(
326 outerClassMapping.getInnerClassByObfSimple(parts[i]),
327 outerClassMapping.getInnerClassByDeobfThenObfSimple(parts[i])
328 );
329 }
330 mappingsChain.add(innerClassMapping);
331 outerClassMapping = innerClassMapping;
332 }
333
334 assert (mappingsChain.size() == parts.length);
335 return mappingsChain;
336 }
337
338 private Mappings.EntryModifier getClassModifier(ClassEntry entry) {
339 ClassMapping classMapping = findClassMapping(entry);
340 if (classMapping != null) {
341 return classMapping.getModifier();
342 }
343 return Mappings.EntryModifier.UNCHANGED;
344 }
345
346 private Mappings.EntryModifier getFieldModifier(FieldEntry entry) {
347 ClassMapping classMapping = findClassMapping(entry.getOwnerClassEntry());
348 if (classMapping != null) {
349 FieldMapping fieldMapping = classMapping.getFieldByObf(entry);
350 if (fieldMapping != null) {
351 return fieldMapping.getModifier();
352 }
353 }
354 return Mappings.EntryModifier.UNCHANGED;
355 }
356
357 private Mappings.EntryModifier getMethodModifier(MethodEntry entry) {
358 ClassMapping classMapping = findClassMapping(entry.getOwnerClassEntry());
359 if (classMapping != null) {
360 MethodMapping methodMapping = classMapping.getMethodByObf(entry);
361 if (methodMapping != null) {
362 return methodMapping.getModifier();
363 }
364 }
365 return Mappings.EntryModifier.UNCHANGED;
366 }
367
368 private String remapClass(String name) {
369 return getTranslatedClass(new ClassEntry(name)).getName();
370 }
371}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.mapping;
13
14import cuchaz.enigma.mapping.entry.ClassEntry;
15import cuchaz.enigma.mapping.entry.FieldEntry;
16import cuchaz.enigma.throwables.IllegalNameException;
17
18public class FieldMapping implements Comparable<FieldMapping>, MemberMapping<FieldEntry> {
19
20 private String obfName;
21 private String deobfName;
22 private TypeDescriptor obfDesc;
23 private Mappings.EntryModifier modifier;
24
25 public FieldMapping(String obfName, TypeDescriptor obfDesc, String deobfName, Mappings.EntryModifier modifier) {
26 this.obfName = obfName;
27 this.deobfName = NameValidator.validateFieldName(deobfName);
28 this.obfDesc = obfDesc;
29 this.modifier = modifier;
30 }
31
32 @Override
33 public FieldEntry getObfEntry(ClassEntry classEntry) {
34 return new FieldEntry(classEntry, this.obfName, this.obfDesc);
35 }
36
37 @Override
38 public String getObfName() {
39 return this.obfName;
40 }
41
42 public void setObfName(String name) {
43 try {
44 NameValidator.validateMethodName(name);
45 } catch (IllegalNameException ex) {
46 // Invalid name, damn obfuscation! Map to a deob name with another name to avoid issues
47 if (this.deobfName == null) {
48 System.err.println("WARNING: " + name + " is conflicting, auto deobfuscate to " + (name + "_auto_deob"));
49 setDeobfName(name + "_auto_deob");
50 }
51 }
52 this.obfName = name;
53 }
54
55 public String getDeobfName() {
56 return this.deobfName;
57 }
58
59 public void setDeobfName(String val) {
60 this.deobfName = NameValidator.validateFieldName(val);
61 }
62
63 public TypeDescriptor getObfDesc() {
64 return this.obfDesc;
65 }
66
67 public void setObfDesc(TypeDescriptor val) {
68 this.obfDesc = val;
69 }
70
71 public Mappings.EntryModifier getModifier() {
72 return modifier;
73 }
74
75 public void setModifier(Mappings.EntryModifier modifier) {
76 this.modifier = modifier;
77 }
78
79 @Override
80 public int compareTo(FieldMapping other) {
81 return (this.obfName + this.obfDesc).compareTo(other.obfName + other.obfDesc);
82 }
83
84 public boolean renameObfClass(final String oldObfClassName, final String newObfClassName) {
85 // rename obf classes in the desc
86 TypeDescriptor newDesc = this.obfDesc.remap(className -> {
87 if (className.equals(oldObfClassName)) {
88 return newObfClassName;
89 }
90 return className;
91 });
92
93 if (!newDesc.equals(this.obfDesc)) {
94 this.obfDesc = newDesc;
95 return true;
96 }
97 return false;
98 }
99
100}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.mapping;
13
14import cuchaz.enigma.mapping.entry.LocalVariableEntry;
15import cuchaz.enigma.mapping.entry.MethodEntry;
16
17public class LocalVariableMapping implements Comparable<LocalVariableMapping> {
18
19 private int index;
20 private String name;
21
22 // NOTE: this argument order is important for the MethodReader/MethodWriter
23 public LocalVariableMapping(int index, String name) {
24 this.index = index;
25 this.name = NameValidator.validateArgumentName(name);
26 }
27
28 public LocalVariableMapping(LocalVariableMapping other) {
29 this.index = other.index;
30 this.name = other.name;
31 }
32
33 public int getIndex() {
34 return this.index;
35 }
36
37 public String getName() {
38 return this.name;
39 }
40
41 public void setName(String val) {
42 this.name = NameValidator.validateArgumentName(val);
43 }
44
45 @Deprecated
46 public LocalVariableEntry getObfEntry(MethodEntry methodEntry) {
47 return new LocalVariableEntry(methodEntry, index, name);
48 }
49
50 public LocalVariableEntry getObfEntry(MethodEntry methodEntry, boolean parameter) {
51 return new LocalVariableEntry(methodEntry, index, name, parameter);
52 }
53
54 @Override
55 public int compareTo(LocalVariableMapping other) {
56 return Integer.compare(this.index, other.index);
57 }
58}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.mapping;
13
14import com.google.common.collect.Lists;
15import com.google.common.collect.Maps;
16import com.google.common.collect.Sets;
17import cuchaz.enigma.analysis.TranslationIndex;
18import cuchaz.enigma.api.EnigmaPlugin;
19import cuchaz.enigma.bytecode.AccessFlags;
20import cuchaz.enigma.mapping.entry.ClassEntry;
21import cuchaz.enigma.mapping.entry.MethodEntry;
22import cuchaz.enigma.throwables.MappingConflict;
23
24import java.io.File;
25import java.io.IOException;
26import java.util.*;
27import java.util.stream.Collectors;
28
29public class Mappings {
30
31 private final FormatType originMapping;
32 protected Map<String, ClassMapping> classesByObf;
33 protected Map<String, ClassMapping> classesByDeobf;
34 private Mappings previousState;
35
36 public Mappings() {
37 this(FormatType.ENIGMA_DIRECTORY);
38 }
39
40 public Mappings(FormatType originMapping) {
41 this.originMapping = originMapping;
42 this.classesByObf = Maps.newHashMap();
43 this.classesByDeobf = Maps.newHashMap();
44 }
45
46 public Collection<ClassMapping> classes() {
47 assert (this.classesByObf.size() >= this.classesByDeobf.size());
48 return this.classesByObf.values();
49 }
50
51 public void addClassMapping(ClassMapping classMapping) throws MappingConflict {
52 if (this.classesByObf.containsKey(classMapping.getObfFullName())) {
53 throw new MappingConflict("class", classMapping.getObfFullName(), this.classesByObf.get(classMapping.getObfFullName()).getObfFullName());
54 }
55 this.classesByObf.put(classMapping.getObfFullName(), classMapping);
56
57 if (classMapping.getDeobfName() != null) {
58 if (this.classesByDeobf.containsKey(classMapping.getDeobfName())) {
59 throw new MappingConflict("class", classMapping.getDeobfName(), this.classesByDeobf.get(classMapping.getDeobfName()).getDeobfName());
60 }
61 this.classesByDeobf.put(classMapping.getDeobfName(), classMapping);
62 }
63 }
64
65 public void removeClassMapping(ClassMapping classMapping) {
66 boolean obfWasRemoved = this.classesByObf.remove(classMapping.getObfFullName()) != null;
67 assert (obfWasRemoved);
68 if (classMapping.getDeobfName() != null) {
69 boolean deobfWasRemoved = this.classesByDeobf.remove(classMapping.getDeobfName()) != null;
70 assert (deobfWasRemoved);
71 }
72 }
73
74 public ClassMapping getClassByObf(ClassEntry entry) {
75 return getClassByObf(entry.getName());
76 }
77
78 public ClassMapping getClassByObf(String obfName) {
79 return this.classesByObf.get(obfName);
80 }
81
82 public ClassMapping getClassByDeobf(ClassEntry entry) {
83 return getClassByDeobf(entry.getName());
84 }
85
86 public ClassMapping getClassByDeobf(String deobfName) {
87 return this.classesByDeobf.get(deobfName);
88 }
89
90 public void setClassDeobfName(ClassMapping classMapping, String deobfName) {
91 if (classMapping.getDeobfName() != null) {
92 boolean wasRemoved = this.classesByDeobf.remove(classMapping.getDeobfName()) != null;
93 assert (wasRemoved);
94 }
95 classMapping.setDeobfName(deobfName);
96 if (deobfName != null) {
97 boolean wasAdded = this.classesByDeobf.put(deobfName, classMapping) == null;
98 assert (wasAdded);
99 }
100 }
101
102 public Translator getTranslator(TranslationDirection direction, TranslationIndex index) {
103 switch (direction) {
104 case DEOBFUSCATING:
105
106 return new DirectionalTranslator(direction, this.classesByObf, index);
107
108 case OBFUSCATING:
109
110 // fill in the missing deobf class entries with obf entries
111 Map<String, ClassMapping> classes = Maps.newHashMap();
112 for (ClassMapping classMapping : classes()) {
113 if (classMapping.getDeobfName() != null) {
114 classes.put(classMapping.getDeobfName(), classMapping);
115 } else {
116 classes.put(classMapping.getObfFullName(), classMapping);
117 }
118 }
119
120 // translate the translation index
121 // NOTE: this isn't actually recursive
122 TranslationIndex deobfIndex = new TranslationIndex(index, getTranslator(TranslationDirection.DEOBFUSCATING, index));
123
124 return new DirectionalTranslator(direction, classes, deobfIndex);
125
126 default:
127 throw new Error("Invalid translation direction!");
128 }
129 }
130
131 @Override
132 public String toString() {
133 StringBuilder buf = new StringBuilder();
134 for (ClassMapping classMapping : this.classesByObf.values()) {
135 buf.append(classMapping);
136 buf.append("\n");
137 }
138 return buf.toString();
139 }
140
141 public void renameObfClass(String oldObfName, String newObfName) {
142 new ArrayList<>(classes()).stream().filter(classMapping -> classMapping.renameObfClass(oldObfName, newObfName)).forEach(classMapping -> {
143 boolean wasRemoved = this.classesByObf.remove(oldObfName) != null;
144 assert (wasRemoved);
145 boolean wasAdded = this.classesByObf.put(newObfName, classMapping) == null;
146 assert (wasAdded);
147 });
148 }
149
150 public Set<String> getAllObfClassNames() {
151 final Set<String> classNames = Sets.newHashSet();
152 for (ClassMapping classMapping : classes()) {
153
154 // add the class name
155 classNames.add(classMapping.getObfFullName());
156
157 // add classes from method signatures
158 for (MethodMapping methodMapping : classMapping.methods()) {
159 for (TypeDescriptor desc : methodMapping.getObfDesc().types()) {
160 if (desc.containsType()) {
161 classNames.add(desc.getTypeEntry().getClassName());
162 }
163 }
164 }
165 }
166 return classNames;
167 }
168
169 public boolean containsDeobfClass(String deobfName) {
170 return this.classesByDeobf.containsKey(deobfName);
171 }
172
173 public boolean containsDeobfField(ClassEntry obfClassEntry, String deobfName, TypeDescriptor obfDesc) {
174 ClassMapping classMapping = this.classesByObf.get(obfClassEntry.getName());
175 return classMapping != null && classMapping.containsDeobfField(deobfName, obfDesc);
176 }
177
178 public boolean containsDeobfField(ClassEntry obfClassEntry, String deobfName) {
179 ClassMapping classMapping = this.classesByObf.get(obfClassEntry.getName());
180 if (classMapping != null)
181 for (FieldMapping fieldMapping : classMapping.fields())
182 if (deobfName.equals(fieldMapping.getDeobfName()) || deobfName.equals(fieldMapping.getObfName()))
183 return true;
184
185 return false;
186 }
187
188 public boolean containsDeobfMethod(ClassEntry obfClassEntry, String deobfName, MethodDescriptor obfDescriptor) {
189 ClassMapping classMapping = this.classesByObf.get(obfClassEntry.getName());
190 return classMapping != null && classMapping.containsDeobfMethod(deobfName, obfDescriptor);
191 }
192
193 public boolean containsArgument(MethodEntry obfMethodEntry, String name) {
194 ClassMapping classMapping = this.classesByObf.get(obfMethodEntry.getClassName());
195 return classMapping != null && classMapping.containsArgument(obfMethodEntry, name);
196 }
197
198 public List<ClassMapping> getClassMappingChain(ClassEntry obfClass) {
199 List<ClassMapping> mappingChain = Lists.newArrayList();
200 ClassMapping classMapping = null;
201 for (ClassEntry obfClassEntry : obfClass.getClassChain()) {
202 if (mappingChain.isEmpty()) {
203 classMapping = this.classesByObf.get(obfClassEntry.getName());
204 } else if (classMapping != null) {
205 classMapping = classMapping.getInnerClassByObfSimple(obfClassEntry.getInnermostClassName());
206 }
207 mappingChain.add(classMapping);
208 }
209 return mappingChain;
210 }
211
212 public FormatType getOriginMappingFormat() {
213 return originMapping;
214 }
215
216 public void savePreviousState() {
217 this.previousState = new Mappings(this.originMapping);
218 this.previousState.classesByDeobf = new HashMap<>();
219 for (Map.Entry<String, ClassMapping> entry : this.classesByDeobf.entrySet()) {
220 this.previousState.classesByDeobf.put(entry.getKey(), entry.getValue().copy());
221 }
222 this.previousState.classesByObf = new HashMap<>();
223 for (Map.Entry<String, ClassMapping> entry : this.classesByObf.entrySet()) {
224 this.previousState.classesByObf.put(entry.getKey(), entry.getValue().copy());
225 }
226 classesByDeobf.values().forEach(ClassMapping::resetDirty);
227 classesByObf.values().forEach(ClassMapping::resetDirty);
228 }
229
230 public void saveEnigmaMappings(File file, boolean isDirectoryFormat) throws IOException {
231 new MappingsEnigmaWriter().write(file, this, isDirectoryFormat);
232 this.savePreviousState();
233 }
234
235 public void saveSRGMappings(File file) throws IOException {
236 new MappingsSRGWriter().write(file, this);
237 }
238
239 public Mappings getPreviousState() {
240 return previousState;
241 }
242
243 public enum FormatType {
244 ENIGMA_FILE, ENIGMA_DIRECTORY, TINY_FILE, SRG_FILE
245 }
246
247 public enum EntryModifier {
248 UNCHANGED, PUBLIC, PROTECTED, PRIVATE;
249
250 public String getFormattedName() {
251 return " ACC:" + super.toString();
252 }
253
254 public AccessFlags transform(AccessFlags access) {
255 switch (this) {
256 case PUBLIC:
257 return access.setPublic();
258 case PROTECTED:
259 return access.setProtected();
260 case PRIVATE:
261 return access.setPrivate();
262 case UNCHANGED:
263 default:
264 return access;
265 }
266 }
267 }
268}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.mapping;
13
14import com.google.common.collect.Lists;
15import com.google.common.collect.Maps;
16import cuchaz.enigma.analysis.JarIndex;
17import cuchaz.enigma.mapping.entry.ClassEntry;
18import cuchaz.enigma.mapping.entry.EntryFactory;
19import cuchaz.enigma.mapping.entry.FieldEntry;
20import cuchaz.enigma.mapping.entry.MethodEntry;
21
22import java.util.Map;
23
24public class MappingsChecker {
25
26 private JarIndex index;
27 private Map<ClassEntry, ClassMapping> droppedClassMappings;
28 private Map<ClassEntry, ClassMapping> droppedInnerClassMappings;
29 private Map<FieldEntry, FieldMapping> droppedFieldMappings;
30 private Map<MethodEntry, MethodMapping> droppedMethodMappings;
31
32 public MappingsChecker(JarIndex index) {
33 this.index = index;
34 this.droppedClassMappings = Maps.newHashMap();
35 this.droppedInnerClassMappings = Maps.newHashMap();
36 this.droppedFieldMappings = Maps.newHashMap();
37 this.droppedMethodMappings = Maps.newHashMap();
38 }
39
40 public Map<ClassEntry, ClassMapping> getDroppedClassMappings() {
41 return this.droppedClassMappings;
42 }
43
44 public Map<ClassEntry, ClassMapping> getDroppedInnerClassMappings() {
45 return this.droppedInnerClassMappings;
46 }
47
48 public Map<FieldEntry, FieldMapping> getDroppedFieldMappings() {
49 return this.droppedFieldMappings;
50 }
51
52 public Map<MethodEntry, MethodMapping> getDroppedMethodMappings() {
53 return this.droppedMethodMappings;
54 }
55
56 public void dropBrokenMappings(Mappings mappings) {
57 for (ClassMapping classMapping : Lists.newArrayList(mappings.classes())) {
58 if (!checkClassMapping(classMapping)) {
59 mappings.removeClassMapping(classMapping);
60 this.droppedClassMappings.put(EntryFactory.getObfClassEntry(this.index, classMapping), classMapping);
61 }
62 }
63 }
64
65 private boolean checkClassMapping(ClassMapping classMapping) {
66
67 // check the class
68 ClassEntry classEntry = EntryFactory.getObfClassEntry(this.index, classMapping);
69 if (!this.index.getObfClassEntries().contains(classEntry)) {
70 return false;
71 }
72
73 // check the fields
74 for (FieldMapping fieldMapping : Lists.newArrayList(classMapping.fields())) {
75 FieldEntry obfFieldEntry = EntryFactory.getObfFieldEntry(classMapping, fieldMapping);
76 if (!this.index.containsObfField(obfFieldEntry)) {
77 classMapping.removeFieldMapping(fieldMapping);
78 this.droppedFieldMappings.put(obfFieldEntry, fieldMapping);
79 }
80 }
81
82 // check methods
83 for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) {
84 MethodEntry obfMethodEntry = EntryFactory.getObfMethodEntry(classEntry, methodMapping);
85 if (!this.index.containsObfMethod(obfMethodEntry)) {
86 classMapping.removeMethodMapping(methodMapping);
87 this.droppedMethodMappings.put(obfMethodEntry, methodMapping);
88 }
89 }
90
91 // check inner classes
92 for (ClassMapping innerClassMapping : Lists.newArrayList(classMapping.innerClasses())) {
93 if (!checkClassMapping(innerClassMapping)) {
94 classMapping.removeInnerClassMapping(innerClassMapping);
95 this.droppedInnerClassMappings.put(EntryFactory.getObfClassEntry(this.index, innerClassMapping), innerClassMapping);
96 }
97 }
98
99 return true;
100 }
101}
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 @@
1package cuchaz.enigma.mapping;
2
3import com.google.common.base.Charsets;
4import com.google.common.collect.Queues;
5import cuchaz.enigma.throwables.MappingConflict;
6import cuchaz.enigma.throwables.MappingParseException;
7
8import java.io.BufferedReader;
9import java.io.File;
10import java.io.FileInputStream;
11import java.io.IOException;
12import java.io.InputStream;
13import java.io.InputStreamReader;
14import java.util.Deque;
15import java.util.function.Supplier;
16
17public class MappingsEnigmaReader {
18
19 public Mappings read(File file) throws IOException, MappingParseException {
20 Mappings mappings;
21
22 // Multiple file
23 if (file.isDirectory()) {
24 mappings = new Mappings(Mappings.FormatType.ENIGMA_DIRECTORY);
25 readDirectory(mappings, file);
26 } else {
27 mappings = new Mappings();
28 readFile(mappings, file);
29 }
30 return mappings;
31 }
32
33 public void readDirectory(Mappings mappings, File directory) throws IOException, MappingParseException {
34 File[] files = directory.listFiles();
35 if (files != null) {
36 for (File file : files) {
37 if (file.isFile() && !file.getName().startsWith(".") && file.getName().endsWith(".mapping"))
38 readFile(mappings, file);
39 else if (file.isDirectory())
40 readDirectory(mappings, file.getAbsoluteFile());
41 }
42 mappings.savePreviousState();
43 } else
44 throw new IOException("Cannot access directory" + directory.getAbsolutePath());
45 }
46
47 public Mappings readFile(Mappings mappings, File file) throws IOException, MappingParseException {
48 return readFileStream(mappings, new FileInputStream(file), file::getAbsolutePath);
49 }
50
51 public Mappings readFileStream(Mappings mappings, InputStream stream, Supplier<String> filenameSupplier) throws IOException, MappingParseException {
52 try (BufferedReader in = new BufferedReader(new InputStreamReader(stream, Charsets.UTF_8))) {
53 Deque<Object> mappingStack = Queues.newArrayDeque();
54
55 int lineNumber = 0;
56 String line;
57 while ((line = in.readLine()) != null) {
58 lineNumber++;
59
60 // strip comments
61 int commentPos = line.indexOf('#');
62 if (commentPos >= 0) {
63 line = line.substring(0, commentPos);
64 }
65
66 // skip blank lines
67 if (line.trim().length() <= 0) {
68 continue;
69 }
70
71 // get the indent of this line
72 int indent = 0;
73 for (int i = 0; i < line.length(); i++) {
74 if (line.charAt(i) != '\t') {
75 break;
76 }
77 indent++;
78 }
79
80 // handle stack pops
81 while (indent < mappingStack.size()) {
82 mappingStack.pop();
83 }
84
85 String[] parts = line.trim().split("\\s");
86 try {
87 // read the first token
88 String token = parts[0];
89
90 if (token.equalsIgnoreCase("CLASS")) {
91 ClassMapping classMapping;
92 if (indent <= 0) {
93 // outer class
94 classMapping = readClass(parts, false);
95 mappings.addClassMapping(classMapping);
96 } else {
97
98 // inner class
99 if (!(mappingStack.peek() instanceof ClassMapping)) {
100 throw new MappingParseException(filenameSupplier, lineNumber, "Unexpected CLASS entry here!");
101 }
102
103 classMapping = readClass(parts, true);
104 ((ClassMapping) mappingStack.peek()).addInnerClassMapping(classMapping);
105 }
106 mappingStack.push(classMapping);
107 } else if (token.equalsIgnoreCase("FIELD")) {
108 if (mappingStack.isEmpty() || !(mappingStack.peek() instanceof ClassMapping)) {
109 throw new MappingParseException(filenameSupplier, lineNumber, "Unexpected FIELD entry here!");
110 }
111 ((ClassMapping) mappingStack.peek()).addFieldMapping(readField(parts));
112 } else if (token.equalsIgnoreCase("METHOD")) {
113 if (mappingStack.isEmpty() || !(mappingStack.peek() instanceof ClassMapping)) {
114 throw new MappingParseException(filenameSupplier, lineNumber, "Unexpected METHOD entry here!");
115 }
116 MethodMapping methodMapping = readMethod(parts);
117 ((ClassMapping) mappingStack.peek()).addMethodMapping(methodMapping);
118 mappingStack.push(methodMapping);
119 } else if (token.equalsIgnoreCase("ARG")) {
120 if (mappingStack.isEmpty() || !(mappingStack.peek() instanceof MethodMapping)) {
121 throw new MappingParseException(filenameSupplier, lineNumber, "Unexpected ARG entry here!");
122 }
123 ((MethodMapping) mappingStack.peek()).addArgumentMapping(readArgument(parts));
124 }
125 } catch (ArrayIndexOutOfBoundsException | IllegalArgumentException ex) {
126 throw new MappingParseException(filenameSupplier, lineNumber, "Malformed line:\n" + line);
127 } catch (MappingConflict e) {
128 throw new MappingParseException(filenameSupplier, lineNumber, e.getMessage());
129 }
130 }
131 return mappings;
132 }
133 }
134
135 private LocalVariableMapping readArgument(String[] parts) {
136 return new LocalVariableMapping(Integer.parseInt(parts[1]), parts[2]);
137 }
138
139 private ClassMapping readClass(String[] parts, boolean makeSimple) {
140 if (parts.length == 2) {
141 return new ClassMapping(parts[1]);
142 } else if (parts.length == 3) {
143 boolean access = parts[2].startsWith("ACC:");
144 ClassMapping mapping;
145 if (access)
146 mapping = new ClassMapping(parts[1], null, Mappings.EntryModifier.valueOf(parts[2].substring(4)));
147 else
148 mapping = new ClassMapping(parts[1], parts[2]);
149
150 return mapping;
151 } else if (parts.length == 4)
152 return new ClassMapping(parts[1], parts[2], Mappings.EntryModifier.valueOf(parts[3].substring(4)));
153 return null;
154 }
155
156 /* TEMP */
157 protected FieldMapping readField(String[] parts) {
158 FieldMapping mapping = null;
159 if (parts.length == 4) {
160 boolean access = parts[3].startsWith("ACC:");
161 if (access)
162 mapping = new FieldMapping(parts[1], new TypeDescriptor(parts[2]), null,
163 Mappings.EntryModifier.valueOf(parts[3].substring(4)));
164 else
165 mapping = new FieldMapping(parts[1], new TypeDescriptor(parts[3]), parts[2], Mappings.EntryModifier.UNCHANGED);
166 } else if (parts.length == 5)
167 mapping = new FieldMapping(parts[1], new TypeDescriptor(parts[3]), parts[2], Mappings.EntryModifier.valueOf(parts[4].substring(4)));
168 return mapping;
169 }
170
171 private MethodMapping readMethod(String[] parts) {
172 MethodMapping mapping = null;
173 if (parts.length == 3)
174 mapping = new MethodMapping(parts[1], new MethodDescriptor(parts[2]));
175 else if (parts.length == 4) {
176 boolean access = parts[3].startsWith("ACC:");
177 if (access)
178 mapping = new MethodMapping(parts[1], new MethodDescriptor(parts[2]), null, Mappings.EntryModifier.valueOf(parts[3].substring(4)));
179 else
180 mapping = new MethodMapping(parts[1], new MethodDescriptor(parts[3]), parts[2]);
181 } else if (parts.length == 5)
182 mapping = new MethodMapping(parts[1], new MethodDescriptor(parts[3]), parts[2],
183 Mappings.EntryModifier.valueOf(parts[4].substring(4)));
184 return mapping;
185 }
186}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.mapping;
13
14import com.google.common.base.Charsets;
15
16import java.io.*;
17import java.util.*;
18
19public class MappingsEnigmaWriter {
20
21 public void write(File out, Mappings mappings, boolean isDirectoryFormat) throws IOException {
22 if (!isDirectoryFormat) {
23 PrintWriter outputWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(out), Charsets.UTF_8));
24 write(outputWriter, mappings);
25 outputWriter.close();
26 } else
27 writeAsDirectory(out, mappings);
28 }
29
30 public void writeAsDirectory(File target, Mappings mappings) throws IOException {
31 if (!target.exists() && !target.mkdirs())
32 throw new IOException("Cannot create mapping directory!");
33
34 Mappings previousState = mappings.getPreviousState();
35 for (ClassMapping classMapping : sorted(mappings.classes())) {
36 File result = new File(target, classMapping.getSaveName() + ".mapping");
37
38 if (!classMapping.isDirty()) {
39 continue;
40 }
41
42 if (classMapping.isEmpty()) {
43 if (result.exists()) {
44 result.delete();
45 }
46 continue;
47 }
48
49 if (previousState != null) {
50 ClassMapping previousClass = previousState.classesByObf.get(classMapping.getObfFullName());
51 File previousFile;
52 if (previousClass != null) {
53 previousFile = new File(target, previousClass.getSaveName() + ".mapping");
54 } else {
55 previousFile = new File(target, classMapping.getObfFullName() + ".mapping");
56 }
57 if (previousFile.exists() && !previousFile.delete()) {
58 System.err.println("Failed to delete old class mapping " + previousFile.getName());
59 }
60 }
61
62 File packageFile = result.getParentFile();
63 if (!packageFile.exists()) {
64 packageFile.mkdirs();
65 }
66 result.createNewFile();
67
68 try (PrintWriter outputWriter = new PrintWriter(new BufferedWriter(new FileWriter(result)))) {
69 write(outputWriter, classMapping, 0);
70 }
71 }
72
73 // Remove dropped mappings
74 if (previousState != null) {
75 Set<ClassMapping> droppedClassMappings = new HashSet<>(previousState.classes());
76 droppedClassMappings.removeAll(mappings.classes());
77 for (ClassMapping droppedMapping : droppedClassMappings) {
78 File result = new File(target, droppedMapping.getSaveName() + ".mapping");
79 if (!result.exists()) {
80 continue;
81 }
82 if (!result.delete()) {
83 System.err.println("Failed to delete dropped class mapping " + result.getName());
84 }
85 }
86 }
87 }
88
89 public void write(PrintWriter out, Mappings mappings) throws IOException {
90 for (ClassMapping classMapping : sorted(mappings.classes())) {
91 write(out, classMapping, 0);
92 }
93 }
94
95 protected void write(PrintWriter out, ClassMapping classMapping, int depth) throws IOException {
96 if (classMapping.getDeobfName() == null) {
97 out.format("%sCLASS %s%s\n", getIndent(depth), classMapping.getObfFullName(),
98 classMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : classMapping.getModifier().getFormattedName());
99 } else {
100 out.format("%sCLASS %s %s%s\n", getIndent(depth), classMapping.getObfFullName(), classMapping.getDeobfName(),
101 classMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : classMapping.getModifier().getFormattedName());
102 }
103
104 for (ClassMapping innerClassMapping : sorted(classMapping.innerClasses())) {
105 write(out, innerClassMapping, depth + 1);
106 }
107
108 for (FieldMapping fieldMapping : sorted(classMapping.fields())) {
109 write(out, fieldMapping, depth + 1);
110 }
111
112 for (MethodMapping methodMapping : sorted(classMapping.methods())) {
113 write(out, methodMapping, depth + 1);
114 }
115 }
116
117 private void write(PrintWriter out, FieldMapping fieldMapping, int depth) {
118 if (fieldMapping.getDeobfName() == null)
119 out.format("%sFIELD %s %s%s\n", getIndent(depth), fieldMapping.getObfName(), fieldMapping.getObfDesc().toString(),
120 fieldMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : fieldMapping.getModifier().getFormattedName());
121 else
122 out.format("%sFIELD %s %s %s%s\n", getIndent(depth), fieldMapping.getObfName(), fieldMapping.getDeobfName(), fieldMapping.getObfDesc().toString(),
123 fieldMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : fieldMapping.getModifier().getFormattedName());
124 }
125
126 private void write(PrintWriter out, MethodMapping methodMapping, int depth) throws IOException {
127 if (methodMapping.isObfuscated()) {
128 out.format("%sMETHOD %s %s%s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getObfDesc(),
129 methodMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : methodMapping.getModifier().getFormattedName());
130 } else {
131 out.format("%sMETHOD %s %s %s%s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getDeobfName(), methodMapping.getObfDesc(),
132 methodMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : methodMapping.getModifier().getFormattedName());
133 }
134
135 for (LocalVariableMapping localVariableMapping : sorted(methodMapping.arguments())) {
136 write(out, localVariableMapping, depth + 1);
137 }
138 }
139
140 private void write(PrintWriter out, LocalVariableMapping localVariableMapping, int depth) {
141 out.format("%sARG %d %s\n", getIndent(depth), localVariableMapping.getIndex(), localVariableMapping.getName());
142 }
143
144 protected <T extends Comparable<T>> List<T> sorted(Iterable<T> classes) {
145 List<T> out = new ArrayList<>();
146 for (T t : classes) {
147 out.add(t);
148 }
149 Collections.sort(out);
150 return out;
151 }
152
153 private String getIndent(int depth) {
154 StringBuilder buf = new StringBuilder();
155 for (int i = 0; i < depth; i++) {
156 buf.append("\t");
157 }
158 return buf.toString();
159 }
160}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.mapping;
13
14import com.google.common.collect.Lists;
15import cuchaz.enigma.analysis.JarIndex;
16import cuchaz.enigma.mapping.entry.*;
17import cuchaz.enigma.throwables.IllegalNameException;
18import cuchaz.enigma.throwables.MappingConflict;
19
20import java.io.IOException;
21import java.io.ObjectOutputStream;
22import java.io.OutputStream;
23import java.util.List;
24import java.util.Set;
25import java.util.zip.GZIPOutputStream;
26
27public class MappingsRenamer {
28
29 private final JarIndex index;
30 private final ReferencedEntryPool entryPool;
31 private Mappings mappings;
32
33 public MappingsRenamer(JarIndex index, Mappings mappings, ReferencedEntryPool entryPool) {
34 this.index = index;
35 this.mappings = mappings;
36 this.entryPool = entryPool;
37 }
38
39 public void setMappings(Mappings mappings) {
40 this.mappings = mappings;
41 }
42
43 public void setClassName(ClassEntry obf, String deobfName) {
44
45 deobfName = NameValidator.validateClassName(deobfName, !obf.isInnerClass());
46
47 List<ClassMapping> mappingChain = getOrCreateClassMappingChain(obf);
48 if (mappingChain.size() == 1) {
49
50 if (deobfName != null) {
51 // make sure we don't rename to an existing obf or deobf class
52 if (mappings.containsDeobfClass(deobfName) || index.containsObfClass(entryPool.getClass(deobfName))) {
53 throw new IllegalNameException(deobfName, "There is already a class with that name");
54 }
55 }
56
57 ClassMapping classMapping = mappingChain.get(0);
58 mappings.setClassDeobfName(classMapping, deobfName);
59
60 } else {
61
62 ClassMapping outerClassMapping = mappingChain.get(mappingChain.size() - 2);
63
64 if (deobfName != null) {
65 // make sure we don't rename to an existing obf or deobf inner class
66 if (outerClassMapping.hasInnerClassByDeobf(deobfName) || outerClassMapping.hasInnerClassByObfSimple(deobfName)) {
67 throw new IllegalNameException(deobfName, "There is already a class with that name");
68 }
69 }
70
71 outerClassMapping.setInnerClassName(obf, deobfName);
72 }
73 }
74
75 public void removeClassMapping(ClassEntry obf) {
76 setClassName(obf, null);
77 }
78
79 public void markClassAsDeobfuscated(ClassEntry obf) {
80 String deobfName = obf.isInnerClass() ? obf.getInnermostClassName() : obf.getName();
81 List<ClassMapping> mappingChain = getOrCreateClassMappingChain(obf);
82 if (mappingChain.size() == 1) {
83 ClassMapping classMapping = mappingChain.get(0);
84 mappings.setClassDeobfName(classMapping, deobfName);
85 } else {
86 ClassMapping outerClassMapping = mappingChain.get(mappingChain.size() - 2);
87 outerClassMapping.setInnerClassName(obf, deobfName);
88 }
89 }
90
91 public void setFieldName(FieldEntry obf, String deobfName) {
92 deobfName = NameValidator.validateFieldName(deobfName);
93 FieldEntry targetEntry = entryPool.getField(obf.getOwnerClassEntry(), deobfName, obf.getDesc());
94 ClassEntry definedClass = null;
95 if (mappings.containsDeobfField(obf.getOwnerClassEntry(), deobfName) || index.containsEntryWithSameName(targetEntry)) {
96 definedClass = obf.getOwnerClassEntry();
97 } else {
98 for (ClassEntry ancestorEntry : this.index.getTranslationIndex().getAncestry(obf.getOwnerClassEntry())) {
99 if (mappings.containsDeobfField(ancestorEntry, deobfName) || index.containsEntryWithSameName(targetEntry.updateOwnership(ancestorEntry))) {
100 definedClass = ancestorEntry;
101 break;
102 }
103 }
104 }
105
106 if (definedClass != null) {
107 Translator translator = mappings.getTranslator(TranslationDirection.DEOBFUSCATING, index.getTranslationIndex());
108 String className = translator.getTranslatedClass(entryPool.getClass(definedClass.getClassName())).getName();
109 if (className == null)
110 className = definedClass.getClassName();
111 throw new IllegalNameException(deobfName, "There is already a field with that name in " + className);
112 }
113
114 ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry());
115 classMapping.setFieldName(obf.getName(), obf.getDesc(), deobfName);
116 }
117
118 public void removeFieldMapping(FieldEntry obf) {
119 ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry());
120 classMapping.removeFieldMapping(classMapping.getFieldByObf(obf.getName(), obf.getDesc()));
121 }
122
123 public void markFieldAsDeobfuscated(FieldEntry obf) {
124 ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry());
125 classMapping.setFieldName(obf.getName(), obf.getDesc(), obf.getName());
126 }
127
128 private void validateMethodTreeName(MethodEntry entry, String deobfName) {
129 MethodEntry targetEntry = entryPool.getMethod(entry.getOwnerClassEntry(), deobfName, entry.getDesc());
130
131 // TODO: Verify if I don't break things
132 ClassMapping classMapping = mappings.getClassByObf(entry.getOwnerClassEntry());
133 if ((classMapping != null && classMapping.containsDeobfMethod(deobfName, entry.getDesc()) && classMapping.getMethodByObf(entry.getName(), entry.getDesc()) != classMapping.getMethodByDeobf(deobfName, entry.getDesc()))
134 || index.containsObfMethod(targetEntry)) {
135 Translator translator = mappings.getTranslator(TranslationDirection.DEOBFUSCATING, index.getTranslationIndex());
136 String deobfClassName = translator.getTranslatedClass(entryPool.getClass(entry.getClassName())).getClassName();
137 if (deobfClassName == null) {
138 deobfClassName = entry.getClassName();
139 }
140 throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName);
141 }
142
143 for (ClassEntry child : index.getTranslationIndex().getSubclass(entry.getOwnerClassEntry())) {
144 validateMethodTreeName(entry.updateOwnership(child), deobfName);
145 }
146 }
147
148 public void setMethodTreeName(MethodEntry obf, String deobfName) {
149 Set<MethodEntry> implementations = index.getRelatedMethodImplementations(obf);
150
151 deobfName = NameValidator.validateMethodName(deobfName);
152 for (MethodEntry entry : implementations) {
153 validateMethodTreeName(entry, deobfName);
154 }
155
156 for (MethodEntry entry : implementations) {
157 setMethodName(entry, deobfName);
158 }
159 }
160
161 public void setMethodName(MethodEntry obf, String deobfName) {
162 deobfName = NameValidator.validateMethodName(deobfName);
163 MethodEntry targetEntry = entryPool.getMethod(obf.getOwnerClassEntry(), deobfName, obf.getDesc());
164 ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry());
165
166 // TODO: Verify if I don't break things
167 if ((mappings.containsDeobfMethod(obf.getOwnerClassEntry(), deobfName, obf.getDesc()) && classMapping.getMethodByObf(obf.getName(), obf.getDesc()) != classMapping.getMethodByDeobf(deobfName, obf.getDesc()))
168 || index.containsObfMethod(targetEntry)) {
169 Translator translator = mappings.getTranslator(TranslationDirection.DEOBFUSCATING, index.getTranslationIndex());
170 String deobfClassName = translator.getTranslatedClass(entryPool.getClass(obf.getClassName())).getClassName();
171 if (deobfClassName == null) {
172 deobfClassName = obf.getClassName();
173 }
174 throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName);
175 }
176
177 classMapping.setMethodName(obf.getName(), obf.getDesc(), deobfName);
178 }
179
180 public void removeMethodTreeMapping(MethodEntry obf) {
181 index.getRelatedMethodImplementations(obf).forEach(this::removeMethodMapping);
182 }
183
184 public void removeMethodMapping(MethodEntry obf) {
185 ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry());
186 classMapping.setMethodName(obf.getName(), obf.getDesc(), null);
187 }
188
189 public void markMethodTreeAsDeobfuscated(MethodEntry obf) {
190 index.getRelatedMethodImplementations(obf).forEach(this::markMethodAsDeobfuscated);
191 }
192
193 public void markMethodAsDeobfuscated(MethodEntry obf) {
194 ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry());
195 classMapping.setMethodName(obf.getName(), obf.getDesc(), obf.getName());
196 }
197
198 public void setLocalVariableTreeName(LocalVariableEntry obf, String deobfName) {
199 MethodEntry obfMethod = obf.getOwnerEntry();
200 if (!obf.isParameter()) {
201 setLocalVariableName(obf, deobfName);
202 return;
203 }
204
205 Set<MethodEntry> implementations = index.getRelatedMethodImplementations(obfMethod);
206 for (MethodEntry entry : implementations) {
207 ClassMapping classMapping = mappings.getClassByObf(entry.getOwnerClassEntry());
208 if (classMapping != null) {
209 MethodMapping mapping = classMapping.getMethodByObf(entry.getName(), entry.getDesc());
210 // NOTE: don't need to check arguments for name collisions with names determined by Procyon
211 // TODO: Verify if I don't break things
212 if (mapping != null) {
213 for (LocalVariableMapping localVariableMapping : Lists.newArrayList(mapping.arguments())) {
214 if (localVariableMapping.getIndex() != obf.getIndex()) {
215 if (mapping.getDeobfLocalVariableName(localVariableMapping.getIndex()).equals(deobfName)
216 || localVariableMapping.getName().equals(deobfName)) {
217 throw new IllegalNameException(deobfName, "There is already an argument with that name");
218 }
219 }
220 }
221 }
222 }
223 }
224
225 for (MethodEntry entry : implementations) {
226 setLocalVariableName(new LocalVariableEntry(entry, obf.getIndex(), obf.getName(), obf.isParameter()), deobfName);
227 }
228 }
229
230 public void setLocalVariableName(LocalVariableEntry obf, String deobfName) {
231 deobfName = NameValidator.validateArgumentName(deobfName);
232 ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry());
233 MethodMapping mapping = classMapping.getMethodByObf(obf.getMethodName(), obf.getMethodDesc());
234 // NOTE: don't need to check arguments for name collisions with names determined by Procyon
235 // TODO: Verify if I don't break things
236 if (mapping != null) {
237 for (LocalVariableMapping localVariableMapping : Lists.newArrayList(mapping.arguments())) {
238 if (localVariableMapping.getIndex() != obf.getIndex()) {
239 if (mapping.getDeobfLocalVariableName(localVariableMapping.getIndex()).equals(deobfName)
240 || localVariableMapping.getName().equals(deobfName)) {
241 throw new IllegalNameException(deobfName, "There is already an argument with that name");
242 }
243 }
244 }
245 }
246
247 classMapping.setArgumentName(obf.getMethodName(), obf.getMethodDesc(), obf.getIndex(), deobfName);
248 }
249
250 public void removeLocalVariableMapping(LocalVariableEntry obf) {
251 ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry());
252 classMapping.removeArgumentName(obf.getMethodName(), obf.getMethodDesc(), obf.getIndex());
253 }
254
255 public void markArgumentAsDeobfuscated(LocalVariableEntry obf) {
256 ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry());
257 classMapping.setArgumentName(obf.getMethodName(), obf.getMethodDesc(), obf.getIndex(), obf.getName());
258 }
259
260 public boolean moveFieldToObfClass(ClassMapping classMapping, FieldMapping fieldMapping, ClassEntry obfClass) {
261 classMapping.removeFieldMapping(fieldMapping);
262 ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass);
263 if (!targetClassMapping.containsObfField(fieldMapping.getObfName(), fieldMapping.getObfDesc())) {
264 if (!targetClassMapping.containsDeobfField(fieldMapping.getDeobfName(), fieldMapping.getObfDesc())) {
265 targetClassMapping.addFieldMapping(fieldMapping);
266 return true;
267 } else {
268 System.err.println("WARNING: deobf field was already there: " + obfClass + "." + fieldMapping.getDeobfName());
269 }
270 }
271 return false;
272 }
273
274 public boolean moveMethodToObfClass(ClassMapping classMapping, MethodMapping methodMapping, ClassEntry obfClass) {
275 classMapping.removeMethodMapping(methodMapping);
276 ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass);
277 if (!targetClassMapping.containsObfMethod(methodMapping.getObfName(), methodMapping.getObfDesc())) {
278 if (!targetClassMapping.containsDeobfMethod(methodMapping.getDeobfName(), methodMapping.getObfDesc())) {
279 targetClassMapping.addMethodMapping(methodMapping);
280 return true;
281 } else {
282 System.err.println("WARNING: deobf method was already there: " + obfClass + "." + methodMapping.getDeobfName() + methodMapping.getObfDesc());
283 }
284 }
285 return false;
286 }
287
288 public void write(OutputStream out) throws IOException {
289 // TEMP: just use the object output for now. We can find a more efficient storage format later
290 GZIPOutputStream gzipout = new GZIPOutputStream(out);
291 ObjectOutputStream oout = new ObjectOutputStream(gzipout);
292 oout.writeObject(this);
293 gzipout.finish();
294 }
295
296 private ClassMapping getOrCreateClassMapping(ClassEntry obfClassEntry) {
297 List<ClassMapping> mappingChain = getOrCreateClassMappingChain(obfClassEntry);
298 return mappingChain.get(mappingChain.size() - 1);
299 }
300
301 private List<ClassMapping> getOrCreateClassMappingChain(ClassEntry obfClassEntry) {
302 List<ClassEntry> classChain = obfClassEntry.getClassChain();
303 List<ClassMapping> mappingChain = mappings.getClassMappingChain(obfClassEntry);
304 for (int i = 0; i < classChain.size(); i++) {
305 ClassEntry classEntry = classChain.get(i);
306 ClassMapping classMapping = mappingChain.get(i);
307 if (classMapping == null) {
308
309 // create it
310 classMapping = new ClassMapping(classEntry.getName());
311 mappingChain.set(i, classMapping);
312
313 // add it to the right parent
314 try {
315 if (i == 0) {
316 mappings.addClassMapping(classMapping);
317 } else {
318 mappingChain.get(i - 1).addInnerClassMapping(classMapping);
319 }
320 } catch (MappingConflict mappingConflict) {
321 mappingConflict.printStackTrace();
322 }
323 }
324 }
325 return mappingChain;
326 }
327
328 public void setClassModifier(ClassEntry obEntry, Mappings.EntryModifier modifier) {
329 ClassMapping classMapping = getOrCreateClassMapping(obEntry);
330 classMapping.setModifier(modifier);
331 }
332
333 public void setFieldModifier(FieldEntry obEntry, Mappings.EntryModifier modifier) {
334 ClassMapping classMapping = getOrCreateClassMapping(obEntry.getOwnerClassEntry());
335 classMapping.setFieldModifier(obEntry.getName(), obEntry.getDesc(), modifier);
336 }
337
338 public void setMethodModifier(MethodEntry obEntry, Mappings.EntryModifier modifier) {
339 ClassMapping classMapping = getOrCreateClassMapping(obEntry.getOwnerClassEntry());
340 classMapping.setMethodModifier(obEntry.getName(), obEntry.getDesc(), modifier);
341 }
342
343 public Mappings.EntryModifier getClassModifier(ClassEntry obfEntry) {
344 ClassMapping classMapping = getOrCreateClassMapping(obfEntry);
345 return classMapping.getModifier();
346 }
347
348 public Mappings.EntryModifier getFieldModifier(FieldEntry obfEntry) {
349 ClassMapping classMapping = getOrCreateClassMapping(obfEntry.getOwnerClassEntry());
350 FieldMapping fieldMapping = classMapping.getFieldByObf(obfEntry);
351 if (fieldMapping == null) {
352 return Mappings.EntryModifier.UNCHANGED;
353 }
354 return fieldMapping.getModifier();
355 }
356
357 public Mappings.EntryModifier getMethodModfifier(MethodEntry obfEntry) {
358 ClassMapping classMapping = getOrCreateClassMapping(obfEntry.getOwnerClassEntry());
359 MethodMapping methodMapping = classMapping.getMethodByObf(obfEntry);
360 if (methodMapping == null) {
361 return Mappings.EntryModifier.UNCHANGED;
362 }
363 return methodMapping.getModifier();
364 }
365}
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 @@
1package cuchaz.enigma.mapping;
2
3import com.google.common.base.Charsets;
4import cuchaz.enigma.analysis.TranslationIndex;
5import cuchaz.enigma.mapping.entry.ReferencedEntryPool;
6
7import java.io.*;
8import java.util.ArrayList;
9import java.util.Collections;
10import java.util.List;
11
12/**
13 * Created by Mark on 11/08/2016.
14 */
15public class MappingsSRGWriter {
16
17 public void write(File file, Mappings mappings) throws IOException {
18 if (file.exists()) {
19 file.delete();
20 }
21 file.createNewFile();
22
23 TranslationIndex index = new TranslationIndex(new ReferencedEntryPool());
24
25 PrintWriter writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(file), Charsets.UTF_8));
26 List<String> fieldMappings = new ArrayList<>();
27 List<String> methodMappings = new ArrayList<>();
28 for (ClassMapping classMapping : sorted(mappings.classes())) {
29 if (classMapping.getDeobfName() == null || classMapping.getObfSimpleName() == null || classMapping.getDeobfName() == null) {
30 continue;
31 }
32 writer.write("CL: " + classMapping.getObfSimpleName() + " " + classMapping.getDeobfName());
33 writer.write(System.lineSeparator());
34 for (ClassMapping innerClassMapping : sorted(classMapping.innerClasses())) {
35 if (innerClassMapping.getDeobfName() == null || innerClassMapping.getObfSimpleName() == null || innerClassMapping.getDeobfName() == null) {
36 continue;
37 }
38 String innerClassName = classMapping.getObfSimpleName() + "$" + innerClassMapping.getObfSimpleName();
39 String innerDeobfClassName = classMapping.getDeobfName() + "$" + innerClassMapping.getDeobfName();
40 writer.write("CL: " + innerClassName + " " + classMapping.getDeobfName() + "$" + innerClassMapping.getDeobfName());
41 writer.write(System.lineSeparator());
42 for (FieldMapping fieldMapping : sorted(innerClassMapping.fields())) {
43 fieldMappings.add("FD: " + innerClassName + "/" + fieldMapping.getObfName() + " " + innerDeobfClassName + "/" + fieldMapping.getDeobfName());
44 }
45
46 for (MethodMapping methodMapping : sorted(innerClassMapping.methods())) {
47 methodMappings.add("MD: " + innerClassName + "/" + methodMapping.getObfName() + " " + methodMapping.getObfDesc() + " " + innerDeobfClassName + "/" + methodMapping.getDeobfName() + " " + mappings.getTranslator(TranslationDirection.DEOBFUSCATING, index).getTranslatedMethodDesc(methodMapping.getObfDesc()));
48 }
49 }
50
51 for (FieldMapping fieldMapping : sorted(classMapping.fields())) {
52 fieldMappings.add("FD: " + classMapping.getObfFullName() + "/" + fieldMapping.getObfName() + " " + classMapping.getDeobfName() + "/" + fieldMapping.getDeobfName());
53 }
54
55 for (MethodMapping methodMapping : sorted(classMapping.methods())) {
56 methodMappings.add("MD: " + classMapping.getObfFullName() + "/" + methodMapping.getObfName() + " " + methodMapping.getObfDesc() + " " + classMapping.getDeobfName() + "/" + methodMapping.getDeobfName() + " " + mappings.getTranslator(TranslationDirection.DEOBFUSCATING, index).getTranslatedMethodDesc(methodMapping.getObfDesc()));
57 }
58 }
59 for (String fd : fieldMappings) {
60 writer.write(fd);
61 writer.write(System.lineSeparator());
62 }
63
64 for (String md : methodMappings) {
65 writer.write(md);
66 writer.write(System.lineSeparator());
67 }
68
69 writer.close();
70 }
71
72 private <T extends Comparable<T>> List<T> sorted(Iterable<T> classes) {
73 List<T> out = new ArrayList<>();
74 for (T t : classes) {
75 out.add(t);
76 }
77 Collections.sort(out);
78 return out;
79 }
80}
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 @@
1package cuchaz.enigma.mapping;
2
3import com.google.common.base.Charsets;
4import com.google.common.collect.Maps;
5import cuchaz.enigma.mapping.entry.ClassEntry;
6import cuchaz.enigma.throwables.MappingConflict;
7import cuchaz.enigma.throwables.MappingParseException;
8
9import java.io.File;
10import java.io.IOException;
11import java.nio.file.Files;
12import java.util.ArrayList;
13import java.util.List;
14import java.util.Map;
15
16public class MappingsTinyReader {
17 public ClassMapping readClass(String[] parts) {
18 // Extract the inner naming of the deob form if it have one
19 String deobName = parts[2].contains("$") ? parts[2].substring(parts[2].lastIndexOf('$') + 1) : parts[2];
20 return new ClassMapping(parts[1], deobName).setDeobfInner(parts[2]);
21 }
22
23 public FieldMapping readField(String[] parts) {
24 return new FieldMapping(parts[3], new TypeDescriptor(parts[2]), parts[4], Mappings.EntryModifier.UNCHANGED);
25 }
26
27 public MethodMapping readMethod(String[] parts) {
28 return new MethodMapping(parts[3], new MethodDescriptor(parts[2]), parts[4]);
29 }
30
31 public Mappings read(File file) throws IOException, MappingParseException {
32 Mappings mappings = new Mappings(Mappings.FormatType.TINY_FILE);
33 List<String> lines = Files.readAllLines(file.toPath(), Charsets.UTF_8);
34 Map<String, ClassMapping> classMappingMap = Maps.newHashMap();
35 lines.remove(0); // TODO: use the header
36 for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) {
37 String line = lines.get(lineNumber);
38 String[] parts = line.split("\t");
39 try {
40 String token = parts[0];
41 ClassMapping classMapping;
42 switch (token) {
43 case "CLASS":
44
45 // Check for orphan created by field or method entries. It shouldn't be possible but I prefer to handle this case
46 if (classMappingMap.containsKey(parts[1])) {
47 classMapping = classMappingMap.get(parts[1]);
48
49 // We have the full deob name, Enigma only support simple class name so we extract it.
50 String deobName = parts[2].contains("$") ?
51 parts[2].substring(parts[2].lastIndexOf('$') + 1) :
52 parts[2];
53
54 // Add full deob name to the class mapping to handle inner class after this loop
55 classMappingMap.put(parts[2], classMapping.setDeobfInner(parts[2]));
56 classMapping.setDeobfName(deobName);
57
58 // Avoid to make the mapping dirty directly at the startup
59 classMapping.resetDirty();
60 } else
61 classMapping = readClass(parts);
62 classMappingMap.put(parts[1], classMapping);
63 break;
64 case "FIELD":
65 // We can have missing classes mappings because they don't have a ob name, so we create it and use it
66 classMapping = classMappingMap.computeIfAbsent(parts[1], k -> new ClassMapping(parts[1]));
67 classMapping.addFieldMapping(readField(parts));
68 break;
69 case "METHOD":
70 // We can have missing classes mappings because they don't have a ob name, so we create it and use it
71 classMapping = classMappingMap.computeIfAbsent(parts[1], k -> new ClassMapping(parts[1]));
72 classMapping.addMethodMapping(readMethod(parts));
73 break;
74 case "MTH-ARG":
75 classMapping = classMappingMap.computeIfAbsent(parts[1], k -> new ClassMapping(parts[1]));
76 classMapping.setArgumentName(parts[3], new MethodDescriptor(parts[2]), Integer.parseInt(parts[4]), parts[5]);
77 break;
78 default:
79 throw new MappingParseException(file, lineNumber, "Unknown token '" + token + "' !");
80 }
81 } catch (ArrayIndexOutOfBoundsException | IllegalArgumentException ex) {
82 ex.printStackTrace();
83 throw new MappingParseException(file, lineNumber, "Malformed line:\n" + line);
84 }
85 }
86
87 List<ClassMapping> toRegister = new ArrayList<>(classMappingMap.values());
88
89 // After having completely parsed the file, we need to register it to the real mapping
90 for (ClassMapping classMapping : toRegister) {
91 ClassEntry obEntry = classMapping.getObfEntry();
92 ClassEntry deobEntry = classMapping.getDeObfEntry();
93 try {
94 if (obEntry.isInnerClass()) {
95 ClassMapping parent = classMappingMap.get(obEntry.getOuterClassName());
96 // Inner class can miss their parent... So we create it and add it to the mappings
97 if (parent == null) {
98 parent = new ClassMapping(obEntry.getOuterClassName()); // FIXME: WE ACTUALLY DON'T MANAGE INNER CLASS OF INNER CLASS
99 classMappingMap.put(obEntry.getOuterClassName(), parent);
100 mappings.addClassMapping(parent);
101 }
102 // Add the inner class to the parent
103 parent.addInnerClassMapping(classMapping);
104 }
105 // obf class can become deobf inner classs, manage this case.
106 else if (deobEntry != null && deobEntry.isInnerClass()) {
107 String outerClassName = deobEntry.getOuterClassName();
108 ClassMapping parent = classMappingMap.get(outerClassName);
109
110 // Only the inner is deob??? Okay
111 if (parent == null) {
112 parent = classMappingMap.get(outerClassName);
113 if (parent == null) {
114 parent = new ClassMapping(outerClassName); // FIXME: WE ACTUALLY DON'T MANAGE INNER CLASS OF INNER CLASS
115 classMappingMap.put(outerClassName, parent);
116 mappings.addClassMapping(parent);
117 }
118 }
119 parent.addInnerClassMapping(classMapping);
120 } else
121 mappings.addClassMapping(classMapping);
122 } catch (MappingConflict e) {
123 throw new MappingParseException(file, -1, e.getMessage());
124 }
125 }
126 lines.clear();
127 classMappingMap.clear();
128 return mappings;
129 }
130}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.mapping;
13
14import cuchaz.enigma.mapping.entry.ClassEntry;
15import cuchaz.enigma.mapping.entry.Entry;
16
17public interface MemberMapping<T extends Entry> {
18 T getObfEntry(ClassEntry classEntry);
19
20 String getObfName();
21}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.mapping;
13
14import com.google.common.base.Preconditions;
15import com.google.common.collect.Maps;
16import cuchaz.enigma.mapping.entry.ClassEntry;
17import cuchaz.enigma.mapping.entry.MethodEntry;
18import cuchaz.enigma.throwables.IllegalNameException;
19import cuchaz.enigma.throwables.MappingConflict;
20
21import java.util.Map;
22
23public class MethodMapping implements Comparable<MethodMapping>, MemberMapping<MethodEntry> {
24
25 private String obfName;
26 private String deobfName;
27 private MethodDescriptor obfDescriptor;
28 private Map<Integer, LocalVariableMapping> localVariables;
29 private Mappings.EntryModifier modifier;
30
31 public MethodMapping(String obfName, MethodDescriptor obfDescriptor) {
32 this(obfName, obfDescriptor, null, Mappings.EntryModifier.UNCHANGED);
33 }
34
35 public MethodMapping(String obfName, MethodDescriptor obfDescriptor, String deobfName) {
36 this(obfName, obfDescriptor, deobfName, Mappings.EntryModifier.UNCHANGED);
37 }
38
39 public MethodMapping(String obfName, MethodDescriptor obfDescriptor, String deobfName, Mappings.EntryModifier modifier) {
40 Preconditions.checkNotNull(obfName, "Method obf name cannot be null");
41 Preconditions.checkNotNull(obfDescriptor, "Method obf desc cannot be null");
42 this.obfName = obfName;
43 this.deobfName = NameValidator.validateMethodName(deobfName);
44 this.obfDescriptor = obfDescriptor;
45 this.localVariables = Maps.newTreeMap();
46 this.modifier = modifier;
47 }
48
49 public MethodMapping(MethodMapping other, Translator translator) {
50 this.obfName = other.obfName;
51 this.deobfName = other.deobfName;
52 this.modifier = other.modifier;
53 this.obfDescriptor = translator.getTranslatedMethodDesc(other.obfDescriptor);
54 this.localVariables = Maps.newTreeMap();
55 for (Map.Entry<Integer, LocalVariableMapping> entry : other.localVariables.entrySet()) {
56 this.localVariables.put(entry.getKey(), new LocalVariableMapping(entry.getValue()));
57 }
58 }
59
60 @Override
61 public String getObfName() {
62 return this.obfName;
63 }
64
65 public void setObfName(String name) {
66 try {
67 NameValidator.validateMethodName(name);
68 } catch (IllegalNameException ex) {
69 // Invalid name, damn obfuscation! Map to a deob name with another name to avoid issues
70 if (this.deobfName == null) {
71 System.err.println("WARNING: " + name + " is conflicting, auto deobfuscate to " + (name + "_auto_deob"));
72 setDeobfName(name + "_auto_deob");
73 }
74 }
75 this.obfName = name;
76 }
77
78 public String getDeobfName() {
79 if (deobfName == null) {
80 return obfName;
81 }
82 return this.deobfName;
83 }
84
85 public void setDeobfName(String val) {
86 this.deobfName = NameValidator.validateMethodName(val);
87 }
88
89 public MethodDescriptor getObfDesc() {
90 return this.obfDescriptor;
91 }
92
93 public void setObfDescriptor(MethodDescriptor val) {
94 this.obfDescriptor = val;
95 }
96
97 public Iterable<LocalVariableMapping> arguments() {
98 return this.localVariables.values();
99 }
100
101 public void addArgumentMapping(LocalVariableMapping localVariableMapping) throws MappingConflict {
102 if (this.localVariables.containsKey(localVariableMapping.getIndex())) {
103 throw new MappingConflict("argument", localVariableMapping.getName(), this.localVariables.get(localVariableMapping.getIndex()).getName());
104 }
105 this.localVariables.put(localVariableMapping.getIndex(), localVariableMapping);
106 }
107
108 public String getObfLocalVariableName(int index) {
109 LocalVariableMapping localVariableMapping = this.localVariables.get(index);
110 if (localVariableMapping != null) {
111 return localVariableMapping.getName();
112 }
113
114 return null;
115 }
116
117 public String getDeobfLocalVariableName(int index) {
118 LocalVariableMapping localVariableMapping = this.localVariables.get(index);
119 if (localVariableMapping != null) {
120 return localVariableMapping.getName();
121 }
122
123 return null;
124 }
125
126 public void setLocalVariableName(int index, String name) {
127 LocalVariableMapping localVariableMapping = this.localVariables.get(index);
128 if (localVariableMapping == null) {
129 localVariableMapping = new LocalVariableMapping(index, name);
130 boolean wasAdded = this.localVariables.put(index, localVariableMapping) == null;
131 assert (wasAdded);
132 } else {
133 localVariableMapping.setName(name);
134 }
135 }
136
137 public void removeLocalVariableName(int index) {
138 boolean wasRemoved = this.localVariables.remove(index) != null;
139 assert (wasRemoved);
140 }
141
142 @Override
143 public String toString() {
144 StringBuilder buf = new StringBuilder();
145 buf.append("\t");
146 buf.append(this.obfName);
147 buf.append(" <-> ");
148 buf.append(this.deobfName);
149 buf.append("\n");
150 buf.append("\t");
151 buf.append(this.obfDescriptor);
152 buf.append("\n");
153 buf.append("\tLocal Variables:\n");
154 for (LocalVariableMapping localVariableMapping : this.localVariables.values()) {
155 buf.append("\t\t");
156 buf.append(localVariableMapping.getIndex());
157 buf.append(" -> ");
158 buf.append(localVariableMapping.getName());
159 buf.append("\n");
160 }
161 return buf.toString();
162 }
163
164 @Override
165 public int compareTo(MethodMapping other) {
166 return (this.obfName + this.obfDescriptor).compareTo(other.obfName + other.obfDescriptor);
167 }
168
169 public boolean containsLocalVariable(String name) {
170 for (LocalVariableMapping localVariableMapping : this.localVariables.values()) {
171 if (localVariableMapping.getName().equals(name)) {
172 return true;
173 }
174 }
175 return false;
176 }
177
178 public boolean renameObfClass(final String oldObfClassName, final String newObfClassName) {
179 // rename obf classes in the signature
180 MethodDescriptor newDescriptor = obfDescriptor.remap(className -> {
181 if (className.equals(oldObfClassName)) {
182 return newObfClassName;
183 }
184 return className;
185 });
186
187 if (!newDescriptor.equals(this.obfDescriptor)) {
188 this.obfDescriptor = newDescriptor;
189 return true;
190 }
191 return false;
192 }
193
194 @Override
195 public MethodEntry getObfEntry(ClassEntry classEntry) {
196 return new MethodEntry(classEntry, this.obfName, this.obfDescriptor);
197 }
198
199 public Mappings.EntryModifier getModifier() {
200 return modifier;
201 }
202
203 public void setModifier(Mappings.EntryModifier modifier) {
204 this.modifier = modifier;
205 }
206
207 public boolean isObfuscated() {
208 return deobfName == null || deobfName.equals(obfName);
209 }
210}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.mapping;
13
14import cuchaz.enigma.mapping.entry.*;
15import org.objectweb.asm.Handle;
16import org.objectweb.asm.Type;
17
18public interface Translator {
19 ClassEntry getTranslatedClass(ClassEntry entry);
20
21 ClassDefEntry getTranslatedClassDef(ClassDefEntry entry);
22
23 FieldEntry getTranslatedField(FieldEntry entry);
24
25 FieldDefEntry getTranslatedFieldDef(FieldDefEntry entry);
26
27 MethodEntry getTranslatedMethod(MethodEntry entry);
28
29 MethodDefEntry getTranslatedMethodDef(MethodDefEntry entry);
30
31 LocalVariableEntry getTranslatedVariable(LocalVariableEntry entry);
32
33 LocalVariableDefEntry getTranslatedVariableDef(LocalVariableDefEntry entry);
34
35 boolean hasClassMapping(ClassEntry entry);
36
37 boolean hasFieldMapping(FieldEntry entry);
38
39 boolean hasMethodMapping(MethodEntry entry);
40
41 boolean hasLocalVariableMapping(LocalVariableEntry entry);
42
43 TypeDescriptor getTranslatedTypeDesc(TypeDescriptor desc);
44
45 MethodDescriptor getTranslatedMethodDesc(MethodDescriptor descriptor);
46
47 Signature getTranslatedSignature(Signature signature);
48
49 default Type getTranslatedType(Type type) {
50 String descString = type.getDescriptor();
51 switch (type.getSort()) {
52 case Type.OBJECT: {
53 ClassEntry classEntry = new ClassEntry(type.getInternalName());
54 return Type.getObjectType(getTranslatedClass(classEntry).getName());
55 }
56 case Type.ARRAY: {
57 TypeDescriptor descriptor = new TypeDescriptor(descString);
58 return Type.getType(getTranslatedTypeDesc(descriptor).toString());
59 }
60 case Type.METHOD: {
61 MethodDescriptor descriptor = new MethodDescriptor(descString);
62 return Type.getMethodType(getTranslatedMethodDesc(descriptor).toString());
63 }
64 }
65 return type;
66 }
67
68 default Handle getTranslatedHandle(Handle handle) {
69 MethodEntry entry = new MethodEntry(new ClassEntry(handle.getOwner()), handle.getName(), new MethodDescriptor(handle.getDesc()));
70 MethodEntry translatedMethod = getTranslatedMethod(entry);
71 ClassEntry ownerClass = translatedMethod.getOwnerClassEntry();
72 return new Handle(handle.getTag(), ownerClass.getName(), translatedMethod.getName(), translatedMethod.getDesc().toString(), handle.isInterface());
73 }
74
75 default Object getTranslatedValue(Object value) {
76 if (value instanceof Type) {
77 return this.getTranslatedType((Type) value);
78 } else if (value instanceof Handle) {
79 return getTranslatedHandle((Handle) value);
80 }
81 return value;
82 }
83
84 @SuppressWarnings("unchecked")
85 default <T extends Entry> T getTranslatedEntry(T entry) {
86 if (entry instanceof ClassDefEntry) {
87 return (T) getTranslatedClassDef((ClassDefEntry) entry);
88 } else if (entry instanceof ClassEntry) {
89 return (T) getTranslatedClass((ClassEntry) entry);
90 } else if (entry instanceof FieldDefEntry) {
91 return (T) getTranslatedFieldDef((FieldDefEntry) entry);
92 } else if (entry instanceof MethodDefEntry) {
93 return (T) getTranslatedMethodDef((MethodDefEntry) entry);
94 } else if (entry instanceof FieldEntry) {
95 return (T) getTranslatedField((FieldEntry) entry);
96 } else if (entry instanceof MethodEntry) {
97 return (T) getTranslatedMethod((MethodEntry) entry);
98 } else if (entry instanceof LocalVariableDefEntry) {
99 return (T) getTranslatedVariableDef((LocalVariableDefEntry) entry);
100 } else if (entry instanceof LocalVariableEntry) {
101 return (T) getTranslatedVariable((LocalVariableEntry) entry);
102 } else if (entry instanceof TypeDescriptor) {
103 return (T) getTranslatedTypeDesc((TypeDescriptor) entry);
104 } else if (entry instanceof MethodDescriptor) {
105 return (T) getTranslatedMethodDesc((MethodDescriptor) entry);
106 }
107 throw new IllegalArgumentException("Cannot translate unknown entry type");
108 }
109}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.mapping.entry;
13
14import com.google.common.base.Preconditions;
15import cuchaz.enigma.bytecode.AccessFlags;
16import cuchaz.enigma.mapping.Signature;
17
18public class ClassDefEntry extends ClassEntry implements DefEntry {
19 private final AccessFlags access;
20 private final Signature signature;
21
22 public ClassDefEntry(String className, Signature signature, AccessFlags access) {
23 super(className);
24 Preconditions.checkNotNull(signature, "Class signature cannot be null");
25 Preconditions.checkNotNull(access, "Class access cannot be null");
26 this.signature = signature;
27 this.access = access;
28 }
29
30 public Signature getSignature() {
31 return signature;
32 }
33
34 @Override
35 public AccessFlags getAccess() {
36 return access;
37 }
38}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.mapping.entry;
13
14import com.google.common.base.Preconditions;
15import com.google.common.collect.Lists;
16
17import java.util.List;
18
19public class ClassEntry implements Entry {
20
21 private final String name;
22
23 public ClassEntry(String className) {
24 Preconditions.checkNotNull(className, "Class name cannot be null");
25
26 if (className.indexOf('.') >= 0) {
27 throw new IllegalArgumentException("Class name must be in JVM format. ie, path/to/package/class$inner : " + className);
28 }
29
30 this.name = className;
31
32 if (isInnerClass() && getInnermostClassName().indexOf('/') >= 0) {
33 throw new IllegalArgumentException("Inner class must not have a package: " + className);
34 }
35 }
36
37 public ClassEntry(ClassEntry other) {
38 this.name = other.name;
39 }
40
41 @Override
42 public String getName() {
43 return this.name;
44 }
45
46 @Override
47 public String getClassName() {
48 return this.name;
49 }
50
51 @Override
52 public ClassEntry getOwnerClassEntry() {
53 return this;
54 }
55
56 @Override
57 public ClassEntry updateOwnership(ClassEntry classEntry) {
58 return classEntry;
59 }
60
61 @Override
62 public int hashCode() {
63 return this.name.hashCode();
64 }
65
66 @Override
67 public boolean equals(Object other) {
68 return other instanceof ClassEntry && equals((ClassEntry) other);
69 }
70
71 public boolean equals(ClassEntry other) {
72 return other != null && this.name.equals(other.name);
73 }
74
75 @Override
76 public String toString() {
77 return this.name;
78 }
79
80 public boolean isArray() {
81 return this.name.lastIndexOf('[') >= 0;
82 }
83
84 public boolean isInnerClass() {
85 return this.name.lastIndexOf('$') >= 0;
86 }
87
88 public List<String> getClassChainNames() {
89 return Lists.newArrayList(this.name.split("\\$"));
90 }
91
92 public List<ClassEntry> getClassChain() {
93 List<ClassEntry> entries = Lists.newArrayList();
94 StringBuilder buf = new StringBuilder();
95 for (String name : getClassChainNames()) {
96 if (buf.length() > 0) {
97 buf.append("$");
98 }
99 buf.append(name);
100 entries.add(new ClassEntry(buf.toString()));
101 }
102 return entries;
103 }
104
105 public String getOutermostClassName() {
106 if (isInnerClass()) {
107 return this.name.substring(0, this.name.indexOf('$'));
108 }
109 return this.name;
110 }
111
112 public ClassEntry getOutermostClassEntry() {
113 return new ClassEntry(getOutermostClassName());
114 }
115
116 public String getOuterClassName() {
117 if (!isInnerClass()) {
118 throw new Error("This is not an inner class!");
119 }
120 return this.name.substring(0, this.name.lastIndexOf('$'));
121 }
122
123 public ClassEntry getOuterClassEntry() {
124 return new ClassEntry(getOuterClassName());
125 }
126
127 public String getInnermostClassName() {
128 if (!isInnerClass()) {
129 throw new Error("This is not an inner class!");
130 }
131 return this.name.substring(this.name.lastIndexOf('$') + 1);
132 }
133
134 public boolean isInDefaultPackage() {
135 return this.name.indexOf('/') < 0;
136 }
137
138 public String getPackageName() {
139 return getPackageName(this.name);
140 }
141
142 public String getSimpleName() {
143 int pos = this.name.lastIndexOf('/');
144 if (pos > 0) {
145 return this.name.substring(pos + 1);
146 }
147 return this.name;
148 }
149
150 public static String getPackageName(String name) {
151 int pos = name.lastIndexOf('/');
152 if (pos > 0) {
153 return name.substring(0, pos);
154 }
155 return null;
156 }
157
158 public ClassEntry buildClassEntry(List<ClassEntry> classChain) {
159 assert (classChain.contains(this));
160 StringBuilder buf = new StringBuilder();
161 for (ClassEntry chainEntry : classChain) {
162 if (buf.length() == 0) {
163 buf.append(chainEntry.getName());
164 } else {
165 buf.append("$");
166 buf.append(chainEntry.isInnerClass() ? chainEntry.getInnermostClassName() : chainEntry.getSimpleName());
167 }
168
169 if (chainEntry == this) {
170 break;
171 }
172 }
173 return new ClassEntry(buf.toString());
174 }
175}
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 @@
1package cuchaz.enigma.mapping.entry;
2
3import cuchaz.enigma.bytecode.AccessFlags;
4
5public interface DefEntry extends Entry {
6 AccessFlags getAccess();
7}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.mapping.entry;
13
14public interface Entry {
15 String getName();
16
17 String getClassName();
18
19 ClassEntry getOwnerClassEntry();
20
21 Entry updateOwnership(ClassEntry classEntry);
22}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.mapping.entry;
13
14import cuchaz.enigma.analysis.JarIndex;
15import cuchaz.enigma.mapping.ClassMapping;
16import cuchaz.enigma.mapping.FieldMapping;
17import cuchaz.enigma.mapping.MethodDescriptor;
18import cuchaz.enigma.mapping.MethodMapping;
19
20public class EntryFactory {
21 public static ClassEntry getObfClassEntry(JarIndex jarIndex, ClassMapping classMapping) {
22 ClassEntry obfClassEntry = new ClassEntry(classMapping.getObfFullName());
23 return obfClassEntry.buildClassEntry(jarIndex.getObfClassChain(obfClassEntry));
24 }
25
26 private static ClassEntry getObfClassEntry(ClassMapping classMapping) {
27 return new ClassEntry(classMapping.getObfFullName());
28 }
29
30 public static ClassEntry getDeobfClassEntry(ClassMapping classMapping) {
31 return new ClassEntry(classMapping.getDeobfName());
32 }
33
34 public static FieldEntry getObfFieldEntry(ClassMapping classMapping, FieldMapping fieldMapping) {
35 return new FieldEntry(getObfClassEntry(classMapping), fieldMapping.getObfName(), fieldMapping.getObfDesc());
36 }
37
38 public static MethodEntry getMethodEntry(ClassEntry classEntry, String name, MethodDescriptor desc) {
39 return new MethodEntry(classEntry, name, desc);
40 }
41
42 public static MethodEntry getObfMethodEntry(ClassEntry classEntry, MethodMapping methodMapping) {
43 return getMethodEntry(classEntry, methodMapping.getObfName(), methodMapping.getObfDesc());
44 }
45
46 public static MethodEntry getObfMethodEntry(ClassMapping classMapping, MethodMapping methodMapping) {
47 return getObfMethodEntry(getObfClassEntry(classMapping), methodMapping);
48 }
49}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.mapping.entry;
13
14import com.google.common.base.Preconditions;
15import cuchaz.enigma.bytecode.AccessFlags;
16import cuchaz.enigma.mapping.Signature;
17import cuchaz.enigma.mapping.TypeDescriptor;
18
19public class FieldDefEntry extends FieldEntry implements DefEntry {
20 private final AccessFlags access;
21 private final Signature signature;
22
23 public FieldDefEntry(ClassEntry ownerEntry, String name, TypeDescriptor desc, Signature signature, AccessFlags access) {
24 super(ownerEntry, name, desc);
25 Preconditions.checkNotNull(access, "Field access cannot be null");
26 Preconditions.checkNotNull(signature, "Field signature cannot be null");
27 this.access = access;
28 this.signature = signature;
29 }
30
31 @Override
32 public AccessFlags getAccess() {
33 return access;
34 }
35
36 public Signature getSignature() {
37 return signature;
38 }
39
40 @Override
41 public FieldDefEntry updateOwnership(ClassEntry owner) {
42 return new FieldDefEntry(owner, this.name, this.desc, signature, access);
43 }
44}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.mapping.entry;
13
14import com.google.common.base.Preconditions;
15import cuchaz.enigma.mapping.TypeDescriptor;
16import cuchaz.enigma.utils.Utils;
17
18public class FieldEntry implements Entry {
19
20 protected final ClassEntry ownerEntry;
21 protected final String name;
22 protected final TypeDescriptor desc;
23
24 // NOTE: this argument order is important for the MethodReader/MethodWriter
25 public FieldEntry(ClassEntry ownerEntry, String name, TypeDescriptor desc) {
26 Preconditions.checkNotNull(ownerEntry, "Owner cannot be null");
27 Preconditions.checkNotNull(name, "Field name cannot be null");
28 Preconditions.checkNotNull(desc, "Field descriptor cannot be null");
29
30 this.ownerEntry = ownerEntry;
31 this.name = name;
32 this.desc = desc;
33 }
34
35 @Override
36 public ClassEntry getOwnerClassEntry() {
37 return this.ownerEntry;
38 }
39
40 @Override
41 public String getName() {
42 return this.name;
43 }
44
45 @Override
46 public String getClassName() {
47 return this.ownerEntry.getName();
48 }
49
50 public TypeDescriptor getDesc() {
51 return this.desc;
52 }
53
54 @Override
55 public FieldEntry updateOwnership(ClassEntry owner) {
56 return new FieldEntry(owner, this.name, this.desc);
57 }
58
59 @Override
60 public int hashCode() {
61 return Utils.combineHashesOrdered(this.ownerEntry, this.name, this.desc);
62 }
63
64 @Override
65 public boolean equals(Object other) {
66 return other instanceof FieldEntry && equals((FieldEntry) other);
67 }
68
69 public boolean equals(FieldEntry other) {
70 return this.ownerEntry.equals(other.ownerEntry) && this.name.equals(other.name) && this.desc.equals(other.desc);
71 }
72
73 @Override
74 public String toString() {
75 return this.ownerEntry.getName() + "." + this.name + ":" + this.desc;
76 }
77}
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 @@
1package cuchaz.enigma.mapping.entry;
2
3import com.google.common.base.Preconditions;
4import cuchaz.enigma.mapping.TypeDescriptor;
5import cuchaz.enigma.utils.Utils;
6
7/**
8 * TypeDescriptor...
9 * Created by Thog
10 * 19/10/2016
11 */
12public class LocalVariableDefEntry extends LocalVariableEntry {
13
14 protected final MethodDefEntry ownerEntry;
15 protected final TypeDescriptor desc;
16
17 public LocalVariableDefEntry(MethodDefEntry ownerEntry, int index, String name, TypeDescriptor desc) {
18 this(ownerEntry, index, name, true, desc);
19 }
20
21 public LocalVariableDefEntry(MethodDefEntry ownerEntry, int index, String name, boolean parameter, TypeDescriptor desc) {
22 super(ownerEntry, index, name, parameter);
23 Preconditions.checkNotNull(desc, "Variable desc cannot be null");
24
25 this.ownerEntry = ownerEntry;
26 this.desc = desc;
27 }
28
29 @Override
30 public MethodDefEntry getOwnerEntry() {
31 return this.ownerEntry;
32 }
33
34 public TypeDescriptor getDesc() {
35 return desc;
36 }
37
38 @Override
39 public LocalVariableDefEntry updateOwnership(ClassEntry classEntry) {
40 return new LocalVariableDefEntry(ownerEntry.updateOwnership(classEntry), index, name, parameter, desc);
41 }
42
43 @Override
44 public int hashCode() {
45 return Utils.combineHashesOrdered(this.ownerEntry, this.desc.hashCode(), this.name.hashCode(), Integer.hashCode(this.index));
46 }
47
48 @Override
49 public boolean equals(Object other) {
50 return other instanceof LocalVariableDefEntry && equals((LocalVariableDefEntry) other);
51 }
52
53 public boolean equals(LocalVariableDefEntry other) {
54 return this.ownerEntry.equals(other.ownerEntry) && this.desc.equals(other.desc) && this.name.equals(other.name) && this.index == other.index;
55 }
56
57 @Override
58 public String toString() {
59 return this.ownerEntry + "(" + this.index + ":" + this.name + ":" + this.desc + ")";
60 }
61}
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 @@
1package cuchaz.enigma.mapping.entry;
2
3import com.google.common.base.Preconditions;
4import cuchaz.enigma.mapping.MethodDescriptor;
5import cuchaz.enigma.utils.Utils;
6
7/**
8 * TypeDescriptor...
9 * Created by Thog
10 * 19/10/2016
11 */
12public class LocalVariableEntry implements Entry {
13
14 protected final MethodEntry ownerEntry;
15 protected final String name;
16 protected final int index;
17 protected final boolean parameter;
18
19 @Deprecated
20 public LocalVariableEntry(MethodEntry ownerEntry, int index, String name) {
21 this(ownerEntry, index, name, true);
22 }
23
24 public LocalVariableEntry(MethodEntry ownerEntry, int index, String name, boolean parameter) {
25 Preconditions.checkNotNull(ownerEntry, "Variable owner cannot be null");
26 Preconditions.checkNotNull(name, "Variable name cannot be null");
27 Preconditions.checkArgument(index >= 0, "Index must be positive");
28
29 this.ownerEntry = ownerEntry;
30 this.name = name;
31 this.index = index;
32 this.parameter = parameter;
33 }
34
35 public boolean isParameter() {
36 return this.parameter;
37 }
38
39 public MethodEntry getOwnerEntry() {
40 return this.ownerEntry;
41 }
42
43 public int getIndex() {
44 return index;
45 }
46
47 @Override
48 public String getName() {
49 return this.name;
50 }
51
52 @Override
53 public ClassEntry getOwnerClassEntry() {
54 return this.ownerEntry.getOwnerClassEntry();
55 }
56
57 @Override
58 public String getClassName() {
59 return this.ownerEntry.getClassName();
60 }
61
62 @Override
63 public LocalVariableEntry updateOwnership(ClassEntry classEntry) {
64 return new LocalVariableEntry(ownerEntry.updateOwnership(classEntry), index, name, parameter);
65 }
66
67 public String getMethodName() {
68 return this.ownerEntry.getName();
69 }
70
71 public MethodDescriptor getMethodDesc() {
72 return this.ownerEntry.getDesc();
73 }
74
75 @Override
76 public int hashCode() {
77 return Utils.combineHashesOrdered(this.ownerEntry, this.name.hashCode(), Integer.hashCode(this.index));
78 }
79
80 @Override
81 public boolean equals(Object other) {
82 return other instanceof LocalVariableEntry && equals((LocalVariableEntry) other);
83 }
84
85 public boolean equals(LocalVariableEntry other) {
86 return this.ownerEntry.equals(other.ownerEntry) && this.name.equals(other.name) && this.index == other.index;
87 }
88
89 @Override
90 public String toString() {
91 return this.ownerEntry + "(" + this.index + ":" + this.name + ")";
92 }
93}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.mapping.entry;
13
14import com.google.common.base.Preconditions;
15import cuchaz.enigma.mapping.MethodDescriptor;
16import cuchaz.enigma.utils.Utils;
17
18public class MethodEntry implements Entry {
19
20 protected final ClassEntry classEntry;
21 protected final String name;
22 protected final MethodDescriptor descriptor;
23
24 public MethodEntry(ClassEntry classEntry, String name, MethodDescriptor descriptor) {
25 Preconditions.checkNotNull(classEntry, "Class cannot be null");
26 Preconditions.checkNotNull(name, "Method name cannot be null");
27 Preconditions.checkNotNull(descriptor, "Method descriptor cannot be null");
28
29 this.classEntry = classEntry;
30 this.name = name;
31 this.descriptor = descriptor;
32 }
33
34 @Override
35 public ClassEntry getOwnerClassEntry() {
36 return this.classEntry;
37 }
38
39 @Override
40 public String getName() {
41 return this.name;
42 }
43
44 public MethodDescriptor getDesc() {
45 return this.descriptor;
46 }
47
48 public boolean isConstructor() {
49 return name.equals("<init>") || name.equals("<clinit>");
50 }
51
52 @Override
53 public String getClassName() {
54 return this.classEntry.getName();
55 }
56
57 @Override
58 public MethodEntry updateOwnership(ClassEntry classEntry) {
59 return new MethodEntry(new ClassEntry(classEntry.getName()), name, descriptor);
60 }
61
62 @Override
63 public int hashCode() {
64 return Utils.combineHashesOrdered(this.classEntry, this.name, this.descriptor);
65 }
66
67 @Override
68 public boolean equals(Object other) {
69 return other instanceof MethodEntry && equals((MethodEntry) other);
70 }
71
72 public boolean equals(MethodEntry other) {
73 return this.classEntry.equals(other.getOwnerClassEntry()) && this.name.equals(other.getName()) && this.descriptor.equals(other.getDesc());
74 }
75
76 @Override
77 public String toString() {
78 return this.classEntry.getName() + "." + this.name + this.descriptor;
79 }
80}
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 @@
1package cuchaz.enigma.translation;
2
3import cuchaz.enigma.translation.mapping.EntryMapping;
4import cuchaz.enigma.translation.mapping.EntryResolver;
5import cuchaz.enigma.translation.mapping.EntryMap;
6
7public class MappingTranslator implements Translator {
8 private final EntryMap<EntryMapping> mappings;
9 private final EntryResolver resolver;
10
11 public MappingTranslator(EntryMap<EntryMapping> mappings, EntryResolver resolver) {
12 this.mappings = mappings;
13 this.resolver = resolver;
14 }
15
16 @SuppressWarnings("unchecked")
17 @Override
18 public <T extends Translatable> T translate(T translatable) {
19 if (translatable == null) {
20 return null;
21 }
22 return (T) translatable.translate(this, resolver, mappings);
23 }
24}
diff --git a/src/main/java/cuchaz/enigma/mapping/SignatureUpdater.java b/src/main/java/cuchaz/enigma/translation/SignatureUpdater.java
index ddc5af4..3783053 100644
--- a/src/main/java/cuchaz/enigma/mapping/SignatureUpdater.java
+++ b/src/main/java/cuchaz/enigma/translation/SignatureUpdater.java
@@ -9,7 +9,7 @@
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11 11
12package cuchaz.enigma.mapping; 12package cuchaz.enigma.translation;
13 13
14import com.google.common.collect.Lists; 14import com.google.common.collect.Lists;
15 15
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 @@
1package cuchaz.enigma.translation;
2
3import cuchaz.enigma.translation.mapping.EntryMapping;
4import cuchaz.enigma.translation.mapping.EntryResolver;
5import cuchaz.enigma.translation.mapping.EntryMap;
6
7public interface Translatable {
8 Translatable translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings);
9}
diff --git a/src/main/java/cuchaz/enigma/mapping/TranslationDirection.java b/src/main/java/cuchaz/enigma/translation/TranslationDirection.java
index 4bbde54..2ecb30b 100644
--- a/src/main/java/cuchaz/enigma/mapping/TranslationDirection.java
+++ b/src/main/java/cuchaz/enigma/translation/TranslationDirection.java
@@ -9,7 +9,7 @@
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11 11
12package cuchaz.enigma.mapping; 12package cuchaz.enigma.translation;
13 13
14public enum TranslationDirection { 14public enum TranslationDirection {
15 15
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation;
13
14import com.google.common.collect.HashMultimap;
15import com.google.common.collect.Multimap;
16
17import java.util.Collection;
18import java.util.HashMap;
19import java.util.Map;
20import java.util.stream.Collectors;
21
22public interface Translator {
23 <T extends Translatable> T translate(T translatable);
24
25 default <T extends Translatable> Collection<T> translate(Collection<T> translatable) {
26 return translatable.stream()
27 .map(this::translate)
28 .collect(Collectors.toList());
29 }
30
31 default <T extends Translatable, V> Map<T, V> translateKeys(Map<T, V> translatable) {
32 Map<T, V> result = new HashMap<>(translatable.size());
33 for (Map.Entry<T, V> entry : translatable.entrySet()) {
34 result.put(translate(entry.getKey()), entry.getValue());
35 }
36 return result;
37 }
38
39 default <K extends Translatable, V extends Translatable> Map<K, V> translate(Map<K, V> translatable) {
40 Map<K, V> result = new HashMap<>(translatable.size());
41 for (Map.Entry<K, V> entry : translatable.entrySet()) {
42 result.put(translate(entry.getKey()), translate(entry.getValue()));
43 }
44 return result;
45 }
46
47 default <K extends Translatable, V extends Translatable> Multimap<K, V> translate(Multimap<K, V> translatable) {
48 Multimap<K, V> result = HashMultimap.create(translatable.size(), 1);
49 for (Map.Entry<K, Collection<V>> entry : translatable.asMap().entrySet()) {
50 result.putAll(translate(entry.getKey()), translate(entry.getValue()));
51 }
52 return result;
53 }
54}
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 @@
1package cuchaz.enigma.translation;
2
3public enum VoidTranslator implements Translator {
4 INSTANCE;
5
6 @Override
7 public <T extends Translatable> T translate(T translatable) {
8 return translatable;
9 }
10}
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 @@
1package cuchaz.enigma.translation.mapping;
2
3import cuchaz.enigma.translation.representation.AccessFlags;
4
5public enum AccessModifier {
6 UNCHANGED, PUBLIC, PROTECTED, PRIVATE;
7
8 public String getFormattedName() {
9 return "ACC:" + super.toString();
10 }
11
12 public AccessFlags transform(AccessFlags access) {
13 switch (this) {
14 case PUBLIC:
15 return access.setPublic();
16 case PROTECTED:
17 return access.setProtected();
18 case PRIVATE:
19 return access.setPrivate();
20 case UNCHANGED:
21 default:
22 return access;
23 }
24 }
25}
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 @@
1package cuchaz.enigma.translation.mapping;
2
3import cuchaz.enigma.translation.representation.entry.Entry;
4
5import javax.annotation.Nullable;
6import java.util.Collection;
7
8public interface EntryMap<T> {
9 void insert(Entry<?> entry, T value);
10
11 @Nullable
12 T remove(Entry<?> entry);
13
14 @Nullable
15 T get(Entry<?> entry);
16
17 default boolean contains(Entry<?> entry) {
18 return get(entry) != null;
19 }
20
21 Collection<Entry<?>> getAllEntries();
22
23 boolean isEmpty();
24}
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 @@
1package cuchaz.enigma.translation.mapping;
2
3import javax.annotation.Nonnull;
4
5public class EntryMapping {
6 private final String targetName;
7 private final AccessModifier accessModifier;
8
9 public EntryMapping(@Nonnull String targetName) {
10 this(targetName, AccessModifier.UNCHANGED);
11 }
12
13 public EntryMapping(@Nonnull String targetName, AccessModifier accessModifier) {
14 this.targetName = targetName;
15 this.accessModifier = accessModifier;
16 }
17
18 @Nonnull
19 public String getTargetName() {
20 return targetName;
21 }
22
23 @Nonnull
24 public AccessModifier getAccessModifier() {
25 if (accessModifier == null) {
26 return AccessModifier.UNCHANGED;
27 }
28 return accessModifier;
29 }
30}
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 @@
1package cuchaz.enigma.translation.mapping;
2
3import cuchaz.enigma.analysis.index.JarIndex;
4import cuchaz.enigma.translation.MappingTranslator;
5import cuchaz.enigma.translation.Translatable;
6import cuchaz.enigma.translation.Translator;
7import cuchaz.enigma.translation.mapping.tree.DeltaTrackingTree;
8import cuchaz.enigma.translation.mapping.tree.EntryTree;
9import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
10import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
11import cuchaz.enigma.translation.representation.entry.Entry;
12
13import javax.annotation.Nullable;
14import java.util.Collection;
15
16public class EntryRemapper {
17 private final EntryTree<EntryMapping> obfToDeobf;
18 private final DeltaTrackingTree<EntryMapping> deobfToObf;
19
20 private final JarIndex obfIndex;
21
22 private final EntryResolver obfResolver;
23 private EntryResolver deobfResolver;
24
25 private final Translator deobfuscator;
26 private Translator obfuscator;
27
28 private final MappingValidator validator;
29
30 private EntryRemapper(JarIndex jarIndex, EntryTree<EntryMapping> obfToDeobf, EntryTree<EntryMapping> deobfToObf) {
31 this.obfToDeobf = obfToDeobf;
32 this.deobfToObf = new DeltaTrackingTree<>(deobfToObf);
33
34 this.obfIndex = jarIndex;
35 this.obfResolver = jarIndex.getEntryResolver();
36
37 this.deobfuscator = new MappingTranslator(obfToDeobf, obfResolver);
38 rebuildDeobfIndex();
39
40 this.validator = new MappingValidator(this.deobfToObf, deobfuscator, obfResolver);
41 }
42
43 public EntryRemapper(JarIndex jarIndex) {
44 this(jarIndex, new HashEntryTree<>(), new HashEntryTree<>());
45 }
46
47 public EntryRemapper(JarIndex jarIndex, EntryTree<EntryMapping> deobfuscationTrees) {
48 this(jarIndex, deobfuscationTrees, inverse(deobfuscationTrees));
49 }
50
51 private static EntryTree<EntryMapping> inverse(EntryTree<EntryMapping> tree) {
52 Translator translator = new MappingTranslator(tree, VoidEntryResolver.INSTANCE);
53 EntryTree<EntryMapping> inverse = new HashEntryTree<>();
54
55 // Naive approach, could operate on the nodes of the tree. However, this runs infrequently.
56 Collection<Entry<?>> entries = tree.getAllEntries();
57 for (Entry<?> sourceEntry : entries) {
58 Entry<?> targetEntry = translator.translate(sourceEntry);
59 inverse.insert(targetEntry, new EntryMapping(sourceEntry.getName()));
60 }
61
62 return inverse;
63 }
64
65 private void rebuildDeobfIndex() {
66 JarIndex deobfIndex = obfIndex.remapped(deobfuscator);
67
68 this.deobfResolver = deobfIndex.getEntryResolver();
69 this.obfuscator = new MappingTranslator(deobfToObf, deobfResolver);
70 }
71
72 public <E extends Entry<?>> void mapFromObf(E obfuscatedEntry, @Nullable EntryMapping deobfMapping) {
73 Collection<E> resolvedEntries = obfResolver.resolveEntry(obfuscatedEntry, ResolutionStrategy.RESOLVE_ROOT);
74 for (E resolvedEntry : resolvedEntries) {
75 if (deobfMapping != null) {
76 validator.validateRename(resolvedEntry, deobfMapping.getTargetName());
77 }
78
79 setObfToDeobf(resolvedEntry, deobfMapping);
80 }
81
82 // Temporary hack, not very performant
83 rebuildDeobfIndex();
84 }
85
86 public <E extends Entry<?>> void mapFromDeobf(E deobfuscatedEntry, @Nullable EntryMapping deobfMapping) {
87 E obfuscatedEntry = obfuscate(deobfuscatedEntry);
88 mapFromObf(obfuscatedEntry, deobfMapping);
89 }
90
91 public void removeByObf(Entry<?> obfuscatedEntry) {
92 mapFromObf(obfuscatedEntry, null);
93 }
94
95 public void removeByDeobf(Entry<?> deobfuscatedEntry) {
96 mapFromObf(obfuscate(deobfuscatedEntry), null);
97 }
98
99 private <E extends Entry<?>> void setObfToDeobf(E obfuscatedEntry, @Nullable EntryMapping deobfMapping) {
100 E prevDeobf = deobfuscate(obfuscatedEntry);
101 obfToDeobf.insert(obfuscatedEntry, deobfMapping);
102
103 E newDeobf = deobfuscate(obfuscatedEntry);
104
105 // Reconstruct the children of this node in the deobf -> obf tree with our new mapping
106 // We only need to do this for deobf -> obf because the obf tree is always consistent on the left hand side
107 // We lookup by obf, and the obf never changes. This is not the case for deobf so we need to update the tree.
108
109 EntryTreeNode<EntryMapping> node = deobfToObf.findNode(prevDeobf);
110 if (node != null) {
111 for (EntryTreeNode<EntryMapping> child : node.getNodesRecursively()) {
112 Entry<?> entry = child.getEntry();
113 EntryMapping mapping = new EntryMapping(obfuscate(entry).getName());
114
115 deobfToObf.insert(entry.replaceAncestor(prevDeobf, newDeobf), mapping);
116 deobfToObf.remove(entry);
117 }
118 } else {
119 deobfToObf.insert(newDeobf, new EntryMapping(obfuscatedEntry.getName()));
120 }
121 }
122
123 @Nullable
124 public EntryMapping getDeobfMapping(Entry<?> entry) {
125 return obfToDeobf.get(entry);
126 }
127
128 @Nullable
129 public EntryMapping getObfMapping(Entry<?> entry) {
130 return deobfToObf.get(entry);
131 }
132
133 public boolean hasDeobfMapping(Entry<?> obfEntry) {
134 return obfToDeobf.contains(obfEntry);
135 }
136
137 public boolean hasObfMapping(Entry<?> deobfEntry) {
138 return deobfToObf.contains(deobfEntry);
139 }
140
141 public <T extends Translatable> T deobfuscate(T translatable) {
142 return deobfuscator.translate(translatable);
143 }
144
145 public <T extends Translatable> T obfuscate(T translatable) {
146 return obfuscator.translate(translatable);
147 }
148
149 public Translator getDeobfuscator() {
150 return deobfuscator;
151 }
152
153 public Translator getObfuscator() {
154 return obfuscator;
155 }
156
157 public Collection<Entry<?>> getObfEntries() {
158 return obfToDeobf.getAllEntries();
159 }
160
161 public Collection<Entry<?>> getObfRootEntries() {
162 return obfToDeobf.getRootEntries();
163 }
164
165 public Collection<Entry<?>> getDeobfEntries() {
166 return deobfToObf.getAllEntries();
167 }
168
169 public Collection<Entry<?>> getObfChildren(Entry<?> obfuscatedEntry) {
170 return obfToDeobf.getChildren(obfuscatedEntry);
171 }
172
173 public Collection<Entry<?>> getDeobfChildren(Entry<?> deobfuscatedEntry) {
174 return deobfToObf.getChildren(deobfuscatedEntry);
175 }
176
177 public EntryTree<EntryMapping> getObfToDeobf() {
178 return obfToDeobf;
179 }
180
181 public DeltaTrackingTree<EntryMapping> getDeobfToObf() {
182 return deobfToObf;
183 }
184
185 public MappingDelta takeMappingDelta() {
186 MappingDelta delta = deobfToObf.takeDelta();
187 return delta.translate(obfuscator, VoidEntryResolver.INSTANCE, deobfToObf);
188 }
189
190 public boolean isDirty() {
191 return deobfToObf.isDirty();
192 }
193
194 public EntryResolver getObfResolver() {
195 return obfResolver;
196 }
197
198 public EntryResolver getDeobfResolver() {
199 return deobfResolver;
200 }
201}
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 @@
1package cuchaz.enigma.translation.mapping;
2
3import com.google.common.collect.Streams;
4import cuchaz.enigma.analysis.EntryReference;
5import cuchaz.enigma.translation.representation.entry.Entry;
6import cuchaz.enigma.translation.representation.entry.MethodEntry;
7
8import java.util.Collection;
9import java.util.Set;
10import java.util.stream.Collectors;
11
12public interface EntryResolver {
13 <E extends Entry<?>> Collection<E> resolveEntry(E entry, ResolutionStrategy strategy);
14
15 default <E extends Entry<?>> E resolveFirstEntry(E entry, ResolutionStrategy strategy) {
16 return resolveEntry(entry, strategy).stream().findFirst().orElse(entry);
17 }
18
19 default <E extends Entry<?>, C extends Entry<?>> Collection<EntryReference<E, C>> resolveReference(EntryReference<E, C> reference, ResolutionStrategy strategy) {
20 Collection<E> entry = resolveEntry(reference.entry, strategy);
21 if (reference.context != null) {
22 Collection<C> context = resolveEntry(reference.context, strategy);
23 return Streams.zip(entry.stream(), context.stream(), (e, c) -> new EntryReference<>(e, c, reference))
24 .collect(Collectors.toList());
25 } else {
26 return entry.stream()
27 .map(e -> new EntryReference<>(e, null, reference))
28 .collect(Collectors.toList());
29 }
30 }
31
32 default <E extends Entry<?>, C extends Entry<?>> EntryReference<E, C> resolveFirstReference(EntryReference<E, C> reference, ResolutionStrategy strategy) {
33 E entry = resolveFirstEntry(reference.entry, strategy);
34 C context = resolveFirstEntry(reference.context, strategy);
35 return new EntryReference<>(entry, context, reference);
36 }
37
38 Set<Entry<?>> resolveEquivalentEntries(Entry<?> entry);
39
40 Set<MethodEntry> resolveEquivalentMethods(MethodEntry methodEntry);
41}
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 @@
1package cuchaz.enigma.translation.mapping;
2
3import com.google.common.collect.Sets;
4import cuchaz.enigma.analysis.IndexTreeBuilder;
5import cuchaz.enigma.analysis.MethodImplementationsTreeNode;
6import cuchaz.enigma.analysis.MethodInheritanceTreeNode;
7import cuchaz.enigma.analysis.index.BridgeMethodIndex;
8import cuchaz.enigma.analysis.index.EntryIndex;
9import cuchaz.enigma.analysis.index.InheritanceIndex;
10import cuchaz.enigma.analysis.index.JarIndex;
11import cuchaz.enigma.translation.VoidTranslator;
12import cuchaz.enigma.translation.representation.AccessFlags;
13import cuchaz.enigma.translation.representation.entry.ClassEntry;
14import cuchaz.enigma.translation.representation.entry.Entry;
15import cuchaz.enigma.translation.representation.entry.MethodEntry;
16
17import javax.annotation.Nullable;
18import java.util.*;
19import java.util.stream.Collectors;
20
21public class IndexEntryResolver implements EntryResolver {
22 private final EntryIndex entryIndex;
23 private final InheritanceIndex inheritanceIndex;
24 private final BridgeMethodIndex bridgeMethodIndex;
25
26 private final IndexTreeBuilder treeBuilder;
27
28 public IndexEntryResolver(JarIndex index) {
29 this.entryIndex = index.getEntryIndex();
30 this.inheritanceIndex = index.getInheritanceIndex();
31 this.bridgeMethodIndex = index.getBridgeMethodIndex();
32
33 this.treeBuilder = new IndexTreeBuilder(index);
34 }
35
36 @Override
37 @SuppressWarnings("unchecked")
38 public <E extends Entry<?>> Collection<E> resolveEntry(E entry, ResolutionStrategy strategy) {
39 if (entry == null) {
40 return Collections.emptySet();
41 }
42
43 Entry<ClassEntry> classChild = getClassChild(entry);
44 if (classChild != null && !(classChild instanceof ClassEntry)) {
45 AccessFlags access = entryIndex.getEntryAccess(classChild);
46
47 // If we're looking for the closest and this entry exists, we're done looking
48 if (strategy == ResolutionStrategy.RESOLVE_CLOSEST && access != null) {
49 return Collections.singleton(entry);
50 }
51
52 if (access == null || !access.isPrivate()) {
53 Collection<Entry<ClassEntry>> resolvedChildren = resolveChildEntry(classChild, strategy);
54 if (!resolvedChildren.isEmpty()) {
55 return resolvedChildren.stream()
56 .map(resolvedChild -> (E) entry.replaceAncestor(classChild, resolvedChild))
57 .collect(Collectors.toList());
58 }
59 }
60 }
61
62 return Collections.singleton(entry);
63 }
64
65 @Nullable
66 private Entry<ClassEntry> getClassChild(Entry<?> entry) {
67 if (entry instanceof ClassEntry) {
68 return null;
69 }
70
71 // get the entry in the hierarchy that is the child of a class
72 List<Entry<?>> ancestry = entry.getAncestry();
73 for (int i = ancestry.size() - 1; i > 0; i--) {
74 Entry<?> child = ancestry.get(i);
75 Entry<ClassEntry> cast = child.castParent(ClassEntry.class);
76 if (cast != null && !(cast instanceof ClassEntry)) {
77 // we found the entry which is a child of a class, we are now able to resolve the owner of this entry
78 return cast;
79 }
80 }
81
82 return null;
83 }
84
85 private Set<Entry<ClassEntry>> resolveChildEntry(Entry<ClassEntry> entry, ResolutionStrategy strategy) {
86 ClassEntry ownerClass = entry.getParent();
87
88 if (entry instanceof MethodEntry) {
89 MethodEntry bridgeMethod = bridgeMethodIndex.getBridgeFromAccessed((MethodEntry) entry);
90 if (bridgeMethod != null && ownerClass.equals(bridgeMethod.getParent())) {
91 Set<Entry<ClassEntry>> resolvedBridge = resolveChildEntry(bridgeMethod, strategy);
92 if (!resolvedBridge.isEmpty()) {
93 return resolvedBridge;
94 }
95 }
96 }
97
98 Set<Entry<ClassEntry>> resolvedEntries = new HashSet<>();
99
100 for (ClassEntry parentClass : inheritanceIndex.getParents(ownerClass)) {
101 Entry<ClassEntry> parentEntry = entry.withParent(parentClass);
102
103 if (strategy == ResolutionStrategy.RESOLVE_ROOT) {
104 resolvedEntries.addAll(resolveRoot(parentEntry, strategy));
105 } else {
106 resolvedEntries.addAll(resolveClosest(parentEntry, strategy));
107 }
108 }
109
110 return resolvedEntries;
111 }
112
113 private Collection<Entry<ClassEntry>> resolveRoot(Entry<ClassEntry> entry, ResolutionStrategy strategy) {
114 // When resolving root, we want to first look for the lowest entry before returning ourselves
115 Set<Entry<ClassEntry>> parentResolution = resolveChildEntry(entry, strategy);
116
117 if (parentResolution.isEmpty()) {
118 AccessFlags parentAccess = entryIndex.getEntryAccess(entry);
119 if (parentAccess != null && !parentAccess.isPrivate()) {
120 return Collections.singleton(entry);
121 }
122 }
123
124 return parentResolution;
125 }
126
127 private Collection<Entry<ClassEntry>> resolveClosest(Entry<ClassEntry> entry, ResolutionStrategy strategy) {
128 // When resolving closest, we want to first check if we exist before looking further down
129 AccessFlags parentAccess = entryIndex.getEntryAccess(entry);
130 if (parentAccess != null && !parentAccess.isPrivate()) {
131 return Collections.singleton(entry);
132 } else {
133 return resolveChildEntry(entry, strategy);
134 }
135 }
136
137 @Override
138 public Set<Entry<?>> resolveEquivalentEntries(Entry<?> entry) {
139 MethodEntry relevantMethod = entry.findAncestor(MethodEntry.class);
140 if (relevantMethod == null || !entryIndex.hasMethod(relevantMethod)) {
141 return Collections.singleton(entry);
142 }
143
144 Set<MethodEntry> equivalentMethods = resolveEquivalentMethods(relevantMethod);
145 Set<Entry<?>> equivalentEntries = new HashSet<>(equivalentMethods.size());
146
147 for (MethodEntry equivalentMethod : equivalentMethods) {
148 Entry<?> equivalentEntry = entry.replaceAncestor(relevantMethod, equivalentMethod);
149 equivalentEntries.add(equivalentEntry);
150 }
151
152 return equivalentEntries;
153 }
154
155 @Override
156 public Set<MethodEntry> resolveEquivalentMethods(MethodEntry methodEntry) {
157 AccessFlags access = entryIndex.getMethodAccess(methodEntry);
158 if (access == null) {
159 throw new IllegalArgumentException("Could not find method " + methodEntry);
160 }
161
162 if (!canInherit(methodEntry, access)) {
163 return Collections.singleton(methodEntry);
164 }
165
166 Set<MethodEntry> methodEntries = Sets.newHashSet();
167 resolveEquivalentMethods(methodEntries, treeBuilder.buildMethodInheritance(VoidTranslator.INSTANCE, methodEntry));
168 return methodEntries;
169 }
170
171 private void resolveEquivalentMethods(Set<MethodEntry> methodEntries, MethodInheritanceTreeNode node) {
172 MethodEntry methodEntry = node.getMethodEntry();
173 if (methodEntries.contains(methodEntry)) {
174 return;
175 }
176
177 AccessFlags flags = entryIndex.getMethodAccess(methodEntry);
178 if (flags != null && canInherit(methodEntry, flags)) {
179 // collect the entry
180 methodEntries.add(methodEntry);
181 }
182
183 // look at bridge methods!
184 MethodEntry bridgedMethod = bridgeMethodIndex.getBridgeFromAccessed(methodEntry);
185 while (bridgedMethod != null) {
186 methodEntries.addAll(resolveEquivalentMethods(bridgedMethod));
187 bridgedMethod = bridgeMethodIndex.getBridgeFromAccessed(bridgedMethod);
188 }
189
190 // look at interface methods too
191 for (MethodImplementationsTreeNode implementationsNode : treeBuilder.buildMethodImplementations(VoidTranslator.INSTANCE, methodEntry)) {
192 resolveEquivalentMethods(methodEntries, implementationsNode);
193 }
194
195 // recurse
196 for (int i = 0; i < node.getChildCount(); i++) {
197 resolveEquivalentMethods(methodEntries, (MethodInheritanceTreeNode) node.getChildAt(i));
198 }
199 }
200
201 private void resolveEquivalentMethods(Set<MethodEntry> methodEntries, MethodImplementationsTreeNode node) {
202 MethodEntry methodEntry = node.getMethodEntry();
203 AccessFlags flags = entryIndex.getMethodAccess(methodEntry);
204 if (flags != null && !flags.isPrivate() && !flags.isStatic()) {
205 // collect the entry
206 methodEntries.add(methodEntry);
207 }
208
209 // look at bridge methods!
210 MethodEntry bridgedMethod = bridgeMethodIndex.getBridgeFromAccessed(methodEntry);
211 while (bridgedMethod != null) {
212 methodEntries.addAll(resolveEquivalentMethods(bridgedMethod));
213 bridgedMethod = bridgeMethodIndex.getBridgeFromAccessed(bridgedMethod);
214 }
215
216 // recurse
217 for (int i = 0; i < node.getChildCount(); i++) {
218 resolveEquivalentMethods(methodEntries, (MethodImplementationsTreeNode) node.getChildAt(i));
219 }
220 }
221
222 private boolean canInherit(MethodEntry entry, AccessFlags access) {
223 return !entry.isConstructor() && !access.isPrivate() && !access.isStatic() && !access.isFinal();
224 }
225}
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 @@
1package cuchaz.enigma.translation.mapping;
2
3import cuchaz.enigma.translation.Translatable;
4import cuchaz.enigma.translation.Translator;
5import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
6import cuchaz.enigma.translation.mapping.tree.EntryTree;
7import cuchaz.enigma.translation.representation.entry.Entry;
8
9public class MappingDelta implements Translatable {
10 public static final Object PLACEHOLDER = new Object();
11
12 private final EntryTree<Object> additions;
13 private final EntryTree<Object> deletions;
14
15 public MappingDelta(EntryTree<Object> additions, EntryTree<Object> deletions) {
16 this.additions = additions;
17 this.deletions = deletions;
18 }
19
20 public MappingDelta() {
21 this(new HashEntryTree<>(), new HashEntryTree<>());
22 }
23
24 public static MappingDelta added(EntryTree<EntryMapping> mappings) {
25 EntryTree<Object> additions = new HashEntryTree<>();
26 for (Entry<?> entry : mappings.getAllEntries()) {
27 additions.insert(entry, PLACEHOLDER);
28 }
29
30 return new MappingDelta(additions, new HashEntryTree<>());
31 }
32
33 public EntryTree<?> getAdditions() {
34 return additions;
35 }
36
37 public EntryTree<?> getDeletions() {
38 return deletions;
39 }
40
41 @Override
42 public MappingDelta translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) {
43 return new MappingDelta(
44 translate(translator, additions),
45 translate(translator, deletions)
46 );
47 }
48
49 private EntryTree<Object> translate(Translator translator, EntryTree<Object> tree) {
50 EntryTree<Object> translatedTree = new HashEntryTree<>();
51 for (Entry<?> entry : tree.getAllEntries()) {
52 translatedTree.insert(translator.translate(entry), PLACEHOLDER);
53 }
54 return translatedTree;
55 }
56}
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 @@
1package cuchaz.enigma.translation.mapping;
2
3import cuchaz.enigma.translation.representation.entry.Entry;
4
5import javax.annotation.Nullable;
6
7public class MappingPair<E extends Entry<?>, M> {
8 private final E entry;
9 private final M mapping;
10
11 public MappingPair(E entry, @Nullable M mapping) {
12 this.entry = entry;
13 this.mapping = mapping;
14 }
15
16 public MappingPair(E entry) {
17 this(entry, null);
18 }
19
20 public E getEntry() {
21 return entry;
22 }
23
24 @Nullable
25 public M getMapping() {
26 return mapping;
27 }
28}
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 @@
1package cuchaz.enigma.translation.mapping;
2
3import cuchaz.enigma.throwables.IllegalNameException;
4import cuchaz.enigma.translation.Translator;
5import cuchaz.enigma.translation.mapping.tree.EntryTree;
6import cuchaz.enigma.translation.representation.entry.Entry;
7
8import java.util.Collection;
9
10public class MappingValidator {
11 private final EntryTree<EntryMapping> deobfToObf;
12 private final Translator deobfuscator;
13 private final EntryResolver entryResolver;
14
15 public MappingValidator(EntryTree<EntryMapping> deobfToObf, Translator deobfuscator, EntryResolver entryResolver) {
16 this.deobfToObf = deobfToObf;
17 this.deobfuscator = deobfuscator;
18 this.entryResolver = entryResolver;
19 }
20
21 public void validateRename(Entry<?> entry, String name) throws IllegalNameException {
22 Collection<Entry<?>> equivalentEntries = entryResolver.resolveEquivalentEntries(entry);
23 for (Entry<?> equivalentEntry : equivalentEntries) {
24 equivalentEntry.validateName(name);
25 validateUnique(equivalentEntry, name);
26 }
27 }
28
29 private void validateUnique(Entry<?> entry, String name) {
30 Entry<?> translatedEntry = deobfuscator.translate(entry);
31 Collection<Entry<?>> siblings = deobfToObf.getSiblings(translatedEntry);
32 if (!isUnique(translatedEntry, siblings, name)) {
33 throw new IllegalNameException(name, "Name is not unique in " + translatedEntry.getParent() + "!");
34 }
35 }
36
37 private boolean isUnique(Entry<?> entry, Collection<Entry<?>> siblings, String name) {
38 for (Entry<?> child : siblings) {
39 if (entry.canConflictWith(child) && child.getName().equals(name)) {
40 return false;
41 }
42 }
43 return true;
44 }
45}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.mapping;
13
14import cuchaz.enigma.analysis.index.JarIndex;
15import cuchaz.enigma.translation.mapping.tree.EntryTree;
16import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
17import cuchaz.enigma.translation.representation.entry.ClassEntry;
18import cuchaz.enigma.translation.representation.entry.Entry;
19import cuchaz.enigma.translation.representation.entry.FieldEntry;
20import cuchaz.enigma.translation.representation.entry.MethodEntry;
21
22import java.util.Collection;
23import java.util.HashMap;
24import java.util.Map;
25
26public class MappingsChecker {
27 private final JarIndex index;
28 private final EntryTree<EntryMapping> mappings;
29
30 public MappingsChecker(JarIndex index, EntryTree<EntryMapping> mappings) {
31 this.index = index;
32 this.mappings = mappings;
33 }
34
35 public Dropped dropBrokenMappings() {
36 Dropped dropped = new Dropped();
37
38 Collection<Entry<?>> obfEntries = mappings.getAllEntries();
39 for (Entry<?> entry : obfEntries) {
40 if (entry instanceof ClassEntry || entry instanceof MethodEntry || entry instanceof FieldEntry) {
41 tryDropEntry(dropped, entry);
42 }
43 }
44
45 dropped.apply(mappings);
46
47 return dropped;
48 }
49
50 private void tryDropEntry(Dropped dropped, Entry<?> entry) {
51 if (shouldDropEntry(entry)) {
52 EntryMapping mapping = mappings.get(entry);
53 if (mapping != null) {
54 dropped.drop(entry, mapping);
55 }
56 }
57 }
58
59 private boolean shouldDropEntry(Entry<?> entry) {
60 if (!index.getEntryIndex().hasEntry(entry)) {
61 return true;
62 }
63 Collection<Entry<?>> resolvedEntries = index.getEntryResolver().resolveEntry(entry, ResolutionStrategy.RESOLVE_ROOT);
64 return !resolvedEntries.contains(entry);
65 }
66
67 public static class Dropped {
68 private final Map<Entry<?>, String> droppedMappings = new HashMap<>();
69
70 public void drop(Entry<?> entry, EntryMapping mapping) {
71 droppedMappings.put(entry, mapping.getTargetName());
72 }
73
74 void apply(EntryTree<EntryMapping> mappings) {
75 for (Entry<?> entry : droppedMappings.keySet()) {
76 EntryTreeNode<EntryMapping> node = mappings.findNode(entry);
77 if (node == null) {
78 continue;
79 }
80
81 for (Entry<?> childEntry : node.getChildrenRecursively()) {
82 mappings.remove(childEntry);
83 }
84 }
85 }
86
87 public Map<Entry<?>, String> getDroppedMappings() {
88 return droppedMappings;
89 }
90 }
91}
diff --git a/src/main/java/cuchaz/enigma/mapping/NameValidator.java b/src/main/java/cuchaz/enigma/translation/mapping/NameValidator.java
index fca8cfc..19473ea 100644
--- a/src/main/java/cuchaz/enigma/mapping/NameValidator.java
+++ b/src/main/java/cuchaz/enigma/translation/mapping/NameValidator.java
@@ -9,20 +9,19 @@
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11 11
12package cuchaz.enigma.mapping; 12package cuchaz.enigma.translation.mapping;
13 13
14import cuchaz.enigma.mapping.entry.ClassEntry;
15import cuchaz.enigma.throwables.IllegalNameException; 14import cuchaz.enigma.throwables.IllegalNameException;
15import cuchaz.enigma.translation.representation.entry.ClassEntry;
16 16
17import java.util.Arrays; 17import java.util.Arrays;
18import java.util.List; 18import java.util.List;
19import java.util.regex.Pattern; 19import java.util.regex.Pattern;
20 20
21public class NameValidator { 21public class NameValidator {
22 22 private static final Pattern IDENTIFIER_PATTERN;
23 private static final Pattern IdentifierPattern; 23 private static final Pattern CLASS_PATTERN;
24 private static final Pattern ClassPattern; 24 private static final List<String> ILLEGAL_IDENTIFIERS = Arrays.asList(
25 private static final List<String> ReservedWords = Arrays.asList(
26 "abstract", "continue", "for", "new", "switch", "assert", "default", "goto", "package", "synchronized", 25 "abstract", "continue", "for", "new", "switch", "assert", "default", "goto", "package", "synchronized",
27 "boolean", "do", "if", "private", "this", "break", "double", "implements", "protected", "throw", "byte", 26 "boolean", "do", "if", "private", "this", "break", "double", "implements", "protected", "throw", "byte",
28 "else", "import", "public", "throws", "case", "enum", "instanceof", "return", "transient", "catch", 27 "else", "import", "public", "throws", "case", "enum", "instanceof", "return", "transient", "catch",
@@ -32,42 +31,26 @@ public class NameValidator {
32 31
33 static { 32 static {
34 String identifierRegex = "[A-Za-z_<][A-Za-z0-9_>]*"; 33 String identifierRegex = "[A-Za-z_<][A-Za-z0-9_>]*";
35 IdentifierPattern = Pattern.compile(identifierRegex); 34 IDENTIFIER_PATTERN = Pattern.compile(identifierRegex);
36 ClassPattern = Pattern.compile(String.format("^(%s(\\.|/))*(%s)$", identifierRegex, identifierRegex)); 35 CLASS_PATTERN = Pattern.compile(String.format("^(%s(\\.|/))*(%s)$", identifierRegex, identifierRegex));
37 } 36 }
38 37
39 public static String validateClassName(String name, boolean packageRequired) { 38 public static void validateClassName(String name, boolean packageRequired) {
40 if (name == null) { 39 if (!CLASS_PATTERN.matcher(name).matches() || ILLEGAL_IDENTIFIERS.contains(name)) {
41 return null;
42 }
43 if (!ClassPattern.matcher(name).matches() || ReservedWords.contains(name)) {
44 throw new IllegalNameException(name, "This doesn't look like a legal class name"); 40 throw new IllegalNameException(name, "This doesn't look like a legal class name");
45 } 41 }
46 if (packageRequired && ClassEntry.getPackageName(name) == null) { 42 if (packageRequired && ClassEntry.getPackageName(name) == null) {
47 throw new IllegalNameException(name, "Class must be in a package"); 43 throw new IllegalNameException(name, "Class must be in a package");
48 } 44 }
49 return name;
50 } 45 }
51 46
52 public static String validateFieldName(String name) { 47 public static void validateIdentifier(String name) {
53 if (name == null) { 48 if (!IDENTIFIER_PATTERN.matcher(name).matches() || ILLEGAL_IDENTIFIERS.contains(name)) {
54 return null;
55 }
56 if (!IdentifierPattern.matcher(name).matches() || ReservedWords.contains(name)) {
57 throw new IllegalNameException(name, "This doesn't look like a legal identifier"); 49 throw new IllegalNameException(name, "This doesn't look like a legal identifier");
58 } 50 }
59 return name;
60 }
61
62 public static String validateMethodName(String name) {
63 return validateFieldName(name);
64 }
65
66 public static String validateArgumentName(String name) {
67 return validateFieldName(name);
68 } 51 }
69 52
70 public static boolean isReserved(String name) { 53 public static boolean isReserved(String name) {
71 return ReservedWords.contains(name); 54 return ILLEGAL_IDENTIFIERS.contains(name);
72 } 55 }
73} 56}
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 @@
1package cuchaz.enigma.translation.mapping;
2
3public enum ResolutionStrategy {
4 RESOLVE_ROOT,
5 RESOLVE_CLOSEST
6}
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 @@
1package cuchaz.enigma.translation.mapping;
2
3import cuchaz.enigma.translation.representation.entry.Entry;
4import cuchaz.enigma.translation.representation.entry.MethodEntry;
5
6import java.util.Collection;
7import java.util.Collections;
8import java.util.Set;
9
10public enum VoidEntryResolver implements EntryResolver {
11 INSTANCE;
12
13 @Override
14 public <E extends Entry<?>> Collection<E> resolveEntry(E entry, ResolutionStrategy strategy) {
15 return Collections.singleton(entry);
16 }
17
18 @Override
19 public Set<Entry<?>> resolveEquivalentEntries(Entry<?> entry) {
20 return Collections.singleton(entry);
21 }
22
23 @Override
24 public Set<MethodEntry> resolveEquivalentMethods(MethodEntry methodEntry) {
25 return Collections.singleton(methodEntry);
26 }
27}
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 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import com.google.common.base.Charsets;
4import cuchaz.enigma.throwables.MappingParseException;
5import cuchaz.enigma.translation.mapping.AccessModifier;
6import cuchaz.enigma.translation.mapping.EntryMapping;
7import cuchaz.enigma.translation.mapping.MappingPair;
8import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
9import cuchaz.enigma.translation.mapping.tree.EntryTree;
10import cuchaz.enigma.translation.representation.MethodDescriptor;
11import cuchaz.enigma.translation.representation.TypeDescriptor;
12import cuchaz.enigma.translation.representation.entry.*;
13
14import javax.annotation.Nullable;
15import java.io.IOException;
16import java.nio.file.Files;
17import java.nio.file.Path;
18import java.util.ArrayDeque;
19import java.util.Deque;
20import java.util.List;
21import java.util.Locale;
22import java.util.stream.Collectors;
23
24public enum EnigmaMappingsReader implements MappingsReader {
25 FILE {
26 @Override
27 public EntryTree<EntryMapping> read(Path path) throws IOException, MappingParseException {
28 EntryTree<EntryMapping> mappings = new HashEntryTree<>();
29 readFile(path, mappings);
30 return mappings;
31 }
32 },
33 DIRECTORY {
34 @Override
35 public EntryTree<EntryMapping> read(Path path) throws IOException, MappingParseException {
36 EntryTree<EntryMapping> mappings = new HashEntryTree<>();
37
38 List<Path> files = Files.walk(path)
39 .filter(f -> !Files.isDirectory(f))
40 .filter(f -> f.toString().endsWith(".mapping"))
41 .collect(Collectors.toList());
42 for (Path file : files) {
43 if (Files.isHidden(file)) {
44 continue;
45 }
46 readFile(file, mappings);
47 }
48
49 return mappings;
50 }
51 };
52
53 protected void readFile(Path path, EntryTree<EntryMapping> mappings) throws IOException, MappingParseException {
54 List<String> lines = Files.readAllLines(path, Charsets.UTF_8);
55 Deque<Entry<?>> mappingStack = new ArrayDeque<>();
56
57 for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) {
58 String line = lines.get(lineNumber);
59 int indentation = countIndentation(line);
60
61 line = formatLine(line);
62 if (line == null) {
63 continue;
64 }
65
66 while (indentation < mappingStack.size()) {
67 mappingStack.pop();
68 }
69
70 try {
71 MappingPair<?, EntryMapping> pair = parseLine(mappingStack.peek(), line);
72 mappingStack.push(pair.getEntry());
73 if (pair.getMapping() != null) {
74 mappings.insert(pair.getEntry(), pair.getMapping());
75 }
76 } catch (Throwable t) {
77 t.printStackTrace();
78 throw new MappingParseException(path::toString, lineNumber, t.toString());
79 }
80 }
81 }
82
83 @Nullable
84 private String formatLine(String line) {
85 line = stripComment(line);
86 line = line.trim();
87
88 if (line.isEmpty()) {
89 return null;
90 }
91
92 return line;
93 }
94
95 private String stripComment(String line) {
96 int commentPos = line.indexOf('#');
97 if (commentPos >= 0) {
98 return line.substring(0, commentPos);
99 }
100 return line;
101 }
102
103 private int countIndentation(String line) {
104 int indent = 0;
105 for (int i = 0; i < line.length(); i++) {
106 if (line.charAt(i) != '\t') {
107 break;
108 }
109 indent++;
110 }
111 return indent;
112 }
113
114 private MappingPair<?, EntryMapping> parseLine(@Nullable Entry<?> parent, String line) {
115 String[] tokens = line.trim().split("\\s");
116 String keyToken = tokens[0].toLowerCase(Locale.ROOT);
117
118 switch (keyToken) {
119 case "class":
120 return parseClass(parent, tokens);
121 case "field":
122 return parseField(parent, tokens);
123 case "method":
124 return parseMethod(parent, tokens);
125 case "arg":
126 return parseArgument(parent, tokens);
127 default:
128 throw new RuntimeException("Unknown token '" + keyToken + "'");
129 }
130 }
131
132 private MappingPair<ClassEntry, EntryMapping> parseClass(@Nullable Entry<?> parent, String[] tokens) {
133 String obfuscatedName = ClassEntry.getInnerName(tokens[1]);
134 ClassEntry obfuscatedEntry;
135 if (parent instanceof ClassEntry) {
136 obfuscatedEntry = new ClassEntry((ClassEntry) parent, obfuscatedName);
137 } else {
138 obfuscatedEntry = new ClassEntry(obfuscatedName);
139 }
140
141 String mapping = null;
142 AccessModifier modifier = AccessModifier.UNCHANGED;
143
144 if (tokens.length == 3) {
145 AccessModifier parsedModifier = parseModifier(tokens[2]);
146 if (parsedModifier != null) {
147 modifier = parsedModifier;
148 mapping = obfuscatedName;
149 } else {
150 mapping = tokens[2];
151 }
152 } else if (tokens.length == 4) {
153 mapping = tokens[2];
154 modifier = parseModifier(tokens[3]);
155 }
156
157 if (mapping != null) {
158 return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping, modifier));
159 } else {
160 return new MappingPair<>(obfuscatedEntry);
161 }
162 }
163
164 private MappingPair<FieldEntry, EntryMapping> parseField(@Nullable Entry<?> parent, String[] tokens) {
165 if (!(parent instanceof ClassEntry)) {
166 throw new RuntimeException("Field must be a child of a class!");
167 }
168
169 ClassEntry ownerEntry = (ClassEntry) parent;
170
171 String obfuscatedName = tokens[1];
172 String mapping = obfuscatedName;
173 AccessModifier modifier = AccessModifier.UNCHANGED;
174 TypeDescriptor descriptor;
175
176 if (tokens.length == 4) {
177 AccessModifier parsedModifier = parseModifier(tokens[3]);
178 if (parsedModifier != null) {
179 descriptor = new TypeDescriptor(tokens[2]);
180 modifier = parsedModifier;
181 } else {
182 mapping = tokens[2];
183 descriptor = new TypeDescriptor(tokens[3]);
184 }
185 } else if (tokens.length == 5) {
186 descriptor = new TypeDescriptor(tokens[3]);
187 mapping = tokens[2];
188 modifier = parseModifier(tokens[4]);
189 } else {
190 throw new RuntimeException("Invalid method declaration");
191 }
192
193 FieldEntry obfuscatedEntry = new FieldEntry(ownerEntry, obfuscatedName, descriptor);
194 if (mapping != null) {
195 return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping, modifier));
196 } else {
197 return new MappingPair<>(obfuscatedEntry);
198 }
199 }
200
201 private MappingPair<MethodEntry, EntryMapping> parseMethod(@Nullable Entry<?> parent, String[] tokens) {
202 if (!(parent instanceof ClassEntry)) {
203 throw new RuntimeException("Method must be a child of a class!");
204 }
205
206 ClassEntry ownerEntry = (ClassEntry) parent;
207
208 String obfuscatedName = tokens[1];
209 String mapping = null;
210 AccessModifier modifier = AccessModifier.UNCHANGED;
211 MethodDescriptor descriptor;
212
213 if (tokens.length == 3) {
214 descriptor = new MethodDescriptor(tokens[2]);
215 } else if (tokens.length == 4) {
216 AccessModifier parsedModifier = parseModifier(tokens[3]);
217 if (parsedModifier != null) {
218 modifier = parsedModifier;
219 mapping = obfuscatedName;
220 descriptor = new MethodDescriptor(tokens[2]);
221 } else {
222 mapping = tokens[2];
223 descriptor = new MethodDescriptor(tokens[3]);
224 }
225 } else if (tokens.length == 5) {
226 mapping = tokens[2];
227 modifier = parseModifier(tokens[4]);
228 descriptor = new MethodDescriptor(tokens[3]);
229 } else {
230 throw new RuntimeException("Invalid method declaration");
231 }
232
233 MethodEntry obfuscatedEntry = new MethodEntry(ownerEntry, obfuscatedName, descriptor);
234 if (mapping != null) {
235 return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping, modifier));
236 } else {
237 return new MappingPair<>(obfuscatedEntry);
238 }
239 }
240
241 private MappingPair<LocalVariableEntry, EntryMapping> parseArgument(@Nullable Entry<?> parent, String[] tokens) {
242 if (!(parent instanceof MethodEntry)) {
243 throw new RuntimeException("Method arg must be a child of a method!");
244 }
245
246 MethodEntry ownerEntry = (MethodEntry) parent;
247 LocalVariableEntry obfuscatedEntry = new LocalVariableEntry(ownerEntry, Integer.parseInt(tokens[1]), "", true);
248 String mapping = tokens[2];
249
250 return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping));
251 }
252
253 @Nullable
254 private AccessModifier parseModifier(String token) {
255 if (token.startsWith("ACC:")) {
256 return AccessModifier.valueOf(token.substring(4));
257 }
258 return null;
259 }
260}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.mapping.serde;
13
14import cuchaz.enigma.ProgressListener;
15import cuchaz.enigma.translation.MappingTranslator;
16import cuchaz.enigma.translation.Translator;
17import cuchaz.enigma.translation.mapping.AccessModifier;
18import cuchaz.enigma.translation.mapping.EntryMapping;
19import cuchaz.enigma.translation.mapping.MappingDelta;
20import cuchaz.enigma.translation.mapping.VoidEntryResolver;
21import cuchaz.enigma.translation.mapping.tree.EntryTree;
22import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
23import cuchaz.enigma.translation.representation.entry.*;
24
25import java.io.IOException;
26import java.io.PrintWriter;
27import java.nio.file.DirectoryStream;
28import java.nio.file.Files;
29import java.nio.file.Path;
30import java.nio.file.Paths;
31import java.util.ArrayList;
32import java.util.Collection;
33import java.util.concurrent.atomic.AtomicInteger;
34import java.util.stream.Collectors;
35
36public enum EnigmaMappingsWriter implements MappingsWriter {
37 FILE {
38 @Override
39 public void write(EntryTree<EntryMapping> mappings, MappingDelta delta, Path path, ProgressListener progress) {
40 Collection<ClassEntry> classes = mappings.getRootEntries().stream()
41 .filter(entry -> entry instanceof ClassEntry)
42 .map(entry -> (ClassEntry) entry)
43 .collect(Collectors.toList());
44
45 progress.init(classes.size(), "Writing classes");
46
47 int steps = 0;
48 try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(path))) {
49 for (ClassEntry classEntry : classes) {
50 progress.step(steps++, classEntry.getFullName());
51 writeRoot(writer, mappings, classEntry);
52 }
53 } catch (IOException e) {
54 e.printStackTrace();
55 }
56 }
57 },
58 DIRECTORY {
59 @Override
60 public void write(EntryTree<EntryMapping> mappings, MappingDelta delta, Path path, ProgressListener progress) {
61 applyDeletions(delta.getDeletions(), path);
62
63 Collection<ClassEntry> classes = delta.getAdditions().getRootEntries().stream()
64 .filter(entry -> entry instanceof ClassEntry)
65 .map(entry -> (ClassEntry) entry)
66 .collect(Collectors.toList());
67
68 progress.init(classes.size(), "Writing classes");
69
70 Translator translator = new MappingTranslator(mappings, VoidEntryResolver.INSTANCE);
71 AtomicInteger steps = new AtomicInteger();
72
73 classes.parallelStream().forEach(classEntry -> {
74 progress.step(steps.getAndIncrement(), classEntry.getFullName());
75
76 try {
77 Path classPath = resolve(path, translator.translate(classEntry));
78 Files.deleteIfExists(classPath);
79 Files.createDirectories(classPath.getParent());
80
81 try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(classPath))) {
82 writeRoot(writer, mappings, classEntry);
83 }
84 } catch (Throwable t) {
85 System.err.println("Failed to write class '" + classEntry.getFullName() + "'");
86 t.printStackTrace();
87 }
88 });
89 }
90
91 private void applyDeletions(EntryTree<?> deletions, Path root) {
92 Collection<ClassEntry> deletedClasses = deletions.getRootEntries().stream()
93 .filter(e -> e instanceof ClassEntry)
94 .map(e -> (ClassEntry) e)
95 .collect(Collectors.toList());
96
97 for (ClassEntry classEntry : deletedClasses) {
98 try {
99 Files.deleteIfExists(resolve(root, classEntry));
100 } catch (IOException e) {
101 System.err.println("Failed to delete deleted class '" + classEntry + "'");
102 e.printStackTrace();
103 }
104 }
105
106 for (ClassEntry classEntry : deletedClasses) {
107 String packageName = classEntry.getPackageName();
108 if (packageName != null) {
109 Path packagePath = Paths.get(packageName);
110 try {
111 deleteDeadPackages(root, packagePath);
112 } catch (IOException e) {
113 System.err.println("Failed to delete dead package '" + packageName + "'");
114 e.printStackTrace();
115 }
116 }
117 }
118 }
119
120 private void deleteDeadPackages(Path root, Path packagePath) throws IOException {
121 for (int i = packagePath.getNameCount() - 1; i >= 0; i--) {
122 Path subPath = packagePath.subpath(0, i + 1);
123 Path packagePart = root.resolve(subPath);
124 if (isEmpty(packagePart)) {
125 Files.deleteIfExists(packagePart);
126 }
127 }
128 }
129
130 private boolean isEmpty(Path path) {
131 try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {
132 return !stream.iterator().hasNext();
133 } catch (IOException e) {
134 return false;
135 }
136 }
137
138 private Path resolve(Path root, ClassEntry classEntry) {
139 return root.resolve(classEntry.getFullName() + ".mapping");
140 }
141 };
142
143 protected void writeRoot(PrintWriter writer, EntryTree<EntryMapping> mappings, ClassEntry classEntry) {
144 Collection<Entry<?>> children = groupChildren(mappings.getChildren(classEntry));
145
146 writer.println(writeClass(classEntry, mappings.get(classEntry)).trim());
147 for (Entry<?> child : children) {
148 writeEntry(writer, mappings, child, 1);
149 }
150 }
151
152 protected void writeEntry(PrintWriter writer, EntryTree<EntryMapping> mappings, Entry<?> entry, int depth) {
153 EntryTreeNode<EntryMapping> node = mappings.findNode(entry);
154 if (node == null) {
155 return;
156 }
157
158 EntryMapping mapping = node.getValue();
159 if (entry instanceof ClassEntry) {
160 String line = writeClass((ClassEntry) entry, mapping);
161 writer.println(indent(line, depth));
162 } else if (entry instanceof MethodEntry) {
163 String line = writeMethod((MethodEntry) entry, mapping);
164 writer.println(indent(line, depth));
165 } else if (entry instanceof FieldEntry) {
166 String line = writeField((FieldEntry) entry, mapping);
167 writer.println(indent(line, depth));
168 } else if (entry instanceof LocalVariableEntry) {
169 String line = writeArgument((LocalVariableEntry) entry, mapping);
170 writer.println(indent(line, depth));
171 }
172
173 Collection<Entry<?>> children = groupChildren(node.getChildren());
174 for (Entry<?> child : children) {
175 writeEntry(writer, mappings, child, depth + 1);
176 }
177 }
178
179 private Collection<Entry<?>> groupChildren(Collection<Entry<?>> children) {
180 Collection<Entry<?>> result = new ArrayList<>(children.size());
181
182 children.stream().filter(e -> e instanceof ClassEntry)
183 .map(e -> (ClassEntry) e)
184 .sorted()
185 .forEach(result::add);
186
187 children.stream().filter(e -> e instanceof FieldEntry)
188 .map(e -> (FieldEntry) e)
189 .sorted()
190 .forEach(result::add);
191
192 children.stream().filter(e -> e instanceof MethodEntry)
193 .map(e -> (MethodEntry) e)
194 .sorted()
195 .forEach(result::add);
196
197 children.stream().filter(e -> e instanceof LocalVariableEntry)
198 .map(e -> (LocalVariableEntry) e)
199 .sorted()
200 .forEach(result::add);
201
202 return result;
203 }
204
205 protected String writeClass(ClassEntry entry, EntryMapping mapping) {
206 StringBuilder builder = new StringBuilder("CLASS ");
207 builder.append(entry.getFullName()).append(' ');
208 writeMapping(builder, mapping);
209
210 return builder.toString();
211 }
212
213 protected String writeMethod(MethodEntry entry, EntryMapping mapping) {
214 StringBuilder builder = new StringBuilder("METHOD ");
215 builder.append(entry.getName()).append(' ');
216 writeMapping(builder, mapping);
217
218 builder.append(entry.getDesc().toString());
219
220 return builder.toString();
221 }
222
223 protected String writeField(FieldEntry entry, EntryMapping mapping) {
224 StringBuilder builder = new StringBuilder("FIELD ");
225 builder.append(entry.getName()).append(' ');
226 writeMapping(builder, mapping);
227
228 builder.append(entry.getDesc().toString());
229
230 return builder.toString();
231 }
232
233 protected String writeArgument(LocalVariableEntry entry, EntryMapping mapping) {
234 StringBuilder builder = new StringBuilder("ARG ");
235 builder.append(entry.getIndex()).append(' ');
236
237 String mappedName = mapping != null ? mapping.getTargetName() : entry.getName();
238 builder.append(mappedName);
239
240 return builder.toString();
241 }
242
243 private void writeMapping(StringBuilder builder, EntryMapping mapping) {
244 if (mapping != null) {
245 builder.append(mapping.getTargetName()).append(' ');
246 if (mapping.getAccessModifier() != AccessModifier.UNCHANGED) {
247 builder.append(mapping.getAccessModifier().getFormattedName()).append(' ');
248 }
249 }
250 }
251
252 private String indent(String line, int depth) {
253 StringBuilder builder = new StringBuilder();
254 for (int i = 0; i < depth; i++) {
255 builder.append("\t");
256 }
257 builder.append(line.trim());
258 return builder.toString();
259 }
260}
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 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import cuchaz.enigma.ProgressListener;
4import cuchaz.enigma.throwables.MappingParseException;
5import cuchaz.enigma.translation.mapping.EntryMapping;
6import cuchaz.enigma.translation.mapping.MappingDelta;
7import cuchaz.enigma.translation.mapping.tree.EntryTree;
8
9import javax.annotation.Nullable;
10import java.io.IOException;
11import java.nio.file.Path;
12
13public enum MappingFormat {
14 ENIGMA_FILE(EnigmaMappingsWriter.FILE, EnigmaMappingsReader.FILE),
15 ENIGMA_DIRECTORY(EnigmaMappingsWriter.DIRECTORY, EnigmaMappingsReader.DIRECTORY),
16 TINY_FILE(null, TinyMappingsReader.INSTANCE),
17 SRG_FILE(SrgMappingsWriter.INSTANCE, null);
18
19 private final MappingsWriter writer;
20 private final MappingsReader reader;
21
22 MappingFormat(MappingsWriter writer, MappingsReader reader) {
23 this.writer = writer;
24 this.reader = reader;
25 }
26
27 public void write(EntryTree<EntryMapping> mappings, Path path, ProgressListener progressListener) {
28 write(mappings, MappingDelta.added(mappings), path, progressListener);
29 }
30
31 public void write(EntryTree<EntryMapping> mappings, MappingDelta delta, Path path, ProgressListener progressListener) {
32 if (writer == null) {
33 throw new IllegalStateException(name() + " does not support writing");
34 }
35 writer.write(mappings, delta, path, progressListener);
36 }
37
38 public EntryTree<EntryMapping> read(Path path) throws IOException, MappingParseException {
39 if (reader == null) {
40 throw new IllegalStateException(name() + " does not support reading");
41 }
42 return reader.read(path);
43 }
44
45 @Nullable
46 public MappingsWriter getWriter() {
47 return writer;
48 }
49
50 @Nullable
51 public MappingsReader getReader() {
52 return reader;
53 }
54}
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 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import cuchaz.enigma.throwables.MappingParseException;
4import cuchaz.enigma.translation.mapping.EntryMapping;
5import cuchaz.enigma.translation.mapping.tree.EntryTree;
6
7import java.io.IOException;
8import java.nio.file.Path;
9
10public interface MappingsReader {
11 EntryTree<EntryMapping> read(Path path) throws MappingParseException, IOException;
12}
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 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import cuchaz.enigma.ProgressListener;
4import cuchaz.enigma.translation.mapping.EntryMapping;
5import cuchaz.enigma.translation.mapping.MappingDelta;
6import cuchaz.enigma.translation.mapping.tree.EntryTree;
7
8import java.nio.file.Path;
9
10public interface MappingsWriter {
11 void write(EntryTree<EntryMapping> mappings, MappingDelta delta, Path path, ProgressListener progress);
12}
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 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import com.google.common.collect.Lists;
4import cuchaz.enigma.ProgressListener;
5import cuchaz.enigma.translation.MappingTranslator;
6import cuchaz.enigma.translation.Translator;
7import cuchaz.enigma.translation.mapping.EntryMapping;
8import cuchaz.enigma.translation.mapping.MappingDelta;
9import cuchaz.enigma.translation.mapping.VoidEntryResolver;
10import cuchaz.enigma.translation.mapping.tree.EntryTree;
11import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
12import cuchaz.enigma.translation.representation.entry.ClassEntry;
13import cuchaz.enigma.translation.representation.entry.Entry;
14import cuchaz.enigma.translation.representation.entry.FieldEntry;
15import cuchaz.enigma.translation.representation.entry.MethodEntry;
16
17import java.io.IOException;
18import java.io.PrintWriter;
19import java.nio.file.Files;
20import java.nio.file.Path;
21import java.util.ArrayList;
22import java.util.Collection;
23import java.util.Comparator;
24import java.util.List;
25import java.util.stream.Collectors;
26
27public enum SrgMappingsWriter implements MappingsWriter {
28 INSTANCE;
29
30 @Override
31 public void write(EntryTree<EntryMapping> mappings, MappingDelta delta, Path path, ProgressListener progress) {
32 try {
33 Files.deleteIfExists(path);
34 Files.createFile(path);
35 } catch (IOException e) {
36 e.printStackTrace();
37 }
38
39 List<String> classLines = new ArrayList<>();
40 List<String> fieldLines = new ArrayList<>();
41 List<String> methodLines = new ArrayList<>();
42
43 Collection<Entry<?>> rootEntries = Lists.newArrayList(mappings).stream()
44 .map(EntryTreeNode::getEntry)
45 .collect(Collectors.toList());
46 progress.init(rootEntries.size(), "Generating mappings");
47
48 int steps = 0;
49 for (Entry<?> entry : sorted(rootEntries)) {
50 progress.step(steps++, entry.getName());
51 writeEntry(classLines, fieldLines, methodLines, mappings, entry);
52 }
53
54 progress.init(3, "Writing mappings");
55 try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(path))) {
56 progress.step(0, "Classes");
57 classLines.forEach(writer::println);
58 progress.step(1, "Fields");
59 fieldLines.forEach(writer::println);
60 progress.step(2, "Methods");
61 methodLines.forEach(writer::println);
62 } catch (IOException e) {
63 e.printStackTrace();
64 }
65 }
66
67 private void writeEntry(List<String> classes, List<String> fields, List<String> methods, EntryTree<EntryMapping> mappings, Entry<?> entry) {
68 EntryTreeNode<EntryMapping> node = mappings.findNode(entry);
69 if (node == null) {
70 return;
71 }
72
73 Translator translator = new MappingTranslator(mappings, VoidEntryResolver.INSTANCE);
74 if (entry instanceof ClassEntry) {
75 classes.add(generateClassLine((ClassEntry) entry, translator));
76 } else if (entry instanceof FieldEntry) {
77 fields.add(generateFieldLine((FieldEntry) entry, translator));
78 } else if (entry instanceof MethodEntry) {
79 methods.add(generateMethodLine((MethodEntry) entry, translator));
80 }
81
82 for (Entry<?> child : sorted(node.getChildren())) {
83 writeEntry(classes, fields, methods, mappings, child);
84 }
85 }
86
87 private String generateClassLine(ClassEntry sourceEntry, Translator translator) {
88 ClassEntry targetEntry = translator.translate(sourceEntry);
89 return "CL: " + sourceEntry.getFullName() + " " + targetEntry.getFullName();
90 }
91
92 private String generateMethodLine(MethodEntry sourceEntry, Translator translator) {
93 MethodEntry targetEntry = translator.translate(sourceEntry);
94 return "MD: " + describeMethod(sourceEntry) + " " + describeMethod(targetEntry);
95 }
96
97 private String describeMethod(MethodEntry entry) {
98 return entry.getParent().getFullName() + "/" + entry.getName() + " " + entry.getDesc();
99 }
100
101 private String generateFieldLine(FieldEntry sourceEntry, Translator translator) {
102 FieldEntry targetEntry = translator.translate(sourceEntry);
103 return "FD: " + describeField(sourceEntry) + " " + describeField(targetEntry);
104 }
105
106 private String describeField(FieldEntry entry) {
107 return entry.getParent().getFullName() + "/" + entry.getName();
108 }
109
110 private Collection<Entry<?>> sorted(Iterable<Entry<?>> iterable) {
111 ArrayList<Entry<?>> sorted = Lists.newArrayList(iterable);
112 sorted.sort(Comparator.comparing(Entry::getName));
113 return sorted;
114 }
115}
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 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import com.google.common.base.Charsets;
4import cuchaz.enigma.throwables.MappingParseException;
5import cuchaz.enigma.translation.mapping.EntryMapping;
6import cuchaz.enigma.translation.mapping.MappingPair;
7import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
8import cuchaz.enigma.translation.mapping.tree.EntryTree;
9import cuchaz.enigma.translation.representation.MethodDescriptor;
10import cuchaz.enigma.translation.representation.TypeDescriptor;
11import cuchaz.enigma.translation.representation.entry.ClassEntry;
12import cuchaz.enigma.translation.representation.entry.FieldEntry;
13import cuchaz.enigma.translation.representation.entry.LocalVariableEntry;
14import cuchaz.enigma.translation.representation.entry.MethodEntry;
15
16import java.io.IOException;
17import java.nio.file.Files;
18import java.nio.file.Path;
19import java.util.List;
20
21public enum TinyMappingsReader implements MappingsReader {
22 INSTANCE;
23
24 @Override
25 public EntryTree<EntryMapping> read(Path path) throws IOException, MappingParseException {
26 return read(path, Files.readAllLines(path, Charsets.UTF_8));
27 }
28
29 private EntryTree<EntryMapping> read(Path path, List<String> lines) throws MappingParseException {
30 EntryTree<EntryMapping> mappings = new HashEntryTree<>();
31 lines.remove(0);
32
33 for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) {
34 String line = lines.get(lineNumber);
35
36 try {
37 MappingPair<?, EntryMapping> mapping = parseLine(line);
38 mappings.insert(mapping.getEntry(), mapping.getMapping());
39 } catch (Throwable t) {
40 t.printStackTrace();
41 throw new MappingParseException(path::toString, lineNumber, t.toString());
42 }
43 }
44
45 return mappings;
46 }
47
48 private MappingPair<?, EntryMapping> parseLine(String line) {
49 String[] tokens = line.split("\t");
50
51 String key = tokens[0];
52 switch (key) {
53 case "CLASS":
54 return parseClass(tokens);
55 case "FIELD":
56 return parseField(tokens);
57 case "METHOD":
58 return parseMethod(tokens);
59 case "MTH-ARG":
60 return parseArgument(tokens);
61 default:
62 throw new RuntimeException("Unknown token '" + key + "'!");
63 }
64 }
65
66 private MappingPair<ClassEntry, EntryMapping> parseClass(String[] tokens) {
67 ClassEntry obfuscatedEntry = new ClassEntry(tokens[1]);
68 String mapping = tokens[2];
69 return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping));
70 }
71
72 private MappingPair<FieldEntry, EntryMapping> parseField(String[] tokens) {
73 ClassEntry ownerClass = new ClassEntry(tokens[1]);
74 TypeDescriptor descriptor = new TypeDescriptor(tokens[2]);
75
76 FieldEntry obfuscatedEntry = new FieldEntry(ownerClass, tokens[3], descriptor);
77 String mapping = tokens[4];
78 return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping));
79 }
80
81 private MappingPair<MethodEntry, EntryMapping> parseMethod(String[] tokens) {
82 ClassEntry ownerClass = new ClassEntry(tokens[1]);
83 MethodDescriptor descriptor = new MethodDescriptor(tokens[2]);
84
85 MethodEntry obfuscatedEntry = new MethodEntry(ownerClass, tokens[3], descriptor);
86 String mapping = tokens[4];
87 return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping));
88 }
89
90 private MappingPair<LocalVariableEntry, EntryMapping> parseArgument(String[] tokens) {
91 ClassEntry ownerClass = new ClassEntry(tokens[1]);
92 MethodDescriptor ownerDescriptor = new MethodDescriptor(tokens[2]);
93 MethodEntry ownerMethod = new MethodEntry(ownerClass, tokens[3], ownerDescriptor);
94 int variableIndex = Integer.parseInt(tokens[4]);
95
96 String mapping = tokens[5];
97 LocalVariableEntry obfuscatedEntry = new LocalVariableEntry(ownerMethod, variableIndex, "", true);
98 return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping));
99 }
100}
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 @@
1package cuchaz.enigma.translation.mapping.tree;
2
3import cuchaz.enigma.translation.mapping.MappingDelta;
4import cuchaz.enigma.translation.representation.entry.Entry;
5
6import javax.annotation.Nullable;
7import java.util.Collection;
8import java.util.Iterator;
9
10public class DeltaTrackingTree<T> implements EntryTree<T> {
11 private final EntryTree<T> delegate;
12
13 private EntryTree<Object> additions = new HashEntryTree<>();
14 private EntryTree<Object> deletions = new HashEntryTree<>();
15
16 public DeltaTrackingTree(EntryTree<T> delegate) {
17 this.delegate = delegate;
18 }
19
20 public DeltaTrackingTree() {
21 this(new HashEntryTree<>());
22 }
23
24 @Override
25 public void insert(Entry<?> entry, T value) {
26 if (value != null) {
27 trackAddition(entry);
28 } else {
29 trackDeletion(entry);
30 }
31 delegate.insert(entry, value);
32 }
33
34 @Nullable
35 @Override
36 public T remove(Entry<?> entry) {
37 T value = delegate.remove(entry);
38 trackDeletion(entry);
39 return value;
40 }
41
42 public void trackAddition(Entry<?> entry) {
43 deletions.remove(entry);
44 additions.insert(entry, MappingDelta.PLACEHOLDER);
45 }
46
47 public void trackDeletion(Entry<?> entry) {
48 additions.remove(entry);
49 deletions.insert(entry, MappingDelta.PLACEHOLDER);
50 }
51
52 @Nullable
53 @Override
54 public T get(Entry<?> entry) {
55 return delegate.get(entry);
56 }
57
58 @Override
59 public Collection<Entry<?>> getChildren(Entry<?> entry) {
60 return delegate.getChildren(entry);
61 }
62
63 @Override
64 public Collection<Entry<?>> getSiblings(Entry<?> entry) {
65 return delegate.getSiblings(entry);
66 }
67
68 @Nullable
69 @Override
70 public EntryTreeNode<T> findNode(Entry<?> entry) {
71 return delegate.findNode(entry);
72 }
73
74 @Override
75 public Collection<EntryTreeNode<T>> getAllNodes() {
76 return delegate.getAllNodes();
77 }
78
79 @Override
80 public Collection<Entry<?>> getRootEntries() {
81 return delegate.getRootEntries();
82 }
83
84 @Override
85 public Collection<Entry<?>> getAllEntries() {
86 return delegate.getAllEntries();
87 }
88
89 @Override
90 public boolean isEmpty() {
91 return delegate.isEmpty();
92 }
93
94 @Override
95 public Iterator<EntryTreeNode<T>> iterator() {
96 return delegate.iterator();
97 }
98
99 public MappingDelta takeDelta() {
100 MappingDelta delta = new MappingDelta(additions, deletions);
101 resetDelta();
102 return delta;
103 }
104
105 private void resetDelta() {
106 additions = new HashEntryTree<>();
107 deletions = new HashEntryTree<>();
108 }
109
110 public boolean isDirty() {
111 return !additions.isEmpty() || !deletions.isEmpty();
112 }
113}
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 @@
1package cuchaz.enigma.translation.mapping.tree;
2
3import cuchaz.enigma.translation.mapping.EntryMap;
4import cuchaz.enigma.translation.representation.entry.Entry;
5
6import javax.annotation.Nullable;
7import java.util.Collection;
8
9public interface EntryTree<T> extends EntryMap<T>, Iterable<EntryTreeNode<T>> {
10 Collection<Entry<?>> getChildren(Entry<?> entry);
11
12 Collection<Entry<?>> getSiblings(Entry<?> entry);
13
14 @Nullable
15 EntryTreeNode<T> findNode(Entry<?> entry);
16
17 Collection<EntryTreeNode<T>> getAllNodes();
18
19 Collection<Entry<?>> getRootEntries();
20}
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 @@
1package cuchaz.enigma.translation.mapping.tree;
2
3import cuchaz.enigma.translation.representation.entry.Entry;
4
5import javax.annotation.Nullable;
6import java.util.ArrayList;
7import java.util.Collection;
8import java.util.stream.Collectors;
9
10public interface EntryTreeNode<T> {
11 @Nullable
12 T getValue();
13
14 Entry<?> getEntry();
15
16 boolean isEmpty();
17
18 Collection<Entry<?>> getChildren();
19
20 Collection<? extends EntryTreeNode<T>> getChildNodes();
21
22 default Collection<? extends EntryTreeNode<T>> getNodesRecursively() {
23 Collection<EntryTreeNode<T>> nodes = new ArrayList<>();
24 nodes.add(this);
25 for (EntryTreeNode<T> node : getChildNodes()) {
26 nodes.addAll(node.getNodesRecursively());
27 }
28 return nodes;
29 }
30
31 default Collection<Entry<?>> getChildrenRecursively() {
32 return getNodesRecursively().stream()
33 .map(EntryTreeNode::getEntry)
34 .collect(Collectors.toList());
35 }
36}
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 @@
1package cuchaz.enigma.translation.mapping.tree;
2
3import cuchaz.enigma.translation.representation.entry.Entry;
4
5import javax.annotation.Nullable;
6import java.util.*;
7import java.util.stream.Collectors;
8
9public class HashEntryTree<T> implements EntryTree<T> {
10 private final Map<Entry<?>, HashTreeNode<T>> root = new HashMap<>();
11
12 @Override
13 public void insert(Entry<?> entry, T value) {
14 List<HashTreeNode<T>> path = computePath(entry);
15 path.get(path.size() - 1).putValue(value);
16 if (value == null) {
17 removeDeadAlong(path);
18 }
19 }
20
21 @Override
22 @Nullable
23 public T remove(Entry<?> entry) {
24 List<HashTreeNode<T>> path = computePath(entry);
25 T value = path.get(path.size() - 1).removeValue();
26
27 removeDeadAlong(path);
28
29 return value;
30 }
31
32 @Override
33 @Nullable
34 public T get(Entry<?> entry) {
35 HashTreeNode<T> node = findNode(entry);
36 if (node == null) {
37 return null;
38 }
39 return node.getValue();
40 }
41
42 @Override
43 public boolean contains(Entry<?> entry) {
44 return get(entry) != null;
45 }
46
47 @Override
48 public Collection<Entry<?>> getChildren(Entry<?> entry) {
49 HashTreeNode<T> leaf = findNode(entry);
50 if (leaf == null) {
51 return Collections.emptyList();
52 }
53 return leaf.getChildren();
54 }
55
56 @Override
57 public Collection<Entry<?>> getSiblings(Entry<?> entry) {
58 List<HashTreeNode<T>> path = computePath(entry);
59 if (path.size() <= 1) {
60 return getSiblings(entry, root.keySet());
61 }
62 HashTreeNode<T> parent = path.get(path.size() - 2);
63 return getSiblings(entry, parent.getChildren());
64 }
65
66 private Collection<Entry<?>> getSiblings(Entry<?> entry, Collection<Entry<?>> children) {
67 Set<Entry<?>> siblings = new HashSet<>(children);
68 siblings.remove(entry);
69 return siblings;
70 }
71
72 @Override
73 @Nullable
74 public HashTreeNode<T> findNode(Entry<?> target) {
75 List<Entry<?>> parentChain = target.getAncestry();
76 if (parentChain.isEmpty()) {
77 return null;
78 }
79
80 HashTreeNode<T> node = root.get(parentChain.get(0));
81 for (int i = 1; i < parentChain.size(); i++) {
82 if (node == null) {
83 return null;
84 }
85 node = node.getChild(parentChain.get(i), false);
86 }
87
88 return node;
89 }
90
91 private List<HashTreeNode<T>> computePath(Entry<?> target) {
92 List<Entry<?>> ancestry = target.getAncestry();
93 if (ancestry.isEmpty()) {
94 return Collections.emptyList();
95 }
96
97 List<HashTreeNode<T>> path = new ArrayList<>(ancestry.size());
98
99 Entry<?> rootEntry = ancestry.get(0);
100 HashTreeNode<T> node = root.computeIfAbsent(rootEntry, HashTreeNode::new);
101 path.add(node);
102
103 for (int i = 1; i < ancestry.size(); i++) {
104 node = node.getChild(ancestry.get(i), true);
105 path.add(node);
106 }
107
108 return path;
109 }
110
111 private void removeDeadAlong(List<HashTreeNode<T>> path) {
112 for (int i = path.size() - 1; i >= 0; i--) {
113 HashTreeNode<T> node = path.get(i);
114 if (node.isEmpty()) {
115 if (i > 0) {
116 HashTreeNode<T> parentNode = path.get(i - 1);
117 parentNode.remove(node.getEntry());
118 } else {
119 root.remove(node.getEntry());
120 }
121 } else {
122 break;
123 }
124 }
125 }
126
127 @Override
128 @SuppressWarnings("unchecked")
129 public Iterator<EntryTreeNode<T>> iterator() {
130 Collection<EntryTreeNode<T>> values = (Collection) root.values();
131 return values.iterator();
132 }
133
134 @Override
135 public Collection<EntryTreeNode<T>> getAllNodes() {
136 Collection<EntryTreeNode<T>> nodes = new ArrayList<>();
137 for (EntryTreeNode<T> node : root.values()) {
138 nodes.addAll(node.getNodesRecursively());
139 }
140 return nodes;
141 }
142
143 @Override
144 public Collection<Entry<?>> getAllEntries() {
145 return getAllNodes().stream()
146 .map(EntryTreeNode::getEntry)
147 .collect(Collectors.toList());
148 }
149
150 @Override
151 public Collection<Entry<?>> getRootEntries() {
152 return root.keySet();
153 }
154
155 @Override
156 public boolean isEmpty() {
157 return root.isEmpty();
158 }
159}
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 @@
1package cuchaz.enigma.translation.mapping.tree;
2
3import cuchaz.enigma.translation.representation.entry.Entry;
4
5import javax.annotation.Nullable;
6import java.util.Collection;
7import java.util.HashMap;
8import java.util.Iterator;
9import java.util.Map;
10
11public class HashTreeNode<T> implements EntryTreeNode<T>, Iterable<HashTreeNode<T>> {
12 private final Entry<?> entry;
13 private final Map<Entry<?>, HashTreeNode<T>> children = new HashMap<>();
14 private T value;
15
16 HashTreeNode(Entry<?> entry) {
17 this.entry = entry;
18 }
19
20 void putValue(T value) {
21 this.value = value;
22 }
23
24 T removeValue() {
25 T value = this.value;
26 this.value = null;
27 return value;
28 }
29
30 HashTreeNode<T> getChild(Entry<?> entry, boolean create) {
31 if (create) {
32 return children.computeIfAbsent(entry, HashTreeNode::new);
33 } else {
34 return children.get(entry);
35 }
36 }
37
38 void remove(Entry<?> entry) {
39 children.remove(entry);
40 }
41
42 @Override
43 @Nullable
44 public T getValue() {
45 return value;
46 }
47
48 @Override
49 public Entry<?> getEntry() {
50 return entry;
51 }
52
53 @Override
54 public boolean isEmpty() {
55 return children.isEmpty() && value == null;
56 }
57
58 @Override
59 public Collection<Entry<?>> getChildren() {
60 return children.keySet();
61 }
62
63 @Override
64 public Collection<? extends EntryTreeNode<T>> getChildNodes() {
65 return children.values();
66 }
67
68 @Override
69 public Iterator<HashTreeNode<T>> iterator() {
70 return children.values().iterator();
71 }
72}
diff --git a/src/main/java/cuchaz/enigma/bytecode/AccessFlags.java b/src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java
index 31c8691..0534edd 100644
--- a/src/main/java/cuchaz/enigma/bytecode/AccessFlags.java
+++ b/src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java
@@ -1,4 +1,4 @@
1package cuchaz.enigma.bytecode; 1package cuchaz.enigma.translation.representation;
2 2
3import cuchaz.enigma.analysis.Access; 3import cuchaz.enigma.analysis.Access;
4import org.objectweb.asm.Opcodes; 4import org.objectweb.asm.Opcodes;
@@ -6,6 +6,9 @@ import org.objectweb.asm.Opcodes;
6import java.lang.reflect.Modifier; 6import java.lang.reflect.Modifier;
7 7
8public class AccessFlags { 8public class AccessFlags {
9 public static final AccessFlags PRIVATE = new AccessFlags(Opcodes.ACC_PRIVATE);
10 public static final AccessFlags PUBLIC = new AccessFlags(Opcodes.ACC_PUBLIC);
11
9 private int flags; 12 private int flags;
10 13
11 public AccessFlags(int flags) { 14 public AccessFlags(int flags) {
@@ -40,6 +43,10 @@ public class AccessFlags {
40 return (flags & Opcodes.ACC_BRIDGE) != 0; 43 return (flags & Opcodes.ACC_BRIDGE) != 0;
41 } 44 }
42 45
46 public boolean isFinal() {
47 return (flags & Opcodes.ACC_FINAL) != 0;
48 }
49
43 public AccessFlags setPrivate() { 50 public AccessFlags setPrivate() {
44 this.setVisibility(Opcodes.ACC_PRIVATE); 51 this.setVisibility(Opcodes.ACC_PRIVATE);
45 return this; 52 return this;
diff --git a/src/main/java/cuchaz/enigma/mapping/MethodDescriptor.java b/src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java
index 0fc0351..c59751f 100644
--- a/src/main/java/cuchaz/enigma/mapping/MethodDescriptor.java
+++ b/src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java
@@ -9,17 +9,22 @@
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11 11
12package cuchaz.enigma.mapping; 12package cuchaz.enigma.translation.representation;
13 13
14import com.google.common.collect.Lists; 14import com.google.common.collect.Lists;
15import cuchaz.enigma.mapping.entry.ClassEntry; 15import cuchaz.enigma.translation.Translatable;
16import cuchaz.enigma.translation.Translator;
17import cuchaz.enigma.translation.mapping.EntryMapping;
18import cuchaz.enigma.translation.mapping.EntryResolver;
19import cuchaz.enigma.translation.mapping.EntryMap;
20import cuchaz.enigma.translation.representation.entry.ClassEntry;
16import cuchaz.enigma.utils.Utils; 21import cuchaz.enigma.utils.Utils;
17 22
18import java.util.ArrayList; 23import java.util.ArrayList;
19import java.util.List; 24import java.util.List;
20import java.util.function.Function; 25import java.util.function.Function;
21 26
22public class MethodDescriptor { 27public class MethodDescriptor implements Translatable {
23 28
24 private List<TypeDescriptor> argumentDescs; 29 private List<TypeDescriptor> argumentDescs;
25 private TypeDescriptor returnDesc; 30 private TypeDescriptor returnDesc;
@@ -111,4 +116,17 @@ public class MethodDescriptor {
111 } 116 }
112 return new MethodDescriptor(argumentDescs, returnDesc.remap(remapper)); 117 return new MethodDescriptor(argumentDescs, returnDesc.remap(remapper));
113 } 118 }
119
120 @Override
121 public Translatable translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) {
122 List<TypeDescriptor> translatedArguments = new ArrayList<>(argumentDescs.size());
123 for (TypeDescriptor argument : argumentDescs) {
124 translatedArguments.add(translator.translate(argument));
125 }
126 return new MethodDescriptor(translatedArguments, translator.translate(returnDesc));
127 }
128
129 public boolean canConflictWith(MethodDescriptor descriptor) {
130 return descriptor.argumentDescs.equals(argumentDescs);
131 }
114} 132}
diff --git a/src/main/java/cuchaz/enigma/mapping/entry/ProcyonEntryFactory.java b/src/main/java/cuchaz/enigma/translation/representation/ProcyonEntryFactory.java
index 73770c5..9c9fa3d 100644
--- a/src/main/java/cuchaz/enigma/mapping/entry/ProcyonEntryFactory.java
+++ b/src/main/java/cuchaz/enigma/translation/representation/ProcyonEntryFactory.java
@@ -9,15 +9,12 @@
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11 11
12package cuchaz.enigma.mapping.entry; 12package cuchaz.enigma.translation.representation;
13 13
14import com.strobel.assembler.metadata.FieldDefinition; 14import com.strobel.assembler.metadata.FieldDefinition;
15import com.strobel.assembler.metadata.MemberReference; 15import com.strobel.assembler.metadata.MemberReference;
16import com.strobel.assembler.metadata.MethodDefinition; 16import com.strobel.assembler.metadata.MethodDefinition;
17import cuchaz.enigma.bytecode.AccessFlags; 17import cuchaz.enigma.translation.representation.entry.*;
18import cuchaz.enigma.mapping.MethodDescriptor;
19import cuchaz.enigma.mapping.Signature;
20import cuchaz.enigma.mapping.TypeDescriptor;
21 18
22public class ProcyonEntryFactory { 19public class ProcyonEntryFactory {
23 private final ReferencedEntryPool entryPool; 20 private final ReferencedEntryPool entryPool;
diff --git a/src/main/java/cuchaz/enigma/mapping/entry/ReferencedEntryPool.java b/src/main/java/cuchaz/enigma/translation/representation/ReferencedEntryPool.java
index 12b3955..631b375 100644
--- a/src/main/java/cuchaz/enigma/mapping/entry/ReferencedEntryPool.java
+++ b/src/main/java/cuchaz/enigma/translation/representation/ReferencedEntryPool.java
@@ -9,10 +9,11 @@
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11 11
12package cuchaz.enigma.mapping.entry; 12package cuchaz.enigma.translation.representation;
13 13
14import cuchaz.enigma.mapping.MethodDescriptor; 14import cuchaz.enigma.translation.representation.entry.ClassEntry;
15import cuchaz.enigma.mapping.TypeDescriptor; 15import cuchaz.enigma.translation.representation.entry.FieldEntry;
16import cuchaz.enigma.translation.representation.entry.MethodEntry;
16 17
17import java.util.HashMap; 18import java.util.HashMap;
18import java.util.Map; 19import java.util.Map;
@@ -38,7 +39,7 @@ public class ReferencedEntryPool {
38 39
39 public MethodEntry getMethod(ClassEntry ownerEntry, String name, MethodDescriptor desc) { 40 public MethodEntry getMethod(ClassEntry ownerEntry, String name, MethodDescriptor desc) {
40 String key = name + desc.toString(); 41 String key = name + desc.toString();
41 return getClassMethods(ownerEntry.getName()).computeIfAbsent(key, s -> new MethodEntry(ownerEntry, name, desc)); 42 return getClassMethods(ownerEntry.getFullName()).computeIfAbsent(key, s -> new MethodEntry(ownerEntry, name, desc));
42 } 43 }
43 44
44 public FieldEntry getField(ClassEntry ownerEntry, String name, String desc) { 45 public FieldEntry getField(ClassEntry ownerEntry, String name, String desc) {
@@ -46,7 +47,7 @@ public class ReferencedEntryPool {
46 } 47 }
47 48
48 public FieldEntry getField(ClassEntry ownerEntry, String name, TypeDescriptor desc) { 49 public FieldEntry getField(ClassEntry ownerEntry, String name, TypeDescriptor desc) {
49 return getClassFields(ownerEntry.getName()).computeIfAbsent(name, s -> new FieldEntry(ownerEntry, name, desc)); 50 return getClassFields(ownerEntry.getFullName()).computeIfAbsent(name, s -> new FieldEntry(ownerEntry, name, desc));
50 } 51 }
51 52
52 private Map<String, MethodEntry> getClassMethods(String name) { 53 private Map<String, MethodEntry> getClassMethods(String name) {
diff --git a/src/main/java/cuchaz/enigma/mapping/Signature.java b/src/main/java/cuchaz/enigma/translation/representation/Signature.java
index 071e4af..dc241b7 100644
--- a/src/main/java/cuchaz/enigma/mapping/Signature.java
+++ b/src/main/java/cuchaz/enigma/translation/representation/Signature.java
@@ -1,6 +1,12 @@
1package cuchaz.enigma.mapping; 1package cuchaz.enigma.translation.representation;
2 2
3import cuchaz.enigma.bytecode.translators.TranslationSignatureVisitor; 3import cuchaz.enigma.bytecode.translators.TranslationSignatureVisitor;
4import cuchaz.enigma.translation.Translatable;
5import cuchaz.enigma.translation.Translator;
6import cuchaz.enigma.translation.mapping.EntryMapping;
7import cuchaz.enigma.translation.mapping.EntryResolver;
8import cuchaz.enigma.translation.mapping.EntryMap;
9import cuchaz.enigma.translation.representation.entry.ClassEntry;
4import org.objectweb.asm.signature.SignatureReader; 10import org.objectweb.asm.signature.SignatureReader;
5import org.objectweb.asm.signature.SignatureVisitor; 11import org.objectweb.asm.signature.SignatureVisitor;
6import org.objectweb.asm.signature.SignatureWriter; 12import org.objectweb.asm.signature.SignatureWriter;
@@ -8,7 +14,7 @@ import org.objectweb.asm.signature.SignatureWriter;
8import java.util.function.Function; 14import java.util.function.Function;
9import java.util.regex.Pattern; 15import java.util.regex.Pattern;
10 16
11public class Signature { 17public class Signature implements Translatable {
12 private static final Pattern OBJECT_PATTERN = Pattern.compile(".*:Ljava/lang/Object;:.*"); 18 private static final Pattern OBJECT_PATTERN = Pattern.compile(".*:Ljava/lang/Object;:.*");
13 19
14 private final String signature; 20 private final String signature;
@@ -79,4 +85,9 @@ public class Signature {
79 public String toString() { 85 public String toString() {
80 return signature; 86 return signature;
81 } 87 }
88
89 @Override
90 public Translatable translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) {
91 return remap(name -> translator.translate(new ClassEntry(name)).getFullName());
92 }
82} 93}
diff --git a/src/main/java/cuchaz/enigma/mapping/TypeDescriptor.java b/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java
index 6e58aa0..f7ba849 100644
--- a/src/main/java/cuchaz/enigma/mapping/TypeDescriptor.java
+++ b/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java
@@ -9,16 +9,21 @@
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11 11
12package cuchaz.enigma.mapping; 12package cuchaz.enigma.translation.representation;
13 13
14import com.google.common.base.Preconditions; 14import com.google.common.base.Preconditions;
15import com.google.common.collect.Maps; 15import com.google.common.collect.Maps;
16import cuchaz.enigma.mapping.entry.ClassEntry; 16import cuchaz.enigma.translation.Translatable;
17import cuchaz.enigma.translation.Translator;
18import cuchaz.enigma.translation.mapping.EntryMapping;
19import cuchaz.enigma.translation.mapping.EntryResolver;
20import cuchaz.enigma.translation.mapping.EntryMap;
21import cuchaz.enigma.translation.representation.entry.ClassEntry;
17 22
18import java.util.Map; 23import java.util.Map;
19import java.util.function.Function; 24import java.util.function.Function;
20 25
21public class TypeDescriptor { 26public class TypeDescriptor implements Translatable {
22 27
23 protected final String desc; 28 protected final String desc;
24 29
@@ -164,7 +169,7 @@ public class TypeDescriptor {
164 if (!isArray()) { 169 if (!isArray()) {
165 throw new IllegalStateException("not an array"); 170 throw new IllegalStateException("not an array");
166 } 171 }
167 return new TypeDescriptor(this.desc.substring(getArrayDimension(), this.desc.length())); 172 return new TypeDescriptor(this.desc.substring(getArrayDimension()));
168 } 173 }
169 174
170 public boolean containsType() { 175 public boolean containsType() {
@@ -188,7 +193,7 @@ public class TypeDescriptor {
188 public TypeDescriptor remap(Function<String, String> remapper) { 193 public TypeDescriptor remap(Function<String, String> remapper) {
189 String desc = this.desc; 194 String desc = this.desc;
190 if (isType() || (isArray() && containsType())) { 195 if (isType() || (isArray() && containsType())) {
191 String replacedName = remapper.apply(this.getTypeEntry().getName()); 196 String replacedName = remapper.apply(this.getTypeEntry().getFullName());
192 if (replacedName != null) { 197 if (replacedName != null) {
193 if (this.isType()) { 198 if (this.isType()) {
194 desc = "L" + replacedName + ";"; 199 desc = "L" + replacedName + ";";
@@ -222,6 +227,11 @@ public class TypeDescriptor {
222 } 227 }
223 } 228 }
224 229
230 @Override
231 public Translatable translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) {
232 return remap(name -> translator.translate(new ClassEntry(name)).getFullName());
233 }
234
225 public enum Primitive { 235 public enum Primitive {
226 BYTE('B'), 236 BYTE('B'),
227 CHARACTER('C'), 237 CHARACTER('C'),
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.representation.entry;
13
14import com.google.common.base.Preconditions;
15import com.strobel.assembler.metadata.TypeDefinition;
16import cuchaz.enigma.translation.Translator;
17import cuchaz.enigma.translation.mapping.EntryMapping;
18import cuchaz.enigma.translation.representation.AccessFlags;
19import cuchaz.enigma.translation.representation.Signature;
20
21import javax.annotation.Nullable;
22import java.util.Arrays;
23
24public class ClassDefEntry extends ClassEntry implements DefEntry<ClassEntry> {
25 private final AccessFlags access;
26 private final Signature signature;
27 private final ClassEntry superClass;
28 private final ClassEntry[] interfaces;
29
30 public ClassDefEntry(String className, Signature signature, AccessFlags access, @Nullable ClassEntry superClass, ClassEntry[] interfaces) {
31 this(getOuterClass(className), getInnerName(className), signature, access, superClass, interfaces);
32 }
33
34 public ClassDefEntry(ClassEntry parent, String className, Signature signature, AccessFlags access, @Nullable ClassEntry superClass, ClassEntry[] interfaces) {
35 super(parent, className);
36 Preconditions.checkNotNull(signature, "Class signature cannot be null");
37 Preconditions.checkNotNull(access, "Class access cannot be null");
38
39 this.signature = signature;
40 this.access = access;
41 this.superClass = superClass;
42 this.interfaces = interfaces != null ? interfaces : new ClassEntry[0];
43 }
44
45 public static ClassDefEntry parse(int access, String name, String signature, String superName, String[] interfaces) {
46 ClassEntry superClass = superName != null ? new ClassEntry(superName) : null;
47 ClassEntry[] interfaceClasses = Arrays.stream(interfaces).map(ClassEntry::new).toArray(ClassEntry[]::new);
48 return new ClassDefEntry(name, Signature.createSignature(signature), new AccessFlags(access), superClass, interfaceClasses);
49 }
50
51 public static ClassDefEntry parse(TypeDefinition def) {
52 String name = def.getInternalName();
53 Signature signature = Signature.createSignature(def.getSignature());
54 AccessFlags access = new AccessFlags(def.getModifiers());
55 ClassEntry superClass = def.getBaseType() != null ? ClassEntry.parse(def.getBaseType()) : null;
56 ClassEntry[] interfaces = def.getExplicitInterfaces().stream().map(ClassEntry::parse).toArray(ClassEntry[]::new);
57 return new ClassDefEntry(name, signature, access, superClass, interfaces);
58 }
59
60 public Signature getSignature() {
61 return signature;
62 }
63
64 @Override
65 public AccessFlags getAccess() {
66 return access;
67 }
68
69 @Nullable
70 public ClassEntry getSuperClass() {
71 return superClass;
72 }
73
74 public ClassEntry[] getInterfaces() {
75 return interfaces;
76 }
77
78 @Override
79 public ClassDefEntry translate(Translator translator, @Nullable EntryMapping mapping) {
80 Signature translatedSignature = translator.translate(signature);
81 String translatedName = mapping != null ? mapping.getTargetName() : name;
82 AccessFlags translatedAccess = mapping != null ? mapping.getAccessModifier().transform(access) : access;
83 ClassEntry translatedSuper = translator.translate(superClass);
84 ClassEntry[] translatedInterfaces = Arrays.stream(interfaces).map(translator::translate).toArray(ClassEntry[]::new);
85 return new ClassDefEntry(parent, translatedName, translatedSignature, translatedAccess, translatedSuper, translatedInterfaces);
86 }
87
88 @Override
89 public ClassDefEntry withParent(ClassEntry parent) {
90 return new ClassDefEntry(parent, name, signature, access, superClass, interfaces);
91 }
92}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.representation.entry;
13
14import com.strobel.assembler.metadata.TypeReference;
15import cuchaz.enigma.throwables.IllegalNameException;
16import cuchaz.enigma.translation.Translator;
17import cuchaz.enigma.translation.mapping.EntryMapping;
18import cuchaz.enigma.translation.mapping.NameValidator;
19
20import javax.annotation.Nullable;
21import java.util.List;
22import java.util.Objects;
23
24public class ClassEntry extends ParentedEntry<ClassEntry> implements Comparable<ClassEntry> {
25 private final String fullName;
26
27 public ClassEntry(String className) {
28 this(getOuterClass(className), getInnerName(className));
29 }
30
31 public ClassEntry(@Nullable ClassEntry parent, String className) {
32 super(parent, className);
33 if (parent != null) {
34 fullName = parent.getFullName() + "$" + name;
35 } else {
36 fullName = name;
37 }
38
39 if (parent == null && className.indexOf('.') >= 0) {
40 throw new IllegalArgumentException("Class name must be in JVM format. ie, path/to/package/class$inner : " + className);
41 }
42 }
43
44 public static ClassEntry parse(TypeReference typeReference) {
45 return new ClassEntry(typeReference.getInternalName());
46 }
47
48 @Override
49 public Class<ClassEntry> getParentType() {
50 return ClassEntry.class;
51 }
52
53 @Override
54 public String getName() {
55 return this.name;
56 }
57
58 public String getFullName() {
59 return fullName;
60 }
61
62 @Override
63 public ClassEntry translate(Translator translator, @Nullable EntryMapping mapping) {
64 String translatedName = mapping != null ? mapping.getTargetName() : name;
65 return new ClassEntry(parent, translatedName);
66 }
67
68 @Override
69 public ClassEntry getContainingClass() {
70 return this;
71 }
72
73 @Override
74 public int hashCode() {
75 return fullName.hashCode();
76 }
77
78 @Override
79 public boolean equals(Object other) {
80 return other instanceof ClassEntry && equals((ClassEntry) other);
81 }
82
83 public boolean equals(ClassEntry other) {
84 return other != null && Objects.equals(parent, other.parent) && this.name.equals(other.name);
85 }
86
87 @Override
88 public boolean canConflictWith(Entry<?> entry) {
89 return true;
90 }
91
92 @Override
93 public void validateName(String name) throws IllegalNameException {
94 NameValidator.validateClassName(name, !isInnerClass());
95 }
96
97 @Override
98 public ClassEntry withParent(ClassEntry parent) {
99 return new ClassEntry(parent, name);
100 }
101
102 @Override
103 public String toString() {
104 return getFullName();
105 }
106
107 public String getPackageName() {
108 return getPackageName(this.name);
109 }
110
111 public String getSimpleName() {
112 int packagePos = name.lastIndexOf('/');
113 if (packagePos > 0) {
114 return name.substring(packagePos + 1);
115 }
116 return name;
117 }
118
119 public boolean isInnerClass() {
120 return parent != null;
121 }
122
123 @Nullable
124 public ClassEntry getOuterClass() {
125 return parent;
126 }
127
128 public ClassEntry buildClassEntry(List<ClassEntry> classChain) {
129 assert (classChain.contains(this));
130 StringBuilder buf = new StringBuilder();
131 for (ClassEntry chainEntry : classChain) {
132 if (buf.length() == 0) {
133 buf.append(chainEntry.getFullName());
134 } else {
135 buf.append("$");
136 buf.append(chainEntry.getSimpleName());
137 }
138
139 if (chainEntry == this) {
140 break;
141 }
142 }
143 return new ClassEntry(buf.toString());
144 }
145
146 public boolean isJre() {
147 String packageName = getPackageName();
148 return packageName != null && (packageName.startsWith("java") || packageName.startsWith("javax"));
149 }
150
151 public static String getPackageName(String name) {
152 int pos = name.lastIndexOf('/');
153 if (pos > 0) {
154 return name.substring(0, pos);
155 }
156 return null;
157 }
158
159 @Nullable
160 public static ClassEntry getOuterClass(String name) {
161 int index = name.lastIndexOf('$');
162 if (index >= 0) {
163 return new ClassEntry(name.substring(0, index));
164 }
165 return null;
166 }
167
168 public static String getInnerName(String name) {
169 int innerClassPos = name.lastIndexOf('$');
170 if (innerClassPos > 0) {
171 return name.substring(innerClassPos + 1);
172 }
173 return name;
174 }
175
176 @Override
177 public int compareTo(ClassEntry entry) {
178 return name.compareTo(entry.name);
179 }
180}
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 @@
1package cuchaz.enigma.translation.representation.entry;
2
3import cuchaz.enigma.translation.representation.AccessFlags;
4
5public interface DefEntry<P extends Entry<?>> extends Entry<P> {
6 AccessFlags getAccess();
7}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.representation.entry;
13
14import cuchaz.enigma.throwables.IllegalNameException;
15import cuchaz.enigma.translation.Translatable;
16import cuchaz.enigma.translation.mapping.NameValidator;
17
18import javax.annotation.Nullable;
19import java.util.ArrayList;
20import java.util.List;
21
22public interface Entry<P extends Entry<?>> extends Translatable {
23 String getName();
24
25 @Nullable
26 P getParent();
27
28 Class<P> getParentType();
29
30 Entry<P> withParent(P parent);
31
32 boolean canConflictWith(Entry<?> entry);
33
34 @Nullable
35 default ClassEntry getContainingClass() {
36 P parent = getParent();
37 if (parent == null) {
38 return null;
39 }
40 if (parent instanceof ClassEntry) {
41 return (ClassEntry) parent;
42 }
43 return parent.getContainingClass();
44 }
45
46 default List<Entry<?>> getAncestry() {
47 P parent = getParent();
48 List<Entry<?>> entries = new ArrayList<>();
49 if (parent != null) {
50 entries.addAll(parent.getAncestry());
51 }
52 entries.add(this);
53 return entries;
54 }
55
56 @Nullable
57 @SuppressWarnings("unchecked")
58 default <E extends Entry<?>> E findAncestor(Class<E> type) {
59 List<Entry<?>> ancestry = getAncestry();
60 for (int i = ancestry.size() - 1; i >= 0; i--) {
61 Entry<?> ancestor = ancestry.get(i);
62 if (type.isAssignableFrom(ancestor.getClass())) {
63 return (E) ancestor;
64 }
65 }
66 return null;
67 }
68
69 @SuppressWarnings("unchecked")
70 default <E extends Entry<?>> Entry<P> replaceAncestor(E target, E replacement) {
71 if (replacement.equals(target)) {
72 return this;
73 }
74
75 if (equals(target)) {
76 return (Entry<P>) replacement;
77 }
78
79 P parent = getParent();
80 if (parent == null) {
81 return this;
82 }
83
84 return withParent((P) parent.replaceAncestor(target, replacement));
85 }
86
87 default void validateName(String name) throws IllegalNameException {
88 NameValidator.validateIdentifier(name);
89 }
90
91 @SuppressWarnings("unchecked")
92 @Nullable
93 default <C extends Entry<?>> Entry<C> castParent(Class<C> parentType) {
94 if (parentType.equals(getParentType())) {
95 return (Entry<C>) this;
96 }
97 return null;
98 }
99}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.representation.entry;
13
14import com.google.common.base.Preconditions;
15import cuchaz.enigma.translation.Translator;
16import cuchaz.enigma.translation.mapping.EntryMapping;
17import cuchaz.enigma.translation.representation.AccessFlags;
18import cuchaz.enigma.translation.representation.Signature;
19import cuchaz.enigma.translation.representation.TypeDescriptor;
20
21import javax.annotation.Nullable;
22
23public class FieldDefEntry extends FieldEntry implements DefEntry<ClassEntry> {
24 private final AccessFlags access;
25 private final Signature signature;
26
27 public FieldDefEntry(ClassEntry owner, String name, TypeDescriptor desc, Signature signature, AccessFlags access) {
28 super(owner, name, desc);
29 Preconditions.checkNotNull(access, "Field access cannot be null");
30 Preconditions.checkNotNull(signature, "Field signature cannot be null");
31 this.access = access;
32 this.signature = signature;
33 }
34
35 public static FieldDefEntry parse(ClassEntry owner, int access, String name, String desc, String signature) {
36 return new FieldDefEntry(owner, name, new TypeDescriptor(desc), Signature.createTypedSignature(signature), new AccessFlags(access));
37 }
38
39 @Override
40 public AccessFlags getAccess() {
41 return access;
42 }
43
44 public Signature getSignature() {
45 return signature;
46 }
47
48 @Override
49 public FieldDefEntry translate(Translator translator, @Nullable EntryMapping mapping) {
50 TypeDescriptor translatedDesc = translator.translate(desc);
51 Signature translatedSignature = translator.translate(signature);
52 String translatedName = mapping != null ? mapping.getTargetName() : name;
53 AccessFlags translatedAccess = mapping != null ? mapping.getAccessModifier().transform(access) : access;
54 return new FieldDefEntry(parent, translatedName, translatedDesc, translatedSignature, translatedAccess);
55 }
56
57 @Override
58 public FieldDefEntry withParent(ClassEntry owner) {
59 return new FieldDefEntry(owner, this.name, this.desc, signature, access);
60 }
61}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.representation.entry;
13
14import com.google.common.base.Preconditions;
15import cuchaz.enigma.translation.Translator;
16import cuchaz.enigma.translation.mapping.EntryMapping;
17import cuchaz.enigma.translation.representation.TypeDescriptor;
18import cuchaz.enigma.utils.Utils;
19
20import javax.annotation.Nullable;
21
22public class FieldEntry extends ParentedEntry<ClassEntry> implements Comparable<FieldEntry> {
23 protected final TypeDescriptor desc;
24
25 public FieldEntry(ClassEntry parent, String name, TypeDescriptor desc) {
26 super(parent, name);
27
28 Preconditions.checkNotNull(parent, "Owner cannot be null");
29 Preconditions.checkNotNull(desc, "Field descriptor cannot be null");
30
31 this.desc = desc;
32 }
33
34 public static FieldEntry parse(String owner, String name, String desc) {
35 return new FieldEntry(new ClassEntry(owner), name, new TypeDescriptor(desc));
36 }
37
38 @Override
39 public Class<ClassEntry> getParentType() {
40 return ClassEntry.class;
41 }
42
43 public TypeDescriptor getDesc() {
44 return this.desc;
45 }
46
47 @Override
48 public FieldEntry withParent(ClassEntry parent) {
49 return new FieldEntry(parent, this.name, this.desc);
50 }
51
52 @Override
53 protected FieldEntry translate(Translator translator, @Nullable EntryMapping mapping) {
54 String translatedName = mapping != null ? mapping.getTargetName() : name;
55 return new FieldEntry(parent, translatedName, translator.translate(desc));
56 }
57
58 @Override
59 public int hashCode() {
60 return Utils.combineHashesOrdered(this.parent, this.name, this.desc);
61 }
62
63 @Override
64 public boolean equals(Object other) {
65 return other instanceof FieldEntry && equals((FieldEntry) other);
66 }
67
68 public boolean equals(FieldEntry other) {
69 return this.parent.equals(other.parent) && name.equals(other.name) && desc.equals(other.desc);
70 }
71
72 @Override
73 public boolean canConflictWith(Entry<?> entry) {
74 return entry instanceof FieldEntry && ((FieldEntry) entry).parent.equals(parent);
75 }
76
77 @Override
78 public String toString() {
79 return this.parent.getFullName() + "." + this.name + ":" + this.desc;
80 }
81
82 @Override
83 public int compareTo(FieldEntry entry) {
84 return (name + desc.toString()).compareTo(entry.name + entry.desc.toString());
85 }
86}
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 @@
1package cuchaz.enigma.translation.representation.entry;
2
3import com.google.common.base.Preconditions;
4import cuchaz.enigma.translation.Translator;
5import cuchaz.enigma.translation.mapping.EntryMapping;
6import cuchaz.enigma.translation.representation.TypeDescriptor;
7
8import javax.annotation.Nullable;
9
10/**
11 * TypeDescriptor...
12 * Created by Thog
13 * 19/10/2016
14 */
15public class LocalVariableDefEntry extends LocalVariableEntry {
16 protected final TypeDescriptor desc;
17
18 public LocalVariableDefEntry(MethodEntry ownerEntry, int index, String name, boolean parameter, TypeDescriptor desc) {
19 super(ownerEntry, index, name, parameter);
20 Preconditions.checkNotNull(desc, "Variable desc cannot be null");
21
22 this.desc = desc;
23 }
24
25 public TypeDescriptor getDesc() {
26 return desc;
27 }
28
29 @Override
30 public LocalVariableDefEntry translate(Translator translator, @Nullable EntryMapping mapping) {
31 TypeDescriptor translatedDesc = translator.translate(desc);
32 String translatedName = mapping != null ? mapping.getTargetName() : name;
33 return new LocalVariableDefEntry(parent, index, translatedName, parameter, translatedDesc);
34 }
35
36 @Override
37 public LocalVariableDefEntry withParent(MethodEntry entry) {
38 return new LocalVariableDefEntry(entry, index, name, parameter, desc);
39 }
40
41 @Override
42 public String toString() {
43 return this.parent + "(" + this.index + ":" + this.name + ":" + this.desc + ")";
44 }
45}
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 @@
1package cuchaz.enigma.translation.representation.entry;
2
3import com.google.common.base.Preconditions;
4import cuchaz.enigma.translation.Translator;
5import cuchaz.enigma.translation.mapping.EntryMapping;
6import cuchaz.enigma.utils.Utils;
7
8import javax.annotation.Nullable;
9
10/**
11 * TypeDescriptor...
12 * Created by Thog
13 * 19/10/2016
14 */
15public class LocalVariableEntry extends ParentedEntry<MethodEntry> implements Comparable<LocalVariableEntry> {
16
17 protected final int index;
18 protected final boolean parameter;
19
20 @Deprecated
21 public LocalVariableEntry(MethodEntry parent, int index, String name) {
22 this(parent, index, name, true);
23 }
24
25 public LocalVariableEntry(MethodEntry parent, int index, String name, boolean parameter) {
26 super(parent, name);
27
28 Preconditions.checkNotNull(parent, "Variable owner cannot be null");
29 Preconditions.checkArgument(index >= 0, "Index must be positive");
30
31 this.index = index;
32 this.parameter = parameter;
33 }
34
35 @Override
36 public Class<MethodEntry> getParentType() {
37 return MethodEntry.class;
38 }
39
40 public boolean isParameter() {
41 return this.parameter;
42 }
43
44 public int getIndex() {
45 return index;
46 }
47
48 @Override
49 public String getName() {
50 return this.name;
51 }
52
53 @Override
54 public LocalVariableEntry translate(Translator translator, @Nullable EntryMapping mapping) {
55 String translatedName = mapping != null ? mapping.getTargetName() : name;
56 return new LocalVariableEntry(parent, index, translatedName, parameter);
57 }
58
59 @Override
60 public LocalVariableEntry withParent(MethodEntry parent) {
61 return new LocalVariableEntry(parent, index, name, parameter);
62 }
63
64 @Override
65 public int hashCode() {
66 return Utils.combineHashesOrdered(this.parent, this.index);
67 }
68
69 @Override
70 public boolean equals(Object other) {
71 return other instanceof LocalVariableEntry && equals((LocalVariableEntry) other);
72 }
73
74 public boolean equals(LocalVariableEntry other) {
75 return this.parent.equals(other.parent) && this.index == other.index;
76 }
77
78 @Override
79 public boolean canConflictWith(Entry<?> entry) {
80 return entry instanceof LocalVariableEntry && ((LocalVariableEntry) entry).parent.equals(parent);
81 }
82
83 @Override
84 public String toString() {
85 return this.parent + "(" + this.index + ":" + this.name + ")";
86 }
87
88 @Override
89 public int compareTo(LocalVariableEntry entry) {
90 return Integer.compare(index, entry.index);
91 }
92}
diff --git a/src/main/java/cuchaz/enigma/mapping/entry/MethodDefEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java
index fa9e668..3ecd470 100644
--- a/src/main/java/cuchaz/enigma/mapping/entry/MethodDefEntry.java
+++ b/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java
@@ -9,26 +9,33 @@
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11 11
12package cuchaz.enigma.mapping.entry; 12package cuchaz.enigma.translation.representation.entry;
13 13
14import com.google.common.base.Preconditions; 14import com.google.common.base.Preconditions;
15import cuchaz.enigma.bytecode.AccessFlags; 15import cuchaz.enigma.translation.Translator;
16import cuchaz.enigma.mapping.MethodDescriptor; 16import cuchaz.enigma.translation.mapping.EntryMapping;
17import cuchaz.enigma.mapping.Signature; 17import cuchaz.enigma.translation.representation.AccessFlags;
18import cuchaz.enigma.translation.representation.MethodDescriptor;
19import cuchaz.enigma.translation.representation.Signature;
18 20
19public class MethodDefEntry extends MethodEntry implements DefEntry { 21import javax.annotation.Nullable;
20 22
23public class MethodDefEntry extends MethodEntry implements DefEntry<ClassEntry> {
21 private final AccessFlags access; 24 private final AccessFlags access;
22 private final Signature signature; 25 private final Signature signature;
23 26
24 public MethodDefEntry(ClassEntry classEntry, String name, MethodDescriptor descriptor, Signature signature, AccessFlags access) { 27 public MethodDefEntry(ClassEntry owner, String name, MethodDescriptor descriptor, Signature signature, AccessFlags access) {
25 super(classEntry, name, descriptor); 28 super(owner, name, descriptor);
26 Preconditions.checkNotNull(access, "Method access cannot be null"); 29 Preconditions.checkNotNull(access, "Method access cannot be null");
27 Preconditions.checkNotNull(signature, "Method signature cannot be null"); 30 Preconditions.checkNotNull(signature, "Method signature cannot be null");
28 this.access = access; 31 this.access = access;
29 this.signature = signature; 32 this.signature = signature;
30 } 33 }
31 34
35 public static MethodDefEntry parse(ClassEntry owner, int access, String name, String desc, String signature) {
36 return new MethodDefEntry(owner, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access));
37 }
38
32 @Override 39 @Override
33 public AccessFlags getAccess() { 40 public AccessFlags getAccess() {
34 return access; 41 return access;
@@ -39,8 +46,17 @@ public class MethodDefEntry extends MethodEntry implements DefEntry {
39 } 46 }
40 47
41 @Override 48 @Override
42 public MethodDefEntry updateOwnership(ClassEntry classEntry) { 49 public MethodDefEntry translate(Translator translator, @Nullable EntryMapping mapping) {
43 return new MethodDefEntry(new ClassEntry(classEntry.getName()), name, descriptor, signature, access); 50 MethodDescriptor translatedDesc = translator.translate(descriptor);
51 Signature translatedSignature = translator.translate(signature);
52 String translatedName = mapping != null ? mapping.getTargetName() : name;
53 AccessFlags translatedAccess = mapping != null ? mapping.getAccessModifier().transform(access) : access;
54 return new MethodDefEntry(parent, translatedName, translatedDesc, translatedSignature, translatedAccess);
55 }
56
57 @Override
58 public MethodDefEntry withParent(ClassEntry parent) {
59 return new MethodDefEntry(new ClassEntry(parent.getFullName()), name, descriptor, signature, access);
44 } 60 }
45 61
46 public int getArgumentIndex(ClassDefEntry ownerEntry, int localVariableIndex) { 62 public int getArgumentIndex(ClassDefEntry ownerEntry, int localVariableIndex) {
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.representation.entry;
13
14import com.google.common.base.Preconditions;
15import cuchaz.enigma.translation.Translator;
16import cuchaz.enigma.translation.mapping.EntryMapping;
17import cuchaz.enigma.translation.representation.MethodDescriptor;
18import cuchaz.enigma.utils.Utils;
19
20import javax.annotation.Nullable;
21
22public class MethodEntry extends ParentedEntry<ClassEntry> implements Comparable<MethodEntry> {
23
24 protected final MethodDescriptor descriptor;
25
26 public MethodEntry(ClassEntry parent, String name, MethodDescriptor descriptor) {
27 super(parent, name);
28
29 Preconditions.checkNotNull(parent, "Parent cannot be null");
30 Preconditions.checkNotNull(descriptor, "Method descriptor cannot be null");
31
32 this.descriptor = descriptor;
33 }
34
35 public static MethodEntry parse(String owner, String name, String desc) {
36 return new MethodEntry(new ClassEntry(owner), name, new MethodDescriptor(desc));
37 }
38
39 @Override
40 public Class<ClassEntry> getParentType() {
41 return ClassEntry.class;
42 }
43
44 public MethodDescriptor getDesc() {
45 return this.descriptor;
46 }
47
48 public boolean isConstructor() {
49 return name.equals("<init>") || name.equals("<clinit>");
50 }
51
52 @Override
53 public MethodEntry translate(Translator translator, @Nullable EntryMapping mapping) {
54 String translatedName = mapping != null ? mapping.getTargetName() : name;
55 return new MethodEntry(parent, translatedName, translator.translate(descriptor));
56 }
57
58 @Override
59 public MethodEntry withParent(ClassEntry parent) {
60 return new MethodEntry(new ClassEntry(parent.getFullName()), name, descriptor);
61 }
62
63 @Override
64 public int hashCode() {
65 return Utils.combineHashesOrdered(this.parent, this.name, this.descriptor);
66 }
67
68 @Override
69 public boolean equals(Object other) {
70 return other instanceof MethodEntry && equals((MethodEntry) other);
71 }
72
73 public boolean equals(MethodEntry other) {
74 return this.parent.equals(other.getParent()) && this.name.equals(other.getName()) && this.descriptor.equals(other.getDesc());
75 }
76
77 @Override
78 public boolean canConflictWith(Entry<?> entry) {
79 if (entry instanceof MethodEntry) {
80 MethodEntry methodEntry = (MethodEntry) entry;
81 return methodEntry.parent.equals(parent) && methodEntry.descriptor.canConflictWith(descriptor);
82 }
83 return false;
84 }
85
86 @Override
87 public String toString() {
88 return this.parent.getFullName() + "." + this.name + this.descriptor;
89 }
90
91 @Override
92 public int compareTo(MethodEntry entry) {
93 return (name + descriptor.toString()).compareTo(entry.name + entry.descriptor.toString());
94 }
95}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.representation.entry;
13
14import com.google.common.base.Preconditions;
15import cuchaz.enigma.translation.Translatable;
16import cuchaz.enigma.translation.Translator;
17import cuchaz.enigma.translation.mapping.EntryMap;
18import cuchaz.enigma.translation.mapping.EntryMapping;
19import cuchaz.enigma.translation.mapping.EntryResolver;
20import cuchaz.enigma.translation.mapping.ResolutionStrategy;
21
22import javax.annotation.Nullable;
23
24public abstract class ParentedEntry<P extends Entry<?>> implements Entry<P> {
25 protected final P parent;
26 protected final String name;
27
28 protected ParentedEntry(P parent, String name) {
29 this.parent = parent;
30 this.name = name;
31
32 Preconditions.checkNotNull(name, "Name cannot be null");
33 }
34
35 @Override
36 public abstract ParentedEntry<P> withParent(P parent);
37
38 protected abstract ParentedEntry<P> translate(Translator translator, @Nullable EntryMapping mapping);
39
40 @Override
41 public String getName() {
42 return name;
43 }
44
45 @Override
46 @Nullable
47 public P getParent() {
48 return parent;
49 }
50
51 @Override
52 public Translatable translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) {
53 P parent = getParent();
54 EntryMapping mapping = resolveMapping(resolver, mappings);
55 if (parent == null) {
56 return translate(translator, mapping);
57 }
58 P translatedParent = translator.translate(parent);
59 return withParent(translatedParent).translate(translator, mapping);
60 }
61
62 private EntryMapping resolveMapping(EntryResolver resolver, EntryMap<EntryMapping> mappings) {
63 for (ParentedEntry<P> entry : resolver.resolveEntry(this, ResolutionStrategy.RESOLVE_ROOT)) {
64 EntryMapping mapping = mappings.get(entry);
65 if (mapping != null) {
66 return mapping;
67 }
68 }
69 return null;
70 }
71}