summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar jeff2015-02-08 21:29:25 -0500
committerGravatar jeff2015-02-08 21:29:25 -0500
commited9b5cdfc648e86fd463bfa8d86b94c41671e14c (patch)
tree2619bbc7e04dfa3b82f8dfd3b1d31f529766cd4b /src
downloadenigma-ed9b5cdfc648e86fd463bfa8d86b94c41671e14c.tar.gz
enigma-ed9b5cdfc648e86fd463bfa8d86b94c41671e14c.tar.xz
enigma-ed9b5cdfc648e86fd463bfa8d86b94c41671e14c.zip
switch all classes to new signature/type system
Diffstat (limited to 'src')
-rw-r--r--src/cuchaz/enigma/CommandMain.java136
-rw-r--r--src/cuchaz/enigma/Constants.java20
-rw-r--r--src/cuchaz/enigma/Deobfuscator.java539
-rw-r--r--src/cuchaz/enigma/Main.java51
-rw-r--r--src/cuchaz/enigma/TranslatingTypeLoader.java211
-rw-r--r--src/cuchaz/enigma/Util.java104
-rw-r--r--src/cuchaz/enigma/analysis/Access.java43
-rw-r--r--src/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java93
-rw-r--r--src/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java80
-rw-r--r--src/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java85
-rw-r--r--src/cuchaz/enigma/analysis/EntryReference.java126
-rw-r--r--src/cuchaz/enigma/analysis/EntryRenamer.java171
-rw-r--r--src/cuchaz/enigma/analysis/FieldReferenceTreeNode.java81
-rw-r--r--src/cuchaz/enigma/analysis/JarClassIterator.java137
-rw-r--r--src/cuchaz/enigma/analysis/JarIndex.java734
-rw-r--r--src/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java100
-rw-r--r--src/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java114
-rw-r--r--src/cuchaz/enigma/analysis/ReferenceTreeNode.java18
-rw-r--r--src/cuchaz/enigma/analysis/SourceIndex.java173
-rw-r--r--src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java164
-rw-r--r--src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java115
-rw-r--r--src/cuchaz/enigma/analysis/SourceIndexVisitor.java452
-rw-r--r--src/cuchaz/enigma/analysis/Token.java56
-rw-r--r--src/cuchaz/enigma/analysis/TranslationIndex.java227
-rw-r--r--src/cuchaz/enigma/analysis/TreeDumpVisitor.java512
-rw-r--r--src/cuchaz/enigma/bytecode/CheckCastIterator.java127
-rw-r--r--src/cuchaz/enigma/bytecode/ClassRenamer.java110
-rw-r--r--src/cuchaz/enigma/bytecode/ClassTranslator.java144
-rw-r--r--src/cuchaz/enigma/bytecode/ConstPoolEditor.java263
-rw-r--r--src/cuchaz/enigma/bytecode/InfoType.java317
-rw-r--r--src/cuchaz/enigma/bytecode/InnerClassWriter.java102
-rw-r--r--src/cuchaz/enigma/bytecode/MethodParameterWriter.java53
-rw-r--r--src/cuchaz/enigma/bytecode/MethodParametersAttribute.java85
-rw-r--r--src/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java55
-rw-r--r--src/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java156
-rw-r--r--src/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java74
-rw-r--r--src/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java74
-rw-r--r--src/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java74
-rw-r--r--src/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java55
-rw-r--r--src/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java74
-rw-r--r--src/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java55
-rw-r--r--src/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java28
-rw-r--r--src/cuchaz/enigma/convert/ClassIdentity.java411
-rw-r--r--src/cuchaz/enigma/convert/ClassMatcher.java406
-rw-r--r--src/cuchaz/enigma/convert/ClassMatching.java173
-rw-r--r--src/cuchaz/enigma/convert/ClassNamer.java64
-rw-r--r--src/cuchaz/enigma/gui/AboutDialog.java86
-rw-r--r--src/cuchaz/enigma/gui/BoxHighlightPainter.java64
-rw-r--r--src/cuchaz/enigma/gui/BrowserCaret.java45
-rw-r--r--src/cuchaz/enigma/gui/ClassListCellRenderer.java36
-rw-r--r--src/cuchaz/enigma/gui/ClassSelector.java164
-rw-r--r--src/cuchaz/enigma/gui/ClassSelectorClassNode.java35
-rw-r--r--src/cuchaz/enigma/gui/ClassSelectorPackageNode.java33
-rw-r--r--src/cuchaz/enigma/gui/CrashDialog.java101
-rw-r--r--src/cuchaz/enigma/gui/DeobfuscatedHighlightPainter.java21
-rw-r--r--src/cuchaz/enigma/gui/Gui.java1165
-rw-r--r--src/cuchaz/enigma/gui/GuiController.java355
-rw-r--r--src/cuchaz/enigma/gui/GuiTricks.java36
-rw-r--r--src/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java21
-rw-r--r--src/cuchaz/enigma/gui/OtherHighlightPainter.java21
-rw-r--r--src/cuchaz/enigma/gui/ProgressDialog.java105
-rw-r--r--src/cuchaz/enigma/gui/ReadableToken.java36
-rw-r--r--src/cuchaz/enigma/gui/RenameListener.java17
-rw-r--r--src/cuchaz/enigma/gui/SelectionHighlightPainter.java34
-rw-r--r--src/cuchaz/enigma/gui/TokenListCellRenderer.java38
-rw-r--r--src/cuchaz/enigma/mapping/ArgumentEntry.java116
-rw-r--r--src/cuchaz/enigma/mapping/ArgumentMapping.java44
-rw-r--r--src/cuchaz/enigma/mapping/BehaviorEntry.java15
-rw-r--r--src/cuchaz/enigma/mapping/BehaviorEntryFactory.java57
-rw-r--r--src/cuchaz/enigma/mapping/ClassEntry.java123
-rw-r--r--src/cuchaz/enigma/mapping/ClassMapping.java405
-rw-r--r--src/cuchaz/enigma/mapping/ClassNameReplacer.java5
-rw-r--r--src/cuchaz/enigma/mapping/ConstructorEntry.java116
-rw-r--r--src/cuchaz/enigma/mapping/Entry.java18
-rw-r--r--src/cuchaz/enigma/mapping/EntryPair.java22
-rw-r--r--src/cuchaz/enigma/mapping/FieldEntry.java88
-rw-r--r--src/cuchaz/enigma/mapping/FieldMapping.java43
-rw-r--r--src/cuchaz/enigma/mapping/IllegalNameException.java44
-rw-r--r--src/cuchaz/enigma/mapping/JavassistUtil.java83
-rw-r--r--src/cuchaz/enigma/mapping/MappingParseException.java29
-rw-r--r--src/cuchaz/enigma/mapping/Mappings.java188
-rw-r--r--src/cuchaz/enigma/mapping/MappingsReader.java175
-rw-r--r--src/cuchaz/enigma/mapping/MappingsRenamer.java237
-rw-r--r--src/cuchaz/enigma/mapping/MappingsWriter.java88
-rw-r--r--src/cuchaz/enigma/mapping/MethodEntry.java104
-rw-r--r--src/cuchaz/enigma/mapping/MethodMapping.java161
-rw-r--r--src/cuchaz/enigma/mapping/NameValidator.java80
-rw-r--r--src/cuchaz/enigma/mapping/Signature.java109
-rw-r--r--src/cuchaz/enigma/mapping/SignatureUpdater.java94
-rw-r--r--src/cuchaz/enigma/mapping/TranslationDirection.java29
-rw-r--r--src/cuchaz/enigma/mapping/Translator.java239
-rw-r--r--src/cuchaz/enigma/mapping/Type.java218
92 files changed, 12785 insertions, 0 deletions
diff --git a/src/cuchaz/enigma/CommandMain.java b/src/cuchaz/enigma/CommandMain.java
new file mode 100644
index 00000000..1ec2ad23
--- /dev/null
+++ b/src/cuchaz/enigma/CommandMain.java
@@ -0,0 +1,136 @@
1package cuchaz.enigma;
2
3import java.io.File;
4import java.io.FileReader;
5import java.util.jar.JarFile;
6
7import cuchaz.enigma.Deobfuscator.ProgressListener;
8import cuchaz.enigma.mapping.Mappings;
9import cuchaz.enigma.mapping.MappingsReader;
10
11public class CommandMain {
12
13 public static class ConsoleProgressListener implements ProgressListener {
14
15 private static final int ReportTime = 5000; // 5s
16
17 private int m_totalWork;
18 private long m_startTime;
19 private long m_lastReportTime;
20
21 @Override
22 public void init(int totalWork, String title) {
23 m_totalWork = totalWork;
24 m_startTime = System.currentTimeMillis();
25 m_lastReportTime = m_startTime;
26 System.out.println(title);
27 }
28
29 @Override
30 public void onProgress(int numDone, String message) {
31
32 long now = System.currentTimeMillis();
33 boolean isLastUpdate = numDone == m_totalWork;
34 boolean shouldReport = isLastUpdate || now - m_lastReportTime > ReportTime;
35
36 if (shouldReport) {
37 int percent = numDone*100/m_totalWork;
38 System.out.println(String.format("\tProgress: %3d%%", percent));
39 m_lastReportTime = now;
40 }
41 if (isLastUpdate) {
42 double elapsedSeconds = (now - m_startTime)/1000;
43 System.out.println(String.format("Finished in %.1f seconds", elapsedSeconds));
44 }
45 }
46 }
47
48 public static void main(String[] args)
49 throws Exception {
50
51 try {
52
53 // process the command
54 String command = getArg(args, 0, "command");
55 if (command.equalsIgnoreCase("deobfuscate")) {
56 deobfuscate(args);
57 } else if(command.equalsIgnoreCase("decompile")) {
58 decompile(args);
59 } else {
60 throw new IllegalArgumentException("Command not recognized: " + command);
61 }
62 } catch (IllegalArgumentException ex) {
63 System.out.println(ex.getMessage());
64 printHelp();
65 }
66 }
67
68 private static void printHelp() {
69 System.out.println(String.format("%s - %s", Constants.Name, Constants.Version));
70 System.out.println("Usage:");
71 System.out.println("\tjava -cp enigma.jar cuchaz.enigma.CommandMain <command>");
72 System.out.println("\twhere <command> is one of:");
73 System.out.println("\t\tdeobfuscate <mappings file> <in jar> <out jar>");
74 System.out.println("\t\tdecompile <mappings file> <in jar> <out folder>");
75 }
76
77 private static void decompile(String[] args)
78 throws Exception {
79 File fileMappings = getReadableFile(getArg(args, 1, "mappings file"));
80 File fileJarIn = getReadableFile(getArg(args, 2, "in jar"));
81 File fileJarOut = getWritableFolder(getArg(args, 3, "out folder"));
82 Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn));
83 deobfuscator.writeSources(fileJarOut, new ConsoleProgressListener());
84 }
85
86 private static void deobfuscate(String[] args)
87 throws Exception {
88 File fileMappings = getReadableFile(getArg(args, 1, "mappings file"));
89 File fileJarIn = getReadableFile(getArg(args, 2, "in jar"));
90 File fileJarOut = getWritableFile(getArg(args, 3, "out jar"));
91 Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn));
92 deobfuscator.writeJar(fileJarOut, new ConsoleProgressListener());
93 }
94
95 private static Deobfuscator getDeobfuscator(File fileMappings, JarFile jar)
96 throws Exception {
97 System.out.println("Reading mappings...");
98 Mappings mappings = new MappingsReader().read(new FileReader(fileMappings));
99 System.out.println("Reading jar...");
100 Deobfuscator deobfuscator = new Deobfuscator(jar);
101 deobfuscator.setMappings(mappings);
102 return deobfuscator;
103 }
104
105 private static String getArg(String[] args, int i, String name) {
106 if (i >= args.length) {
107 throw new IllegalArgumentException(name + " is required");
108 }
109 return args[i];
110 }
111
112 private static File getWritableFile(String path) {
113 File file = new File(path).getAbsoluteFile();
114 File dir = file.getParentFile();
115 if (dir == null || !dir.exists()) {
116 throw new IllegalArgumentException("Cannot write to folder: " + file);
117 }
118 return file;
119 }
120
121 private static File getWritableFolder(String path) {
122 File dir = new File(path).getAbsoluteFile();
123 if (!dir.exists()) {
124 throw new IllegalArgumentException("Cannot write to folder: " + dir);
125 }
126 return dir;
127 }
128
129 private static File getReadableFile(String path) {
130 File file = new File(path).getAbsoluteFile();
131 if (!file.exists()) {
132 throw new IllegalArgumentException("Cannot find file: " + file.getAbsolutePath());
133 }
134 return file;
135 }
136}
diff --git a/src/cuchaz/enigma/Constants.java b/src/cuchaz/enigma/Constants.java
new file mode 100644
index 00000000..a1ba2e98
--- /dev/null
+++ b/src/cuchaz/enigma/Constants.java
@@ -0,0 +1,20 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13public class Constants {
14 public static final String Name = "Enigma";
15 public static final String Version = "0.6 beta";
16 public static final String Url = "http://www.cuchazinteractive.com/enigma";
17 public static final int MiB = 1024 * 1024; // 1 mebibyte
18 public static final int KiB = 1024; // 1 kebibyte
19 public static final String NonePackage = "none";
20}
diff --git a/src/cuchaz/enigma/Deobfuscator.java b/src/cuchaz/enigma/Deobfuscator.java
new file mode 100644
index 00000000..5f61686b
--- /dev/null
+++ b/src/cuchaz/enigma/Deobfuscator.java
@@ -0,0 +1,539 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.\
3 *
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the GNU Public License v3.0
6 * which accompanies this distribution, and is available at
7 * http://www.gnu.org/licenses/gpl.html
8 *
9 * Contributors:
10 * Jeff Martin - initial API and implementation
11 ******************************************************************************/
12package cuchaz.enigma;
13
14import java.io.File;
15import java.io.FileOutputStream;
16import java.io.FileWriter;
17import java.io.IOException;
18import java.io.StringWriter;
19import java.util.List;
20import java.util.Map;
21import java.util.Set;
22import java.util.jar.JarEntry;
23import java.util.jar.JarFile;
24import java.util.jar.JarOutputStream;
25
26import javassist.CtClass;
27import javassist.bytecode.Descriptor;
28
29import com.google.common.collect.Lists;
30import com.google.common.collect.Maps;
31import com.google.common.collect.Sets;
32import com.strobel.assembler.metadata.MetadataSystem;
33import com.strobel.assembler.metadata.TypeDefinition;
34import com.strobel.decompiler.DecompilerContext;
35import com.strobel.decompiler.DecompilerSettings;
36import com.strobel.decompiler.PlainTextOutput;
37import com.strobel.decompiler.languages.java.JavaOutputVisitor;
38import com.strobel.decompiler.languages.java.ast.AstBuilder;
39import com.strobel.decompiler.languages.java.ast.CompilationUnit;
40import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor;
41
42import cuchaz.enigma.analysis.EntryReference;
43import cuchaz.enigma.analysis.JarClassIterator;
44import cuchaz.enigma.analysis.JarIndex;
45import cuchaz.enigma.analysis.SourceIndex;
46import cuchaz.enigma.analysis.SourceIndexVisitor;
47import cuchaz.enigma.analysis.Token;
48import cuchaz.enigma.mapping.ArgumentEntry;
49import cuchaz.enigma.mapping.BehaviorEntry;
50import cuchaz.enigma.mapping.BehaviorEntryFactory;
51import cuchaz.enigma.mapping.ClassEntry;
52import cuchaz.enigma.mapping.ClassMapping;
53import cuchaz.enigma.mapping.ConstructorEntry;
54import cuchaz.enigma.mapping.Entry;
55import cuchaz.enigma.mapping.FieldEntry;
56import cuchaz.enigma.mapping.FieldMapping;
57import cuchaz.enigma.mapping.Mappings;
58import cuchaz.enigma.mapping.MappingsRenamer;
59import cuchaz.enigma.mapping.MethodEntry;
60import cuchaz.enigma.mapping.MethodMapping;
61import cuchaz.enigma.mapping.TranslationDirection;
62import cuchaz.enigma.mapping.Translator;
63
64public class Deobfuscator {
65
66 public interface ProgressListener {
67 void init(int totalWork, String title);
68 void onProgress(int numDone, String message);
69 }
70
71 private JarFile m_jar;
72 private DecompilerSettings m_settings;
73 private JarIndex m_jarIndex;
74 private Mappings m_mappings;
75 private MappingsRenamer m_renamer;
76 private Map<TranslationDirection,Translator> m_translatorCache;
77
78 public Deobfuscator(JarFile jar) throws IOException {
79 m_jar = jar;
80
81 // build the jar index
82 m_jarIndex = new JarIndex();
83 m_jarIndex.indexJar(m_jar, true);
84
85 // config the decompiler
86 m_settings = DecompilerSettings.javaDefaults();
87 m_settings.setMergeVariables(true);
88 m_settings.setForceExplicitImports(true);
89 m_settings.setForceExplicitTypeArguments(true);
90 // DEBUG
91 //m_settings.setShowSyntheticMembers(true);
92
93 // init defaults
94 m_translatorCache = Maps.newTreeMap();
95
96 // init mappings
97 setMappings(new Mappings());
98 }
99
100 public String getJarName() {
101 return m_jar.getName();
102 }
103
104 public JarIndex getJarIndex() {
105 return m_jarIndex;
106 }
107
108 public Mappings getMappings() {
109 return m_mappings;
110 }
111
112 public void setMappings(Mappings val) {
113 if (val == null) {
114 val = new Mappings();
115 }
116
117 // pass 1: look for any classes that got moved to inner classes
118 Map<String,String> renames = Maps.newHashMap();
119 for (ClassMapping classMapping : val.classes()) {
120 // make sure we strip the packages off of obfuscated inner classes
121 String innerClassName = new ClassEntry(classMapping.getObfName()).getSimpleName();
122 String outerClassName = m_jarIndex.getOuterClass(innerClassName);
123 if (outerClassName != null) {
124 // build the composite class name
125 String newName = outerClassName + "$" + innerClassName;
126
127 // add a rename
128 renames.put(classMapping.getObfName(), newName);
129
130 System.out.println(String.format("Converted class mapping %s to %s", classMapping.getObfName(), newName));
131 }
132 }
133 for (Map.Entry<String,String> entry : renames.entrySet()) {
134 val.renameObfClass(entry.getKey(), entry.getValue());
135 }
136
137 // pass 2: look for fields/methods that are actually declared in superclasses
138 MappingsRenamer renamer = new MappingsRenamer(m_jarIndex, val);
139 for (ClassMapping classMapping : Lists.newArrayList(val.classes())) {
140 ClassEntry obfClassEntry = new ClassEntry(classMapping.getObfName());
141
142 // fields
143 for (FieldMapping fieldMapping : Lists.newArrayList(classMapping.fields())) {
144 FieldEntry fieldEntry = new FieldEntry(obfClassEntry, fieldMapping.getObfName());
145 ClassEntry resolvedObfClassEntry = m_jarIndex.getTranslationIndex().resolveEntryClass(fieldEntry);
146 if (resolvedObfClassEntry != null && !resolvedObfClassEntry.equals(fieldEntry.getClassEntry())) {
147 boolean wasMoved = renamer.moveFieldToObfClass(classMapping, fieldMapping, resolvedObfClassEntry);
148 if (wasMoved) {
149 System.out.println(String.format("Moved field %s to class %s", fieldEntry, resolvedObfClassEntry));
150 } else {
151 System.err.println(String.format("WARNING: Would move field %s to class %s but the field was already there. Dropping instead.", fieldEntry, resolvedObfClassEntry));
152 }
153 }
154 }
155
156 // methods
157 for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) {
158 // skip constructors
159 if (methodMapping.isConstructor()) {
160 continue;
161 }
162
163 MethodEntry methodEntry = new MethodEntry(
164 obfClassEntry,
165 methodMapping.getObfName(),
166 methodMapping.getObfSignature()
167 );
168 ClassEntry resolvedObfClassEntry = m_jarIndex.getTranslationIndex().resolveEntryClass(methodEntry);
169 if (resolvedObfClassEntry != null && !resolvedObfClassEntry.equals(methodEntry.getClassEntry())) {
170 boolean wasMoved = renamer.moveMethodToObfClass(classMapping, methodMapping, resolvedObfClassEntry);
171 if (wasMoved) {
172 System.out.println(String.format("Moved method %s to class %s", methodEntry, resolvedObfClassEntry));
173 } else {
174 System.err.println(String.format("WARNING: Would move method %s to class %s but the method was already there. Dropping instead.", methodEntry, resolvedObfClassEntry));
175 }
176 }
177 }
178
179 // TODO: recurse to inner classes?
180 }
181
182 // drop mappings that don't match the jar
183 List<ClassEntry> unknownClasses = Lists.newArrayList();
184 for (ClassMapping classMapping : val.classes()) {
185 checkClassMapping(unknownClasses, classMapping);
186 }
187 if (!unknownClasses.isEmpty()) {
188 throw new Error("Unable to find classes in jar: " + unknownClasses);
189 }
190
191 m_mappings = val;
192 m_renamer = renamer;
193 m_translatorCache.clear();
194 }
195
196 private void checkClassMapping(List<ClassEntry> unknownClasses, ClassMapping classMapping) {
197 // check the class
198 ClassEntry classEntry = new ClassEntry(classMapping.getObfName());
199 String outerClassName = m_jarIndex.getOuterClass(classEntry.getSimpleName());
200 if (outerClassName != null) {
201 classEntry = new ClassEntry(outerClassName + "$" + classMapping.getObfName());
202 }
203 if (!m_jarIndex.getObfClassEntries().contains(classEntry)) {
204 unknownClasses.add(classEntry);
205 }
206
207 // check the fields
208 for (FieldMapping fieldMapping : Lists.newArrayList(classMapping.fields())) {
209 FieldEntry fieldEntry = new FieldEntry(classEntry, fieldMapping.getObfName());
210 if (!m_jarIndex.containsObfField(fieldEntry)) {
211 System.err.println("WARNING: unable to find field " + fieldEntry + ". dropping mapping.");
212 classMapping.removeFieldMapping(fieldMapping);
213 }
214 }
215
216 // check methods
217 for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) {
218 BehaviorEntry obfBehaviorEntry = BehaviorEntryFactory.createObf(classEntry, methodMapping);
219 if (!m_jarIndex.containsObfBehavior(obfBehaviorEntry)) {
220 System.err.println("WARNING: unable to find behavior " + obfBehaviorEntry + ". dropping mapping.");
221 classMapping.removeMethodMapping(methodMapping);
222 }
223 }
224
225 // check inner classes
226 for (ClassMapping innerClassMapping : classMapping.innerClasses()) {
227 checkClassMapping(unknownClasses, innerClassMapping);
228 }
229 }
230
231 public Translator getTranslator(TranslationDirection direction) {
232 Translator translator = m_translatorCache.get(direction);
233 if (translator == null) {
234 translator = m_mappings.getTranslator(direction, m_jarIndex.getTranslationIndex());
235 m_translatorCache.put(direction, translator);
236 }
237 return translator;
238 }
239
240 public void getSeparatedClasses(List<ClassEntry> obfClasses, List<ClassEntry> deobfClasses) {
241 for (ClassEntry obfClassEntry : m_jarIndex.getObfClassEntries()) {
242 // skip inner classes
243 if (obfClassEntry.isInnerClass()) {
244 continue;
245 }
246
247 // separate the classes
248 ClassEntry deobfClassEntry = deobfuscateEntry(obfClassEntry);
249 if (!deobfClassEntry.equals(obfClassEntry)) {
250 // if the class has a mapping, clearly it's deobfuscated
251 deobfClasses.add(deobfClassEntry);
252 } else if (!obfClassEntry.getPackageName().equals(Constants.NonePackage)) {
253 // also call it deobufscated if it's not in the none package
254 deobfClasses.add(obfClassEntry);
255 } else {
256 // otherwise, assume it's still obfuscated
257 obfClasses.add(obfClassEntry);
258 }
259 }
260 }
261
262 public CompilationUnit getSourceTree(String obfClassName) {
263 // is this class deobfuscated?
264 // we need to tell the decompiler the deobfuscated name so it doesn't get freaked out
265 // the decompiler only sees the deobfuscated class, so we need to load it by the deobfuscated name
266 String lookupClassName = obfClassName;
267 ClassMapping classMapping = m_mappings.getClassByObf(obfClassName);
268 if (classMapping != null && classMapping.getDeobfName() != null) {
269 lookupClassName = classMapping.getDeobfName();
270 }
271
272 // is this class even in the jar?
273 if (!m_jarIndex.containsObfClass(new ClassEntry(obfClassName))) {
274 return null;
275 }
276
277 // set the type loader
278 m_settings.setTypeLoader(new TranslatingTypeLoader(
279 m_jar,
280 m_jarIndex,
281 getTranslator(TranslationDirection.Obfuscating),
282 getTranslator(TranslationDirection.Deobfuscating)
283 ));
284
285 // decompile it!
286 TypeDefinition resolvedType = new MetadataSystem(m_settings.getTypeLoader()).lookupType(lookupClassName).resolve();
287 DecompilerContext context = new DecompilerContext();
288 context.setCurrentType(resolvedType);
289 context.setSettings(m_settings);
290 AstBuilder builder = new AstBuilder(context);
291 builder.addType(resolvedType);
292 builder.runTransformations(null);
293 return builder.getCompilationUnit();
294 }
295
296 public SourceIndex getSourceIndex(CompilationUnit sourceTree, String source) {
297 // build the source index
298 SourceIndex index = new SourceIndex(source);
299 sourceTree.acceptVisitor(new SourceIndexVisitor(), index);
300
301 // DEBUG
302 // sourceTree.acceptVisitor( new TreeDumpVisitor( new File( "tree.txt" ) ), null );
303
304 // resolve all the classes in the source references
305 for (Token token : index.referenceTokens()) {
306 EntryReference<Entry,Entry> deobfReference = index.getDeobfReference(token);
307
308 // get the obfuscated entry
309 Entry obfEntry = obfuscateEntry(deobfReference.entry);
310
311 // try to resolve the class
312 ClassEntry resolvedObfClassEntry = m_jarIndex.getTranslationIndex().resolveEntryClass(obfEntry);
313 if (resolvedObfClassEntry != null && !resolvedObfClassEntry.equals(obfEntry.getClassEntry())) {
314 // change the class of the entry
315 obfEntry = obfEntry.cloneToNewClass(resolvedObfClassEntry);
316
317 // save the new deobfuscated reference
318 deobfReference.entry = deobfuscateEntry(obfEntry);
319 index.replaceDeobfReference(token, deobfReference);
320 }
321
322 // DEBUG
323 // System.out.println( token + " -> " + reference + " -> " + index.getReferenceToken( reference ) );
324 }
325
326 return index;
327 }
328
329 public String getSource(CompilationUnit sourceTree) {
330 // render the AST into source
331 StringWriter buf = new StringWriter();
332 sourceTree.acceptVisitor(new InsertParenthesesVisitor(), null);
333 sourceTree.acceptVisitor(new JavaOutputVisitor(new PlainTextOutput(buf), m_settings), null);
334 return buf.toString();
335 }
336
337 public void writeSources(File dirOut, ProgressListener progress) throws IOException {
338 // get the classes to decompile
339 Set<ClassEntry> classEntries = Sets.newHashSet();
340 for (ClassEntry obfClassEntry : m_jarIndex.getObfClassEntries()) {
341 // skip inner classes
342 if (obfClassEntry.isInnerClass()) {
343 continue;
344 }
345
346 classEntries.add(obfClassEntry);
347 }
348
349 if (progress != null) {
350 progress.init(classEntries.size(), "Decompiling classes...");
351 }
352
353 // DEOBFUSCATE ALL THE THINGS!! @_@
354 int i = 0;
355 for (ClassEntry obfClassEntry : classEntries) {
356 ClassEntry deobfClassEntry = deobfuscateEntry(new ClassEntry(obfClassEntry));
357 if (progress != null) {
358 progress.onProgress(i++, deobfClassEntry.toString());
359 }
360
361 try {
362 // get the source
363 String source = getSource(getSourceTree(obfClassEntry.getName()));
364
365 // write the file
366 File file = new File(dirOut, deobfClassEntry.getName().replace('.', '/') + ".java");
367 file.getParentFile().mkdirs();
368 try (FileWriter out = new FileWriter(file)) {
369 out.write(source);
370 }
371 } catch (Throwable t) {
372 throw new Error("Unable to deobfuscate class " + deobfClassEntry.toString() + " (" + obfClassEntry.toString() + ")", t);
373 }
374 }
375 if (progress != null) {
376 progress.onProgress(i, "Done!");
377 }
378 }
379
380 public void writeJar(File out, ProgressListener progress) {
381 try (JarOutputStream outJar = new JarOutputStream(new FileOutputStream(out))) {
382 if (progress != null) {
383 progress.init(JarClassIterator.getClassEntries(m_jar).size(), "Translating classes...");
384 }
385
386 // prep the loader
387 TranslatingTypeLoader loader = new TranslatingTypeLoader(
388 m_jar,
389 m_jarIndex,
390 getTranslator(TranslationDirection.Obfuscating),
391 getTranslator(TranslationDirection.Deobfuscating)
392 );
393
394 int i = 0;
395 for (CtClass c : JarClassIterator.classes(m_jar)) {
396 if (progress != null) {
397 progress.onProgress(i++, c.getName());
398 }
399
400 try {
401 c = loader.transformClass(c);
402 outJar.putNextEntry(new JarEntry(c.getName().replace('.', '/') + ".class"));
403 outJar.write(c.toBytecode());
404 outJar.closeEntry();
405 } catch (Throwable t) {
406 throw new Error("Unable to deobfuscate class " + c.getName(), t);
407 }
408 }
409 if (progress != null) {
410 progress.onProgress(i, "Done!");
411 }
412
413 outJar.close();
414 } catch (IOException ex) {
415 throw new Error("Unable to write to Jar file!");
416 }
417 }
418
419 public <T extends Entry> T obfuscateEntry(T deobfEntry) {
420 if (deobfEntry == null) {
421 return null;
422 }
423 return getTranslator(TranslationDirection.Obfuscating).translateEntry(deobfEntry);
424 }
425
426 public <T extends Entry> T deobfuscateEntry(T obfEntry) {
427 if (obfEntry == null) {
428 return null;
429 }
430 return getTranslator(TranslationDirection.Deobfuscating).translateEntry(obfEntry);
431 }
432
433 public <E extends Entry,C extends Entry> EntryReference<E,C> obfuscateReference(EntryReference<E,C> deobfReference) {
434 if (deobfReference == null) {
435 return null;
436 }
437 return new EntryReference<E,C>(
438 obfuscateEntry(deobfReference.entry),
439 obfuscateEntry(deobfReference.context),
440 deobfReference
441 );
442 }
443
444 public <E extends Entry,C extends Entry> EntryReference<E,C> deobfuscateReference(EntryReference<E,C> obfReference) {
445 if (obfReference == null) {
446 return null;
447 }
448 return new EntryReference<E,C>(
449 deobfuscateEntry(obfReference.entry),
450 deobfuscateEntry(obfReference.context),
451 obfReference
452 );
453 }
454
455 public boolean isObfuscatedIdentifier(Entry obfEntry) {
456 return m_jarIndex.containsObfEntry(obfEntry);
457 }
458
459 public boolean isRenameable(EntryReference<Entry,Entry> obfReference) {
460 return obfReference.isNamed() && isObfuscatedIdentifier(obfReference.getNameableEntry());
461 }
462
463 // NOTE: these methods are a bit messy... oh well
464
465 public boolean hasDeobfuscatedName(Entry obfEntry) {
466 Translator translator = getTranslator(TranslationDirection.Deobfuscating);
467 if (obfEntry instanceof ClassEntry) {
468 return translator.translate((ClassEntry)obfEntry) != null;
469 } else if (obfEntry instanceof FieldEntry) {
470 return translator.translate((FieldEntry)obfEntry) != null;
471 } else if (obfEntry instanceof MethodEntry) {
472 return translator.translate((MethodEntry)obfEntry) != null;
473 } else if (obfEntry instanceof ConstructorEntry) {
474 // constructors have no names
475 return false;
476 } else if (obfEntry instanceof ArgumentEntry) {
477 return translator.translate((ArgumentEntry)obfEntry) != null;
478 } else {
479 throw new Error("Unknown entry type: " + obfEntry.getClass().getName());
480 }
481 }
482
483 public void rename(Entry obfEntry, String newName) {
484 if (obfEntry instanceof ClassEntry) {
485 m_renamer.setClassName((ClassEntry)obfEntry, Descriptor.toJvmName(newName));
486 } else if (obfEntry instanceof FieldEntry) {
487 m_renamer.setFieldName((FieldEntry)obfEntry, newName);
488 } else if (obfEntry instanceof MethodEntry) {
489 m_renamer.setMethodTreeName((MethodEntry)obfEntry, newName);
490 } else if (obfEntry instanceof ConstructorEntry) {
491 throw new IllegalArgumentException("Cannot rename constructors");
492 } else if (obfEntry instanceof ArgumentEntry) {
493 m_renamer.setArgumentName((ArgumentEntry)obfEntry, newName);
494 } else {
495 throw new Error("Unknown entry type: " + obfEntry.getClass().getName());
496 }
497
498 // clear caches
499 m_translatorCache.clear();
500 }
501
502 public void removeMapping(Entry obfEntry) {
503 if (obfEntry instanceof ClassEntry) {
504 m_renamer.removeClassMapping((ClassEntry)obfEntry);
505 } else if (obfEntry instanceof FieldEntry) {
506 m_renamer.removeFieldMapping((FieldEntry)obfEntry);
507 } else if (obfEntry instanceof MethodEntry) {
508 m_renamer.removeMethodTreeMapping((MethodEntry)obfEntry);
509 } else if (obfEntry instanceof ConstructorEntry) {
510 throw new IllegalArgumentException("Cannot rename constructors");
511 } else if (obfEntry instanceof ArgumentEntry) {
512 m_renamer.removeArgumentMapping((ArgumentEntry)obfEntry);
513 } else {
514 throw new Error("Unknown entry type: " + obfEntry);
515 }
516
517 // clear caches
518 m_translatorCache.clear();
519 }
520
521 public void markAsDeobfuscated(Entry obfEntry) {
522 if (obfEntry instanceof ClassEntry) {
523 m_renamer.markClassAsDeobfuscated((ClassEntry)obfEntry);
524 } else if (obfEntry instanceof FieldEntry) {
525 m_renamer.markFieldAsDeobfuscated((FieldEntry)obfEntry);
526 } else if (obfEntry instanceof MethodEntry) {
527 m_renamer.markMethodTreeAsDeobfuscated((MethodEntry)obfEntry);
528 } else if (obfEntry instanceof ConstructorEntry) {
529 throw new IllegalArgumentException("Cannot rename constructors");
530 } else if (obfEntry instanceof ArgumentEntry) {
531 m_renamer.markArgumentAsDeobfuscated((ArgumentEntry)obfEntry);
532 } else {
533 throw new Error("Unknown entry type: " + obfEntry);
534 }
535
536 // clear caches
537 m_translatorCache.clear();
538 }
539}
diff --git a/src/cuchaz/enigma/Main.java b/src/cuchaz/enigma/Main.java
new file mode 100644
index 00000000..acae94b1
--- /dev/null
+++ b/src/cuchaz/enigma/Main.java
@@ -0,0 +1,51 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import java.io.File;
14import java.util.jar.JarFile;
15
16import cuchaz.enigma.gui.Gui;
17
18public class Main {
19
20 public static void main(String[] args) throws Exception {
21 Gui gui = new Gui();
22
23 // parse command-line args
24 if (args.length >= 1) {
25 gui.getController().openJar(new JarFile(getFile(args[0])));
26 }
27 if (args.length >= 2) {
28 gui.getController().openMappings(getFile(args[1]));
29 }
30
31 // DEBUG
32 //gui.getController().openDeclaration(new ClassEntry("none/bxq"));
33 }
34
35 private static File getFile(String path) {
36 // expand ~ to the home dir
37 if (path.startsWith("~")) {
38 // get the home dir
39 File dirHome = new File(System.getProperty("user.home"));
40
41 // is the path just ~/ or is it ~user/ ?
42 if (path.startsWith("~/")) {
43 return new File(dirHome, path.substring(2));
44 } else {
45 return new File(dirHome.getParentFile(), path.substring(1));
46 }
47 }
48
49 return new File(path);
50 }
51}
diff --git a/src/cuchaz/enigma/TranslatingTypeLoader.java b/src/cuchaz/enigma/TranslatingTypeLoader.java
new file mode 100644
index 00000000..cfa03a1a
--- /dev/null
+++ b/src/cuchaz/enigma/TranslatingTypeLoader.java
@@ -0,0 +1,211 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import java.io.ByteArrayOutputStream;
14import java.io.IOException;
15import java.io.InputStream;
16import java.util.Map;
17import java.util.jar.JarEntry;
18import java.util.jar.JarFile;
19
20import javassist.ByteArrayClassPath;
21import javassist.CannotCompileException;
22import javassist.ClassPool;
23import javassist.CtClass;
24import javassist.NotFoundException;
25import javassist.bytecode.Descriptor;
26
27import com.google.common.collect.Maps;
28import com.strobel.assembler.metadata.Buffer;
29import com.strobel.assembler.metadata.ClasspathTypeLoader;
30import com.strobel.assembler.metadata.ITypeLoader;
31
32import cuchaz.enigma.analysis.JarIndex;
33import cuchaz.enigma.bytecode.ClassRenamer;
34import cuchaz.enigma.bytecode.ClassTranslator;
35import cuchaz.enigma.bytecode.InnerClassWriter;
36import cuchaz.enigma.bytecode.MethodParameterWriter;
37import cuchaz.enigma.mapping.ClassEntry;
38import cuchaz.enigma.mapping.Translator;
39
40public class TranslatingTypeLoader implements ITypeLoader {
41
42 private JarFile m_jar;
43 private JarIndex m_jarIndex;
44 private Translator m_obfuscatingTranslator;
45 private Translator m_deobfuscatingTranslator;
46 private Map<String,byte[]> m_cache;
47 private ClasspathTypeLoader m_defaultTypeLoader;
48
49 public TranslatingTypeLoader(JarFile jar, JarIndex jarIndex) {
50 this(jar, jarIndex, new Translator(), new Translator());
51 }
52
53 public TranslatingTypeLoader(JarFile jar, JarIndex jarIndex, Translator obfuscatingTranslator, Translator deobfuscatingTranslator) {
54 m_jar = jar;
55 m_jarIndex = jarIndex;
56 m_obfuscatingTranslator = obfuscatingTranslator;
57 m_deobfuscatingTranslator = deobfuscatingTranslator;
58 m_cache = Maps.newHashMap();
59 m_defaultTypeLoader = new ClasspathTypeLoader();
60 }
61
62 public void clearCache() {
63 m_cache.clear();
64 }
65
66 @Override
67 public boolean tryLoadType(String deobfClassName, Buffer out) {
68 // check the cache
69 byte[] data;
70 if (m_cache.containsKey(deobfClassName)) {
71 data = m_cache.get(deobfClassName);
72 } else {
73 data = loadType(deobfClassName);
74 m_cache.put(deobfClassName, data);
75 }
76
77 if (data == null) {
78 // chain to default type loader
79 return m_defaultTypeLoader.tryLoadType(deobfClassName, out);
80 }
81
82 // send the class to the decompiler
83 out.reset(data.length);
84 System.arraycopy(data, 0, out.array(), out.position(), data.length);
85 out.position(0);
86 return true;
87 }
88
89 public CtClass loadClass(String deobfClassName) {
90 byte[] data = loadType(deobfClassName);
91 if (data == null) {
92 return null;
93 }
94
95 // return a javassist handle for the class
96 String javaClassFileName = Descriptor.toJavaName(deobfClassName);
97 ClassPool classPool = new ClassPool();
98 classPool.insertClassPath(new ByteArrayClassPath(javaClassFileName, data));
99 try {
100 return classPool.get(javaClassFileName);
101 } catch (NotFoundException ex) {
102 throw new Error(ex);
103 }
104 }
105
106 private byte[] loadType(String deobfClassName) {
107 ClassEntry deobfClassEntry = new ClassEntry(deobfClassName);
108 ClassEntry obfClassEntry = m_obfuscatingTranslator.translateEntry(deobfClassEntry);
109
110 // is this an inner class referenced directly?
111 String obfOuterClassName = m_jarIndex.getOuterClass(obfClassEntry.getSimpleName());
112 if (obfOuterClassName != null) {
113 // this class doesn't really exist. Reference it by outer$inner instead
114 System.err.println(String.format("WARNING: class %s referenced by bare inner name instead of via outer class %s", deobfClassName, obfOuterClassName));
115 return null;
116 }
117
118 /* DEBUG
119 if( !Arrays.asList( "java", "org", "io" ).contains( deobfClassName.split( "/" )[0] ) ) {
120 System.out.println( String.format( "Looking for %s (%s)", deobfClassEntry.getName(), obfClassEntry.getName() ) );
121 }
122 */
123
124 // get the jar entry
125 String classFileName;
126 if (obfClassEntry.isInnerClass()) {
127 // use just the inner class name for inner classes
128 classFileName = obfClassEntry.getInnerClassName();
129 } else if (obfClassEntry.getPackageName().equals(Constants.NonePackage)) {
130 // use the outer class simple name for classes in the none package
131 classFileName = obfClassEntry.getSimpleName();
132 } else {
133 // otherwise, just use the class name (ie for classes in packages)
134 classFileName = obfClassEntry.getName();
135 }
136
137 JarEntry entry = m_jar.getJarEntry(classFileName + ".class");
138 if (entry == null) {
139 return null;
140 }
141
142 try {
143 // read the class file into a buffer
144 ByteArrayOutputStream data = new ByteArrayOutputStream();
145 byte[] buf = new byte[1024 * 1024]; // 1 KiB
146 InputStream in = m_jar.getInputStream(entry);
147 while (true) {
148 int bytesRead = in.read(buf);
149 if (bytesRead <= 0) {
150 break;
151 }
152 data.write(buf, 0, bytesRead);
153 }
154 data.close();
155 in.close();
156 buf = data.toByteArray();
157
158 // load the javassist handle to the raw class
159 String javaClassFileName = Descriptor.toJavaName(classFileName);
160 ClassPool classPool = new ClassPool();
161 classPool.insertClassPath(new ByteArrayClassPath(javaClassFileName, buf));
162 CtClass c = classPool.get(javaClassFileName);
163
164 c = transformClass(c);
165
166 // sanity checking
167 assertClassName(c, deobfClassEntry);
168
169 // DEBUG
170 //Util.writeClass( c );
171
172 // we have a transformed class!
173 return c.toBytecode();
174 } catch (IOException | NotFoundException | CannotCompileException ex) {
175 throw new Error(ex);
176 }
177 }
178
179 public CtClass transformClass(CtClass c) throws IOException, NotFoundException, CannotCompileException {
180 // we moved a lot of classes out of the default package into the none package
181 // make sure all the class references are consistent
182 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
183
184 // reconstruct inner classes
185 new InnerClassWriter(m_jarIndex).write(c);
186
187 // re-get the javassist handle since we changed class names
188 ClassEntry obfClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
189 String javaClassReconstructedName = Descriptor.toJavaName(obfClassEntry.getName());
190 ClassPool classPool = new ClassPool();
191 classPool.insertClassPath(new ByteArrayClassPath(javaClassReconstructedName, c.toBytecode()));
192 c = classPool.get(javaClassReconstructedName);
193
194 // check that the file is correct after inner class reconstruction (ie cause Javassist to fail fast if something is wrong)
195 assertClassName(c, obfClassEntry);
196
197 // do all kinds of deobfuscating transformations on the class
198 new MethodParameterWriter(m_deobfuscatingTranslator).writeMethodArguments(c);
199 new ClassTranslator(m_deobfuscatingTranslator).translate(c);
200
201 return c;
202 }
203
204 private void assertClassName(CtClass c, ClassEntry obfClassEntry) {
205 String name1 = Descriptor.toJvmName(c.getName());
206 assert (name1.equals(obfClassEntry.getName())) : String.format("Looking for %s, instead found %s", obfClassEntry.getName(), name1);
207
208 String name2 = Descriptor.toJvmName(c.getClassFile().getName());
209 assert (name2.equals(obfClassEntry.getName())) : String.format("Looking for %s, instead found %s", obfClassEntry.getName(), name2);
210 }
211}
diff --git a/src/cuchaz/enigma/Util.java b/src/cuchaz/enigma/Util.java
new file mode 100644
index 00000000..7f04bda0
--- /dev/null
+++ b/src/cuchaz/enigma/Util.java
@@ -0,0 +1,104 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import java.awt.Desktop;
14import java.io.Closeable;
15import java.io.File;
16import java.io.FileOutputStream;
17import java.io.IOException;
18import java.io.InputStream;
19import java.io.InputStreamReader;
20import java.net.URI;
21import java.net.URISyntaxException;
22import java.util.Arrays;
23import java.util.jar.JarFile;
24
25import javassist.CannotCompileException;
26import javassist.CtClass;
27import javassist.bytecode.Descriptor;
28
29import com.google.common.io.CharStreams;
30
31public class Util {
32
33 public static int combineHashesOrdered(Object... objs) {
34 return combineHashesOrdered(Arrays.asList(objs));
35 }
36
37 public static int combineHashesOrdered(Iterable<Object> objs) {
38 final int prime = 67;
39 int result = 1;
40 for (Object obj : objs) {
41 result *= prime;
42 if (obj != null) {
43 result += obj.hashCode();
44 }
45 }
46 return result;
47 }
48
49 public static void closeQuietly(Closeable closeable) {
50 if (closeable != null) {
51 try {
52 closeable.close();
53 } catch (IOException ex) {
54 // just ignore any further exceptions
55 }
56 }
57 }
58
59 public static void closeQuietly(JarFile jarFile) {
60 // silly library should implement Closeable...
61 if (jarFile != null) {
62 try {
63 jarFile.close();
64 } catch (IOException ex) {
65 // just ignore any further exceptions
66 }
67 }
68 }
69
70 public static String readStreamToString(InputStream in) throws IOException {
71 return CharStreams.toString(new InputStreamReader(in, "UTF-8"));
72 }
73
74 public static String readResourceToString(String path) throws IOException {
75 InputStream in = Util.class.getResourceAsStream(path);
76 if (in == null) {
77 throw new IllegalArgumentException("Resource not found! " + path);
78 }
79 return readStreamToString(in);
80 }
81
82 public static void openUrl(String url) {
83 if (Desktop.isDesktopSupported()) {
84 Desktop desktop = Desktop.getDesktop();
85 try {
86 desktop.browse(new URI(url));
87 } catch (IOException ex) {
88 throw new Error(ex);
89 } catch (URISyntaxException ex) {
90 throw new IllegalArgumentException(ex);
91 }
92 }
93 }
94
95 public static void writeClass(CtClass c) {
96 String name = Descriptor.toJavaName(c.getName());
97 File file = new File(name + ".class");
98 try (FileOutputStream out = new FileOutputStream(file)) {
99 out.write(c.toBytecode());
100 } catch (IOException | CannotCompileException ex) {
101 throw new Error(ex);
102 }
103 }
104}
diff --git a/src/cuchaz/enigma/analysis/Access.java b/src/cuchaz/enigma/analysis/Access.java
new file mode 100644
index 00000000..8d3409ac
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/Access.java
@@ -0,0 +1,43 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.lang.reflect.Modifier;
14
15import javassist.CtBehavior;
16import javassist.CtField;
17
18public enum Access {
19
20 Public,
21 Protected,
22 Private;
23
24 public static Access get(CtBehavior behavior) {
25 return get(behavior.getModifiers());
26 }
27
28 public static Access get(CtField field) {
29 return get(field.getModifiers());
30 }
31
32 public static Access get(int modifiers) {
33 if (Modifier.isPublic(modifiers)) {
34 return Public;
35 } else if (Modifier.isProtected(modifiers)) {
36 return Protected;
37 } else if (Modifier.isPrivate(modifiers)) {
38 return Private;
39 }
40 // assume public by default
41 return Public;
42 }
43}
diff --git a/src/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java b/src/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java
new file mode 100644
index 00000000..9adac5e9
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java
@@ -0,0 +1,93 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.util.Set;
14
15import javax.swing.tree.DefaultMutableTreeNode;
16import javax.swing.tree.TreeNode;
17
18import com.google.common.collect.Sets;
19
20import cuchaz.enigma.mapping.BehaviorEntry;
21import cuchaz.enigma.mapping.Entry;
22import cuchaz.enigma.mapping.Translator;
23
24public class BehaviorReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode<BehaviorEntry,BehaviorEntry> {
25
26 private static final long serialVersionUID = -3658163700783307520L;
27
28 private Translator m_deobfuscatingTranslator;
29 private BehaviorEntry m_entry;
30 private EntryReference<BehaviorEntry,BehaviorEntry> m_reference;
31 private Access m_access;
32
33 public BehaviorReferenceTreeNode(Translator deobfuscatingTranslator, BehaviorEntry entry) {
34 m_deobfuscatingTranslator = deobfuscatingTranslator;
35 m_entry = entry;
36 m_reference = null;
37 }
38
39 public BehaviorReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference<BehaviorEntry,BehaviorEntry> reference, Access access) {
40 m_deobfuscatingTranslator = deobfuscatingTranslator;
41 m_entry = reference.entry;
42 m_reference = reference;
43 m_access = access;
44 }
45
46 @Override
47 public BehaviorEntry getEntry() {
48 return m_entry;
49 }
50
51 @Override
52 public EntryReference<BehaviorEntry,BehaviorEntry> getReference() {
53 return m_reference;
54 }
55
56 @Override
57 public String toString() {
58 if (m_reference != null) {
59 return String.format("%s (%s)", m_deobfuscatingTranslator.translateEntry(m_reference.context), m_access);
60 }
61 return m_deobfuscatingTranslator.translateEntry(m_entry).toString();
62 }
63
64 public void load(JarIndex index, boolean recurse) {
65 // get all the child nodes
66 for (EntryReference<BehaviorEntry,BehaviorEntry> reference : index.getBehaviorReferences(m_entry)) {
67 add(new BehaviorReferenceTreeNode(m_deobfuscatingTranslator, reference, index.getAccess(m_entry)));
68 }
69
70 if (recurse && children != null) {
71 for (Object child : children) {
72 if (child instanceof BehaviorReferenceTreeNode) {
73 BehaviorReferenceTreeNode node = (BehaviorReferenceTreeNode)child;
74
75 // don't recurse into ancestor
76 Set<Entry> ancestors = Sets.newHashSet();
77 TreeNode n = (TreeNode)node;
78 while (n.getParent() != null) {
79 n = n.getParent();
80 if (n instanceof BehaviorReferenceTreeNode) {
81 ancestors.add( ((BehaviorReferenceTreeNode)n).getEntry());
82 }
83 }
84 if (ancestors.contains(node.getEntry())) {
85 continue;
86 }
87
88 node.load(index, true);
89 }
90 }
91 }
92 }
93}
diff --git a/src/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java b/src/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java
new file mode 100644
index 00000000..49aac5f0
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java
@@ -0,0 +1,80 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.util.List;
14
15import javax.swing.tree.DefaultMutableTreeNode;
16
17import com.google.common.collect.Lists;
18
19import cuchaz.enigma.mapping.ClassEntry;
20import cuchaz.enigma.mapping.MethodEntry;
21import cuchaz.enigma.mapping.Translator;
22
23public class ClassImplementationsTreeNode extends DefaultMutableTreeNode {
24
25 private static final long serialVersionUID = 3112703459157851912L;
26
27 private Translator m_deobfuscatingTranslator;
28 private ClassEntry m_entry;
29
30 public ClassImplementationsTreeNode(Translator deobfuscatingTranslator, ClassEntry entry) {
31 m_deobfuscatingTranslator = deobfuscatingTranslator;
32 m_entry = entry;
33 }
34
35 public ClassEntry getClassEntry() {
36 return m_entry;
37 }
38
39 public String getDeobfClassName() {
40 return m_deobfuscatingTranslator.translateClass(m_entry.getClassName());
41 }
42
43 @Override
44 public String toString() {
45 String className = getDeobfClassName();
46 if (className == null) {
47 className = m_entry.getClassName();
48 }
49 return className;
50 }
51
52 public void load(JarIndex index) {
53 // get all method implementations
54 List<ClassImplementationsTreeNode> nodes = Lists.newArrayList();
55 for (String implementingClassName : index.getImplementingClasses(m_entry.getClassName())) {
56 nodes.add(new ClassImplementationsTreeNode(m_deobfuscatingTranslator, new ClassEntry(implementingClassName)));
57 }
58
59 // add them to this node
60 for (ClassImplementationsTreeNode node : nodes) {
61 this.add(node);
62 }
63 }
64
65 public static ClassImplementationsTreeNode findNode(ClassImplementationsTreeNode node, MethodEntry entry) {
66 // is this the node?
67 if (node.m_entry.equals(entry)) {
68 return node;
69 }
70
71 // recurse
72 for (int i = 0; i < node.getChildCount(); i++) {
73 ClassImplementationsTreeNode foundNode = findNode((ClassImplementationsTreeNode)node.getChildAt(i), entry);
74 if (foundNode != null) {
75 return foundNode;
76 }
77 }
78 return null;
79 }
80}
diff --git a/src/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java b/src/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java
new file mode 100644
index 00000000..3eaa3912
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java
@@ -0,0 +1,85 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.util.List;
14
15import javax.swing.tree.DefaultMutableTreeNode;
16
17import com.google.common.collect.Lists;
18
19import cuchaz.enigma.mapping.ClassEntry;
20import cuchaz.enigma.mapping.Translator;
21
22public class ClassInheritanceTreeNode extends DefaultMutableTreeNode {
23
24 private static final long serialVersionUID = 4432367405826178490L;
25
26 private Translator m_deobfuscatingTranslator;
27 private String m_obfClassName;
28
29 public ClassInheritanceTreeNode(Translator deobfuscatingTranslator, String obfClassName) {
30 m_deobfuscatingTranslator = deobfuscatingTranslator;
31 m_obfClassName = obfClassName;
32 }
33
34 public String getObfClassName() {
35 return m_obfClassName;
36 }
37
38 public String getDeobfClassName() {
39 return m_deobfuscatingTranslator.translateClass(m_obfClassName);
40 }
41
42 @Override
43 public String toString() {
44 String deobfClassName = getDeobfClassName();
45 if (deobfClassName != null) {
46 return deobfClassName;
47 }
48 return m_obfClassName;
49 }
50
51 public void load(TranslationIndex ancestries, boolean recurse) {
52 // get all the child nodes
53 List<ClassInheritanceTreeNode> nodes = Lists.newArrayList();
54 for (ClassEntry subclassEntry : ancestries.getSubclass(new ClassEntry(m_obfClassName))) {
55 nodes.add(new ClassInheritanceTreeNode(m_deobfuscatingTranslator, subclassEntry.getName()));
56 }
57
58 // add them to this node
59 for (ClassInheritanceTreeNode node : nodes) {
60 this.add(node);
61 }
62
63 if (recurse) {
64 for (ClassInheritanceTreeNode node : nodes) {
65 node.load(ancestries, true);
66 }
67 }
68 }
69
70 public static ClassInheritanceTreeNode findNode(ClassInheritanceTreeNode node, ClassEntry entry) {
71 // is this the node?
72 if (node.getObfClassName().equals(entry.getName())) {
73 return node;
74 }
75
76 // recurse
77 for (int i = 0; i < node.getChildCount(); i++) {
78 ClassInheritanceTreeNode foundNode = findNode((ClassInheritanceTreeNode)node.getChildAt(i), entry);
79 if (foundNode != null) {
80 return foundNode;
81 }
82 }
83 return null;
84 }
85}
diff --git a/src/cuchaz/enigma/analysis/EntryReference.java b/src/cuchaz/enigma/analysis/EntryReference.java
new file mode 100644
index 00000000..bb611df5
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/EntryReference.java
@@ -0,0 +1,126 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.util.Arrays;
14import java.util.List;
15
16import cuchaz.enigma.Util;
17import cuchaz.enigma.mapping.ClassEntry;
18import cuchaz.enigma.mapping.ConstructorEntry;
19import cuchaz.enigma.mapping.Entry;
20
21public class EntryReference<E extends Entry,C extends Entry> {
22
23 private static final List<String> ConstructorNonNames = Arrays.asList("this", "super", "static");
24 public E entry;
25 public C context;
26
27 private boolean m_isNamed;
28
29 public EntryReference(E entry, String sourceName) {
30 this(entry, sourceName, null);
31 }
32
33 public EntryReference(E entry, String sourceName, C context) {
34 if (entry == null) {
35 throw new IllegalArgumentException("Entry cannot be null!");
36 }
37
38 this.entry = entry;
39 this.context = context;
40
41 m_isNamed = sourceName != null && sourceName.length() > 0;
42 if (entry instanceof ConstructorEntry && ConstructorNonNames.contains(sourceName)) {
43 m_isNamed = false;
44 }
45 }
46
47 public EntryReference(E entry, C context, EntryReference<E,C> other) {
48 this.entry = entry;
49 this.context = context;
50 m_isNamed = other.m_isNamed;
51 }
52
53 public ClassEntry getLocationClassEntry() {
54 if (context != null) {
55 return context.getClassEntry();
56 }
57 return entry.getClassEntry();
58 }
59
60 public boolean isNamed() {
61 return m_isNamed;
62 }
63
64 public Entry getNameableEntry() {
65 if (entry instanceof ConstructorEntry) {
66 // renaming a constructor really means renaming the class
67 return entry.getClassEntry();
68 }
69 return entry;
70 }
71
72 public String getNamableName() {
73 if (getNameableEntry() instanceof ClassEntry) {
74 ClassEntry classEntry = (ClassEntry)getNameableEntry();
75 if (classEntry.isInnerClass()) {
76 // make sure we only rename the inner class name
77 return classEntry.getInnerClassName();
78 }
79 }
80
81 return getNameableEntry().getName();
82 }
83
84 @Override
85 public int hashCode() {
86 if (context != null) {
87 return Util.combineHashesOrdered(entry.hashCode(), context.hashCode());
88 }
89 return entry.hashCode();
90 }
91
92 @Override
93 public boolean equals(Object other) {
94 if (other instanceof EntryReference) {
95 return equals((EntryReference<?,?>)other);
96 }
97 return false;
98 }
99
100 public boolean equals(EntryReference<?,?> other) {
101 // check entry first
102 boolean isEntrySame = entry.equals(other.entry);
103 if (!isEntrySame) {
104 return false;
105 }
106
107 // check caller
108 if (context == null && other.context == null) {
109 return true;
110 } else if (context != null && other.context != null) {
111 return context.equals(other.context);
112 }
113 return false;
114 }
115
116 @Override
117 public String toString() {
118 StringBuilder buf = new StringBuilder();
119 buf.append(entry);
120 if (context != null) {
121 buf.append(" called from ");
122 buf.append(context);
123 }
124 return buf.toString();
125 }
126}
diff --git a/src/cuchaz/enigma/analysis/EntryRenamer.java b/src/cuchaz/enigma/analysis/EntryRenamer.java
new file mode 100644
index 00000000..b54489cd
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/EntryRenamer.java
@@ -0,0 +1,171 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.util.AbstractMap;
14import java.util.List;
15import java.util.Map;
16import java.util.Set;
17
18import com.google.common.collect.Lists;
19import com.google.common.collect.Multimap;
20import com.google.common.collect.Sets;
21
22import cuchaz.enigma.mapping.ArgumentEntry;
23import cuchaz.enigma.mapping.ClassEntry;
24import cuchaz.enigma.mapping.ConstructorEntry;
25import cuchaz.enigma.mapping.Entry;
26import cuchaz.enigma.mapping.FieldEntry;
27import cuchaz.enigma.mapping.MethodEntry;
28
29public class EntryRenamer {
30
31 public static <T> void renameClassesInSet(Map<String,String> renames, Set<T> set) {
32 List<T> entries = Lists.newArrayList();
33 for (T val : set) {
34 entries.add(renameClassesInThing(renames, val));
35 }
36 set.clear();
37 set.addAll(entries);
38 }
39
40 public static <Key,Val> void renameClassesInMap(Map<String,String> renames, Map<Key,Val> map) {
41 // for each key/value pair...
42 Set<Map.Entry<Key,Val>> entriesToAdd = Sets.newHashSet();
43 for (Map.Entry<Key,Val> entry : map.entrySet()) {
44 entriesToAdd.add(new AbstractMap.SimpleEntry<Key,Val>(
45 renameClassesInThing(renames, entry.getKey()),
46 renameClassesInThing(renames, entry.getValue())
47 ));
48 }
49 map.clear();
50 for (Map.Entry<Key,Val> entry : entriesToAdd) {
51 map.put(entry.getKey(), entry.getValue());
52 }
53 }
54
55 public static <Key,Val> void renameClassesInMultimap(Map<String,String> renames, Multimap<Key,Val> map) {
56 // for each key/value pair...
57 Set<Map.Entry<Key,Val>> entriesToAdd = Sets.newHashSet();
58 for (Map.Entry<Key,Val> entry : map.entries()) {
59 entriesToAdd.add(new AbstractMap.SimpleEntry<Key,Val>(
60 renameClassesInThing(renames, entry.getKey()),
61 renameClassesInThing(renames, entry.getValue())
62 ));
63 }
64 map.clear();
65 for (Map.Entry<Key,Val> entry : entriesToAdd) {
66 map.put(entry.getKey(), entry.getValue());
67 }
68 }
69
70 public static <Key,Val> void renameMethodsInMultimap(Map<MethodEntry,MethodEntry> renames, Multimap<Key,Val> map) {
71 // for each key/value pair...
72 Set<Map.Entry<Key,Val>> entriesToAdd = Sets.newHashSet();
73 for (Map.Entry<Key,Val> entry : map.entries()) {
74 entriesToAdd.add(new AbstractMap.SimpleEntry<Key,Val>(
75 renameMethodsInThing(renames, entry.getKey()),
76 renameMethodsInThing(renames, entry.getValue())
77 ));
78 }
79 map.clear();
80 for (Map.Entry<Key,Val> entry : entriesToAdd) {
81 map.put(entry.getKey(), entry.getValue());
82 }
83 }
84
85 public static <Key,Val> void renameMethodsInMap(Map<MethodEntry,MethodEntry> renames, Map<Key,Val> map) {
86 // for each key/value pair...
87 Set<Map.Entry<Key,Val>> entriesToAdd = Sets.newHashSet();
88 for (Map.Entry<Key,Val> entry : map.entrySet()) {
89 entriesToAdd.add(new AbstractMap.SimpleEntry<Key,Val>(
90 renameMethodsInThing(renames, entry.getKey()),
91 renameMethodsInThing(renames, entry.getValue())
92 ));
93 }
94 map.clear();
95 for (Map.Entry<Key,Val> entry : entriesToAdd) {
96 map.put(entry.getKey(), entry.getValue());
97 }
98 }
99
100 @SuppressWarnings("unchecked")
101 public static <T> T renameMethodsInThing(Map<MethodEntry,MethodEntry> renames, T thing) {
102 if (thing instanceof MethodEntry) {
103 MethodEntry methodEntry = (MethodEntry)thing;
104 MethodEntry newMethodEntry = renames.get(methodEntry);
105 if (newMethodEntry != null) {
106 return (T)new MethodEntry(
107 methodEntry.getClassEntry(),
108 newMethodEntry.getName(),
109 methodEntry.getSignature()
110 );
111 }
112 return thing;
113 } else if (thing instanceof ArgumentEntry) {
114 ArgumentEntry argumentEntry = (ArgumentEntry)thing;
115 return (T)new ArgumentEntry(
116 renameMethodsInThing(renames, argumentEntry.getBehaviorEntry()),
117 argumentEntry.getIndex(),
118 argumentEntry.getName()
119 );
120 } else if (thing instanceof EntryReference) {
121 EntryReference<Entry,Entry> reference = (EntryReference<Entry,Entry>)thing;
122 reference.entry = renameMethodsInThing(renames, reference.entry);
123 reference.context = renameMethodsInThing(renames, reference.context);
124 return thing;
125 }
126 return thing;
127 }
128
129 @SuppressWarnings("unchecked")
130 public static <T> T renameClassesInThing(Map<String,String> renames, T thing) {
131 if (thing instanceof String) {
132 String stringEntry = (String)thing;
133 if (renames.containsKey(stringEntry)) {
134 return (T)renames.get(stringEntry);
135 }
136 } else if (thing instanceof ClassEntry) {
137 ClassEntry classEntry = (ClassEntry)thing;
138 return (T)new ClassEntry(renameClassesInThing(renames, classEntry.getClassName()));
139 } else if (thing instanceof FieldEntry) {
140 FieldEntry fieldEntry = (FieldEntry)thing;
141 return (T)new FieldEntry(renameClassesInThing(renames, fieldEntry.getClassEntry()), fieldEntry.getName());
142 } else if (thing instanceof ConstructorEntry) {
143 ConstructorEntry constructorEntry = (ConstructorEntry)thing;
144 return (T)new ConstructorEntry(
145 renameClassesInThing(renames, constructorEntry.getClassEntry()),
146 constructorEntry.getSignature()
147 );
148 } else if (thing instanceof MethodEntry) {
149 MethodEntry methodEntry = (MethodEntry)thing;
150 return (T)new MethodEntry(
151 renameClassesInThing(renames, methodEntry.getClassEntry()),
152 methodEntry.getName(),
153 methodEntry.getSignature()
154 );
155 } else if (thing instanceof ArgumentEntry) {
156 ArgumentEntry argumentEntry = (ArgumentEntry)thing;
157 return (T)new ArgumentEntry(
158 renameClassesInThing(renames, argumentEntry.getBehaviorEntry()),
159 argumentEntry.getIndex(),
160 argumentEntry.getName()
161 );
162 } else if (thing instanceof EntryReference) {
163 EntryReference<Entry,Entry> reference = (EntryReference<Entry,Entry>)thing;
164 reference.entry = renameClassesInThing(renames, reference.entry);
165 reference.context = renameClassesInThing(renames, reference.context);
166 return thing;
167 }
168
169 return thing;
170 }
171}
diff --git a/src/cuchaz/enigma/analysis/FieldReferenceTreeNode.java b/src/cuchaz/enigma/analysis/FieldReferenceTreeNode.java
new file mode 100644
index 00000000..2173eea6
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/FieldReferenceTreeNode.java
@@ -0,0 +1,81 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import javax.swing.tree.DefaultMutableTreeNode;
14
15import cuchaz.enigma.mapping.BehaviorEntry;
16import cuchaz.enigma.mapping.FieldEntry;
17import cuchaz.enigma.mapping.Translator;
18
19public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode<FieldEntry,BehaviorEntry> {
20
21 private static final long serialVersionUID = -7934108091928699835L;
22
23 private Translator m_deobfuscatingTranslator;
24 private FieldEntry m_entry;
25 private EntryReference<FieldEntry,BehaviorEntry> m_reference;
26 private Access m_access;
27
28 public FieldReferenceTreeNode(Translator deobfuscatingTranslator, FieldEntry entry) {
29 m_deobfuscatingTranslator = deobfuscatingTranslator;
30 m_entry = entry;
31 m_reference = null;
32 }
33
34 private FieldReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference<FieldEntry,BehaviorEntry> reference, Access access) {
35 m_deobfuscatingTranslator = deobfuscatingTranslator;
36 m_entry = reference.entry;
37 m_reference = reference;
38 m_access = access;
39 }
40
41 @Override
42 public FieldEntry getEntry() {
43 return m_entry;
44 }
45
46 @Override
47 public EntryReference<FieldEntry,BehaviorEntry> getReference() {
48 return m_reference;
49 }
50
51 @Override
52 public String toString() {
53 if (m_reference != null) {
54 return String.format("%s (%s)", m_deobfuscatingTranslator.translateEntry(m_reference.context), m_access);
55 }
56 return m_deobfuscatingTranslator.translateEntry(m_entry).toString();
57 }
58
59 public void load(JarIndex index, boolean recurse) {
60 // get all the child nodes
61 if (m_reference == null) {
62 for (EntryReference<FieldEntry,BehaviorEntry> reference : index.getFieldReferences(m_entry)) {
63 add(new FieldReferenceTreeNode(m_deobfuscatingTranslator, reference, index.getAccess(m_entry)));
64 }
65 } else {
66 for (EntryReference<BehaviorEntry,BehaviorEntry> reference : index.getBehaviorReferences(m_reference.context)) {
67 add(new BehaviorReferenceTreeNode(m_deobfuscatingTranslator, reference, index.getAccess(m_reference.context)));
68 }
69 }
70
71 if (recurse && children != null) {
72 for (Object node : children) {
73 if (node instanceof BehaviorReferenceTreeNode) {
74 ((BehaviorReferenceTreeNode)node).load(index, true);
75 } else if (node instanceof FieldReferenceTreeNode) {
76 ((FieldReferenceTreeNode)node).load(index, true);
77 }
78 }
79 }
80 }
81}
diff --git a/src/cuchaz/enigma/analysis/JarClassIterator.java b/src/cuchaz/enigma/analysis/JarClassIterator.java
new file mode 100644
index 00000000..72a99122
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/JarClassIterator.java
@@ -0,0 +1,137 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.io.ByteArrayOutputStream;
14import java.io.IOException;
15import java.io.InputStream;
16import java.util.Enumeration;
17import java.util.Iterator;
18import java.util.List;
19import java.util.jar.JarEntry;
20import java.util.jar.JarFile;
21
22import javassist.ByteArrayClassPath;
23import javassist.ClassPool;
24import javassist.CtClass;
25import javassist.NotFoundException;
26import javassist.bytecode.Descriptor;
27
28import com.google.common.collect.Lists;
29
30import cuchaz.enigma.Constants;
31import cuchaz.enigma.mapping.ClassEntry;
32
33public class JarClassIterator implements Iterator<CtClass> {
34
35 private JarFile m_jar;
36 private Iterator<JarEntry> m_iter;
37
38 public JarClassIterator(JarFile jar) {
39 m_jar = jar;
40
41 // get the jar entries that correspond to classes
42 List<JarEntry> classEntries = Lists.newArrayList();
43 Enumeration<JarEntry> entries = m_jar.entries();
44 while (entries.hasMoreElements()) {
45 JarEntry entry = entries.nextElement();
46
47 // is this a class file?
48 if (entry.getName().endsWith(".class")) {
49 classEntries.add(entry);
50 }
51 }
52 m_iter = classEntries.iterator();
53 }
54
55 @Override
56 public boolean hasNext() {
57 return m_iter.hasNext();
58 }
59
60 @Override
61 public CtClass next() {
62 JarEntry entry = m_iter.next();
63 try {
64 return getClass(m_jar, entry);
65 } catch (IOException | NotFoundException ex) {
66 throw new Error("Unable to load class: " + entry.getName());
67 }
68 }
69
70 @Override
71 public void remove() {
72 throw new UnsupportedOperationException();
73 }
74
75 public static List<ClassEntry> getClassEntries(JarFile jar) {
76 List<ClassEntry> classEntries = Lists.newArrayList();
77 Enumeration<JarEntry> entries = jar.entries();
78 while (entries.hasMoreElements()) {
79 JarEntry entry = entries.nextElement();
80
81 // is this a class file?
82 if (!entry.isDirectory() && entry.getName().endsWith(".class")) {
83 classEntries.add(getClassEntry(entry));
84 }
85 }
86 return classEntries;
87 }
88
89 public static Iterable<CtClass> classes(final JarFile jar) {
90 return new Iterable<CtClass>() {
91 @Override
92 public Iterator<CtClass> iterator() {
93 return new JarClassIterator(jar);
94 }
95 };
96 }
97
98 public static CtClass getClass(JarFile jar, ClassEntry classEntry) {
99 try {
100 return getClass(jar, new JarEntry(classEntry.getName() + ".class"));
101 } catch (IOException | NotFoundException ex) {
102 throw new Error("Unable to load class: " + classEntry.getName());
103 }
104 }
105
106 private static CtClass getClass(JarFile jar, JarEntry entry) throws IOException, NotFoundException {
107 // read the class into a buffer
108 ByteArrayOutputStream bos = new ByteArrayOutputStream();
109 byte[] buf = new byte[Constants.KiB];
110 int totalNumBytesRead = 0;
111 InputStream in = jar.getInputStream(entry);
112 while (in.available() > 0) {
113 int numBytesRead = in.read(buf);
114 if (numBytesRead < 0) {
115 break;
116 }
117 bos.write(buf, 0, numBytesRead);
118
119 // sanity checking
120 totalNumBytesRead += numBytesRead;
121 if (totalNumBytesRead > Constants.MiB) {
122 throw new Error("Class file " + entry.getName() + " larger than 1 MiB! Something is wrong!");
123 }
124 }
125
126 // get a javassist handle for the class
127 String className = Descriptor.toJavaName(getClassEntry(entry).getName());
128 ClassPool classPool = new ClassPool();
129 classPool.appendSystemPath();
130 classPool.insertClassPath(new ByteArrayClassPath(className, bos.toByteArray()));
131 return classPool.get(className);
132 }
133
134 private static ClassEntry getClassEntry(JarEntry entry) {
135 return new ClassEntry(entry.getName().substring(0, entry.getName().length() - ".class".length()));
136 }
137}
diff --git a/src/cuchaz/enigma/analysis/JarIndex.java b/src/cuchaz/enigma/analysis/JarIndex.java
new file mode 100644
index 00000000..3aac8bd0
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/JarIndex.java
@@ -0,0 +1,734 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.lang.reflect.Modifier;
14import java.util.Collection;
15import java.util.HashSet;
16import java.util.List;
17import java.util.Map;
18import java.util.Set;
19import java.util.jar.JarFile;
20
21import javassist.CannotCompileException;
22import javassist.CtBehavior;
23import javassist.CtClass;
24import javassist.CtConstructor;
25import javassist.CtField;
26import javassist.bytecode.AccessFlag;
27import javassist.bytecode.Descriptor;
28import javassist.bytecode.FieldInfo;
29import javassist.expr.ConstructorCall;
30import javassist.expr.ExprEditor;
31import javassist.expr.FieldAccess;
32import javassist.expr.MethodCall;
33import javassist.expr.NewExpr;
34
35import com.google.common.collect.HashMultimap;
36import com.google.common.collect.Lists;
37import com.google.common.collect.Maps;
38import com.google.common.collect.Multimap;
39import com.google.common.collect.Sets;
40
41import cuchaz.enigma.Constants;
42import cuchaz.enigma.bytecode.ClassRenamer;
43import cuchaz.enigma.mapping.ArgumentEntry;
44import cuchaz.enigma.mapping.BehaviorEntry;
45import cuchaz.enigma.mapping.BehaviorEntryFactory;
46import cuchaz.enigma.mapping.ClassEntry;
47import cuchaz.enigma.mapping.ConstructorEntry;
48import cuchaz.enigma.mapping.Entry;
49import cuchaz.enigma.mapping.FieldEntry;
50import cuchaz.enigma.mapping.JavassistUtil;
51import cuchaz.enigma.mapping.MethodEntry;
52import cuchaz.enigma.mapping.Translator;
53
54public class JarIndex {
55
56 private Set<ClassEntry> m_obfClassEntries;
57 private TranslationIndex m_translationIndex;
58 private Multimap<String,String> m_interfaces;
59 private Map<Entry,Access> m_access;
60 private Map<FieldEntry,ClassEntry> m_fieldClasses; // TODO: this will become obsolete!
61 private Multimap<String,MethodEntry> m_methodImplementations;
62 private Multimap<BehaviorEntry,EntryReference<BehaviorEntry,BehaviorEntry>> m_behaviorReferences;
63 private Multimap<FieldEntry,EntryReference<FieldEntry,BehaviorEntry>> m_fieldReferences;
64 private Multimap<String,String> m_innerClasses;
65 private Map<String,String> m_outerClasses;
66 private Map<String,BehaviorEntry> m_anonymousClasses;
67
68 public JarIndex() {
69 m_obfClassEntries = Sets.newHashSet();
70 m_translationIndex = new TranslationIndex();
71 m_interfaces = HashMultimap.create();
72 m_access = Maps.newHashMap();
73 m_fieldClasses = Maps.newHashMap();
74 m_methodImplementations = HashMultimap.create();
75 m_behaviorReferences = HashMultimap.create();
76 m_fieldReferences = HashMultimap.create();
77 m_innerClasses = HashMultimap.create();
78 m_outerClasses = Maps.newHashMap();
79 m_anonymousClasses = Maps.newHashMap();
80 }
81
82 public void indexJar(JarFile jar, boolean buildInnerClasses) {
83
84 // step 1: read the class names
85 for (ClassEntry classEntry : JarClassIterator.getClassEntries(jar)) {
86 if (classEntry.isInDefaultPackage()) {
87 // move out of default package
88 classEntry = new ClassEntry(Constants.NonePackage + "/" + classEntry.getName());
89 }
90 m_obfClassEntries.add(classEntry);
91 }
92
93 // step 2: index field/method/constructor access
94 for (CtClass c : JarClassIterator.classes(jar)) {
95 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
96 for (CtField field : c.getDeclaredFields()) {
97 m_access.put(JavassistUtil.getFieldEntry(field), Access.get(field));
98 }
99 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
100 m_access.put(JavassistUtil.getBehaviorEntry(behavior), Access.get(behavior));
101 }
102 }
103
104 // step 3: index extends, implements, fields, and methods
105 for (CtClass c : JarClassIterator.classes(jar)) {
106 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
107 m_translationIndex.indexClass(c);
108 String className = Descriptor.toJvmName(c.getName());
109 for (String interfaceName : c.getClassFile().getInterfaces()) {
110 className = Descriptor.toJvmName(className);
111 interfaceName = Descriptor.toJvmName(interfaceName);
112 if (className.equals(interfaceName)) {
113 throw new IllegalArgumentException("Class cannot be its own interface! " + className);
114 }
115 m_interfaces.put(className, interfaceName);
116 }
117 for (CtField field : c.getDeclaredFields()) {
118 indexField(field);
119 }
120 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
121 indexBehavior(behavior);
122 }
123 }
124
125 // step 4: index field, method, constructor references
126 for (CtClass c : JarClassIterator.classes(jar)) {
127 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
128 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
129 indexBehaviorReferences(behavior);
130 }
131 }
132
133 if (buildInnerClasses) {
134 // step 5: index inner classes and anonymous classes
135 for (CtClass c : JarClassIterator.classes(jar)) {
136 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
137 String outerClassName = findOuterClass(c);
138 if (outerClassName != null) {
139 String innerClassName = c.getSimpleName();
140 m_innerClasses.put(outerClassName, innerClassName);
141 boolean innerWasAdded = m_outerClasses.put(innerClassName, outerClassName) == null;
142 assert (innerWasAdded);
143
144 BehaviorEntry enclosingBehavior = isAnonymousClass(c, outerClassName);
145 if (enclosingBehavior != null) {
146 m_anonymousClasses.put(innerClassName, enclosingBehavior);
147
148 // DEBUG
149 // System.out.println( "ANONYMOUS: " + outerClassName + "$" + innerClassName );
150 } else {
151 // DEBUG
152 // System.out.println( "INNER: " + outerClassName + "$" + innerClassName );
153 }
154 }
155 }
156
157 // step 6: update other indices with inner class info
158 Map<String,String> renames = Maps.newHashMap();
159 for (Map.Entry<String,String> entry : m_outerClasses.entrySet()) {
160 renames.put(Constants.NonePackage + "/" + entry.getKey(), entry.getValue() + "$" + entry.getKey());
161 }
162 EntryRenamer.renameClassesInSet(renames, m_obfClassEntries);
163 m_translationIndex.renameClasses(renames);
164 EntryRenamer.renameClassesInMultimap(renames, m_interfaces);
165 EntryRenamer.renameClassesInMultimap(renames, m_methodImplementations);
166 EntryRenamer.renameClassesInMultimap(renames, m_behaviorReferences);
167 EntryRenamer.renameClassesInMultimap(renames, m_fieldReferences);
168 EntryRenamer.renameClassesInMap(renames, m_access);
169 }
170 }
171
172 private void indexField(CtField field) {
173 // get the field entry
174 String className = Descriptor.toJvmName(field.getDeclaringClass().getName());
175 FieldEntry fieldEntry = new FieldEntry(new ClassEntry(className), field.getName());
176
177 // is the field a class type?
178 if (field.getSignature().startsWith("L")) {
179 ClassEntry fieldTypeEntry = new ClassEntry(field.getSignature().substring(1, field.getSignature().length() - 1));
180 m_fieldClasses.put(fieldEntry, fieldTypeEntry);
181 }
182 }
183
184 private void indexBehavior(CtBehavior behavior) {
185 // get the behavior entry
186 final BehaviorEntry behaviorEntry = BehaviorEntryFactory.create(behavior);
187 if (behaviorEntry instanceof MethodEntry) {
188 MethodEntry methodEntry = (MethodEntry)behaviorEntry;
189
190 // index implementation
191 m_methodImplementations.put(behaviorEntry.getClassName(), methodEntry);
192 }
193 // looks like we don't care about constructors here
194 }
195
196 private void indexBehaviorReferences(CtBehavior behavior) {
197 // index method calls
198 final BehaviorEntry behaviorEntry = BehaviorEntryFactory.create(behavior);
199 try {
200 behavior.instrument(new ExprEditor() {
201 @Override
202 public void edit(MethodCall call) {
203 MethodEntry calledMethodEntry = JavassistUtil.getMethodEntry(call);
204 ClassEntry resolvedClassEntry = m_translationIndex.resolveEntryClass(calledMethodEntry);
205 if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledMethodEntry.getClassEntry())) {
206 calledMethodEntry = new MethodEntry(
207 resolvedClassEntry,
208 calledMethodEntry.getName(),
209 calledMethodEntry.getSignature()
210 );
211 }
212 EntryReference<BehaviorEntry,BehaviorEntry> reference = new EntryReference<BehaviorEntry,BehaviorEntry>(
213 calledMethodEntry,
214 call.getMethodName(),
215 behaviorEntry
216 );
217 m_behaviorReferences.put(calledMethodEntry, reference);
218 }
219
220 @Override
221 public void edit(FieldAccess call) {
222 FieldEntry calledFieldEntry = JavassistUtil.getFieldEntry(call);
223 ClassEntry resolvedClassEntry = m_translationIndex.resolveEntryClass(calledFieldEntry);
224 if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledFieldEntry.getClassEntry())) {
225 calledFieldEntry = new FieldEntry(resolvedClassEntry, call.getFieldName());
226 }
227 EntryReference<FieldEntry,BehaviorEntry> reference = new EntryReference<FieldEntry,BehaviorEntry>(
228 calledFieldEntry,
229 call.getFieldName(),
230 behaviorEntry
231 );
232 m_fieldReferences.put(calledFieldEntry, reference);
233 }
234
235 @Override
236 public void edit(ConstructorCall call) {
237 ConstructorEntry calledConstructorEntry = JavassistUtil.getConstructorEntry(call);
238 EntryReference<BehaviorEntry,BehaviorEntry> reference = new EntryReference<BehaviorEntry,BehaviorEntry>(
239 calledConstructorEntry,
240 call.getMethodName(),
241 behaviorEntry
242 );
243 m_behaviorReferences.put(calledConstructorEntry, reference);
244 }
245
246 @Override
247 public void edit(NewExpr call) {
248 ConstructorEntry calledConstructorEntry = JavassistUtil.getConstructorEntry(call);
249 EntryReference<BehaviorEntry,BehaviorEntry> reference = new EntryReference<BehaviorEntry,BehaviorEntry>(
250 calledConstructorEntry,
251 call.getClassName(),
252 behaviorEntry
253 );
254 m_behaviorReferences.put(calledConstructorEntry, reference);
255 }
256 });
257 } catch (CannotCompileException ex) {
258 throw new Error(ex);
259 }
260 }
261
262 private String findOuterClass(CtClass c) {
263
264 // inner classes:
265 // have constructors that can (illegally) set synthetic fields
266 // the outer class is the only class that calls constructors
267
268 // use the synthetic fields to find the synthetic constructors
269 for (CtConstructor constructor : c.getDeclaredConstructors()) {
270 Set<String> syntheticFieldTypes = Sets.newHashSet();
271 if (!isIllegalConstructor(syntheticFieldTypes, constructor)) {
272 continue;
273 }
274
275 ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
276 ConstructorEntry constructorEntry = JavassistUtil.getConstructorEntry(constructor);
277
278 // gather the classes from the illegally-set synthetic fields
279 Set<ClassEntry> illegallySetClasses = Sets.newHashSet();
280 for (String type : syntheticFieldTypes) {
281 if (type.startsWith("L")) {
282 ClassEntry outerClassEntry = new ClassEntry(type.substring(1, type.length() - 1));
283 if (isSaneOuterClass(outerClassEntry, classEntry)) {
284 illegallySetClasses.add(outerClassEntry);
285 }
286 }
287 }
288
289 // who calls this constructor?
290 Set<ClassEntry> callerClasses = Sets.newHashSet();
291 for (EntryReference<BehaviorEntry,BehaviorEntry> reference : getBehaviorReferences(constructorEntry)) {
292
293 // make sure it's not a call to super
294 if (reference.entry instanceof ConstructorEntry && reference.context instanceof ConstructorEntry) {
295
296 // is the entry a superclass of the context?
297 ClassEntry calledClassEntry = reference.entry.getClassEntry();
298 ClassEntry superclassEntry = m_translationIndex.getSuperclass(reference.context.getClassEntry());
299 if (superclassEntry != null && superclassEntry.equals(calledClassEntry)) {
300 // it's a super call, skip
301 continue;
302 }
303 }
304
305 if (isSaneOuterClass(reference.context.getClassEntry(), classEntry)) {
306 callerClasses.add(reference.context.getClassEntry());
307 }
308 }
309
310 // do we have an answer yet?
311 if (callerClasses.isEmpty()) {
312 if (illegallySetClasses.size() == 1) {
313 return illegallySetClasses.iterator().next().getName();
314 } else {
315 System.out.println(String.format("WARNING: Unable to find outer class for %s. No caller and no illegally set field classes.", classEntry));
316 }
317 } else {
318 if (callerClasses.size() == 1) {
319 return callerClasses.iterator().next().getName();
320 } else {
321 // multiple callers, do the illegally set classes narrow it down?
322 Set<ClassEntry> intersection = Sets.newHashSet(callerClasses);
323 intersection.retainAll(illegallySetClasses);
324 if (intersection.size() == 1) {
325 return intersection.iterator().next().getName();
326 } else {
327 System.out.println(String.format("WARNING: Unable to choose outer class for %s among options: %s", classEntry, callerClasses));
328 }
329 }
330 }
331 }
332
333 return null;
334 }
335
336 private boolean isSaneOuterClass(ClassEntry outerClassEntry, ClassEntry innerClassEntry) {
337
338 // clearly this would be silly
339 if (outerClassEntry.equals(innerClassEntry)) {
340 return false;
341 }
342
343 // is the outer class in the jar?
344 if (!m_obfClassEntries.contains(outerClassEntry)) {
345 return false;
346 }
347
348 return true;
349 }
350
351 @SuppressWarnings("unchecked")
352 private boolean isIllegalConstructor(Set<String> syntheticFieldTypes, CtConstructor constructor) {
353
354 // illegal constructors only set synthetic member fields, then call super()
355 String className = constructor.getDeclaringClass().getName();
356
357 // collect all the field accesses, constructor calls, and method calls
358 final List<FieldAccess> illegalFieldWrites = Lists.newArrayList();
359 final List<ConstructorCall> constructorCalls = Lists.newArrayList();
360 try {
361 constructor.instrument(new ExprEditor() {
362 @Override
363 public void edit(FieldAccess fieldAccess) {
364 if (fieldAccess.isWriter() && constructorCalls.isEmpty()) {
365 illegalFieldWrites.add(fieldAccess);
366 }
367 }
368
369 @Override
370 public void edit(ConstructorCall constructorCall) {
371 constructorCalls.add(constructorCall);
372 }
373 });
374 } catch (CannotCompileException ex) {
375 // we're not compiling anything... this is stupid
376 throw new Error(ex);
377 }
378
379 // are there any illegal field writes?
380 if (illegalFieldWrites.isEmpty()) {
381 return false;
382 }
383
384 // are all the writes to synthetic fields?
385 for (FieldAccess fieldWrite : illegalFieldWrites) {
386
387 // all illegal writes have to be to the local class
388 if (!fieldWrite.getClassName().equals(className)) {
389 System.err.println(String.format("WARNING: illegal write to non-member field %s.%s", fieldWrite.getClassName(), fieldWrite.getFieldName()));
390 return false;
391 }
392
393 // find the field
394 FieldInfo fieldInfo = null;
395 for (FieldInfo info : (List<FieldInfo>)constructor.getDeclaringClass().getClassFile().getFields()) {
396 if (info.getName().equals(fieldWrite.getFieldName()) && info.getDescriptor().equals(fieldWrite.getSignature())) {
397 fieldInfo = info;
398 break;
399 }
400 }
401 if (fieldInfo == null) {
402 // field is in a superclass or something, can't be a local synthetic member
403 return false;
404 }
405
406 // is this field synthetic?
407 boolean isSynthetic = (fieldInfo.getAccessFlags() & AccessFlag.SYNTHETIC) != 0;
408 if (isSynthetic) {
409 syntheticFieldTypes.add(fieldInfo.getDescriptor());
410 } else {
411 System.err.println(String.format("WARNING: illegal write to non synthetic field %s %s.%s", fieldInfo.getDescriptor(), className, fieldInfo.getName()));
412 return false;
413 }
414 }
415
416 // we passed all the tests!
417 return true;
418 }
419
420 private BehaviorEntry isAnonymousClass(CtClass c, String outerClassName) {
421
422 ClassEntry innerClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
423
424 // anonymous classes:
425 // can't be abstract
426 // have only one constructor
427 // it's called exactly once by the outer class
428 // the type the instance is assigned to can't be this type
429
430 // is abstract?
431 if (Modifier.isAbstract(c.getModifiers())) {
432 return null;
433 }
434
435 // is there exactly one constructor?
436 if (c.getDeclaredConstructors().length != 1) {
437 return null;
438 }
439 CtConstructor constructor = c.getDeclaredConstructors()[0];
440
441 // is this constructor called exactly once?
442 ConstructorEntry constructorEntry = JavassistUtil.getConstructorEntry(constructor);
443 Collection<EntryReference<BehaviorEntry,BehaviorEntry>> references = getBehaviorReferences(constructorEntry);
444 if (references.size() != 1) {
445 return null;
446 }
447
448 // does the caller use this type?
449 BehaviorEntry caller = references.iterator().next().context;
450 for (FieldEntry fieldEntry : getReferencedFields(caller)) {
451 ClassEntry fieldClass = getFieldClass(fieldEntry);
452 if (fieldClass != null && fieldClass.equals(innerClassEntry)) {
453 // caller references this type, so it can't be anonymous
454 return null;
455 }
456 }
457 for (BehaviorEntry behaviorEntry : getReferencedBehaviors(caller)) {
458 if (behaviorEntry.getSignature().hasClass(innerClassEntry)) {
459 return null;
460 }
461 }
462
463 return caller;
464 }
465
466 public Set<ClassEntry> getObfClassEntries() {
467 return m_obfClassEntries;
468 }
469
470 public TranslationIndex getTranslationIndex() {
471 return m_translationIndex;
472 }
473
474 public Access getAccess(Entry entry) {
475 return m_access.get(entry);
476 }
477
478 public ClassEntry getFieldClass(FieldEntry fieldEntry) {
479 return m_fieldClasses.get(fieldEntry);
480 }
481
482 public ClassInheritanceTreeNode getClassInheritance(Translator deobfuscatingTranslator, ClassEntry obfClassEntry) {
483
484 // get the root node
485 List<String> ancestry = Lists.newArrayList();
486 ancestry.add(obfClassEntry.getName());
487 for (ClassEntry classEntry : m_translationIndex.getAncestry(obfClassEntry)) {
488 ancestry.add(classEntry.getName());
489 }
490 ClassInheritanceTreeNode rootNode = new ClassInheritanceTreeNode(
491 deobfuscatingTranslator,
492 ancestry.get(ancestry.size() - 1)
493 );
494
495 // expand all children recursively
496 rootNode.load(m_translationIndex, true);
497
498 return rootNode;
499 }
500
501 public ClassImplementationsTreeNode getClassImplementations(Translator deobfuscatingTranslator, ClassEntry obfClassEntry) {
502
503 // is this even an interface?
504 if (isInterface(obfClassEntry.getClassName())) {
505 ClassImplementationsTreeNode node = new ClassImplementationsTreeNode(deobfuscatingTranslator, obfClassEntry);
506 node.load(this);
507 return node;
508 }
509 return null;
510 }
511
512 public MethodInheritanceTreeNode getMethodInheritance(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) {
513
514 // travel to the ancestor implementation
515 ClassEntry baseImplementationClassEntry = obfMethodEntry.getClassEntry();
516 for (ClassEntry ancestorClassEntry : m_translationIndex.getAncestry(obfMethodEntry.getClassEntry())) {
517 MethodEntry ancestorMethodEntry = new MethodEntry(
518 new ClassEntry(ancestorClassEntry),
519 obfMethodEntry.getName(),
520 obfMethodEntry.getSignature()
521 );
522 if (containsObfBehavior(ancestorMethodEntry)) {
523 baseImplementationClassEntry = ancestorClassEntry;
524 }
525 }
526
527 // make a root node at the base
528 MethodEntry methodEntry = new MethodEntry(
529 baseImplementationClassEntry,
530 obfMethodEntry.getName(),
531 obfMethodEntry.getSignature()
532 );
533 MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode(
534 deobfuscatingTranslator,
535 methodEntry,
536 containsObfBehavior(methodEntry)
537 );
538
539 // expand the full tree
540 rootNode.load(this, true);
541
542 return rootNode;
543 }
544
545 public MethodImplementationsTreeNode getMethodImplementations(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) {
546
547 MethodEntry interfaceMethodEntry;
548
549 // is this method on an interface?
550 if (isInterface(obfMethodEntry.getClassName())) {
551 interfaceMethodEntry = obfMethodEntry;
552 } else {
553 // get the interface class
554 List<MethodEntry> methodInterfaces = Lists.newArrayList();
555 for (String interfaceName : getInterfaces(obfMethodEntry.getClassName())) {
556 // is this method defined in this interface?
557 MethodEntry methodInterface = new MethodEntry(
558 new ClassEntry(interfaceName),
559 obfMethodEntry.getName(),
560 obfMethodEntry.getSignature()
561 );
562 if (containsObfBehavior(methodInterface)) {
563 methodInterfaces.add(methodInterface);
564 }
565 }
566 if (methodInterfaces.isEmpty()) {
567 return null;
568 }
569 if (methodInterfaces.size() > 1) {
570 throw new Error("Too many interfaces define this method! This is not yet supported by Enigma!");
571 }
572 interfaceMethodEntry = methodInterfaces.get(0);
573 }
574
575 MethodImplementationsTreeNode rootNode = new MethodImplementationsTreeNode(deobfuscatingTranslator, interfaceMethodEntry);
576 rootNode.load(this);
577 return rootNode;
578 }
579
580 public Set<MethodEntry> getRelatedMethodImplementations(MethodEntry obfMethodEntry) {
581 Set<MethodEntry> methodEntries = Sets.newHashSet();
582 getRelatedMethodImplementations(methodEntries, getMethodInheritance(null, obfMethodEntry));
583 return methodEntries;
584 }
585
586 private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodInheritanceTreeNode node) {
587 MethodEntry methodEntry = node.getMethodEntry();
588 if (containsObfBehavior(methodEntry)) {
589 // collect the entry
590 methodEntries.add(methodEntry);
591 }
592
593 // look at interface methods too
594 MethodImplementationsTreeNode implementations = getMethodImplementations(null, methodEntry);
595 if (implementations != null) {
596 getRelatedMethodImplementations(methodEntries, implementations);
597 }
598
599 // recurse
600 for (int i = 0; i < node.getChildCount(); i++) {
601 getRelatedMethodImplementations(methodEntries, (MethodInheritanceTreeNode)node.getChildAt(i));
602 }
603 }
604
605 private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodImplementationsTreeNode node) {
606 MethodEntry methodEntry = node.getMethodEntry();
607 if (containsObfBehavior(methodEntry)) {
608 // collect the entry
609 methodEntries.add(methodEntry);
610 }
611
612 // recurse
613 for (int i = 0; i < node.getChildCount(); i++) {
614 getRelatedMethodImplementations(methodEntries, (MethodImplementationsTreeNode)node.getChildAt(i));
615 }
616 }
617
618 public Collection<EntryReference<FieldEntry,BehaviorEntry>> getFieldReferences(FieldEntry fieldEntry) {
619 return m_fieldReferences.get(fieldEntry);
620 }
621
622 public Collection<FieldEntry> getReferencedFields(BehaviorEntry behaviorEntry) {
623 // linear search is fast enough for now
624 Set<FieldEntry> fieldEntries = Sets.newHashSet();
625 for (EntryReference<FieldEntry,BehaviorEntry> reference : m_fieldReferences.values()) {
626 if (reference.context == behaviorEntry) {
627 fieldEntries.add(reference.entry);
628 }
629 }
630 return fieldEntries;
631 }
632
633 public Collection<EntryReference<BehaviorEntry,BehaviorEntry>> getBehaviorReferences(BehaviorEntry behaviorEntry) {
634 return m_behaviorReferences.get(behaviorEntry);
635 }
636
637 public Collection<BehaviorEntry> getReferencedBehaviors(BehaviorEntry behaviorEntry) {
638 // linear search is fast enough for now
639 Set<BehaviorEntry> behaviorEntries = Sets.newHashSet();
640 for (EntryReference<BehaviorEntry,BehaviorEntry> reference : m_behaviorReferences.values()) {
641 if (reference.context == behaviorEntry) {
642 behaviorEntries.add(reference.entry);
643 }
644 }
645 return behaviorEntries;
646 }
647
648 public Collection<String> getInnerClasses(String obfOuterClassName) {
649 return m_innerClasses.get(obfOuterClassName);
650 }
651
652 public String getOuterClass(String obfInnerClassName) {
653 // make sure we use the right name
654 if (new ClassEntry(obfInnerClassName).getPackageName() != null) {
655 throw new IllegalArgumentException("Don't reference obfuscated inner classes using packages: " + obfInnerClassName);
656 }
657 return m_outerClasses.get(obfInnerClassName);
658 }
659
660 public boolean isAnonymousClass(String obfInnerClassName) {
661 return m_anonymousClasses.containsKey(obfInnerClassName);
662 }
663
664 public BehaviorEntry getAnonymousClassCaller(String obfInnerClassName) {
665 return m_anonymousClasses.get(obfInnerClassName);
666 }
667
668 public Set<String> getInterfaces(String className) {
669 Set<String> interfaceNames = new HashSet<String>();
670 interfaceNames.addAll(m_interfaces.get(className));
671 for (ClassEntry ancestor : m_translationIndex.getAncestry(new ClassEntry(className))) {
672 interfaceNames.addAll(m_interfaces.get(ancestor.getName()));
673 }
674 return interfaceNames;
675 }
676
677 public Set<String> getImplementingClasses(String targetInterfaceName) {
678 // linear search is fast enough for now
679 Set<String> classNames = Sets.newHashSet();
680 for (Map.Entry<String,String> entry : m_interfaces.entries()) {
681 String className = entry.getKey();
682 String interfaceName = entry.getValue();
683 if (interfaceName.equals(targetInterfaceName)) {
684 classNames.add(className);
685 m_translationIndex.getSubclassNamesRecursively(classNames, new ClassEntry(className));
686 }
687 }
688 return classNames;
689 }
690
691 public boolean isInterface(String className) {
692 return m_interfaces.containsValue(className);
693 }
694
695 public boolean containsObfClass(ClassEntry obfClassEntry) {
696 return m_obfClassEntries.contains(obfClassEntry);
697 }
698
699 public boolean containsObfField(FieldEntry obfFieldEntry) {
700 return m_access.containsKey(obfFieldEntry);
701 }
702
703 public boolean containsObfBehavior(BehaviorEntry obfBehaviorEntry) {
704 return m_access.containsKey(obfBehaviorEntry);
705 }
706
707 public boolean containsObfArgument(ArgumentEntry obfArgumentEntry) {
708 // check the behavior
709 if (!containsObfBehavior(obfArgumentEntry.getBehaviorEntry())) {
710 return false;
711 }
712
713 // check the argument
714 if (obfArgumentEntry.getIndex() >= obfArgumentEntry.getBehaviorEntry().getSignature().getArgumentTypes().size()) {
715 return false;
716 }
717
718 return true;
719 }
720
721 public boolean containsObfEntry(Entry obfEntry) {
722 if (obfEntry instanceof ClassEntry) {
723 return containsObfClass((ClassEntry)obfEntry);
724 } else if (obfEntry instanceof FieldEntry) {
725 return containsObfField((FieldEntry)obfEntry);
726 } else if (obfEntry instanceof BehaviorEntry) {
727 return containsObfBehavior((BehaviorEntry)obfEntry);
728 } else if (obfEntry instanceof ArgumentEntry) {
729 return containsObfArgument((ArgumentEntry)obfEntry);
730 } else {
731 throw new Error("Entry type not supported: " + obfEntry.getClass().getName());
732 }
733 }
734}
diff --git a/src/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java b/src/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java
new file mode 100644
index 00000000..10092268
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java
@@ -0,0 +1,100 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.util.List;
14
15import javax.swing.tree.DefaultMutableTreeNode;
16
17import com.google.common.collect.Lists;
18
19import cuchaz.enigma.mapping.ClassEntry;
20import cuchaz.enigma.mapping.MethodEntry;
21import cuchaz.enigma.mapping.Translator;
22
23public class MethodImplementationsTreeNode extends DefaultMutableTreeNode {
24
25 private static final long serialVersionUID = 3781080657461899915L;
26
27 private Translator m_deobfuscatingTranslator;
28 private MethodEntry m_entry;
29
30 public MethodImplementationsTreeNode(Translator deobfuscatingTranslator, MethodEntry entry) {
31 if (entry == null) {
32 throw new IllegalArgumentException("entry cannot be null!");
33 }
34
35 m_deobfuscatingTranslator = deobfuscatingTranslator;
36 m_entry = entry;
37 }
38
39 public MethodEntry getMethodEntry() {
40 return m_entry;
41 }
42
43 public String getDeobfClassName() {
44 return m_deobfuscatingTranslator.translateClass(m_entry.getClassName());
45 }
46
47 public String getDeobfMethodName() {
48 return m_deobfuscatingTranslator.translate(m_entry);
49 }
50
51 @Override
52 public String toString() {
53 String className = getDeobfClassName();
54 if (className == null) {
55 className = m_entry.getClassName();
56 }
57
58 String methodName = getDeobfMethodName();
59 if (methodName == null) {
60 methodName = m_entry.getName();
61 }
62 return className + "." + methodName + "()";
63 }
64
65 public void load(JarIndex index) {
66 // get all method implementations
67 List<MethodImplementationsTreeNode> nodes = Lists.newArrayList();
68 for (String implementingClassName : index.getImplementingClasses(m_entry.getClassName())) {
69 MethodEntry methodEntry = new MethodEntry(
70 new ClassEntry(implementingClassName),
71 m_entry.getName(),
72 m_entry.getSignature()
73 );
74 if (index.containsObfBehavior(methodEntry)) {
75 nodes.add(new MethodImplementationsTreeNode(m_deobfuscatingTranslator, methodEntry));
76 }
77 }
78
79 // add them to this node
80 for (MethodImplementationsTreeNode node : nodes) {
81 this.add(node);
82 }
83 }
84
85 public static MethodImplementationsTreeNode findNode(MethodImplementationsTreeNode node, MethodEntry entry) {
86 // is this the node?
87 if (node.getMethodEntry().equals(entry)) {
88 return node;
89 }
90
91 // recurse
92 for (int i = 0; i < node.getChildCount(); i++) {
93 MethodImplementationsTreeNode foundNode = findNode((MethodImplementationsTreeNode)node.getChildAt(i), entry);
94 if (foundNode != null) {
95 return foundNode;
96 }
97 }
98 return null;
99 }
100}
diff --git a/src/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java b/src/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java
new file mode 100644
index 00000000..87182204
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java
@@ -0,0 +1,114 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.util.List;
14
15import javax.swing.tree.DefaultMutableTreeNode;
16
17import com.google.common.collect.Lists;
18
19import cuchaz.enigma.mapping.ClassEntry;
20import cuchaz.enigma.mapping.MethodEntry;
21import cuchaz.enigma.mapping.Translator;
22
23public class MethodInheritanceTreeNode extends DefaultMutableTreeNode {
24
25 private static final long serialVersionUID = 1096677030991810007L;
26
27 private Translator m_deobfuscatingTranslator;
28 private MethodEntry m_entry;
29 private boolean m_isImplemented;
30
31 public MethodInheritanceTreeNode(Translator deobfuscatingTranslator, MethodEntry entry, boolean isImplemented) {
32 m_deobfuscatingTranslator = deobfuscatingTranslator;
33 m_entry = entry;
34 m_isImplemented = isImplemented;
35 }
36
37 public MethodEntry getMethodEntry() {
38 return m_entry;
39 }
40
41 public String getDeobfClassName() {
42 return m_deobfuscatingTranslator.translateClass(m_entry.getClassName());
43 }
44
45 public String getDeobfMethodName() {
46 return m_deobfuscatingTranslator.translate(m_entry);
47 }
48
49 public boolean isImplemented() {
50 return m_isImplemented;
51 }
52
53 @Override
54 public String toString() {
55 String className = getDeobfClassName();
56 if (className == null) {
57 className = m_entry.getClassName();
58 }
59
60 if (!m_isImplemented) {
61 return className;
62 } else {
63 String methodName = getDeobfMethodName();
64 if (methodName == null) {
65 methodName = m_entry.getName();
66 }
67 return className + "." + methodName + "()";
68 }
69 }
70
71 public void load(JarIndex index, boolean recurse) {
72 // get all the child nodes
73 List<MethodInheritanceTreeNode> nodes = Lists.newArrayList();
74 for (ClassEntry subclassEntry : index.getTranslationIndex().getSubclass(m_entry.getClassEntry())) {
75 MethodEntry methodEntry = new MethodEntry(
76 subclassEntry,
77 m_entry.getName(),
78 m_entry.getSignature()
79 );
80 nodes.add(new MethodInheritanceTreeNode(
81 m_deobfuscatingTranslator,
82 methodEntry,
83 index.containsObfBehavior(methodEntry)
84 ));
85 }
86
87 // add them to this node
88 for (MethodInheritanceTreeNode node : nodes) {
89 this.add(node);
90 }
91
92 if (recurse) {
93 for (MethodInheritanceTreeNode node : nodes) {
94 node.load(index, true);
95 }
96 }
97 }
98
99 public static MethodInheritanceTreeNode findNode(MethodInheritanceTreeNode node, MethodEntry entry) {
100 // is this the node?
101 if (node.getMethodEntry().equals(entry)) {
102 return node;
103 }
104
105 // recurse
106 for (int i = 0; i < node.getChildCount(); i++) {
107 MethodInheritanceTreeNode foundNode = findNode((MethodInheritanceTreeNode)node.getChildAt(i), entry);
108 if (foundNode != null) {
109 return foundNode;
110 }
111 }
112 return null;
113 }
114}
diff --git a/src/cuchaz/enigma/analysis/ReferenceTreeNode.java b/src/cuchaz/enigma/analysis/ReferenceTreeNode.java
new file mode 100644
index 00000000..2b08616a
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/ReferenceTreeNode.java
@@ -0,0 +1,18 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import cuchaz.enigma.mapping.Entry;
14
15public interface ReferenceTreeNode<E extends Entry,C extends Entry> {
16 E getEntry();
17 EntryReference<E,C> getReference();
18}
diff --git a/src/cuchaz/enigma/analysis/SourceIndex.java b/src/cuchaz/enigma/analysis/SourceIndex.java
new file mode 100644
index 00000000..b43ab614
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/SourceIndex.java
@@ -0,0 +1,173 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.util.Collection;
14import java.util.List;
15import java.util.Map;
16import java.util.TreeMap;
17
18import com.google.common.collect.HashMultimap;
19import com.google.common.collect.Lists;
20import com.google.common.collect.Maps;
21import com.google.common.collect.Multimap;
22import com.strobel.decompiler.languages.Region;
23import com.strobel.decompiler.languages.java.ast.AstNode;
24import com.strobel.decompiler.languages.java.ast.Identifier;
25
26import cuchaz.enigma.mapping.Entry;
27
28public class SourceIndex {
29
30 private String m_source;
31 private TreeMap<Token,EntryReference<Entry,Entry>> m_tokenToReference;
32 private Multimap<EntryReference<Entry,Entry>,Token> m_referenceToTokens;
33 private Map<Entry,Token> m_declarationToToken;
34 private List<Integer> m_lineOffsets;
35
36 public SourceIndex(String source) {
37 m_source = source;
38 m_tokenToReference = Maps.newTreeMap();
39 m_referenceToTokens = HashMultimap.create();
40 m_declarationToToken = Maps.newHashMap();
41 m_lineOffsets = Lists.newArrayList();
42
43 // count the lines
44 m_lineOffsets.add(0);
45 for (int i = 0; i < source.length(); i++) {
46 if (source.charAt(i) == '\n') {
47 m_lineOffsets.add(i + 1);
48 }
49 }
50 }
51
52 public String getSource() {
53 return m_source;
54 }
55
56 public Token getToken(AstNode node) {
57
58 // get the text of the node
59 String name = "";
60 if (node instanceof Identifier) {
61 name = ((Identifier)node).getName();
62 }
63
64 // get a token for this node's region
65 Region region = node.getRegion();
66 if (region.getBeginLine() == 0 || region.getEndLine() == 0) {
67 // DEBUG
68 System.err.println(String.format("WARNING: %s \"%s\" has invalid region: %s", node.getNodeType(), name, region));
69 return null;
70 }
71 Token token = new Token(
72 toPos(region.getBeginLine(), region.getBeginColumn()),
73 toPos(region.getEndLine(), region.getEndColumn()),
74 m_source
75 );
76 if (token.start == 0) {
77 // DEBUG
78 System.err.println(String.format("WARNING: %s \"%s\" has invalid start: %s", node.getNodeType(), name, region));
79 return null;
80 }
81
82 // DEBUG
83 // System.out.println( String.format( "%s \"%s\" region: %s", node.getNodeType(), name, region ) );
84
85 // for tokens representing inner classes, make sure we only get the simple name
86 int pos = name.lastIndexOf('$');
87 if (pos >= 0) {
88 token.end -= pos + 1;
89 }
90
91 return token;
92 }
93
94 public void addReference(AstNode node, Entry deobfEntry, Entry deobfContext) {
95 Token token = getToken(node);
96 if (token != null) {
97 EntryReference<Entry,Entry> deobfReference = new EntryReference<Entry,Entry>(deobfEntry, token.text, deobfContext);
98 m_tokenToReference.put(token, deobfReference);
99 m_referenceToTokens.put(deobfReference, token);
100 }
101 }
102
103 public void addDeclaration(AstNode node, Entry deobfEntry) {
104 Token token = getToken(node);
105 if (token != null) {
106 EntryReference<Entry,Entry> reference = new EntryReference<Entry,Entry>(deobfEntry, token.text);
107 m_tokenToReference.put(token, reference);
108 m_referenceToTokens.put(reference, token);
109 m_declarationToToken.put(deobfEntry, token);
110 }
111 }
112
113 public Token getReferenceToken(int pos) {
114 Token token = m_tokenToReference.floorKey(new Token(pos, pos, null));
115 if (token != null && token.contains(pos)) {
116 return token;
117 }
118 return null;
119 }
120
121 public Collection<Token> getReferenceTokens(EntryReference<Entry,Entry> deobfReference) {
122 return m_referenceToTokens.get(deobfReference);
123 }
124
125 public EntryReference<Entry,Entry> getDeobfReference(Token token) {
126 if (token == null) {
127 return null;
128 }
129 return m_tokenToReference.get(token);
130 }
131
132 public void replaceDeobfReference(Token token, EntryReference<Entry,Entry> newDeobfReference) {
133 EntryReference<Entry,Entry> oldDeobfReference = m_tokenToReference.get(token);
134 m_tokenToReference.put(token, newDeobfReference);
135 Collection<Token> tokens = m_referenceToTokens.get(oldDeobfReference);
136 m_referenceToTokens.removeAll(oldDeobfReference);
137 m_referenceToTokens.putAll(newDeobfReference, tokens);
138 }
139
140 public Iterable<Token> referenceTokens() {
141 return m_tokenToReference.keySet();
142 }
143
144 public Iterable<Token> declarationTokens() {
145 return m_declarationToToken.values();
146 }
147
148 public Token getDeclarationToken(Entry deobfEntry) {
149 return m_declarationToToken.get(deobfEntry);
150 }
151
152 public int getLineNumber(int pos) {
153 // line number is 1-based
154 int line = 0;
155 for (Integer offset : m_lineOffsets) {
156 if (offset > pos) {
157 break;
158 }
159 line++;
160 }
161 return line;
162 }
163
164 public int getColumnNumber(int pos) {
165 // column number is 1-based
166 return pos - m_lineOffsets.get(getLineNumber(pos) - 1) + 1;
167 }
168
169 private int toPos(int line, int col) {
170 // line and col are 1-based
171 return m_lineOffsets.get(line - 1) + col - 1;
172 }
173}
diff --git a/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java b/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java
new file mode 100644
index 00000000..41551283
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java
@@ -0,0 +1,164 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.strobel.assembler.metadata.MemberReference;
14import com.strobel.assembler.metadata.MethodDefinition;
15import com.strobel.assembler.metadata.MethodReference;
16import com.strobel.assembler.metadata.ParameterDefinition;
17import com.strobel.assembler.metadata.TypeReference;
18import com.strobel.decompiler.languages.TextLocation;
19import com.strobel.decompiler.languages.java.ast.AstNode;
20import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration;
21import com.strobel.decompiler.languages.java.ast.IdentifierExpression;
22import com.strobel.decompiler.languages.java.ast.InvocationExpression;
23import com.strobel.decompiler.languages.java.ast.Keys;
24import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression;
25import com.strobel.decompiler.languages.java.ast.MethodDeclaration;
26import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression;
27import com.strobel.decompiler.languages.java.ast.ParameterDeclaration;
28import com.strobel.decompiler.languages.java.ast.SimpleType;
29import com.strobel.decompiler.languages.java.ast.SuperReferenceExpression;
30import com.strobel.decompiler.languages.java.ast.ThisReferenceExpression;
31
32import cuchaz.enigma.mapping.ArgumentEntry;
33import cuchaz.enigma.mapping.BehaviorEntry;
34import cuchaz.enigma.mapping.ClassEntry;
35import cuchaz.enigma.mapping.ConstructorEntry;
36import cuchaz.enigma.mapping.FieldEntry;
37import cuchaz.enigma.mapping.MethodEntry;
38import cuchaz.enigma.mapping.Signature;
39
40public class SourceIndexBehaviorVisitor extends SourceIndexVisitor {
41
42 private BehaviorEntry m_behaviorEntry;
43
44 public SourceIndexBehaviorVisitor(BehaviorEntry behaviorEntry) {
45 m_behaviorEntry = behaviorEntry;
46 }
47
48 @Override
49 public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) {
50 return recurse(node, index);
51 }
52
53 @Override
54 public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) {
55 return recurse(node, index);
56 }
57
58 @Override
59 public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) {
60 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
61
62 // get the behavior entry
63 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
64 BehaviorEntry behaviorEntry = null;
65 if (ref instanceof MethodReference) {
66 MethodReference methodRef = (MethodReference)ref;
67 if (methodRef.isConstructor()) {
68 behaviorEntry = new ConstructorEntry(classEntry, new Signature(ref.getSignature()));
69 } else if (methodRef.isTypeInitializer()) {
70 behaviorEntry = new ConstructorEntry(classEntry);
71 } else {
72 behaviorEntry = new MethodEntry(classEntry, ref.getName(), new Signature(ref.getSignature()));
73 }
74 }
75 if (behaviorEntry != null) {
76 // get the node for the token
77 AstNode tokenNode = null;
78 if (node.getTarget() instanceof MemberReferenceExpression) {
79 tokenNode = ((MemberReferenceExpression)node.getTarget()).getMemberNameToken();
80 } else if (node.getTarget() instanceof SuperReferenceExpression) {
81 tokenNode = node.getTarget();
82 } else if (node.getTarget() instanceof ThisReferenceExpression) {
83 tokenNode = node.getTarget();
84 }
85 if (tokenNode != null) {
86 index.addReference(tokenNode, behaviorEntry, m_behaviorEntry);
87 }
88 }
89
90 return recurse(node, index);
91 }
92
93 @Override
94 public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) {
95 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
96 if (ref != null) {
97 // make sure this is actually a field
98 if (ref.getSignature().indexOf('(') >= 0) {
99 throw new Error("Expected a field here! got " + ref);
100 }
101
102 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
103 FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName());
104 index.addReference(node.getMemberNameToken(), fieldEntry, m_behaviorEntry);
105 }
106
107 return recurse(node, index);
108 }
109
110 @Override
111 public Void visitSimpleType(SimpleType node, SourceIndex index) {
112 TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE);
113 if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) {
114 ClassEntry classEntry = new ClassEntry(ref.getInternalName());
115 index.addReference(node.getIdentifierToken(), classEntry, m_behaviorEntry);
116 }
117
118 return recurse(node, index);
119 }
120
121 @Override
122 public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) {
123 ParameterDefinition def = node.getUserData(Keys.PARAMETER_DEFINITION);
124 ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName());
125 MethodDefinition methodDef = (MethodDefinition)def.getMethod();
126 BehaviorEntry behaviorEntry;
127 if (methodDef.isConstructor()) {
128 behaviorEntry = new ConstructorEntry(classEntry, new Signature(methodDef.getSignature()));
129 } else {
130 behaviorEntry = new MethodEntry(classEntry, methodDef.getName(), new Signature(methodDef.getSignature()));
131 }
132 ArgumentEntry argumentEntry = new ArgumentEntry(behaviorEntry, def.getPosition(), node.getName());
133 index.addDeclaration(node.getNameToken(), argumentEntry);
134
135 return recurse(node, index);
136 }
137
138 @Override
139 public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) {
140 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
141 if (ref != null) {
142 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
143 FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName());
144 index.addReference(node.getIdentifierToken(), fieldEntry, m_behaviorEntry);
145 }
146
147 return recurse(node, index);
148 }
149
150 @Override
151 public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) {
152 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
153 if (ref != null) {
154 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
155 ConstructorEntry constructorEntry = new ConstructorEntry(classEntry, new Signature(ref.getSignature()));
156 if (node.getType() instanceof SimpleType) {
157 SimpleType simpleTypeNode = (SimpleType)node.getType();
158 index.addReference(simpleTypeNode.getIdentifierToken(), constructorEntry, m_behaviorEntry);
159 }
160 }
161
162 return recurse(node, index);
163 }
164}
diff --git a/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java b/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java
new file mode 100644
index 00000000..72220350
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java
@@ -0,0 +1,115 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.strobel.assembler.metadata.FieldDefinition;
14import com.strobel.assembler.metadata.MethodDefinition;
15import com.strobel.assembler.metadata.TypeDefinition;
16import com.strobel.assembler.metadata.TypeReference;
17import com.strobel.decompiler.languages.TextLocation;
18import com.strobel.decompiler.languages.java.ast.AstNode;
19import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration;
20import com.strobel.decompiler.languages.java.ast.EnumValueDeclaration;
21import com.strobel.decompiler.languages.java.ast.FieldDeclaration;
22import com.strobel.decompiler.languages.java.ast.Keys;
23import com.strobel.decompiler.languages.java.ast.MethodDeclaration;
24import com.strobel.decompiler.languages.java.ast.SimpleType;
25import com.strobel.decompiler.languages.java.ast.TypeDeclaration;
26import com.strobel.decompiler.languages.java.ast.VariableInitializer;
27
28import cuchaz.enigma.mapping.BehaviorEntry;
29import cuchaz.enigma.mapping.BehaviorEntryFactory;
30import cuchaz.enigma.mapping.ClassEntry;
31import cuchaz.enigma.mapping.ConstructorEntry;
32import cuchaz.enigma.mapping.FieldEntry;
33import cuchaz.enigma.mapping.Signature;
34
35public class SourceIndexClassVisitor extends SourceIndexVisitor {
36
37 private ClassEntry m_classEntry;
38
39 public SourceIndexClassVisitor(ClassEntry classEntry) {
40 m_classEntry = classEntry;
41 }
42
43 @Override
44 public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) {
45 // is this this class, or a subtype?
46 TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION);
47 ClassEntry classEntry = new ClassEntry(def.getInternalName());
48 if (!classEntry.equals(m_classEntry)) {
49 // it's a sub-type, recurse
50 index.addDeclaration(node.getNameToken(), classEntry);
51 return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index);
52 }
53
54 return recurse(node, index);
55 }
56
57 @Override
58 public Void visitSimpleType(SimpleType node, SourceIndex index) {
59 TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE);
60 if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) {
61 ClassEntry classEntry = new ClassEntry(ref.getInternalName());
62 index.addReference(node.getIdentifierToken(), classEntry, m_classEntry);
63 }
64
65 return recurse(node, index);
66 }
67
68 @Override
69 public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) {
70 MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION);
71 ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName());
72 BehaviorEntry behaviorEntry = BehaviorEntryFactory.create(classEntry, def.getName(), def.getSignature());
73 AstNode tokenNode = node.getNameToken();
74 if (behaviorEntry instanceof ConstructorEntry) {
75 ConstructorEntry constructorEntry = (ConstructorEntry)behaviorEntry;
76 if (constructorEntry.isStatic()) {
77 tokenNode = node.getModifiers().firstOrNullObject();
78 }
79 }
80 index.addDeclaration(tokenNode, behaviorEntry);
81 return node.acceptVisitor(new SourceIndexBehaviorVisitor(behaviorEntry), index);
82 }
83
84 @Override
85 public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) {
86 MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION);
87 ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName());
88 ConstructorEntry constructorEntry = new ConstructorEntry(classEntry, new Signature(def.getSignature()));
89 index.addDeclaration(node.getNameToken(), constructorEntry);
90 return node.acceptVisitor(new SourceIndexBehaviorVisitor(constructorEntry), index);
91 }
92
93 @Override
94 public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) {
95 FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION);
96 ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName());
97 FieldEntry fieldEntry = new FieldEntry(classEntry, def.getName());
98 assert (node.getVariables().size() == 1);
99 VariableInitializer variable = node.getVariables().firstOrNullObject();
100 index.addDeclaration(variable.getNameToken(), fieldEntry);
101
102 return recurse(node, index);
103 }
104
105 @Override
106 public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) {
107 // treat enum declarations as field declarations
108 FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION);
109 ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName());
110 FieldEntry fieldEntry = new FieldEntry(classEntry, def.getName());
111 index.addDeclaration(node.getNameToken(), fieldEntry);
112
113 return recurse(node, index);
114 }
115}
diff --git a/src/cuchaz/enigma/analysis/SourceIndexVisitor.java b/src/cuchaz/enigma/analysis/SourceIndexVisitor.java
new file mode 100644
index 00000000..0d5bdc02
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/SourceIndexVisitor.java
@@ -0,0 +1,452 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.strobel.assembler.metadata.TypeDefinition;
14import com.strobel.decompiler.languages.java.ast.Annotation;
15import com.strobel.decompiler.languages.java.ast.AnonymousObjectCreationExpression;
16import com.strobel.decompiler.languages.java.ast.ArrayCreationExpression;
17import com.strobel.decompiler.languages.java.ast.ArrayInitializerExpression;
18import com.strobel.decompiler.languages.java.ast.ArraySpecifier;
19import com.strobel.decompiler.languages.java.ast.AssertStatement;
20import com.strobel.decompiler.languages.java.ast.AssignmentExpression;
21import com.strobel.decompiler.languages.java.ast.AstNode;
22import com.strobel.decompiler.languages.java.ast.BinaryOperatorExpression;
23import com.strobel.decompiler.languages.java.ast.BlockStatement;
24import com.strobel.decompiler.languages.java.ast.BreakStatement;
25import com.strobel.decompiler.languages.java.ast.CaseLabel;
26import com.strobel.decompiler.languages.java.ast.CastExpression;
27import com.strobel.decompiler.languages.java.ast.CatchClause;
28import com.strobel.decompiler.languages.java.ast.ClassOfExpression;
29import com.strobel.decompiler.languages.java.ast.Comment;
30import com.strobel.decompiler.languages.java.ast.CompilationUnit;
31import com.strobel.decompiler.languages.java.ast.ComposedType;
32import com.strobel.decompiler.languages.java.ast.ConditionalExpression;
33import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration;
34import com.strobel.decompiler.languages.java.ast.ContinueStatement;
35import com.strobel.decompiler.languages.java.ast.DoWhileStatement;
36import com.strobel.decompiler.languages.java.ast.EmptyStatement;
37import com.strobel.decompiler.languages.java.ast.EnumValueDeclaration;
38import com.strobel.decompiler.languages.java.ast.ExpressionStatement;
39import com.strobel.decompiler.languages.java.ast.FieldDeclaration;
40import com.strobel.decompiler.languages.java.ast.ForEachStatement;
41import com.strobel.decompiler.languages.java.ast.ForStatement;
42import com.strobel.decompiler.languages.java.ast.GotoStatement;
43import com.strobel.decompiler.languages.java.ast.IAstVisitor;
44import com.strobel.decompiler.languages.java.ast.Identifier;
45import com.strobel.decompiler.languages.java.ast.IdentifierExpression;
46import com.strobel.decompiler.languages.java.ast.IfElseStatement;
47import com.strobel.decompiler.languages.java.ast.ImportDeclaration;
48import com.strobel.decompiler.languages.java.ast.IndexerExpression;
49import com.strobel.decompiler.languages.java.ast.InstanceInitializer;
50import com.strobel.decompiler.languages.java.ast.InstanceOfExpression;
51import com.strobel.decompiler.languages.java.ast.InvocationExpression;
52import com.strobel.decompiler.languages.java.ast.JavaTokenNode;
53import com.strobel.decompiler.languages.java.ast.Keys;
54import com.strobel.decompiler.languages.java.ast.LabelStatement;
55import com.strobel.decompiler.languages.java.ast.LabeledStatement;
56import com.strobel.decompiler.languages.java.ast.LambdaExpression;
57import com.strobel.decompiler.languages.java.ast.LocalTypeDeclarationStatement;
58import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression;
59import com.strobel.decompiler.languages.java.ast.MethodDeclaration;
60import com.strobel.decompiler.languages.java.ast.MethodGroupExpression;
61import com.strobel.decompiler.languages.java.ast.NewLineNode;
62import com.strobel.decompiler.languages.java.ast.NullReferenceExpression;
63import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression;
64import com.strobel.decompiler.languages.java.ast.PackageDeclaration;
65import com.strobel.decompiler.languages.java.ast.ParameterDeclaration;
66import com.strobel.decompiler.languages.java.ast.ParenthesizedExpression;
67import com.strobel.decompiler.languages.java.ast.PrimitiveExpression;
68import com.strobel.decompiler.languages.java.ast.ReturnStatement;
69import com.strobel.decompiler.languages.java.ast.SimpleType;
70import com.strobel.decompiler.languages.java.ast.SuperReferenceExpression;
71import com.strobel.decompiler.languages.java.ast.SwitchSection;
72import com.strobel.decompiler.languages.java.ast.SwitchStatement;
73import com.strobel.decompiler.languages.java.ast.SynchronizedStatement;
74import com.strobel.decompiler.languages.java.ast.TextNode;
75import com.strobel.decompiler.languages.java.ast.ThisReferenceExpression;
76import com.strobel.decompiler.languages.java.ast.ThrowStatement;
77import com.strobel.decompiler.languages.java.ast.TryCatchStatement;
78import com.strobel.decompiler.languages.java.ast.TypeDeclaration;
79import com.strobel.decompiler.languages.java.ast.TypeParameterDeclaration;
80import com.strobel.decompiler.languages.java.ast.TypeReferenceExpression;
81import com.strobel.decompiler.languages.java.ast.UnaryOperatorExpression;
82import com.strobel.decompiler.languages.java.ast.VariableDeclarationStatement;
83import com.strobel.decompiler.languages.java.ast.VariableInitializer;
84import com.strobel.decompiler.languages.java.ast.WhileStatement;
85import com.strobel.decompiler.languages.java.ast.WildcardType;
86import com.strobel.decompiler.patterns.Pattern;
87
88import cuchaz.enigma.mapping.ClassEntry;
89
90public class SourceIndexVisitor implements IAstVisitor<SourceIndex,Void> {
91
92 @Override
93 public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) {
94 TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION);
95 ClassEntry classEntry = new ClassEntry(def.getInternalName());
96 index.addDeclaration(node.getNameToken(), classEntry);
97
98 return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index);
99 }
100
101 protected Void recurse(AstNode node, SourceIndex index) {
102 for (final AstNode child : node.getChildren()) {
103 child.acceptVisitor(this, index);
104 }
105 return null;
106 }
107
108 @Override
109 public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) {
110 return recurse(node, index);
111 }
112
113 @Override
114 public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) {
115 return recurse(node, index);
116 }
117
118 @Override
119 public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) {
120 return recurse(node, index);
121 }
122
123 @Override
124 public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) {
125 return recurse(node, index);
126 }
127
128 @Override
129 public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) {
130 return recurse(node, index);
131 }
132
133 @Override
134 public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) {
135 return recurse(node, index);
136 }
137
138 @Override
139 public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) {
140 return recurse(node, index);
141 }
142
143 @Override
144 public Void visitSimpleType(SimpleType node, SourceIndex index) {
145 return recurse(node, index);
146 }
147
148 @Override
149 public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) {
150 return recurse(node, index);
151 }
152
153 @Override
154 public Void visitComment(Comment node, SourceIndex index) {
155 return recurse(node, index);
156 }
157
158 @Override
159 public Void visitPatternPlaceholder(AstNode node, Pattern pattern, SourceIndex index) {
160 return recurse(node, index);
161 }
162
163 @Override
164 public Void visitTypeReference(TypeReferenceExpression node, SourceIndex index) {
165 return recurse(node, index);
166 }
167
168 @Override
169 public Void visitJavaTokenNode(JavaTokenNode node, SourceIndex index) {
170 return recurse(node, index);
171 }
172
173 @Override
174 public Void visitIdentifier(Identifier node, SourceIndex index) {
175 return recurse(node, index);
176 }
177
178 @Override
179 public Void visitNullReferenceExpression(NullReferenceExpression node, SourceIndex index) {
180 return recurse(node, index);
181 }
182
183 @Override
184 public Void visitThisReferenceExpression(ThisReferenceExpression node, SourceIndex index) {
185 return recurse(node, index);
186 }
187
188 @Override
189 public Void visitSuperReferenceExpression(SuperReferenceExpression node, SourceIndex index) {
190 return recurse(node, index);
191 }
192
193 @Override
194 public Void visitClassOfExpression(ClassOfExpression node, SourceIndex index) {
195 return recurse(node, index);
196 }
197
198 @Override
199 public Void visitBlockStatement(BlockStatement node, SourceIndex index) {
200 return recurse(node, index);
201 }
202
203 @Override
204 public Void visitExpressionStatement(ExpressionStatement node, SourceIndex index) {
205 return recurse(node, index);
206 }
207
208 @Override
209 public Void visitBreakStatement(BreakStatement node, SourceIndex index) {
210 return recurse(node, index);
211 }
212
213 @Override
214 public Void visitContinueStatement(ContinueStatement node, SourceIndex index) {
215 return recurse(node, index);
216 }
217
218 @Override
219 public Void visitDoWhileStatement(DoWhileStatement node, SourceIndex index) {
220 return recurse(node, index);
221 }
222
223 @Override
224 public Void visitEmptyStatement(EmptyStatement node, SourceIndex index) {
225 return recurse(node, index);
226 }
227
228 @Override
229 public Void visitIfElseStatement(IfElseStatement node, SourceIndex index) {
230 return recurse(node, index);
231 }
232
233 @Override
234 public Void visitLabelStatement(LabelStatement node, SourceIndex index) {
235 return recurse(node, index);
236 }
237
238 @Override
239 public Void visitLabeledStatement(LabeledStatement node, SourceIndex index) {
240 return recurse(node, index);
241 }
242
243 @Override
244 public Void visitReturnStatement(ReturnStatement node, SourceIndex index) {
245 return recurse(node, index);
246 }
247
248 @Override
249 public Void visitSwitchStatement(SwitchStatement node, SourceIndex index) {
250 return recurse(node, index);
251 }
252
253 @Override
254 public Void visitSwitchSection(SwitchSection node, SourceIndex index) {
255 return recurse(node, index);
256 }
257
258 @Override
259 public Void visitCaseLabel(CaseLabel node, SourceIndex index) {
260 return recurse(node, index);
261 }
262
263 @Override
264 public Void visitThrowStatement(ThrowStatement node, SourceIndex index) {
265 return recurse(node, index);
266 }
267
268 @Override
269 public Void visitCatchClause(CatchClause node, SourceIndex index) {
270 return recurse(node, index);
271 }
272
273 @Override
274 public Void visitAnnotation(Annotation node, SourceIndex index) {
275 return recurse(node, index);
276 }
277
278 @Override
279 public Void visitNewLine(NewLineNode node, SourceIndex index) {
280 return recurse(node, index);
281 }
282
283 @Override
284 public Void visitVariableDeclaration(VariableDeclarationStatement node, SourceIndex index) {
285 return recurse(node, index);
286 }
287
288 @Override
289 public Void visitVariableInitializer(VariableInitializer node, SourceIndex index) {
290 return recurse(node, index);
291 }
292
293 @Override
294 public Void visitText(TextNode node, SourceIndex index) {
295 return recurse(node, index);
296 }
297
298 @Override
299 public Void visitImportDeclaration(ImportDeclaration node, SourceIndex index) {
300 return recurse(node, index);
301 }
302
303 @Override
304 public Void visitInitializerBlock(InstanceInitializer node, SourceIndex index) {
305 return recurse(node, index);
306 }
307
308 @Override
309 public Void visitTypeParameterDeclaration(TypeParameterDeclaration node, SourceIndex index) {
310 return recurse(node, index);
311 }
312
313 @Override
314 public Void visitCompilationUnit(CompilationUnit node, SourceIndex index) {
315 return recurse(node, index);
316 }
317
318 @Override
319 public Void visitPackageDeclaration(PackageDeclaration node, SourceIndex index) {
320 return recurse(node, index);
321 }
322
323 @Override
324 public Void visitArraySpecifier(ArraySpecifier node, SourceIndex index) {
325 return recurse(node, index);
326 }
327
328 @Override
329 public Void visitComposedType(ComposedType node, SourceIndex index) {
330 return recurse(node, index);
331 }
332
333 @Override
334 public Void visitWhileStatement(WhileStatement node, SourceIndex index) {
335 return recurse(node, index);
336 }
337
338 @Override
339 public Void visitPrimitiveExpression(PrimitiveExpression node, SourceIndex index) {
340 return recurse(node, index);
341 }
342
343 @Override
344 public Void visitCastExpression(CastExpression node, SourceIndex index) {
345 return recurse(node, index);
346 }
347
348 @Override
349 public Void visitBinaryOperatorExpression(BinaryOperatorExpression node, SourceIndex index) {
350 return recurse(node, index);
351 }
352
353 @Override
354 public Void visitInstanceOfExpression(InstanceOfExpression node, SourceIndex index) {
355 return recurse(node, index);
356 }
357
358 @Override
359 public Void visitIndexerExpression(IndexerExpression node, SourceIndex index) {
360 return recurse(node, index);
361 }
362
363 @Override
364 public Void visitUnaryOperatorExpression(UnaryOperatorExpression node, SourceIndex index) {
365 return recurse(node, index);
366 }
367
368 @Override
369 public Void visitConditionalExpression(ConditionalExpression node, SourceIndex index) {
370 return recurse(node, index);
371 }
372
373 @Override
374 public Void visitArrayInitializerExpression(ArrayInitializerExpression node, SourceIndex index) {
375 return recurse(node, index);
376 }
377
378 @Override
379 public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) {
380 return recurse(node, index);
381 }
382
383 @Override
384 public Void visitArrayCreationExpression(ArrayCreationExpression node, SourceIndex index) {
385 return recurse(node, index);
386 }
387
388 @Override
389 public Void visitAssignmentExpression(AssignmentExpression node, SourceIndex index) {
390 return recurse(node, index);
391 }
392
393 @Override
394 public Void visitForStatement(ForStatement node, SourceIndex index) {
395 return recurse(node, index);
396 }
397
398 @Override
399 public Void visitForEachStatement(ForEachStatement node, SourceIndex index) {
400 return recurse(node, index);
401 }
402
403 @Override
404 public Void visitTryCatchStatement(TryCatchStatement node, SourceIndex index) {
405 return recurse(node, index);
406 }
407
408 @Override
409 public Void visitGotoStatement(GotoStatement node, SourceIndex index) {
410 return recurse(node, index);
411 }
412
413 @Override
414 public Void visitParenthesizedExpression(ParenthesizedExpression node, SourceIndex index) {
415 return recurse(node, index);
416 }
417
418 @Override
419 public Void visitSynchronizedStatement(SynchronizedStatement node, SourceIndex index) {
420 return recurse(node, index);
421 }
422
423 @Override
424 public Void visitAnonymousObjectCreationExpression(AnonymousObjectCreationExpression node, SourceIndex index) {
425 return recurse(node, index);
426 }
427
428 @Override
429 public Void visitWildcardType(WildcardType node, SourceIndex index) {
430 return recurse(node, index);
431 }
432
433 @Override
434 public Void visitMethodGroupExpression(MethodGroupExpression node, SourceIndex index) {
435 return recurse(node, index);
436 }
437
438 @Override
439 public Void visitAssertStatement(AssertStatement node, SourceIndex index) {
440 return recurse(node, index);
441 }
442
443 @Override
444 public Void visitLambdaExpression(LambdaExpression node, SourceIndex index) {
445 return recurse(node, index);
446 }
447
448 @Override
449 public Void visitLocalTypeDeclarationStatement(LocalTypeDeclarationStatement node, SourceIndex index) {
450 return recurse(node, index);
451 }
452}
diff --git a/src/cuchaz/enigma/analysis/Token.java b/src/cuchaz/enigma/analysis/Token.java
new file mode 100644
index 00000000..481d2f47
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/Token.java
@@ -0,0 +1,56 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13public class Token implements Comparable<Token> {
14
15 public int start;
16 public int end;
17 public String text;
18
19 public Token(int start, int end) {
20 this(start, end, null);
21 }
22
23 public Token(int start, int end, String source) {
24 this.start = start;
25 this.end = end;
26 if (source != null) {
27 this.text = source.substring(start, end);
28 }
29 }
30
31 public boolean contains(int pos) {
32 return pos >= start && pos <= end;
33 }
34
35 @Override
36 public int compareTo(Token other) {
37 return start - other.start;
38 }
39
40 @Override
41 public boolean equals(Object other) {
42 if (other instanceof Token) {
43 return equals((Token)other);
44 }
45 return false;
46 }
47
48 public boolean equals(Token other) {
49 return start == other.start && end == other.end;
50 }
51
52 @Override
53 public String toString() {
54 return String.format("[%d,%d]", start, end);
55 }
56}
diff --git a/src/cuchaz/enigma/analysis/TranslationIndex.java b/src/cuchaz/enigma/analysis/TranslationIndex.java
new file mode 100644
index 00000000..7597c3ae
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/TranslationIndex.java
@@ -0,0 +1,227 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.io.IOException;
14import java.io.InputStream;
15import java.io.ObjectInputStream;
16import java.io.ObjectOutputStream;
17import java.io.OutputStream;
18import java.io.Serializable;
19import java.util.HashMap;
20import java.util.List;
21import java.util.Map;
22import java.util.Set;
23import java.util.zip.GZIPInputStream;
24import java.util.zip.GZIPOutputStream;
25
26import javassist.CtBehavior;
27import javassist.CtClass;
28import javassist.CtField;
29
30import com.google.common.collect.HashMultimap;
31import com.google.common.collect.Lists;
32import com.google.common.collect.Maps;
33import com.google.common.collect.Multimap;
34
35import cuchaz.enigma.mapping.ArgumentEntry;
36import cuchaz.enigma.mapping.BehaviorEntry;
37import cuchaz.enigma.mapping.ClassEntry;
38import cuchaz.enigma.mapping.Entry;
39import cuchaz.enigma.mapping.FieldEntry;
40import cuchaz.enigma.mapping.JavassistUtil;
41import cuchaz.enigma.mapping.Translator;
42
43public class TranslationIndex implements Serializable {
44
45 private static final long serialVersionUID = 738687982126844179L;
46
47 private Map<ClassEntry,ClassEntry> m_superclasses;
48 private Multimap<ClassEntry,FieldEntry> m_fieldEntries;
49 private Multimap<ClassEntry,BehaviorEntry> m_behaviorEntries;
50
51 public TranslationIndex() {
52 m_superclasses = Maps.newHashMap();
53 m_fieldEntries = HashMultimap.create();
54 m_behaviorEntries = HashMultimap.create();
55 }
56
57 public TranslationIndex(TranslationIndex other, Translator translator) {
58
59 // translate the superclasses
60 m_superclasses = Maps.newHashMap();
61 for (Map.Entry<ClassEntry,ClassEntry> mapEntry : other.m_superclasses.entrySet()) {
62 m_superclasses.put(
63 translator.translateEntry(mapEntry.getKey()),
64 translator.translateEntry(mapEntry.getValue())
65 );
66 }
67
68 // translate the fields
69 m_fieldEntries = HashMultimap.create();
70 for (Map.Entry<ClassEntry,FieldEntry> mapEntry : other.m_fieldEntries.entries()) {
71 m_fieldEntries.put(
72 translator.translateEntry(mapEntry.getKey()),
73 translator.translateEntry(mapEntry.getValue())
74 );
75 }
76
77 m_behaviorEntries = HashMultimap.create();
78 for (Map.Entry<ClassEntry,BehaviorEntry> mapEntry : other.m_behaviorEntries.entries()) {
79 m_behaviorEntries.put(
80 translator.translateEntry(mapEntry.getKey()),
81 translator.translateEntry(mapEntry.getValue())
82 );
83 }
84 }
85
86 public void indexClass(CtClass c) {
87
88 ClassEntry classEntry = JavassistUtil.getClassEntry(c);
89
90 // add the superclass
91 ClassEntry superclassEntry = JavassistUtil.getSuperclassEntry(c);
92 if (!isJre(classEntry) && superclassEntry != null && !isJre(superclassEntry)) {
93 m_superclasses.put(classEntry, superclassEntry);
94 }
95
96 // add fields
97 for (CtField field : c.getDeclaredFields()) {
98 FieldEntry fieldEntry = JavassistUtil.getFieldEntry(field);
99 m_fieldEntries.put(fieldEntry.getClassEntry(), fieldEntry);
100 }
101
102 // add behaviors
103 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
104 BehaviorEntry behaviorEntry = JavassistUtil.getBehaviorEntry(behavior);
105 m_behaviorEntries.put(behaviorEntry.getClassEntry(), behaviorEntry);
106 }
107 }
108
109 public void renameClasses(Map<String,String> renames) {
110 EntryRenamer.renameClassesInMap(renames, m_superclasses);
111 EntryRenamer.renameClassesInMultimap(renames, m_fieldEntries);
112 EntryRenamer.renameClassesInMultimap(renames, m_behaviorEntries);
113 }
114
115 public ClassEntry getSuperclass(ClassEntry classEntry) {
116 return m_superclasses.get(classEntry);
117 }
118
119 public List<ClassEntry> getAncestry(ClassEntry classEntry) {
120 List<ClassEntry> ancestors = Lists.newArrayList();
121 while (classEntry != null) {
122 classEntry = getSuperclass(classEntry);
123 if (classEntry != null) {
124 ancestors.add(classEntry);
125 }
126 }
127 return ancestors;
128 }
129
130 public List<ClassEntry> getSubclass(ClassEntry classEntry) {
131 // linear search is fast enough for now
132 List<ClassEntry> subclasses = Lists.newArrayList();
133 for (Map.Entry<ClassEntry,ClassEntry> entry : m_superclasses.entrySet()) {
134 ClassEntry subclass = entry.getKey();
135 ClassEntry superclass = entry.getValue();
136 if (classEntry.equals(superclass)) {
137 subclasses.add(subclass);
138 }
139 }
140 return subclasses;
141 }
142
143 public void getSubclassesRecursively(Set<ClassEntry> out, ClassEntry classEntry) {
144 for (ClassEntry subclassEntry : getSubclass(classEntry)) {
145 out.add(subclassEntry);
146 getSubclassesRecursively(out, subclassEntry);
147 }
148 }
149
150 public void getSubclassNamesRecursively(Set<String> out, ClassEntry classEntry) {
151 for (ClassEntry subclassEntry : getSubclass(classEntry)) {
152 out.add(subclassEntry.getName());
153 getSubclassNamesRecursively(out, subclassEntry);
154 }
155 }
156
157 public boolean entryExists(Entry entry) {
158 if (entry instanceof FieldEntry) {
159 return fieldExists((FieldEntry)entry);
160 } else if (entry instanceof BehaviorEntry) {
161 return behaviorExists((BehaviorEntry)entry);
162 } else if (entry instanceof ArgumentEntry) {
163 return behaviorExists(((ArgumentEntry)entry).getBehaviorEntry());
164 }
165 throw new IllegalArgumentException("Cannot check existence for " + entry.getClass());
166 }
167
168 public boolean fieldExists(FieldEntry fieldEntry) {
169 return m_fieldEntries.containsEntry(fieldEntry.getClassEntry(), fieldEntry);
170 }
171
172 public boolean behaviorExists(BehaviorEntry behaviorEntry) {
173 return m_behaviorEntries.containsEntry(behaviorEntry.getClassEntry(), behaviorEntry);
174 }
175
176 public ClassEntry resolveEntryClass(Entry entry) {
177
178 if (entry instanceof ClassEntry) {
179 return (ClassEntry)entry;
180 }
181
182 // this entry could refer to a method on a class where the method is not actually implemented
183 // travel up the inheritance tree to find the closest implementation
184 while (!entryExists(entry)) {
185
186 // is there a parent class?
187 ClassEntry superclassEntry = getSuperclass(entry.getClassEntry());
188 if (superclassEntry == null) {
189 // this is probably a method from a class in a library
190 // we can't trace the implementation up any higher unless we index the library
191 return null;
192 }
193
194 // move up to the parent class
195 entry = entry.cloneToNewClass(superclassEntry);
196 }
197 return entry.getClassEntry();
198 }
199
200 private boolean isJre(ClassEntry classEntry) {
201 String packageName = classEntry.getPackageName();
202 return packageName != null && (packageName.startsWith("java") || packageName.startsWith("javax"));
203 }
204
205 public void write(OutputStream out)
206 throws IOException {
207 GZIPOutputStream gzipout = new GZIPOutputStream(out);
208 ObjectOutputStream oout = new ObjectOutputStream(gzipout);
209 oout.writeObject(m_superclasses);
210 oout.writeObject(m_fieldEntries);
211 oout.writeObject(m_behaviorEntries);
212 gzipout.finish();
213 }
214
215 @SuppressWarnings("unchecked")
216 public void read(InputStream in)
217 throws IOException {
218 try {
219 ObjectInputStream oin = new ObjectInputStream(new GZIPInputStream(in));
220 m_superclasses = (HashMap<ClassEntry,ClassEntry>)oin.readObject();
221 m_fieldEntries = (HashMultimap<ClassEntry,FieldEntry>)oin.readObject();
222 m_behaviorEntries = (HashMultimap<ClassEntry,BehaviorEntry>)oin.readObject();
223 } catch (ClassNotFoundException ex) {
224 throw new Error(ex);
225 }
226 }
227}
diff --git a/src/cuchaz/enigma/analysis/TreeDumpVisitor.java b/src/cuchaz/enigma/analysis/TreeDumpVisitor.java
new file mode 100644
index 00000000..23f80899
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/TreeDumpVisitor.java
@@ -0,0 +1,512 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.io.File;
14import java.io.FileWriter;
15import java.io.IOException;
16import java.io.Writer;
17
18import com.strobel.componentmodel.Key;
19import com.strobel.decompiler.languages.java.ast.Annotation;
20import com.strobel.decompiler.languages.java.ast.AnonymousObjectCreationExpression;
21import com.strobel.decompiler.languages.java.ast.ArrayCreationExpression;
22import com.strobel.decompiler.languages.java.ast.ArrayInitializerExpression;
23import com.strobel.decompiler.languages.java.ast.ArraySpecifier;
24import com.strobel.decompiler.languages.java.ast.AssertStatement;
25import com.strobel.decompiler.languages.java.ast.AssignmentExpression;
26import com.strobel.decompiler.languages.java.ast.AstNode;
27import com.strobel.decompiler.languages.java.ast.BinaryOperatorExpression;
28import com.strobel.decompiler.languages.java.ast.BlockStatement;
29import com.strobel.decompiler.languages.java.ast.BreakStatement;
30import com.strobel.decompiler.languages.java.ast.CaseLabel;
31import com.strobel.decompiler.languages.java.ast.CastExpression;
32import com.strobel.decompiler.languages.java.ast.CatchClause;
33import com.strobel.decompiler.languages.java.ast.ClassOfExpression;
34import com.strobel.decompiler.languages.java.ast.Comment;
35import com.strobel.decompiler.languages.java.ast.CompilationUnit;
36import com.strobel.decompiler.languages.java.ast.ComposedType;
37import com.strobel.decompiler.languages.java.ast.ConditionalExpression;
38import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration;
39import com.strobel.decompiler.languages.java.ast.ContinueStatement;
40import com.strobel.decompiler.languages.java.ast.DoWhileStatement;
41import com.strobel.decompiler.languages.java.ast.EmptyStatement;
42import com.strobel.decompiler.languages.java.ast.EnumValueDeclaration;
43import com.strobel.decompiler.languages.java.ast.ExpressionStatement;
44import com.strobel.decompiler.languages.java.ast.FieldDeclaration;
45import com.strobel.decompiler.languages.java.ast.ForEachStatement;
46import com.strobel.decompiler.languages.java.ast.ForStatement;
47import com.strobel.decompiler.languages.java.ast.GotoStatement;
48import com.strobel.decompiler.languages.java.ast.IAstVisitor;
49import com.strobel.decompiler.languages.java.ast.Identifier;
50import com.strobel.decompiler.languages.java.ast.IdentifierExpression;
51import com.strobel.decompiler.languages.java.ast.IfElseStatement;
52import com.strobel.decompiler.languages.java.ast.ImportDeclaration;
53import com.strobel.decompiler.languages.java.ast.IndexerExpression;
54import com.strobel.decompiler.languages.java.ast.InstanceInitializer;
55import com.strobel.decompiler.languages.java.ast.InstanceOfExpression;
56import com.strobel.decompiler.languages.java.ast.InvocationExpression;
57import com.strobel.decompiler.languages.java.ast.JavaTokenNode;
58import com.strobel.decompiler.languages.java.ast.Keys;
59import com.strobel.decompiler.languages.java.ast.LabelStatement;
60import com.strobel.decompiler.languages.java.ast.LabeledStatement;
61import com.strobel.decompiler.languages.java.ast.LambdaExpression;
62import com.strobel.decompiler.languages.java.ast.LocalTypeDeclarationStatement;
63import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression;
64import com.strobel.decompiler.languages.java.ast.MethodDeclaration;
65import com.strobel.decompiler.languages.java.ast.MethodGroupExpression;
66import com.strobel.decompiler.languages.java.ast.NewLineNode;
67import com.strobel.decompiler.languages.java.ast.NullReferenceExpression;
68import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression;
69import com.strobel.decompiler.languages.java.ast.PackageDeclaration;
70import com.strobel.decompiler.languages.java.ast.ParameterDeclaration;
71import com.strobel.decompiler.languages.java.ast.ParenthesizedExpression;
72import com.strobel.decompiler.languages.java.ast.PrimitiveExpression;
73import com.strobel.decompiler.languages.java.ast.ReturnStatement;
74import com.strobel.decompiler.languages.java.ast.SimpleType;
75import com.strobel.decompiler.languages.java.ast.SuperReferenceExpression;
76import com.strobel.decompiler.languages.java.ast.SwitchSection;
77import com.strobel.decompiler.languages.java.ast.SwitchStatement;
78import com.strobel.decompiler.languages.java.ast.SynchronizedStatement;
79import com.strobel.decompiler.languages.java.ast.TextNode;
80import com.strobel.decompiler.languages.java.ast.ThisReferenceExpression;
81import com.strobel.decompiler.languages.java.ast.ThrowStatement;
82import com.strobel.decompiler.languages.java.ast.TryCatchStatement;
83import com.strobel.decompiler.languages.java.ast.TypeDeclaration;
84import com.strobel.decompiler.languages.java.ast.TypeParameterDeclaration;
85import com.strobel.decompiler.languages.java.ast.TypeReferenceExpression;
86import com.strobel.decompiler.languages.java.ast.UnaryOperatorExpression;
87import com.strobel.decompiler.languages.java.ast.VariableDeclarationStatement;
88import com.strobel.decompiler.languages.java.ast.VariableInitializer;
89import com.strobel.decompiler.languages.java.ast.WhileStatement;
90import com.strobel.decompiler.languages.java.ast.WildcardType;
91import com.strobel.decompiler.patterns.Pattern;
92
93public class TreeDumpVisitor implements IAstVisitor<Void,Void> {
94
95 private File m_file;
96 private Writer m_out;
97
98 public TreeDumpVisitor(File file) {
99 m_file = file;
100 m_out = null;
101 }
102
103 @Override
104 public Void visitCompilationUnit(CompilationUnit node, Void ignored) {
105 try {
106 m_out = new FileWriter(m_file);
107 recurse(node, ignored);
108 m_out.close();
109 return null;
110 } catch (IOException ex) {
111 throw new Error(ex);
112 }
113 }
114
115 private Void recurse(AstNode node, Void ignored) {
116 // show the tree
117 try {
118 m_out.write(getIndent(node) + node.getClass().getSimpleName() + " " + getText(node) + " " + dumpUserData(node) + " " + node.getRegion() + "\n");
119 } catch (IOException ex) {
120 throw new Error(ex);
121 }
122
123 // recurse
124 for (final AstNode child : node.getChildren()) {
125 child.acceptVisitor(this, ignored);
126 }
127 return null;
128 }
129
130 private String getText(AstNode node) {
131 if (node instanceof Identifier) {
132 return "\"" + ((Identifier)node).getName() + "\"";
133 }
134 return "";
135 }
136
137 private String dumpUserData(AstNode node) {
138 StringBuilder buf = new StringBuilder();
139 for (Key<?> key : Keys.ALL_KEYS) {
140 Object val = node.getUserData(key);
141 if (val != null) {
142 buf.append(String.format(" [%s=%s]", key, val));
143 }
144 }
145 return buf.toString();
146 }
147
148 private String getIndent(AstNode node) {
149 StringBuilder buf = new StringBuilder();
150 int depth = getDepth(node);
151 for (int i = 0; i < depth; i++) {
152 buf.append("\t");
153 }
154 return buf.toString();
155 }
156
157 private int getDepth(AstNode node) {
158 int depth = -1;
159 while (node != null) {
160 depth++;
161 node = node.getParent();
162 }
163 return depth;
164 }
165
166 // OVERRIDES WE DON'T CARE ABOUT
167
168 @Override
169 public Void visitInvocationExpression(InvocationExpression node, Void ignored) {
170 return recurse(node, ignored);
171 }
172
173 @Override
174 public Void visitMemberReferenceExpression(MemberReferenceExpression node, Void ignored) {
175 return recurse(node, ignored);
176 }
177
178 @Override
179 public Void visitSimpleType(SimpleType node, Void ignored) {
180 return recurse(node, ignored);
181 }
182
183 @Override
184 public Void visitMethodDeclaration(MethodDeclaration node, Void ignored) {
185 return recurse(node, ignored);
186 }
187
188 @Override
189 public Void visitConstructorDeclaration(ConstructorDeclaration node, Void ignored) {
190 return recurse(node, ignored);
191 }
192
193 @Override
194 public Void visitParameterDeclaration(ParameterDeclaration node, Void ignored) {
195 return recurse(node, ignored);
196 }
197
198 @Override
199 public Void visitFieldDeclaration(FieldDeclaration node, Void ignored) {
200 return recurse(node, ignored);
201 }
202
203 @Override
204 public Void visitTypeDeclaration(TypeDeclaration node, Void ignored) {
205 return recurse(node, ignored);
206 }
207
208 @Override
209 public Void visitComment(Comment node, Void ignored) {
210 return recurse(node, ignored);
211 }
212
213 @Override
214 public Void visitPatternPlaceholder(AstNode node, Pattern pattern, Void ignored) {
215 return recurse(node, ignored);
216 }
217
218 @Override
219 public Void visitTypeReference(TypeReferenceExpression node, Void ignored) {
220 return recurse(node, ignored);
221 }
222
223 @Override
224 public Void visitJavaTokenNode(JavaTokenNode node, Void ignored) {
225 return recurse(node, ignored);
226 }
227
228 @Override
229 public Void visitIdentifier(Identifier node, Void ignored) {
230 return recurse(node, ignored);
231 }
232
233 @Override
234 public Void visitNullReferenceExpression(NullReferenceExpression node, Void ignored) {
235 return recurse(node, ignored);
236 }
237
238 @Override
239 public Void visitThisReferenceExpression(ThisReferenceExpression node, Void ignored) {
240 return recurse(node, ignored);
241 }
242
243 @Override
244 public Void visitSuperReferenceExpression(SuperReferenceExpression node, Void ignored) {
245 return recurse(node, ignored);
246 }
247
248 @Override
249 public Void visitClassOfExpression(ClassOfExpression node, Void ignored) {
250 return recurse(node, ignored);
251 }
252
253 @Override
254 public Void visitBlockStatement(BlockStatement node, Void ignored) {
255 return recurse(node, ignored);
256 }
257
258 @Override
259 public Void visitExpressionStatement(ExpressionStatement node, Void ignored) {
260 return recurse(node, ignored);
261 }
262
263 @Override
264 public Void visitBreakStatement(BreakStatement node, Void ignored) {
265 return recurse(node, ignored);
266 }
267
268 @Override
269 public Void visitContinueStatement(ContinueStatement node, Void ignored) {
270 return recurse(node, ignored);
271 }
272
273 @Override
274 public Void visitDoWhileStatement(DoWhileStatement node, Void ignored) {
275 return recurse(node, ignored);
276 }
277
278 @Override
279 public Void visitEmptyStatement(EmptyStatement node, Void ignored) {
280 return recurse(node, ignored);
281 }
282
283 @Override
284 public Void visitIfElseStatement(IfElseStatement node, Void ignored) {
285 return recurse(node, ignored);
286 }
287
288 @Override
289 public Void visitLabelStatement(LabelStatement node, Void ignored) {
290 return recurse(node, ignored);
291 }
292
293 @Override
294 public Void visitLabeledStatement(LabeledStatement node, Void ignored) {
295 return recurse(node, ignored);
296 }
297
298 @Override
299 public Void visitReturnStatement(ReturnStatement node, Void ignored) {
300 return recurse(node, ignored);
301 }
302
303 @Override
304 public Void visitSwitchStatement(SwitchStatement node, Void ignored) {
305 return recurse(node, ignored);
306 }
307
308 @Override
309 public Void visitSwitchSection(SwitchSection node, Void ignored) {
310 return recurse(node, ignored);
311 }
312
313 @Override
314 public Void visitCaseLabel(CaseLabel node, Void ignored) {
315 return recurse(node, ignored);
316 }
317
318 @Override
319 public Void visitThrowStatement(ThrowStatement node, Void ignored) {
320 return recurse(node, ignored);
321 }
322
323 @Override
324 public Void visitCatchClause(CatchClause node, Void ignored) {
325 return recurse(node, ignored);
326 }
327
328 @Override
329 public Void visitAnnotation(Annotation node, Void ignored) {
330 return recurse(node, ignored);
331 }
332
333 @Override
334 public Void visitNewLine(NewLineNode node, Void ignored) {
335 return recurse(node, ignored);
336 }
337
338 @Override
339 public Void visitVariableDeclaration(VariableDeclarationStatement node, Void ignored) {
340 return recurse(node, ignored);
341 }
342
343 @Override
344 public Void visitVariableInitializer(VariableInitializer node, Void ignored) {
345 return recurse(node, ignored);
346 }
347
348 @Override
349 public Void visitText(TextNode node, Void ignored) {
350 return recurse(node, ignored);
351 }
352
353 @Override
354 public Void visitImportDeclaration(ImportDeclaration node, Void ignored) {
355 return recurse(node, ignored);
356 }
357
358 @Override
359 public Void visitInitializerBlock(InstanceInitializer node, Void ignored) {
360 return recurse(node, ignored);
361 }
362
363 @Override
364 public Void visitTypeParameterDeclaration(TypeParameterDeclaration node, Void ignored) {
365 return recurse(node, ignored);
366 }
367
368 @Override
369 public Void visitPackageDeclaration(PackageDeclaration node, Void ignored) {
370 return recurse(node, ignored);
371 }
372
373 @Override
374 public Void visitArraySpecifier(ArraySpecifier node, Void ignored) {
375 return recurse(node, ignored);
376 }
377
378 @Override
379 public Void visitComposedType(ComposedType node, Void ignored) {
380 return recurse(node, ignored);
381 }
382
383 @Override
384 public Void visitWhileStatement(WhileStatement node, Void ignored) {
385 return recurse(node, ignored);
386 }
387
388 @Override
389 public Void visitPrimitiveExpression(PrimitiveExpression node, Void ignored) {
390 return recurse(node, ignored);
391 }
392
393 @Override
394 public Void visitCastExpression(CastExpression node, Void ignored) {
395 return recurse(node, ignored);
396 }
397
398 @Override
399 public Void visitBinaryOperatorExpression(BinaryOperatorExpression node, Void ignored) {
400 return recurse(node, ignored);
401 }
402
403 @Override
404 public Void visitInstanceOfExpression(InstanceOfExpression node, Void ignored) {
405 return recurse(node, ignored);
406 }
407
408 @Override
409 public Void visitIndexerExpression(IndexerExpression node, Void ignored) {
410 return recurse(node, ignored);
411 }
412
413 @Override
414 public Void visitIdentifierExpression(IdentifierExpression node, Void ignored) {
415 return recurse(node, ignored);
416 }
417
418 @Override
419 public Void visitUnaryOperatorExpression(UnaryOperatorExpression node, Void ignored) {
420 return recurse(node, ignored);
421 }
422
423 @Override
424 public Void visitConditionalExpression(ConditionalExpression node, Void ignored) {
425 return recurse(node, ignored);
426 }
427
428 @Override
429 public Void visitArrayInitializerExpression(ArrayInitializerExpression node, Void ignored) {
430 return recurse(node, ignored);
431 }
432
433 @Override
434 public Void visitObjectCreationExpression(ObjectCreationExpression node, Void ignored) {
435 return recurse(node, ignored);
436 }
437
438 @Override
439 public Void visitArrayCreationExpression(ArrayCreationExpression node, Void ignored) {
440 return recurse(node, ignored);
441 }
442
443 @Override
444 public Void visitAssignmentExpression(AssignmentExpression node, Void ignored) {
445 return recurse(node, ignored);
446 }
447
448 @Override
449 public Void visitForStatement(ForStatement node, Void ignored) {
450 return recurse(node, ignored);
451 }
452
453 @Override
454 public Void visitForEachStatement(ForEachStatement node, Void ignored) {
455 return recurse(node, ignored);
456 }
457
458 @Override
459 public Void visitTryCatchStatement(TryCatchStatement node, Void ignored) {
460 return recurse(node, ignored);
461 }
462
463 @Override
464 public Void visitGotoStatement(GotoStatement node, Void ignored) {
465 return recurse(node, ignored);
466 }
467
468 @Override
469 public Void visitParenthesizedExpression(ParenthesizedExpression node, Void ignored) {
470 return recurse(node, ignored);
471 }
472
473 @Override
474 public Void visitSynchronizedStatement(SynchronizedStatement node, Void ignored) {
475 return recurse(node, ignored);
476 }
477
478 @Override
479 public Void visitAnonymousObjectCreationExpression(AnonymousObjectCreationExpression node, Void ignored) {
480 return recurse(node, ignored);
481 }
482
483 @Override
484 public Void visitWildcardType(WildcardType node, Void ignored) {
485 return recurse(node, ignored);
486 }
487
488 @Override
489 public Void visitMethodGroupExpression(MethodGroupExpression node, Void ignored) {
490 return recurse(node, ignored);
491 }
492
493 @Override
494 public Void visitEnumValueDeclaration(EnumValueDeclaration node, Void ignored) {
495 return recurse(node, ignored);
496 }
497
498 @Override
499 public Void visitAssertStatement(AssertStatement node, Void ignored) {
500 return recurse(node, ignored);
501 }
502
503 @Override
504 public Void visitLambdaExpression(LambdaExpression node, Void ignored) {
505 return recurse(node, ignored);
506 }
507
508 @Override
509 public Void visitLocalTypeDeclarationStatement(LocalTypeDeclarationStatement node, Void ignored) {
510 return recurse(node, ignored);
511 }
512}
diff --git a/src/cuchaz/enigma/bytecode/CheckCastIterator.java b/src/cuchaz/enigma/bytecode/CheckCastIterator.java
new file mode 100644
index 00000000..52845573
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/CheckCastIterator.java
@@ -0,0 +1,127 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.util.Iterator;
14
15import javassist.bytecode.BadBytecode;
16import javassist.bytecode.CodeAttribute;
17import javassist.bytecode.CodeIterator;
18import javassist.bytecode.ConstPool;
19import javassist.bytecode.Descriptor;
20import javassist.bytecode.Opcode;
21import cuchaz.enigma.bytecode.CheckCastIterator.CheckCast;
22import cuchaz.enigma.mapping.ClassEntry;
23import cuchaz.enigma.mapping.MethodEntry;
24import cuchaz.enigma.mapping.Signature;
25
26public class CheckCastIterator implements Iterator<CheckCast> {
27
28 public static class CheckCast {
29
30 public String className;
31 public MethodEntry prevMethodEntry;
32
33 public CheckCast(String className, MethodEntry prevMethodEntry) {
34 this.className = className;
35 this.prevMethodEntry = prevMethodEntry;
36 }
37 }
38
39 private ConstPool m_constants;
40 private CodeAttribute m_attribute;
41 private CodeIterator m_iter;
42 private CheckCast m_next;
43
44 public CheckCastIterator(CodeAttribute codeAttribute) throws BadBytecode {
45 m_constants = codeAttribute.getConstPool();
46 m_attribute = codeAttribute;
47 m_iter = m_attribute.iterator();
48
49 m_next = getNext();
50 }
51
52 @Override
53 public boolean hasNext() {
54 return m_next != null;
55 }
56
57 @Override
58 public CheckCast next() {
59 CheckCast out = m_next;
60 try {
61 m_next = getNext();
62 } catch (BadBytecode ex) {
63 throw new Error(ex);
64 }
65 return out;
66 }
67
68 @Override
69 public void remove() {
70 throw new UnsupportedOperationException();
71 }
72
73 private CheckCast getNext() throws BadBytecode {
74 int prevPos = 0;
75 while (m_iter.hasNext()) {
76 int pos = m_iter.next();
77 int opcode = m_iter.byteAt(pos);
78 switch (opcode) {
79 case Opcode.CHECKCAST:
80
81 // get the type of this op code (next two bytes are a classinfo index)
82 MethodEntry prevMethodEntry = getMethodEntry(prevPos);
83 if (prevMethodEntry != null) {
84 return new CheckCast(m_constants.getClassInfo(m_iter.s16bitAt(pos + 1)), prevMethodEntry);
85 }
86 break;
87 }
88 prevPos = pos;
89 }
90 return null;
91 }
92
93 private MethodEntry getMethodEntry(int pos) {
94 switch (m_iter.byteAt(pos)) {
95 case Opcode.INVOKEVIRTUAL:
96 case Opcode.INVOKESTATIC:
97 case Opcode.INVOKEDYNAMIC:
98 case Opcode.INVOKESPECIAL: {
99 int index = m_iter.s16bitAt(pos + 1);
100 return new MethodEntry(
101 new ClassEntry(Descriptor.toJvmName(m_constants.getMethodrefClassName(index))),
102 m_constants.getMethodrefName(index),
103 new Signature(m_constants.getMethodrefType(index))
104 );
105 }
106
107 case Opcode.INVOKEINTERFACE: {
108 int index = m_iter.s16bitAt(pos + 1);
109 return new MethodEntry(
110 new ClassEntry(Descriptor.toJvmName(m_constants.getInterfaceMethodrefClassName(index))),
111 m_constants.getInterfaceMethodrefName(index),
112 new Signature(m_constants.getInterfaceMethodrefType(index))
113 );
114 }
115 }
116 return null;
117 }
118
119 public Iterable<CheckCast> casts() {
120 return new Iterable<CheckCast>() {
121 @Override
122 public Iterator<CheckCast> iterator() {
123 return CheckCastIterator.this;
124 }
125 };
126 }
127}
diff --git a/src/cuchaz/enigma/bytecode/ClassRenamer.java b/src/cuchaz/enigma/bytecode/ClassRenamer.java
new file mode 100644
index 00000000..a5fea926
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/ClassRenamer.java
@@ -0,0 +1,110 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.util.Map;
14import java.util.Set;
15
16import javassist.ClassMap;
17import javassist.CtClass;
18import javassist.bytecode.ConstPool;
19import javassist.bytecode.Descriptor;
20import javassist.bytecode.InnerClassesAttribute;
21
22import com.google.common.collect.Maps;
23import com.google.common.collect.Sets;
24
25import cuchaz.enigma.mapping.ClassEntry;
26
27public class ClassRenamer {
28
29 public static void renameClasses(CtClass c, Map<ClassEntry,ClassEntry> map) {
30
31 // build the map used by javassist
32 ClassMap nameMap = new ClassMap();
33 for (Map.Entry<ClassEntry,ClassEntry> entry : map.entrySet()) {
34 nameMap.put(entry.getKey().getName(), entry.getValue().getName());
35 }
36
37 c.replaceClassName(nameMap);
38
39 // replace simple names in the InnerClasses attribute too
40 ConstPool constants = c.getClassFile().getConstPool();
41 InnerClassesAttribute attr = (InnerClassesAttribute)c.getClassFile().getAttribute(InnerClassesAttribute.tag);
42 if (attr != null) {
43 for (int i = 0; i < attr.tableLength(); i++) {
44 ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(attr.innerClass(i)));
45 if (attr.innerNameIndex(i) != 0) {
46 attr.setInnerNameIndex(i, constants.addUtf8Info(classEntry.getInnerClassName()));
47 }
48
49 /* DEBUG
50 System.out.println(String.format("\tDEOBF: %s-> ATTR: %s,%s,%s", classEntry, attr.outerClass(i), attr.innerClass(i), attr.innerName(i)));
51 */
52 }
53 }
54 }
55
56 public static Set<ClassEntry> getAllClassEntries(final CtClass c) {
57
58 // get the classes that javassist knows about
59 final Set<ClassEntry> entries = Sets.newHashSet();
60 ClassMap map = new ClassMap() {
61 @Override
62 public Object get(Object obj) {
63 if (obj instanceof String) {
64 String str = (String)obj;
65
66 // javassist throws a lot of weird things at this map
67 // I either have to implement my on class scanner, or just try to filter out the weirdness
68 // I'm opting to filter out the weirdness for now
69
70 // skip anything with generic arguments
71 if (str.indexOf('<') >= 0 || str.indexOf('>') >= 0 || str.indexOf(';') >= 0) {
72 return null;
73 }
74
75 // convert path/to/class.inner to path/to/class$inner
76 str = str.replace('.', '$');
77
78 // remember everything else
79 entries.add(new ClassEntry(str));
80 }
81 return null;
82 }
83
84 private static final long serialVersionUID = -202160293602070641L;
85 };
86 c.replaceClassName(map);
87
88 return entries;
89 }
90
91 public static void moveAllClassesOutOfDefaultPackage(CtClass c, String newPackageName) {
92 Map<ClassEntry,ClassEntry> map = Maps.newHashMap();
93 for (ClassEntry classEntry : ClassRenamer.getAllClassEntries(c)) {
94 if (classEntry.isInDefaultPackage()) {
95 map.put(classEntry, new ClassEntry(newPackageName + "/" + classEntry.getName()));
96 }
97 }
98 ClassRenamer.renameClasses(c, map);
99 }
100
101 public static void moveAllClassesIntoDefaultPackage(CtClass c, String oldPackageName) {
102 Map<ClassEntry,ClassEntry> map = Maps.newHashMap();
103 for (ClassEntry classEntry : ClassRenamer.getAllClassEntries(c)) {
104 if (classEntry.getPackageName().equals(oldPackageName)) {
105 map.put(classEntry, new ClassEntry(classEntry.getSimpleName()));
106 }
107 }
108 ClassRenamer.renameClasses(c, map);
109 }
110}
diff --git a/src/cuchaz/enigma/bytecode/ClassTranslator.java b/src/cuchaz/enigma/bytecode/ClassTranslator.java
new file mode 100644
index 00000000..afd3a777
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/ClassTranslator.java
@@ -0,0 +1,144 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.util.Map;
14
15import javassist.CtBehavior;
16import javassist.CtClass;
17import javassist.CtField;
18import javassist.CtMethod;
19import javassist.bytecode.ConstPool;
20import javassist.bytecode.Descriptor;
21import javassist.bytecode.SourceFileAttribute;
22
23import com.google.common.collect.Maps;
24
25import cuchaz.enigma.mapping.BehaviorEntry;
26import cuchaz.enigma.mapping.BehaviorEntryFactory;
27import cuchaz.enigma.mapping.ClassEntry;
28import cuchaz.enigma.mapping.FieldEntry;
29import cuchaz.enigma.mapping.JavassistUtil;
30import cuchaz.enigma.mapping.MethodEntry;
31import cuchaz.enigma.mapping.Signature;
32import cuchaz.enigma.mapping.Translator;
33import cuchaz.enigma.mapping.Type;
34
35public class ClassTranslator {
36
37 private Translator m_translator;
38
39 public ClassTranslator(Translator translator) {
40 m_translator = translator;
41 }
42
43 public void translate(CtClass c) {
44
45 // NOTE: the order of these translations is very important
46
47 // translate all the field and method references in the code by editing the constant pool
48 ConstPool constants = c.getClassFile().getConstPool();
49 ConstPoolEditor editor = new ConstPoolEditor(constants);
50 for (int i = 1; i < constants.getSize(); i++) {
51 switch (constants.getTag(i)) {
52
53 case ConstPool.CONST_Fieldref: {
54
55 // translate the name
56 FieldEntry entry = new FieldEntry(
57 new ClassEntry(Descriptor.toJvmName(constants.getFieldrefClassName(i))),
58 constants.getFieldrefName(i)
59 );
60 FieldEntry translatedEntry = m_translator.translateEntry(entry);
61
62 // translate the type
63 Type type = new Type(constants.getFieldrefType(i));
64 Type translatedType = m_translator.translateType(type);
65
66 if (!entry.equals(translatedEntry) || !type.equals(translatedType)) {
67 editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedType.toString());
68 }
69 }
70 break;
71
72 case ConstPool.CONST_Methodref:
73 case ConstPool.CONST_InterfaceMethodref: {
74
75 // translate the name and type
76 BehaviorEntry entry = BehaviorEntryFactory.create(
77 Descriptor.toJvmName(editor.getMemberrefClassname(i)),
78 editor.getMemberrefName(i),
79 editor.getMemberrefType(i)
80 );
81 BehaviorEntry translatedEntry = m_translator.translateEntry(entry);
82
83 if (!entry.getName().equals(translatedEntry.getName()) || !entry.getSignature().equals(translatedEntry.getSignature())) {
84 editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedEntry.getSignature().toString());
85 }
86 }
87 break;
88 }
89 }
90
91 ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
92
93 // translate all the fields
94 for (CtField field : c.getDeclaredFields()) {
95
96 // translate the name
97 FieldEntry entry = new FieldEntry(classEntry, field.getName());
98 String translatedName = m_translator.translate(entry);
99 if (translatedName != null) {
100 field.setName(translatedName);
101 }
102
103 // translate the type
104 Type translatedType = m_translator.translateType(new Type(field.getFieldInfo().getDescriptor()));
105 field.getFieldInfo().setDescriptor(translatedType.toString());
106 }
107
108 // translate all the methods and constructors
109 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
110 if (behavior instanceof CtMethod) {
111 CtMethod method = (CtMethod)behavior;
112
113 // translate the name
114 MethodEntry entry = JavassistUtil.getMethodEntry(method);
115 String translatedName = m_translator.translate(entry);
116 if (translatedName != null) {
117 method.setName(translatedName);
118 }
119 }
120
121 // translate the type
122 Signature translatedSignature = m_translator.translateSignature(new Signature(behavior.getMethodInfo().getDescriptor()));
123 behavior.getMethodInfo().setDescriptor(translatedSignature.toString());
124 }
125
126 // translate all the class names referenced in the code
127 // the above code only changed method/field/reference names and types, but not the class names themselves
128 Map<ClassEntry,ClassEntry> map = Maps.newHashMap();
129 for (ClassEntry obfClassEntry : ClassRenamer.getAllClassEntries(c)) {
130 ClassEntry deobfClassEntry = m_translator.translateEntry(obfClassEntry);
131 if (!obfClassEntry.equals(deobfClassEntry)) {
132 map.put(obfClassEntry, deobfClassEntry);
133 }
134 }
135 ClassRenamer.renameClasses(c, map);
136
137 // translate the source file attribute too
138 ClassEntry deobfClassEntry = map.get(classEntry);
139 if (deobfClassEntry != null) {
140 String sourceFile = Descriptor.toJvmName(deobfClassEntry.getOuterClassName()) + ".java";
141 c.getClassFile().addAttribute(new SourceFileAttribute(constants, sourceFile));
142 }
143 }
144}
diff --git a/src/cuchaz/enigma/bytecode/ConstPoolEditor.java b/src/cuchaz/enigma/bytecode/ConstPoolEditor.java
new file mode 100644
index 00000000..2dec3b76
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/ConstPoolEditor.java
@@ -0,0 +1,263 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.io.DataInputStream;
14import java.io.DataOutputStream;
15import java.lang.reflect.Constructor;
16import java.lang.reflect.Field;
17import java.lang.reflect.Method;
18import java.util.HashMap;
19
20import javassist.bytecode.ConstPool;
21import javassist.bytecode.Descriptor;
22import cuchaz.enigma.bytecode.accessors.ClassInfoAccessor;
23import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor;
24import cuchaz.enigma.bytecode.accessors.MemberRefInfoAccessor;
25
26public class ConstPoolEditor {
27
28 private static Method m_getItem;
29 private static Method m_addItem;
30 private static Method m_addItem0;
31 private static Field m_items;
32 private static Field m_cache;
33 private static Field m_numItems;
34 private static Field m_objects;
35 private static Field m_elements;
36 private static Method m_methodWritePool;
37 private static Constructor<ConstPool> m_constructorPool;
38
39 static {
40 try {
41 m_getItem = ConstPool.class.getDeclaredMethod("getItem", int.class);
42 m_getItem.setAccessible(true);
43
44 m_addItem = ConstPool.class.getDeclaredMethod("addItem", Class.forName("javassist.bytecode.ConstInfo"));
45 m_addItem.setAccessible(true);
46
47 m_addItem0 = ConstPool.class.getDeclaredMethod("addItem0", Class.forName("javassist.bytecode.ConstInfo"));
48 m_addItem0.setAccessible(true);
49
50 m_items = ConstPool.class.getDeclaredField("items");
51 m_items.setAccessible(true);
52
53 m_cache = ConstPool.class.getDeclaredField("itemsCache");
54 m_cache.setAccessible(true);
55
56 m_numItems = ConstPool.class.getDeclaredField("numOfItems");
57 m_numItems.setAccessible(true);
58
59 m_objects = Class.forName("javassist.bytecode.LongVector").getDeclaredField("objects");
60 m_objects.setAccessible(true);
61
62 m_elements = Class.forName("javassist.bytecode.LongVector").getDeclaredField("elements");
63 m_elements.setAccessible(true);
64
65 m_methodWritePool = ConstPool.class.getDeclaredMethod("write", DataOutputStream.class);
66 m_methodWritePool.setAccessible(true);
67
68 m_constructorPool = ConstPool.class.getDeclaredConstructor(DataInputStream.class);
69 m_constructorPool.setAccessible(true);
70 } catch (Exception ex) {
71 throw new Error(ex);
72 }
73 }
74
75 private ConstPool m_pool;
76
77 public ConstPoolEditor(ConstPool pool) {
78 m_pool = pool;
79 }
80
81 public void writePool(DataOutputStream out) {
82 try {
83 m_methodWritePool.invoke(m_pool, out);
84 } catch (Exception ex) {
85 throw new Error(ex);
86 }
87 }
88
89 public static ConstPool readPool(DataInputStream in) {
90 try {
91 return m_constructorPool.newInstance(in);
92 } catch (Exception ex) {
93 throw new Error(ex);
94 }
95 }
96
97 public String getMemberrefClassname(int memberrefIndex) {
98 return Descriptor.toJvmName(m_pool.getClassInfo(m_pool.getMemberClass(memberrefIndex)));
99 }
100
101 public String getMemberrefName(int memberrefIndex) {
102 return m_pool.getUtf8Info(m_pool.getNameAndTypeName(m_pool.getMemberNameAndType(memberrefIndex)));
103 }
104
105 public String getMemberrefType(int memberrefIndex) {
106 return m_pool.getUtf8Info(m_pool.getNameAndTypeDescriptor(m_pool.getMemberNameAndType(memberrefIndex)));
107 }
108
109 public ConstInfoAccessor getItem(int index) {
110 try {
111 Object entry = m_getItem.invoke(m_pool, index);
112 if (entry == null) {
113 return null;
114 }
115 return new ConstInfoAccessor(entry);
116 } catch (Exception ex) {
117 throw new Error(ex);
118 }
119 }
120
121 public int addItem(Object item) {
122 try {
123 return (Integer)m_addItem.invoke(m_pool, item);
124 } catch (Exception ex) {
125 throw new Error(ex);
126 }
127 }
128
129 public int addItemForceNew(Object item) {
130 try {
131 return (Integer)m_addItem0.invoke(m_pool, item);
132 } catch (Exception ex) {
133 throw new Error(ex);
134 }
135 }
136
137 @SuppressWarnings("rawtypes")
138 public void removeLastItem() {
139 try {
140 // remove the item from the cache
141 HashMap cache = getCache();
142 if (cache != null) {
143 Object item = getItem(m_pool.getSize() - 1);
144 cache.remove(item);
145 }
146
147 // remove the actual item
148 // based off of LongVector.addElement()
149 Object items = m_items.get(m_pool);
150 Object[][] objects = (Object[][])m_objects.get(items);
151 int numElements = (Integer)m_elements.get(items) - 1;
152 int nth = numElements >> 7;
153 int offset = numElements & (128 - 1);
154 objects[nth][offset] = null;
155
156 // decrement the number of items
157 m_elements.set(items, numElements);
158 m_numItems.set(m_pool, (Integer)m_numItems.get(m_pool) - 1);
159 } catch (Exception ex) {
160 throw new Error(ex);
161 }
162 }
163
164 @SuppressWarnings("rawtypes")
165 public HashMap getCache() {
166 try {
167 return (HashMap)m_cache.get(m_pool);
168 } catch (Exception ex) {
169 throw new Error(ex);
170 }
171 }
172
173 @SuppressWarnings({ "rawtypes", "unchecked" })
174 public void changeMemberrefNameAndType(int memberrefIndex, String newName, String newType) {
175 // NOTE: when changing values, we always need to copy-on-write
176 try {
177 // get the memberref item
178 Object item = getItem(memberrefIndex).getItem();
179
180 // update the cache
181 HashMap cache = getCache();
182 if (cache != null) {
183 cache.remove(item);
184 }
185
186 new MemberRefInfoAccessor(item).setNameAndTypeIndex(m_pool.addNameAndTypeInfo(newName, newType));
187
188 // update the cache
189 if (cache != null) {
190 cache.put(item, item);
191 }
192 } catch (Exception ex) {
193 throw new Error(ex);
194 }
195
196 // make sure the change worked
197 assert (newName.equals(getMemberrefName(memberrefIndex)));
198 assert (newType.equals(getMemberrefType(memberrefIndex)));
199 }
200
201 @SuppressWarnings({ "rawtypes", "unchecked" })
202 public void changeClassName(int classNameIndex, String newName) {
203 // NOTE: when changing values, we always need to copy-on-write
204 try {
205 // get the class item
206 Object item = getItem(classNameIndex).getItem();
207
208 // update the cache
209 HashMap cache = getCache();
210 if (cache != null) {
211 cache.remove(item);
212 }
213
214 // add the new name and repoint the name-and-type to it
215 new ClassInfoAccessor(item).setNameIndex(m_pool.addUtf8Info(newName));
216
217 // update the cache
218 if (cache != null) {
219 cache.put(item, item);
220 }
221 } catch (Exception ex) {
222 throw new Error(ex);
223 }
224 }
225
226 public static ConstPool newConstPool() {
227 // const pool expects the name of a class to initialize itself
228 // but we want an empty pool
229 // so give it a bogus name, and then clear the entries afterwards
230 ConstPool pool = new ConstPool("a");
231
232 ConstPoolEditor editor = new ConstPoolEditor(pool);
233 int size = pool.getSize();
234 for (int i = 0; i < size - 1; i++) {
235 editor.removeLastItem();
236 }
237
238 // make sure the pool is actually empty
239 // although, in this case "empty" means one thing in it
240 // the JVM spec says index 0 should be reserved
241 assert (pool.getSize() == 1);
242 assert (editor.getItem(0) == null);
243 assert (editor.getItem(1) == null);
244 assert (editor.getItem(2) == null);
245 assert (editor.getItem(3) == null);
246
247 // also, clear the cache
248 editor.getCache().clear();
249
250 return pool;
251 }
252
253 public String dump() {
254 StringBuilder buf = new StringBuilder();
255 for (int i = 1; i < m_pool.getSize(); i++) {
256 buf.append(String.format("%4d", i));
257 buf.append(" ");
258 buf.append(getItem(i).toString());
259 buf.append("\n");
260 }
261 return buf.toString();
262 }
263}
diff --git a/src/cuchaz/enigma/bytecode/InfoType.java b/src/cuchaz/enigma/bytecode/InfoType.java
new file mode 100644
index 00000000..deaf6232
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/InfoType.java
@@ -0,0 +1,317 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.util.Collection;
14import java.util.List;
15import java.util.Map;
16
17import com.google.common.collect.Lists;
18import com.google.common.collect.Maps;
19
20import cuchaz.enigma.bytecode.accessors.ClassInfoAccessor;
21import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor;
22import cuchaz.enigma.bytecode.accessors.InvokeDynamicInfoAccessor;
23import cuchaz.enigma.bytecode.accessors.MemberRefInfoAccessor;
24import cuchaz.enigma.bytecode.accessors.MethodHandleInfoAccessor;
25import cuchaz.enigma.bytecode.accessors.MethodTypeInfoAccessor;
26import cuchaz.enigma.bytecode.accessors.NameAndTypeInfoAccessor;
27import cuchaz.enigma.bytecode.accessors.StringInfoAccessor;
28
29public enum InfoType {
30
31 Utf8Info( 1, 0 ),
32 IntegerInfo( 3, 0 ),
33 FloatInfo( 4, 0 ),
34 LongInfo( 5, 0 ),
35 DoubleInfo( 6, 0 ),
36 ClassInfo( 7, 1 ) {
37
38 @Override
39 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
40 ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem());
41 gatherIndexTree(indices, editor, accessor.getNameIndex());
42 }
43
44 @Override
45 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
46 ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem());
47 accessor.setNameIndex(remapIndex(map, accessor.getNameIndex()));
48 }
49
50 @Override
51 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
52 ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem());
53 ConstInfoAccessor nameEntry = pool.getItem(accessor.getNameIndex());
54 return nameEntry != null && nameEntry.getTag() == Utf8Info.getTag();
55 }
56 },
57 StringInfo( 8, 1 ) {
58
59 @Override
60 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
61 StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem());
62 gatherIndexTree(indices, editor, accessor.getStringIndex());
63 }
64
65 @Override
66 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
67 StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem());
68 accessor.setStringIndex(remapIndex(map, accessor.getStringIndex()));
69 }
70
71 @Override
72 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
73 StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem());
74 ConstInfoAccessor stringEntry = pool.getItem(accessor.getStringIndex());
75 return stringEntry != null && stringEntry.getTag() == Utf8Info.getTag();
76 }
77 },
78 FieldRefInfo( 9, 2 ) {
79
80 @Override
81 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
82 MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem());
83 gatherIndexTree(indices, editor, accessor.getClassIndex());
84 gatherIndexTree(indices, editor, accessor.getNameAndTypeIndex());
85 }
86
87 @Override
88 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
89 MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem());
90 accessor.setClassIndex(remapIndex(map, accessor.getClassIndex()));
91 accessor.setNameAndTypeIndex(remapIndex(map, accessor.getNameAndTypeIndex()));
92 }
93
94 @Override
95 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
96 MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem());
97 ConstInfoAccessor classEntry = pool.getItem(accessor.getClassIndex());
98 ConstInfoAccessor nameAndTypeEntry = pool.getItem(accessor.getNameAndTypeIndex());
99 return classEntry != null && classEntry.getTag() == ClassInfo.getTag() && nameAndTypeEntry != null && nameAndTypeEntry.getTag() == NameAndTypeInfo.getTag();
100 }
101 },
102 // same as FieldRefInfo
103 MethodRefInfo( 10, 2 ) {
104
105 @Override
106 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
107 FieldRefInfo.gatherIndexTree(indices, editor, entry);
108 }
109
110 @Override
111 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
112 FieldRefInfo.remapIndices(map, entry);
113 }
114
115 @Override
116 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
117 return FieldRefInfo.subIndicesAreValid(entry, pool);
118 }
119 },
120 // same as FieldRefInfo
121 InterfaceMethodRefInfo( 11, 2 ) {
122
123 @Override
124 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
125 FieldRefInfo.gatherIndexTree(indices, editor, entry);
126 }
127
128 @Override
129 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
130 FieldRefInfo.remapIndices(map, entry);
131 }
132
133 @Override
134 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
135 return FieldRefInfo.subIndicesAreValid(entry, pool);
136 }
137 },
138 NameAndTypeInfo( 12, 1 ) {
139
140 @Override
141 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
142 NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem());
143 gatherIndexTree(indices, editor, accessor.getNameIndex());
144 gatherIndexTree(indices, editor, accessor.getTypeIndex());
145 }
146
147 @Override
148 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
149 NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem());
150 accessor.setNameIndex(remapIndex(map, accessor.getNameIndex()));
151 accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex()));
152 }
153
154 @Override
155 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
156 NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem());
157 ConstInfoAccessor nameEntry = pool.getItem(accessor.getNameIndex());
158 ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex());
159 return nameEntry != null && nameEntry.getTag() == Utf8Info.getTag() && typeEntry != null && typeEntry.getTag() == Utf8Info.getTag();
160 }
161 },
162 MethodHandleInfo( 15, 3 ) {
163
164 @Override
165 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
166 MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem());
167 gatherIndexTree(indices, editor, accessor.getTypeIndex());
168 gatherIndexTree(indices, editor, accessor.getMethodRefIndex());
169 }
170
171 @Override
172 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
173 MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem());
174 accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex()));
175 accessor.setMethodRefIndex(remapIndex(map, accessor.getMethodRefIndex()));
176 }
177
178 @Override
179 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
180 MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem());
181 ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex());
182 ConstInfoAccessor methodRefEntry = pool.getItem(accessor.getMethodRefIndex());
183 return typeEntry != null && typeEntry.getTag() == Utf8Info.getTag() && methodRefEntry != null && methodRefEntry.getTag() == MethodRefInfo.getTag();
184 }
185 },
186 MethodTypeInfo( 16, 1 ) {
187
188 @Override
189 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
190 MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem());
191 gatherIndexTree(indices, editor, accessor.getTypeIndex());
192 }
193
194 @Override
195 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
196 MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem());
197 accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex()));
198 }
199
200 @Override
201 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
202 MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem());
203 ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex());
204 return typeEntry != null && typeEntry.getTag() == Utf8Info.getTag();
205 }
206 },
207 InvokeDynamicInfo( 18, 2 ) {
208
209 @Override
210 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
211 InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem());
212 gatherIndexTree(indices, editor, accessor.getBootstrapIndex());
213 gatherIndexTree(indices, editor, accessor.getNameAndTypeIndex());
214 }
215
216 @Override
217 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
218 InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem());
219 accessor.setBootstrapIndex(remapIndex(map, accessor.getBootstrapIndex()));
220 accessor.setNameAndTypeIndex(remapIndex(map, accessor.getNameAndTypeIndex()));
221 }
222
223 @Override
224 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
225 InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem());
226 ConstInfoAccessor bootstrapEntry = pool.getItem(accessor.getBootstrapIndex());
227 ConstInfoAccessor nameAndTypeEntry = pool.getItem(accessor.getNameAndTypeIndex());
228 return bootstrapEntry != null && bootstrapEntry.getTag() == Utf8Info.getTag() && nameAndTypeEntry != null && nameAndTypeEntry.getTag() == NameAndTypeInfo.getTag();
229 }
230 };
231
232 private static Map<Integer,InfoType> m_types;
233
234 static {
235 m_types = Maps.newTreeMap();
236 for (InfoType type : values()) {
237 m_types.put(type.getTag(), type);
238 }
239 }
240
241 private int m_tag;
242 private int m_level;
243
244 private InfoType(int tag, int level) {
245 m_tag = tag;
246 m_level = level;
247 }
248
249 public int getTag() {
250 return m_tag;
251 }
252
253 public int getLevel() {
254 return m_level;
255 }
256
257 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
258 // by default, do nothing
259 }
260
261 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
262 // by default, do nothing
263 }
264
265 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
266 // by default, everything is good
267 return true;
268 }
269
270 public boolean selfIndexIsValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
271 ConstInfoAccessor entryCheck = pool.getItem(entry.getIndex());
272 if (entryCheck == null) {
273 return false;
274 }
275 return entryCheck.getItem().equals(entry.getItem());
276 }
277
278 public static InfoType getByTag(int tag) {
279 return m_types.get(tag);
280 }
281
282 public static List<InfoType> getByLevel(int level) {
283 List<InfoType> types = Lists.newArrayList();
284 for (InfoType type : values()) {
285 if (type.getLevel() == level) {
286 types.add(type);
287 }
288 }
289 return types;
290 }
291
292 public static List<InfoType> getSortedByLevel() {
293 List<InfoType> types = Lists.newArrayList();
294 types.addAll(getByLevel(0));
295 types.addAll(getByLevel(1));
296 types.addAll(getByLevel(2));
297 types.addAll(getByLevel(3));
298 return types;
299 }
300
301 public static void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, int index) {
302 // add own index
303 indices.add(index);
304
305 // recurse
306 ConstInfoAccessor entry = editor.getItem(index);
307 entry.getType().gatherIndexTree(indices, editor, entry);
308 }
309
310 private static int remapIndex(Map<Integer,Integer> map, int index) {
311 Integer newIndex = map.get(index);
312 if (newIndex == null) {
313 newIndex = index;
314 }
315 return newIndex;
316 }
317}
diff --git a/src/cuchaz/enigma/bytecode/InnerClassWriter.java b/src/cuchaz/enigma/bytecode/InnerClassWriter.java
new file mode 100644
index 00000000..817500b7
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/InnerClassWriter.java
@@ -0,0 +1,102 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.util.Collection;
14
15import javassist.CtClass;
16import javassist.bytecode.AccessFlag;
17import javassist.bytecode.ConstPool;
18import javassist.bytecode.Descriptor;
19import javassist.bytecode.EnclosingMethodAttribute;
20import javassist.bytecode.InnerClassesAttribute;
21import cuchaz.enigma.Constants;
22import cuchaz.enigma.analysis.JarIndex;
23import cuchaz.enigma.mapping.BehaviorEntry;
24import cuchaz.enigma.mapping.ClassEntry;
25
26public class InnerClassWriter {
27
28 private JarIndex m_jarIndex;
29
30 public InnerClassWriter(JarIndex jarIndex) {
31 m_jarIndex = jarIndex;
32 }
33
34 public void write(CtClass c) {
35
36 // is this an inner or outer class?
37 String obfInnerClassName = new ClassEntry(Descriptor.toJvmName(c.getName())).getSimpleName();
38 String obfOuterClassName = m_jarIndex.getOuterClass(obfInnerClassName);
39 if (obfOuterClassName == null) {
40 // this is an outer class
41 obfOuterClassName = Descriptor.toJvmName(c.getName());
42 } else {
43 // this is an inner class, rename it to outer$inner
44 ClassEntry obfClassEntry = new ClassEntry(obfOuterClassName + "$" + obfInnerClassName);
45 c.setName(obfClassEntry.getName());
46
47 BehaviorEntry caller = m_jarIndex.getAnonymousClassCaller(obfInnerClassName);
48 if (caller != null) {
49 // write the enclosing method attribute
50 if (caller.getName().equals("<clinit>")) {
51 c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName()));
52 } else {
53 c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName(), caller.getName(), caller.getSignature().toString()));
54 }
55 }
56 }
57
58 // write the inner classes if needed
59 Collection<String> obfInnerClassNames = m_jarIndex.getInnerClasses(obfOuterClassName);
60 if (obfInnerClassNames != null && !obfInnerClassNames.isEmpty()) {
61 writeInnerClasses(c, obfOuterClassName, obfInnerClassNames);
62 }
63 }
64
65 private void writeInnerClasses(CtClass c, String obfOuterClassName, Collection<String> obfInnerClassNames) {
66 InnerClassesAttribute attr = new InnerClassesAttribute(c.getClassFile().getConstPool());
67 c.getClassFile().addAttribute(attr);
68 for (String obfInnerClassName : obfInnerClassNames) {
69 // get the new inner class name
70 ClassEntry obfClassEntry = new ClassEntry(obfOuterClassName + "$" + obfInnerClassName);
71
72 // here's what the JVM spec says about the InnerClasses attribute
73 // append( inner, outer of inner if inner is member of outer 0 ow, name after $ if inner not anonymous 0 ow, flags );
74
75 // update the attribute with this inner class
76 ConstPool constPool = c.getClassFile().getConstPool();
77 int innerClassIndex = constPool.addClassInfo(obfClassEntry.getName());
78 int outerClassIndex = 0;
79 int innerClassSimpleNameIndex = 0;
80 if (!m_jarIndex.isAnonymousClass(obfInnerClassName)) {
81 outerClassIndex = constPool.addClassInfo(obfClassEntry.getOuterClassName());
82 innerClassSimpleNameIndex = constPool.addUtf8Info(obfClassEntry.getInnerClassName());
83 }
84
85 attr.append(innerClassIndex, outerClassIndex, innerClassSimpleNameIndex, c.getClassFile().getAccessFlags() & ~AccessFlag.SUPER);
86
87 /* DEBUG
88 System.out.println(String.format("\tOBF: %s -> ATTR: %s,%s,%s (replace %s with %s)",
89 obfClassEntry,
90 attr.outerClass(attr.tableLength() - 1),
91 attr.innerClass(attr.tableLength() - 1),
92 attr.innerName(attr.tableLength() - 1),
93 Constants.NonePackage + "/" + obfInnerClassName,
94 obfClassEntry.getName()
95 ));
96 */
97
98 // make sure the outer class references only the new inner class names
99 c.replaceClassName(Constants.NonePackage + "/" + obfInnerClassName, obfClassEntry.getName());
100 }
101 }
102}
diff --git a/src/cuchaz/enigma/bytecode/MethodParameterWriter.java b/src/cuchaz/enigma/bytecode/MethodParameterWriter.java
new file mode 100644
index 00000000..5d4ca1ad
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/MethodParameterWriter.java
@@ -0,0 +1,53 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.util.ArrayList;
14import java.util.List;
15
16import javassist.CtBehavior;
17import javassist.CtClass;
18import cuchaz.enigma.mapping.ArgumentEntry;
19import cuchaz.enigma.mapping.BehaviorEntry;
20import cuchaz.enigma.mapping.BehaviorEntryFactory;
21import cuchaz.enigma.mapping.Translator;
22
23public class MethodParameterWriter {
24
25 private Translator m_translator;
26
27 public MethodParameterWriter(Translator translator) {
28 m_translator = translator;
29 }
30
31 public void writeMethodArguments(CtClass c) {
32
33 // Procyon will read method arguments from the "MethodParameters" attribute, so write those
34 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
35 BehaviorEntry behaviorEntry = BehaviorEntryFactory.create(behavior);
36
37 // get the number of arguments
38 int numParams = behaviorEntry.getSignature().getArgumentTypes().size();
39 if (numParams <= 0) {
40 continue;
41 }
42
43 // get the list of argument names
44 List<String> names = new ArrayList<String>(numParams);
45 for (int i = 0; i < numParams; i++) {
46 names.add(m_translator.translate(new ArgumentEntry(behaviorEntry, i, "")));
47 }
48
49 // save the mappings to the class
50 MethodParametersAttribute.updateClass(behavior.getMethodInfo(), names);
51 }
52 }
53}
diff --git a/src/cuchaz/enigma/bytecode/MethodParametersAttribute.java b/src/cuchaz/enigma/bytecode/MethodParametersAttribute.java
new file mode 100644
index 00000000..bf959564
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/MethodParametersAttribute.java
@@ -0,0 +1,85 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.io.ByteArrayOutputStream;
14import java.io.DataOutputStream;
15import java.io.IOException;
16import java.util.ArrayList;
17import java.util.List;
18
19import javassist.bytecode.AttributeInfo;
20import javassist.bytecode.ConstPool;
21import javassist.bytecode.MethodInfo;
22
23public class MethodParametersAttribute extends AttributeInfo {
24
25 private MethodParametersAttribute(ConstPool pool, List<Integer> parameterNameIndices) {
26 super(pool, "MethodParameters", writeStruct(parameterNameIndices));
27 }
28
29 public static void updateClass(MethodInfo info, List<String> names) {
30 // add the names to the class const pool
31 ConstPool constPool = info.getConstPool();
32 List<Integer> parameterNameIndices = new ArrayList<Integer>();
33 for (String name : names) {
34 if (name != null) {
35 parameterNameIndices.add(constPool.addUtf8Info(name));
36 } else {
37 parameterNameIndices.add(0);
38 }
39 }
40
41 // add the attribute to the method
42 info.addAttribute(new MethodParametersAttribute(constPool, parameterNameIndices));
43 }
44
45 private static byte[] writeStruct(List<Integer> parameterNameIndices) {
46 // JVM 8 Spec says the struct looks like this:
47 // http://cr.openjdk.java.net/~mr/se/8/java-se-8-fr-spec-01/java-se-8-jvms-fr-diffs.pdf
48 // uint8 num_params
49 // for each param:
50 // uint16 name_index -> points to UTF8 entry in constant pool, or 0 for no entry
51 // uint16 access_flags -> don't care, just set to 0
52
53 ByteArrayOutputStream buf = new ByteArrayOutputStream();
54 DataOutputStream out = new DataOutputStream(buf);
55
56 // NOTE: java hates unsigned integers, so we have to be careful here
57 // the writeShort(), writeByte() methods will read 16,8 low-order bits from the int argument
58 // as long as the int argument is in range of the unsigned short/byte type, it will be written as an unsigned short/byte
59 // if the int is out of range, the byte stream won't look the way we want and weird things will happen
60 final int SIZEOF_UINT8 = 1;
61 final int SIZEOF_UINT16 = 2;
62 final int MAX_UINT8 = (1 << 8) - 1;
63 final int MAX_UINT16 = (1 << 16) - 1;
64
65 try {
66 assert (parameterNameIndices.size() >= 0 && parameterNameIndices.size() <= MAX_UINT8);
67 out.writeByte(parameterNameIndices.size());
68
69 for (Integer index : parameterNameIndices) {
70 assert (index >= 0 && index <= MAX_UINT16);
71 out.writeShort(index);
72
73 // just write 0 for the access flags
74 out.writeShort(0);
75 }
76
77 out.close();
78 byte[] data = buf.toByteArray();
79 assert (data.length == SIZEOF_UINT8 + parameterNameIndices.size() * (SIZEOF_UINT16 + SIZEOF_UINT16));
80 return data;
81 } catch (IOException ex) {
82 throw new Error(ex);
83 }
84 }
85}
diff --git a/src/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java
new file mode 100644
index 00000000..d76f0567
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java
@@ -0,0 +1,55 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors;
12
13import java.lang.reflect.Field;
14
15public class ClassInfoAccessor {
16
17 private static Class<?> m_class;
18 private static Field m_nameIndex;
19
20 static {
21 try {
22 m_class = Class.forName("javassist.bytecode.ClassInfo");
23 m_nameIndex = m_class.getDeclaredField("name");
24 m_nameIndex.setAccessible(true);
25 } catch (Exception ex) {
26 throw new Error(ex);
27 }
28 }
29
30 public static boolean isType(ConstInfoAccessor accessor) {
31 return m_class.isAssignableFrom(accessor.getItem().getClass());
32 }
33
34 private Object m_item;
35
36 public ClassInfoAccessor(Object item) {
37 m_item = item;
38 }
39
40 public int getNameIndex() {
41 try {
42 return (Integer)m_nameIndex.get(m_item);
43 } catch (Exception ex) {
44 throw new Error(ex);
45 }
46 }
47
48 public void setNameIndex(int val) {
49 try {
50 m_nameIndex.set(m_item, val);
51 } catch (Exception ex) {
52 throw new Error(ex);
53 }
54 }
55}
diff --git a/src/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java
new file mode 100644
index 00000000..d00c1021
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java
@@ -0,0 +1,156 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors;
12
13import java.io.ByteArrayInputStream;
14import java.io.ByteArrayOutputStream;
15import java.io.DataInputStream;
16import java.io.DataOutputStream;
17import java.io.IOException;
18import java.io.PrintWriter;
19import java.lang.reflect.Constructor;
20import java.lang.reflect.Field;
21import java.lang.reflect.Method;
22
23import cuchaz.enigma.bytecode.InfoType;
24
25public class ConstInfoAccessor {
26
27 private static Class<?> m_class;
28 private static Field m_index;
29 private static Method m_getTag;
30
31 static {
32 try {
33 m_class = Class.forName("javassist.bytecode.ConstInfo");
34 m_index = m_class.getDeclaredField("index");
35 m_index.setAccessible(true);
36 m_getTag = m_class.getMethod("getTag");
37 m_getTag.setAccessible(true);
38 } catch (Exception ex) {
39 throw new Error(ex);
40 }
41 }
42
43 private Object m_item;
44
45 public ConstInfoAccessor(Object item) {
46 if (item == null) {
47 throw new IllegalArgumentException("item cannot be null!");
48 }
49 m_item = item;
50 }
51
52 public ConstInfoAccessor(DataInputStream in) throws IOException {
53 try {
54 // read the entry
55 String className = in.readUTF();
56 int oldIndex = in.readInt();
57
58 // NOTE: ConstInfo instances write a type id (a "tag"), but they don't read it back
59 // so we have to read it here
60 in.readByte();
61
62 Constructor<?> constructor = Class.forName(className).getConstructor(DataInputStream.class, int.class);
63 constructor.setAccessible(true);
64 m_item = constructor.newInstance(in, oldIndex);
65 } catch (IOException ex) {
66 throw ex;
67 } catch (Exception ex) {
68 throw new Error(ex);
69 }
70 }
71
72 public Object getItem() {
73 return m_item;
74 }
75
76 public int getIndex() {
77 try {
78 return (Integer)m_index.get(m_item);
79 } catch (Exception ex) {
80 throw new Error(ex);
81 }
82 }
83
84 public void setIndex(int val) {
85 try {
86 m_index.set(m_item, val);
87 } catch (Exception ex) {
88 throw new Error(ex);
89 }
90 }
91
92 public int getTag() {
93 try {
94 return (Integer)m_getTag.invoke(m_item);
95 } catch (Exception ex) {
96 throw new Error(ex);
97 }
98 }
99
100 public ConstInfoAccessor copy() {
101 return new ConstInfoAccessor(copyItem());
102 }
103
104 public Object copyItem() {
105 // I don't know of a simpler way to copy one of these silly things...
106 try {
107 // serialize the item
108 ByteArrayOutputStream buf = new ByteArrayOutputStream();
109 DataOutputStream out = new DataOutputStream(buf);
110 write(out);
111
112 // deserialize the item
113 DataInputStream in = new DataInputStream(new ByteArrayInputStream(buf.toByteArray()));
114 Object item = new ConstInfoAccessor(in).getItem();
115 in.close();
116
117 return item;
118 } catch (Exception ex) {
119 throw new Error(ex);
120 }
121 }
122
123 public void write(DataOutputStream out) throws IOException {
124 try {
125 out.writeUTF(m_item.getClass().getName());
126 out.writeInt(getIndex());
127
128 Method method = m_item.getClass().getMethod("write", DataOutputStream.class);
129 method.setAccessible(true);
130 method.invoke(m_item, out);
131 } catch (IOException ex) {
132 throw ex;
133 } catch (Exception ex) {
134 throw new Error(ex);
135 }
136 }
137
138 @Override
139 public String toString() {
140 try {
141 ByteArrayOutputStream buf = new ByteArrayOutputStream();
142 PrintWriter out = new PrintWriter(buf);
143 Method print = m_item.getClass().getMethod("print", PrintWriter.class);
144 print.setAccessible(true);
145 print.invoke(m_item, out);
146 out.close();
147 return buf.toString().replace("\n", "");
148 } catch (Exception ex) {
149 throw new Error(ex);
150 }
151 }
152
153 public InfoType getType() {
154 return InfoType.getByTag(getTag());
155 }
156}
diff --git a/src/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java
new file mode 100644
index 00000000..0d780ea6
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java
@@ -0,0 +1,74 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors;
12
13import java.lang.reflect.Field;
14
15public class InvokeDynamicInfoAccessor {
16
17 private static Class<?> m_class;
18 private static Field m_bootstrapIndex;
19 private static Field m_nameAndTypeIndex;
20
21 static {
22 try {
23 m_class = Class.forName("javassist.bytecode.InvokeDynamicInfo");
24 m_bootstrapIndex = m_class.getDeclaredField("bootstrap");
25 m_bootstrapIndex.setAccessible(true);
26 m_nameAndTypeIndex = m_class.getDeclaredField("nameAndType");
27 m_nameAndTypeIndex.setAccessible(true);
28 } catch (Exception ex) {
29 throw new Error(ex);
30 }
31 }
32
33 public static boolean isType(ConstInfoAccessor accessor) {
34 return m_class.isAssignableFrom(accessor.getItem().getClass());
35 }
36
37 private Object m_item;
38
39 public InvokeDynamicInfoAccessor(Object item) {
40 m_item = item;
41 }
42
43 public int getBootstrapIndex() {
44 try {
45 return (Integer)m_bootstrapIndex.get(m_item);
46 } catch (Exception ex) {
47 throw new Error(ex);
48 }
49 }
50
51 public void setBootstrapIndex(int val) {
52 try {
53 m_bootstrapIndex.set(m_item, val);
54 } catch (Exception ex) {
55 throw new Error(ex);
56 }
57 }
58
59 public int getNameAndTypeIndex() {
60 try {
61 return (Integer)m_nameAndTypeIndex.get(m_item);
62 } catch (Exception ex) {
63 throw new Error(ex);
64 }
65 }
66
67 public void setNameAndTypeIndex(int val) {
68 try {
69 m_nameAndTypeIndex.set(m_item, val);
70 } catch (Exception ex) {
71 throw new Error(ex);
72 }
73 }
74}
diff --git a/src/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java
new file mode 100644
index 00000000..9fe945f7
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java
@@ -0,0 +1,74 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors;
12
13import java.lang.reflect.Field;
14
15public class MemberRefInfoAccessor {
16
17 private static Class<?> m_class;
18 private static Field m_classIndex;
19 private static Field m_nameAndTypeIndex;
20
21 static {
22 try {
23 m_class = Class.forName("javassist.bytecode.MemberrefInfo");
24 m_classIndex = m_class.getDeclaredField("classIndex");
25 m_classIndex.setAccessible(true);
26 m_nameAndTypeIndex = m_class.getDeclaredField("nameAndTypeIndex");
27 m_nameAndTypeIndex.setAccessible(true);
28 } catch (Exception ex) {
29 throw new Error(ex);
30 }
31 }
32
33 public static boolean isType(ConstInfoAccessor accessor) {
34 return m_class.isAssignableFrom(accessor.getItem().getClass());
35 }
36
37 private Object m_item;
38
39 public MemberRefInfoAccessor(Object item) {
40 m_item = item;
41 }
42
43 public int getClassIndex() {
44 try {
45 return (Integer)m_classIndex.get(m_item);
46 } catch (Exception ex) {
47 throw new Error(ex);
48 }
49 }
50
51 public void setClassIndex(int val) {
52 try {
53 m_classIndex.set(m_item, val);
54 } catch (Exception ex) {
55 throw new Error(ex);
56 }
57 }
58
59 public int getNameAndTypeIndex() {
60 try {
61 return (Integer)m_nameAndTypeIndex.get(m_item);
62 } catch (Exception ex) {
63 throw new Error(ex);
64 }
65 }
66
67 public void setNameAndTypeIndex(int val) {
68 try {
69 m_nameAndTypeIndex.set(m_item, val);
70 } catch (Exception ex) {
71 throw new Error(ex);
72 }
73 }
74}
diff --git a/src/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java
new file mode 100644
index 00000000..4c95b226
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java
@@ -0,0 +1,74 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors;
12
13import java.lang.reflect.Field;
14
15public class MethodHandleInfoAccessor {
16
17 private static Class<?> m_class;
18 private static Field m_kindIndex;
19 private static Field m_indexIndex;
20
21 static {
22 try {
23 m_class = Class.forName("javassist.bytecode.MethodHandleInfo");
24 m_kindIndex = m_class.getDeclaredField("refKind");
25 m_kindIndex.setAccessible(true);
26 m_indexIndex = m_class.getDeclaredField("refIndex");
27 m_indexIndex.setAccessible(true);
28 } catch (Exception ex) {
29 throw new Error(ex);
30 }
31 }
32
33 public static boolean isType(ConstInfoAccessor accessor) {
34 return m_class.isAssignableFrom(accessor.getItem().getClass());
35 }
36
37 private Object m_item;
38
39 public MethodHandleInfoAccessor(Object item) {
40 m_item = item;
41 }
42
43 public int getTypeIndex() {
44 try {
45 return (Integer)m_kindIndex.get(m_item);
46 } catch (Exception ex) {
47 throw new Error(ex);
48 }
49 }
50
51 public void setTypeIndex(int val) {
52 try {
53 m_kindIndex.set(m_item, val);
54 } catch (Exception ex) {
55 throw new Error(ex);
56 }
57 }
58
59 public int getMethodRefIndex() {
60 try {
61 return (Integer)m_indexIndex.get(m_item);
62 } catch (Exception ex) {
63 throw new Error(ex);
64 }
65 }
66
67 public void setMethodRefIndex(int val) {
68 try {
69 m_indexIndex.set(m_item, val);
70 } catch (Exception ex) {
71 throw new Error(ex);
72 }
73 }
74}
diff --git a/src/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java
new file mode 100644
index 00000000..e1511179
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java
@@ -0,0 +1,55 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors;
12
13import java.lang.reflect.Field;
14
15public class MethodTypeInfoAccessor {
16
17 private static Class<?> m_class;
18 private static Field m_descriptorIndex;
19
20 static {
21 try {
22 m_class = Class.forName("javassist.bytecode.MethodTypeInfo");
23 m_descriptorIndex = m_class.getDeclaredField("descriptor");
24 m_descriptorIndex.setAccessible(true);
25 } catch (Exception ex) {
26 throw new Error(ex);
27 }
28 }
29
30 public static boolean isType(ConstInfoAccessor accessor) {
31 return m_class.isAssignableFrom(accessor.getItem().getClass());
32 }
33
34 private Object m_item;
35
36 public MethodTypeInfoAccessor(Object item) {
37 m_item = item;
38 }
39
40 public int getTypeIndex() {
41 try {
42 return (Integer)m_descriptorIndex.get(m_item);
43 } catch (Exception ex) {
44 throw new Error(ex);
45 }
46 }
47
48 public void setTypeIndex(int val) {
49 try {
50 m_descriptorIndex.set(m_item, val);
51 } catch (Exception ex) {
52 throw new Error(ex);
53 }
54 }
55}
diff --git a/src/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java
new file mode 100644
index 00000000..6e82f3e9
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java
@@ -0,0 +1,74 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors;
12
13import java.lang.reflect.Field;
14
15public class NameAndTypeInfoAccessor {
16
17 private static Class<?> m_class;
18 private static Field m_nameIndex;
19 private static Field m_typeIndex;
20
21 static {
22 try {
23 m_class = Class.forName("javassist.bytecode.NameAndTypeInfo");
24 m_nameIndex = m_class.getDeclaredField("memberName");
25 m_nameIndex.setAccessible(true);
26 m_typeIndex = m_class.getDeclaredField("typeDescriptor");
27 m_typeIndex.setAccessible(true);
28 } catch (Exception ex) {
29 throw new Error(ex);
30 }
31 }
32
33 public static boolean isType(ConstInfoAccessor accessor) {
34 return m_class.isAssignableFrom(accessor.getItem().getClass());
35 }
36
37 private Object m_item;
38
39 public NameAndTypeInfoAccessor(Object item) {
40 m_item = item;
41 }
42
43 public int getNameIndex() {
44 try {
45 return (Integer)m_nameIndex.get(m_item);
46 } catch (Exception ex) {
47 throw new Error(ex);
48 }
49 }
50
51 public void setNameIndex(int val) {
52 try {
53 m_nameIndex.set(m_item, val);
54 } catch (Exception ex) {
55 throw new Error(ex);
56 }
57 }
58
59 public int getTypeIndex() {
60 try {
61 return (Integer)m_typeIndex.get(m_item);
62 } catch (Exception ex) {
63 throw new Error(ex);
64 }
65 }
66
67 public void setTypeIndex(int val) {
68 try {
69 m_typeIndex.set(m_item, val);
70 } catch (Exception ex) {
71 throw new Error(ex);
72 }
73 }
74}
diff --git a/src/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java
new file mode 100644
index 00000000..6665ffe4
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java
@@ -0,0 +1,55 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors;
12
13import java.lang.reflect.Field;
14
15public class StringInfoAccessor {
16
17 private static Class<?> m_class;
18 private static Field m_stringIndex;
19
20 static {
21 try {
22 m_class = Class.forName("javassist.bytecode.StringInfo");
23 m_stringIndex = m_class.getDeclaredField("string");
24 m_stringIndex.setAccessible(true);
25 } catch (Exception ex) {
26 throw new Error(ex);
27 }
28 }
29
30 public static boolean isType(ConstInfoAccessor accessor) {
31 return m_class.isAssignableFrom(accessor.getItem().getClass());
32 }
33
34 private Object m_item;
35
36 public StringInfoAccessor(Object item) {
37 m_item = item;
38 }
39
40 public int getStringIndex() {
41 try {
42 return (Integer)m_stringIndex.get(m_item);
43 } catch (Exception ex) {
44 throw new Error(ex);
45 }
46 }
47
48 public void setStringIndex(int val) {
49 try {
50 m_stringIndex.set(m_item, val);
51 } catch (Exception ex) {
52 throw new Error(ex);
53 }
54 }
55}
diff --git a/src/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java
new file mode 100644
index 00000000..2abf60b4
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java
@@ -0,0 +1,28 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors;
12
13public class Utf8InfoAccessor {
14
15 private static Class<?> m_class;
16
17 static {
18 try {
19 m_class = Class.forName("javassist.bytecode.Utf8Info");
20 } catch (Exception ex) {
21 throw new Error(ex);
22 }
23 }
24
25 public static boolean isType(ConstInfoAccessor accessor) {
26 return m_class.isAssignableFrom(accessor.getItem().getClass());
27 }
28}
diff --git a/src/cuchaz/enigma/convert/ClassIdentity.java b/src/cuchaz/enigma/convert/ClassIdentity.java
new file mode 100644
index 00000000..1be61239
--- /dev/null
+++ b/src/cuchaz/enigma/convert/ClassIdentity.java
@@ -0,0 +1,411 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.io.UnsupportedEncodingException;
14import java.security.MessageDigest;
15import java.security.NoSuchAlgorithmException;
16import java.util.Enumeration;
17import java.util.List;
18import java.util.Map;
19
20import javassist.CannotCompileException;
21import javassist.CtBehavior;
22import javassist.CtClass;
23import javassist.CtConstructor;
24import javassist.CtField;
25import javassist.CtMethod;
26import javassist.bytecode.BadBytecode;
27import javassist.bytecode.CodeIterator;
28import javassist.bytecode.ConstPool;
29import javassist.bytecode.Descriptor;
30import javassist.bytecode.Opcode;
31import javassist.expr.ConstructorCall;
32import javassist.expr.ExprEditor;
33import javassist.expr.FieldAccess;
34import javassist.expr.MethodCall;
35import javassist.expr.NewExpr;
36
37import com.google.common.collect.HashMultiset;
38import com.google.common.collect.Lists;
39import com.google.common.collect.Maps;
40import com.google.common.collect.Multiset;
41
42import cuchaz.enigma.Constants;
43import cuchaz.enigma.Util;
44import cuchaz.enigma.analysis.ClassImplementationsTreeNode;
45import cuchaz.enigma.analysis.EntryReference;
46import cuchaz.enigma.analysis.JarIndex;
47import cuchaz.enigma.bytecode.ConstPoolEditor;
48import cuchaz.enigma.bytecode.InfoType;
49import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor;
50import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
51import cuchaz.enigma.mapping.BehaviorEntry;
52import cuchaz.enigma.mapping.ClassEntry;
53import cuchaz.enigma.mapping.ClassNameReplacer;
54import cuchaz.enigma.mapping.Entry;
55import cuchaz.enigma.mapping.FieldEntry;
56import cuchaz.enigma.mapping.JavassistUtil;
57import cuchaz.enigma.mapping.Signature;
58
59public class ClassIdentity {
60
61 private ClassEntry m_classEntry;
62 private SidedClassNamer m_namer;
63 private Multiset<String> m_fields;
64 private Multiset<String> m_methods;
65 private Multiset<String> m_constructors;
66 private String m_staticInitializer;
67 private String m_extends;
68 private Multiset<String> m_implements;
69 private Multiset<String> m_implementations;
70 private Multiset<String> m_references;
71
72 public ClassIdentity(CtClass c, SidedClassNamer namer, JarIndex index, boolean useReferences) {
73 m_namer = namer;
74
75 // stuff from the bytecode
76
77 m_classEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
78 m_fields = HashMultiset.create();
79 for (CtField field : c.getDeclaredFields()) {
80 m_fields.add(scrubSignature(field.getSignature()));
81 }
82 m_methods = HashMultiset.create();
83 for (CtMethod method : c.getDeclaredMethods()) {
84 m_methods.add(scrubSignature(method.getSignature()) + "0x" + getBehaviorSignature(method));
85 }
86 m_constructors = HashMultiset.create();
87 for (CtConstructor constructor : c.getDeclaredConstructors()) {
88 m_constructors.add(scrubSignature(constructor.getSignature()) + "0x" + getBehaviorSignature(constructor));
89 }
90 m_staticInitializer = "";
91 if (c.getClassInitializer() != null) {
92 m_staticInitializer = getBehaviorSignature(c.getClassInitializer());
93 }
94 m_extends = "";
95 if (c.getClassFile().getSuperclass() != null) {
96 m_extends = scrubClassName(c.getClassFile().getSuperclass());
97 }
98 m_implements = HashMultiset.create();
99 for (String interfaceName : c.getClassFile().getInterfaces()) {
100 m_implements.add(scrubClassName(interfaceName));
101 }
102
103 // stuff from the jar index
104
105 m_implementations = HashMultiset.create();
106 ClassImplementationsTreeNode implementationsNode = index.getClassImplementations(null, m_classEntry);
107 if (implementationsNode != null) {
108 @SuppressWarnings("unchecked")
109 Enumeration<ClassImplementationsTreeNode> implementations = implementationsNode.children();
110 while (implementations.hasMoreElements()) {
111 ClassImplementationsTreeNode node = implementations.nextElement();
112 m_implementations.add(scrubClassName(node.getClassEntry().getName()));
113 }
114 }
115
116 m_references = HashMultiset.create();
117 if (useReferences) {
118 for (CtField field : c.getDeclaredFields()) {
119 FieldEntry fieldEntry = new FieldEntry(m_classEntry, field.getName());
120 for (EntryReference<FieldEntry,BehaviorEntry> reference : index.getFieldReferences(fieldEntry)) {
121 addReference(reference);
122 }
123 }
124 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
125 BehaviorEntry behaviorEntry = JavassistUtil.getBehaviorEntry(behavior);
126 for (EntryReference<BehaviorEntry,BehaviorEntry> reference : index.getBehaviorReferences(behaviorEntry)) {
127 addReference(reference);
128 }
129 }
130 }
131 }
132
133 private void addReference(EntryReference<? extends Entry,BehaviorEntry> reference) {
134 if (reference.context.getSignature() != null) {
135 m_references.add(String.format("%s_%s", scrubClassName(reference.context.getClassName()), scrubSignature(reference.context.getSignature())));
136 } else {
137 m_references.add(String.format("%s_<clinit>", scrubClassName(reference.context.getClassName())));
138 }
139 }
140
141 public ClassEntry getClassEntry() {
142 return m_classEntry;
143 }
144
145 @Override
146 public String toString() {
147 StringBuilder buf = new StringBuilder();
148 buf.append("class: ");
149 buf.append(m_classEntry.getName());
150 buf.append(" ");
151 buf.append(hashCode());
152 buf.append("\n");
153 for (String field : m_fields) {
154 buf.append("\tfield ");
155 buf.append(field);
156 buf.append("\n");
157 }
158 for (String method : m_methods) {
159 buf.append("\tmethod ");
160 buf.append(method);
161 buf.append("\n");
162 }
163 for (String constructor : m_constructors) {
164 buf.append("\tconstructor ");
165 buf.append(constructor);
166 buf.append("\n");
167 }
168 if (m_staticInitializer.length() > 0) {
169 buf.append("\tinitializer ");
170 buf.append(m_staticInitializer);
171 buf.append("\n");
172 }
173 if (m_extends.length() > 0) {
174 buf.append("\textends ");
175 buf.append(m_extends);
176 buf.append("\n");
177 }
178 for (String interfaceName : m_implements) {
179 buf.append("\timplements ");
180 buf.append(interfaceName);
181 buf.append("\n");
182 }
183 for (String implementation : m_implementations) {
184 buf.append("\timplemented by ");
185 buf.append(implementation);
186 buf.append("\n");
187 }
188 for (String reference : m_references) {
189 buf.append("\treference ");
190 buf.append(reference);
191 buf.append("\n");
192 }
193 return buf.toString();
194 }
195
196 private String scrubClassName(String className) {
197 return scrubSignature("L" + Descriptor.toJvmName(className) + ";");
198 }
199
200 private String scrubSignature(String signature) {
201 return scrubSignature(new Signature(signature));
202 }
203
204 private String scrubSignature(Signature signature) {
205
206 return new Signature(signature, new ClassNameReplacer() {
207
208 private Map<String,String> m_classNames = Maps.newHashMap();
209
210 @Override
211 public String replace(String className) {
212
213 // classes not in the none package can be passed through
214 ClassEntry classEntry = new ClassEntry(className);
215 if (!classEntry.getPackageName().equals(Constants.NonePackage)) {
216 return className;
217 }
218
219 // is this class ourself?
220 if (className.equals(m_classEntry.getName())) {
221 return "CSelf";
222 }
223
224 // try the namer
225 if (m_namer != null) {
226 String newName = m_namer.getName(className);
227 if (newName != null) {
228 return newName;
229 }
230 }
231
232 // otherwise, use local naming
233 if (!m_classNames.containsKey(className)) {
234 m_classNames.put(className, getNewClassName());
235 }
236 return m_classNames.get(className);
237 }
238
239 private String getNewClassName() {
240 return String.format("C%03d", m_classNames.size());
241 }
242 }).toString();
243 }
244
245 private boolean isClassMatchedUniquely(String className) {
246 return m_namer != null && m_namer.getName(Descriptor.toJvmName(className)) != null;
247 }
248
249 private String getBehaviorSignature(CtBehavior behavior) {
250 try {
251 // does this method have an implementation?
252 if (behavior.getMethodInfo().getCodeAttribute() == null) {
253 return "(none)";
254 }
255
256 // compute the hash from the opcodes
257 ConstPool constants = behavior.getMethodInfo().getConstPool();
258 final MessageDigest digest = MessageDigest.getInstance("MD5");
259 CodeIterator iter = behavior.getMethodInfo().getCodeAttribute().iterator();
260 while (iter.hasNext()) {
261 int pos = iter.next();
262
263 // update the hash with the opcode
264 int opcode = iter.byteAt(pos);
265 digest.update((byte)opcode);
266
267 switch (opcode) {
268 case Opcode.LDC: {
269 int constIndex = iter.byteAt(pos + 1);
270 updateHashWithConstant(digest, constants, constIndex);
271 }
272 break;
273
274 case Opcode.LDC_W:
275 case Opcode.LDC2_W: {
276 int constIndex = (iter.byteAt(pos + 1) << 8) | iter.byteAt(pos + 2);
277 updateHashWithConstant(digest, constants, constIndex);
278 }
279 break;
280 }
281 }
282
283 // update hash with method and field accesses
284 behavior.instrument(new ExprEditor() {
285 @Override
286 public void edit(MethodCall call) {
287 updateHashWithString(digest, scrubClassName(call.getClassName()));
288 updateHashWithString(digest, scrubSignature(call.getSignature()));
289 if (isClassMatchedUniquely(call.getClassName())) {
290 updateHashWithString(digest, call.getMethodName());
291 }
292 }
293
294 @Override
295 public void edit(FieldAccess access) {
296 updateHashWithString(digest, scrubClassName(access.getClassName()));
297 updateHashWithString(digest, scrubSignature(access.getSignature()));
298 if (isClassMatchedUniquely(access.getClassName())) {
299 updateHashWithString(digest, access.getFieldName());
300 }
301 }
302
303 @Override
304 public void edit(ConstructorCall call) {
305 updateHashWithString(digest, scrubClassName(call.getClassName()));
306 updateHashWithString(digest, scrubSignature(call.getSignature()));
307 }
308
309 @Override
310 public void edit(NewExpr expr) {
311 updateHashWithString(digest, scrubClassName(expr.getClassName()));
312 }
313 });
314
315 // convert the hash to a hex string
316 return toHex(digest.digest());
317 } catch (BadBytecode | NoSuchAlgorithmException | CannotCompileException ex) {
318 throw new Error(ex);
319 }
320 }
321
322 private void updateHashWithConstant(MessageDigest digest, ConstPool constants, int index) {
323 ConstPoolEditor editor = new ConstPoolEditor(constants);
324 ConstInfoAccessor item = editor.getItem(index);
325 if (item.getType() == InfoType.StringInfo) {
326 updateHashWithString(digest, constants.getStringInfo(index));
327 }
328 // TODO: other constants
329 }
330
331 private void updateHashWithString(MessageDigest digest, String val) {
332 try {
333 digest.update(val.getBytes("UTF8"));
334 } catch (UnsupportedEncodingException ex) {
335 throw new Error(ex);
336 }
337 }
338
339 private String toHex(byte[] bytes) {
340 // function taken from:
341 // http://stackoverflow.com/questions/9655181/convert-from-byte-array-to-hex-string-in-java
342 final char[] hexArray = "0123456789ABCDEF".toCharArray();
343 char[] hexChars = new char[bytes.length * 2];
344 for (int j = 0; j < bytes.length; j++) {
345 int v = bytes[j] & 0xFF;
346 hexChars[j * 2] = hexArray[v >>> 4];
347 hexChars[j * 2 + 1] = hexArray[v & 0x0F];
348 }
349 return new String(hexChars);
350 }
351
352 @Override
353 public boolean equals(Object other) {
354 if (other instanceof ClassIdentity) {
355 return equals((ClassIdentity)other);
356 }
357 return false;
358 }
359
360 public boolean equals(ClassIdentity other) {
361 return m_fields.equals(other.m_fields)
362 && m_methods.equals(other.m_methods)
363 && m_constructors.equals(other.m_constructors)
364 && m_staticInitializer.equals(other.m_staticInitializer)
365 && m_extends.equals(other.m_extends)
366 && m_implements.equals(other.m_implements)
367 && m_implementations.equals(other.m_implementations)
368 && m_references.equals(other.m_references);
369 }
370
371 @Override
372 public int hashCode() {
373 List<Object> objs = Lists.newArrayList();
374 objs.addAll(m_fields);
375 objs.addAll(m_methods);
376 objs.addAll(m_constructors);
377 objs.add(m_staticInitializer);
378 objs.add(m_extends);
379 objs.addAll(m_implements);
380 objs.addAll(m_implementations);
381 objs.addAll(m_references);
382 return Util.combineHashesOrdered(objs);
383 }
384
385 public int getMatchScore(ClassIdentity other) {
386 return getNumMatches(m_fields, other.m_fields)
387 + getNumMatches(m_methods, other.m_methods)
388 + getNumMatches(m_constructors, other.m_constructors);
389 }
390
391 public int getMaxMatchScore() {
392 return m_fields.size() + m_methods.size() + m_constructors.size();
393 }
394
395 public boolean matches(CtClass c) {
396 // just compare declaration counts
397 return m_fields.size() == c.getDeclaredFields().length
398 && m_methods.size() == c.getDeclaredMethods().length
399 && m_constructors.size() == c.getDeclaredConstructors().length;
400 }
401
402 private int getNumMatches(Multiset<String> a, Multiset<String> b) {
403 int numMatches = 0;
404 for (String val : a) {
405 if (b.contains(val)) {
406 numMatches++;
407 }
408 }
409 return numMatches;
410 }
411}
diff --git a/src/cuchaz/enigma/convert/ClassMatcher.java b/src/cuchaz/enigma/convert/ClassMatcher.java
new file mode 100644
index 00000000..ccf6b787
--- /dev/null
+++ b/src/cuchaz/enigma/convert/ClassMatcher.java
@@ -0,0 +1,406 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.io.File;
14import java.io.FileReader;
15import java.io.FileWriter;
16import java.io.IOException;
17import java.util.ArrayList;
18import java.util.Arrays;
19import java.util.Collection;
20import java.util.Collections;
21import java.util.Comparator;
22import java.util.Iterator;
23import java.util.LinkedHashMap;
24import java.util.List;
25import java.util.Map;
26import java.util.Set;
27import java.util.jar.JarFile;
28
29import javassist.CtBehavior;
30import javassist.CtClass;
31
32import com.google.common.collect.ArrayListMultimap;
33import com.google.common.collect.BiMap;
34import com.google.common.collect.HashBiMap;
35import com.google.common.collect.Lists;
36import com.google.common.collect.Maps;
37import com.google.common.collect.Multimap;
38import com.google.common.collect.Sets;
39
40import cuchaz.enigma.TranslatingTypeLoader;
41import cuchaz.enigma.analysis.JarIndex;
42import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
43import cuchaz.enigma.mapping.ClassEntry;
44import cuchaz.enigma.mapping.ClassMapping;
45import cuchaz.enigma.mapping.JavassistUtil;
46import cuchaz.enigma.mapping.MappingParseException;
47import cuchaz.enigma.mapping.Mappings;
48import cuchaz.enigma.mapping.MappingsReader;
49import cuchaz.enigma.mapping.MappingsWriter;
50import cuchaz.enigma.mapping.MethodEntry;
51import cuchaz.enigma.mapping.MethodMapping;
52
53public class ClassMatcher {
54
55 public static void main(String[] args) throws IOException, MappingParseException {
56 // TEMP
57 JarFile sourceJar = new JarFile(new File("input/1.8-pre3.jar"));
58 JarFile destJar = new JarFile(new File("input/1.8.jar"));
59 File inMappingsFile = new File("../Enigma Mappings/1.8-pre3.mappings");
60 File outMappingsFile = new File("../Enigma Mappings/1.8.mappings");
61
62 // define a matching to use when the automated system cannot find a match
63 Map<String,String> fallbackMatching = Maps.newHashMap();
64 fallbackMatching.put("none/ayb", "none/ayf");
65 fallbackMatching.put("none/ayd", "none/ayd");
66 fallbackMatching.put("none/bgk", "unknown/bgk");
67
68 // do the conversion
69 Mappings mappings = new MappingsReader().read(new FileReader(inMappingsFile));
70 convertMappings(sourceJar, destJar, mappings, fallbackMatching);
71
72 // write out the converted mappings
73 FileWriter writer = new FileWriter(outMappingsFile);
74 new MappingsWriter().write(writer, mappings);
75 writer.close();
76 System.out.println("Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath());
77 }
78
79 private static void convertMappings(JarFile sourceJar, JarFile destJar, Mappings mappings, Map<String,String> fallbackMatching) {
80 // index jars
81 System.out.println("Indexing source jar...");
82 JarIndex sourceIndex = new JarIndex();
83 sourceIndex.indexJar(sourceJar, false);
84 System.out.println("Indexing dest jar...");
85 JarIndex destIndex = new JarIndex();
86 destIndex.indexJar(destJar, false);
87 TranslatingTypeLoader sourceLoader = new TranslatingTypeLoader(sourceJar, sourceIndex);
88 TranslatingTypeLoader destLoader = new TranslatingTypeLoader(destJar, destIndex);
89
90 // compute the matching
91 ClassMatching matching = computeMatching(sourceIndex, sourceLoader, destIndex, destLoader);
92 Map<String,Map.Entry<ClassIdentity,List<ClassIdentity>>> matchingIndex = matching.getIndex();
93
94 // get all the obf class names used in the mappings
95 Set<String> usedClassNames = mappings.getAllObfClassNames();
96 Set<String> allClassNames = Sets.newHashSet();
97 for (ClassEntry classEntry : sourceIndex.getObfClassEntries()) {
98 allClassNames.add(classEntry.getName());
99 }
100 usedClassNames.retainAll(allClassNames);
101 System.out.println("Used " + usedClassNames.size() + " classes in the mappings");
102
103 // probabilistically match the non-uniquely-matched source classes
104 for (Map.Entry<ClassIdentity,List<ClassIdentity>> entry : matchingIndex.values()) {
105 ClassIdentity sourceClass = entry.getKey();
106 List<ClassIdentity> destClasses = entry.getValue();
107
108 // skip classes that are uniquely matched
109 if (destClasses.size() == 1) {
110 continue;
111 }
112
113 // skip classes that aren't used in the mappings
114 if (!usedClassNames.contains(sourceClass.getClassEntry().getName())) {
115 continue;
116 }
117
118 System.out.println("No exact match for source class " + sourceClass.getClassEntry());
119
120 // find the closest classes
121 Multimap<Integer,ClassIdentity> scoredMatches = ArrayListMultimap.create();
122 for (ClassIdentity c : destClasses) {
123 scoredMatches.put(sourceClass.getMatchScore(c), c);
124 }
125 List<Integer> scores = new ArrayList<Integer>(scoredMatches.keySet());
126 Collections.sort(scores, Collections.reverseOrder());
127 printScoredMatches(sourceClass.getMaxMatchScore(), scores, scoredMatches);
128
129 // does the best match have a non-zero score and the same name?
130 int bestScore = scores.get(0);
131 Collection<ClassIdentity> bestMatches = scoredMatches.get(bestScore);
132 if (bestScore > 0 && bestMatches.size() == 1) {
133 ClassIdentity bestMatch = bestMatches.iterator().next();
134 if (bestMatch.getClassEntry().equals(sourceClass.getClassEntry())) {
135 // use it
136 System.out.println("\tAutomatically choosing likely match: " + bestMatch.getClassEntry().getName());
137 destClasses.clear();
138 destClasses.add(bestMatch);
139 }
140 }
141 }
142
143 // group the matching into unique and non-unique matches
144 BiMap<String,String> matchedClassNames = HashBiMap.create();
145 Set<String> unmatchedSourceClassNames = Sets.newHashSet();
146 for (String className : usedClassNames) {
147 // is there a match for this class?
148 Map.Entry<ClassIdentity,List<ClassIdentity>> entry = matchingIndex.get(className);
149 ClassIdentity sourceClass = entry.getKey();
150 List<ClassIdentity> matches = entry.getValue();
151
152 if (matches.size() == 1) {
153 // unique match! We're good to go!
154 matchedClassNames.put(sourceClass.getClassEntry().getName(), matches.get(0).getClassEntry().getName());
155 } else {
156 // no match, check the fallback matching
157 String fallbackMatch = fallbackMatching.get(className);
158 if (fallbackMatch != null) {
159 matchedClassNames.put(sourceClass.getClassEntry().getName(), fallbackMatch);
160 } else {
161 unmatchedSourceClassNames.add(className);
162 }
163 }
164 }
165
166 // report unmatched classes
167 if (!unmatchedSourceClassNames.isEmpty()) {
168 System.err.println("ERROR: there were unmatched classes!");
169 for (String className : unmatchedSourceClassNames) {
170 System.err.println("\t" + className);
171 }
172 return;
173 }
174
175 // get the class name changes from the matched class names
176 Map<String,String> classChanges = Maps.newHashMap();
177 for (Map.Entry<String,String> entry : matchedClassNames.entrySet()) {
178 if (!entry.getKey().equals(entry.getValue())) {
179 classChanges.put(entry.getKey(), entry.getValue());
180 System.out.println(String.format("Class change: %s -> %s", entry.getKey(), entry.getValue()));
181 /* DEBUG
182 System.out.println(String.format("\n%s\n%s",
183 new ClassIdentity(sourceLoader.loadClass(entry.getKey()), null, sourceIndex, false, false),
184 new ClassIdentity( destLoader.loadClass(entry.getValue()), null, destIndex, false, false)
185 ));
186 */
187 }
188 }
189
190 // sort the changes so classes are renamed in the correct order
191 // ie. if we have the mappings a->b, b->c, we have to apply b->c before a->b
192 LinkedHashMap<String,String> orderedClassChanges = Maps.newLinkedHashMap();
193 int numChangesLeft = classChanges.size();
194 while (!classChanges.isEmpty()) {
195 Iterator<Map.Entry<String,String>> iter = classChanges.entrySet().iterator();
196 while (iter.hasNext()) {
197 Map.Entry<String,String> entry = iter.next();
198 if (classChanges.get(entry.getValue()) == null) {
199 orderedClassChanges.put(entry.getKey(), entry.getValue());
200 iter.remove();
201 }
202 }
203
204 // did we remove any changes?
205 if (numChangesLeft - classChanges.size() > 0) {
206 // keep going
207 numChangesLeft = classChanges.size();
208 } else {
209 // can't sort anymore. There must be a loop
210 break;
211 }
212 }
213 if (classChanges.size() > 0) {
214 throw new Error(String.format("Unable to sort %d/%d class changes!", classChanges.size(), matchedClassNames.size()));
215 }
216
217 // convert the mappings in the correct class order
218 for (Map.Entry<String,String> entry : orderedClassChanges.entrySet()) {
219 mappings.renameObfClass(entry.getKey(), entry.getValue());
220 }
221
222 // check the method matches
223 System.out.println("Checking methods...");
224 for (ClassMapping classMapping : mappings.classes()) {
225 ClassEntry classEntry = new ClassEntry(classMapping.getObfName());
226 for (MethodMapping methodMapping : classMapping.methods()) {
227
228 // skip constructors
229 if (methodMapping.getObfName().equals("<init>")) {
230 continue;
231 }
232
233 MethodEntry methodEntry = new MethodEntry(
234 classEntry,
235 methodMapping.getObfName(),
236 methodMapping.getObfSignature()
237 );
238 if (!destIndex.containsObfBehavior(methodEntry)) {
239 System.err.println("WARNING: method doesn't match: " + methodEntry);
240
241 // show the available methods
242 System.err.println("\tAvailable dest methods:");
243 CtClass c = destLoader.loadClass(classMapping.getObfName());
244 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
245 System.err.println("\t\t" + JavassistUtil.getBehaviorEntry(behavior));
246 }
247
248 System.err.println("\tAvailable source methods:");
249 c = sourceLoader.loadClass(matchedClassNames.inverse().get(classMapping.getObfName()));
250 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
251 System.err.println("\t\t" + JavassistUtil.getBehaviorEntry(behavior));
252 }
253 }
254 }
255 }
256
257 System.out.println("Done!");
258 }
259
260 public static ClassMatching computeMatching(JarIndex sourceIndex, TranslatingTypeLoader sourceLoader, JarIndex destIndex, TranslatingTypeLoader destLoader) {
261
262 System.out.println("Matching classes...");
263
264 ClassMatching matching = null;
265 for (boolean useReferences : Arrays.asList(false, true)) {
266 int numMatches = 0;
267 do {
268 SidedClassNamer sourceNamer = null;
269 SidedClassNamer destNamer = null;
270 if (matching != null) {
271 // build a class namer
272 ClassNamer namer = new ClassNamer(matching.getUniqueMatches());
273 sourceNamer = namer.getSourceNamer();
274 destNamer = namer.getDestNamer();
275
276 // note the number of matches
277 numMatches = matching.getUniqueMatches().size();
278 }
279
280 // get the entries left to match
281 Set<ClassEntry> sourceClassEntries = Sets.newHashSet();
282 Set<ClassEntry> destClassEntries = Sets.newHashSet();
283 if (matching == null) {
284 sourceClassEntries.addAll(sourceIndex.getObfClassEntries());
285 destClassEntries.addAll(destIndex.getObfClassEntries());
286 matching = new ClassMatching();
287 } else {
288 for (Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry : matching.getAmbiguousMatches().entrySet()) {
289 for (ClassIdentity c : entry.getKey()) {
290 sourceClassEntries.add(c.getClassEntry());
291 matching.removeSource(c);
292 }
293 for (ClassIdentity c : entry.getValue()) {
294 destClassEntries.add(c.getClassEntry());
295 matching.removeDest(c);
296 }
297 }
298 for (ClassIdentity c : matching.getUnmatchedSourceClasses()) {
299 sourceClassEntries.add(c.getClassEntry());
300 matching.removeSource(c);
301 }
302 for (ClassIdentity c : matching.getUnmatchedDestClasses()) {
303 destClassEntries.add(c.getClassEntry());
304 matching.removeDest(c);
305 }
306 }
307
308 // compute a matching for the classes
309 for (ClassEntry classEntry : sourceClassEntries) {
310 CtClass c = sourceLoader.loadClass(classEntry.getName());
311 ClassIdentity sourceClass = new ClassIdentity(c, sourceNamer, sourceIndex, useReferences);
312 matching.addSource(sourceClass);
313 }
314 for (ClassEntry classEntry : destClassEntries) {
315 CtClass c = destLoader.loadClass(classEntry.getName());
316 ClassIdentity destClass = new ClassIdentity(c, destNamer, destIndex, useReferences);
317 matching.matchDestClass(destClass);
318 }
319
320 // TEMP
321 System.out.println(matching);
322 } while (matching.getUniqueMatches().size() - numMatches > 0);
323 }
324
325 // check the class matches
326 System.out.println("Checking class matches...");
327 ClassNamer namer = new ClassNamer(matching.getUniqueMatches());
328 SidedClassNamer sourceNamer = namer.getSourceNamer();
329 SidedClassNamer destNamer = namer.getDestNamer();
330 for (Map.Entry<ClassIdentity,ClassIdentity> entry : matching.getUniqueMatches().entrySet()) {
331
332 // check source
333 ClassIdentity sourceClass = entry.getKey();
334 CtClass sourceC = sourceLoader.loadClass(sourceClass.getClassEntry().getName());
335 assert (sourceC != null) : "Unable to load source class " + sourceClass.getClassEntry();
336 assert (sourceClass.matches(sourceC)) : "Source " + sourceClass + " doesn't match " + new ClassIdentity(sourceC, sourceNamer, sourceIndex, false);
337
338 // check dest
339 ClassIdentity destClass = entry.getValue();
340 CtClass destC = destLoader.loadClass(destClass.getClassEntry().getName());
341 assert (destC != null) : "Unable to load dest class " + destClass.getClassEntry();
342 assert (destClass.matches(destC)) : "Dest " + destClass + " doesn't match " + new ClassIdentity(destC, destNamer, destIndex, false);
343 }
344
345 // warn about the ambiguous matchings
346 List<Map.Entry<List<ClassIdentity>,List<ClassIdentity>>> ambiguousMatches = new ArrayList<Map.Entry<List<ClassIdentity>,List<ClassIdentity>>>(matching.getAmbiguousMatches().entrySet());
347 Collections.sort(ambiguousMatches, new Comparator<Map.Entry<List<ClassIdentity>,List<ClassIdentity>>>() {
348 @Override
349 public int compare(Map.Entry<List<ClassIdentity>,List<ClassIdentity>> a, Map.Entry<List<ClassIdentity>,List<ClassIdentity>> b) {
350 String aName = a.getKey().get(0).getClassEntry().getName();
351 String bName = b.getKey().get(0).getClassEntry().getName();
352 return aName.compareTo(bName);
353 }
354 });
355 for (Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry : ambiguousMatches) {
356 System.out.println("Ambiguous matching:");
357 System.out.println("\tSource: " + getClassNames(entry.getKey()));
358 System.out.println("\tDest: " + getClassNames(entry.getValue()));
359 }
360
361 /* DEBUG
362 Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry = ambiguousMatches.get( 7 );
363 for (ClassIdentity c : entry.getKey()) {
364 System.out.println(c);
365 }
366 for(ClassIdentity c : entry.getKey()) {
367 System.out.println(decompile(sourceLoader, c.getClassEntry()));
368 }
369 */
370
371 return matching;
372 }
373
374 private static void printScoredMatches(int maxScore, List<Integer> scores, Multimap<Integer,ClassIdentity> scoredMatches) {
375 int numScoredMatchesShown = 0;
376 for (int score : scores) {
377 for (ClassIdentity scoredMatch : scoredMatches.get(score)) {
378 System.out.println(String.format("\tScore: %3d %3.0f%% %s", score, 100.0 * score / maxScore, scoredMatch.getClassEntry().getName()));
379 if (numScoredMatchesShown++ > 10) {
380 return;
381 }
382 }
383 }
384 }
385
386 private static List<String> getClassNames(Collection<ClassIdentity> classes) {
387 List<String> out = Lists.newArrayList();
388 for (ClassIdentity c : classes) {
389 out.add(c.getClassEntry().getName());
390 }
391 Collections.sort(out);
392 return out;
393 }
394
395 /* DEBUG
396 private static String decompile(TranslatingTypeLoader loader, ClassEntry classEntry) {
397 PlainTextOutput output = new PlainTextOutput();
398 DecompilerSettings settings = DecompilerSettings.javaDefaults();
399 settings.setForceExplicitImports(true);
400 settings.setShowSyntheticMembers(true);
401 settings.setTypeLoader(loader);
402 Decompiler.decompile(classEntry.getName(), output, settings);
403 return output.toString();
404 }
405 */
406}
diff --git a/src/cuchaz/enigma/convert/ClassMatching.java b/src/cuchaz/enigma/convert/ClassMatching.java
new file mode 100644
index 00000000..53b6f7f4
--- /dev/null
+++ b/src/cuchaz/enigma/convert/ClassMatching.java
@@ -0,0 +1,173 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.util.AbstractMap;
14import java.util.ArrayList;
15import java.util.Arrays;
16import java.util.Collection;
17import java.util.List;
18import java.util.Map;
19
20import com.google.common.collect.ArrayListMultimap;
21import com.google.common.collect.BiMap;
22import com.google.common.collect.HashBiMap;
23import com.google.common.collect.Lists;
24import com.google.common.collect.Maps;
25import com.google.common.collect.Multimap;
26
27public class ClassMatching {
28
29 private Multimap<ClassIdentity,ClassIdentity> m_sourceClasses;
30 private Multimap<ClassIdentity,ClassIdentity> m_matchedDestClasses;
31 private List<ClassIdentity> m_unmatchedDestClasses;
32
33 public ClassMatching() {
34 m_sourceClasses = ArrayListMultimap.create();
35 m_matchedDestClasses = ArrayListMultimap.create();
36 m_unmatchedDestClasses = Lists.newArrayList();
37 }
38
39 public void addSource(ClassIdentity c) {
40 m_sourceClasses.put(c, c);
41 }
42
43 public void matchDestClass(ClassIdentity destClass) {
44 Collection<ClassIdentity> matchedSourceClasses = m_sourceClasses.get(destClass);
45 if (matchedSourceClasses.isEmpty()) {
46 // no match
47 m_unmatchedDestClasses.add(destClass);
48 } else {
49 // found a match
50 m_matchedDestClasses.put(destClass, destClass);
51
52 // DEBUG
53 ClassIdentity sourceClass = matchedSourceClasses.iterator().next();
54 assert (sourceClass.hashCode() == destClass.hashCode());
55 assert (sourceClass.equals(destClass));
56 }
57 }
58
59 public void removeSource(ClassIdentity sourceClass) {
60 m_sourceClasses.remove(sourceClass, sourceClass);
61 }
62
63 public void removeDest(ClassIdentity destClass) {
64 m_matchedDestClasses.remove(destClass, destClass);
65 m_unmatchedDestClasses.remove(destClass);
66 }
67
68 public List<ClassIdentity> getSourceClasses() {
69 return new ArrayList<ClassIdentity>(m_sourceClasses.values());
70 }
71
72 public List<ClassIdentity> getDestClasses() {
73 List<ClassIdentity> classes = Lists.newArrayList();
74 classes.addAll(m_matchedDestClasses.values());
75 classes.addAll(m_unmatchedDestClasses);
76 return classes;
77 }
78
79 public BiMap<ClassIdentity,ClassIdentity> getUniqueMatches() {
80 BiMap<ClassIdentity,ClassIdentity> uniqueMatches = HashBiMap.create();
81 for (ClassIdentity sourceClass : m_sourceClasses.keySet()) {
82 Collection<ClassIdentity> matchedSourceClasses = m_sourceClasses.get(sourceClass);
83 Collection<ClassIdentity> matchedDestClasses = m_matchedDestClasses.get(sourceClass);
84 if (matchedSourceClasses.size() == 1 && matchedDestClasses.size() == 1) {
85 ClassIdentity matchedSourceClass = matchedSourceClasses.iterator().next();
86 ClassIdentity matchedDestClass = matchedDestClasses.iterator().next();
87 uniqueMatches.put(matchedSourceClass, matchedDestClass);
88 }
89 }
90 return uniqueMatches;
91 }
92
93 public BiMap<List<ClassIdentity>,List<ClassIdentity>> getAmbiguousMatches() {
94 BiMap<List<ClassIdentity>,List<ClassIdentity>> ambiguousMatches = HashBiMap.create();
95 for (ClassIdentity sourceClass : m_sourceClasses.keySet()) {
96 Collection<ClassIdentity> matchedSourceClasses = m_sourceClasses.get(sourceClass);
97 Collection<ClassIdentity> matchedDestClasses = m_matchedDestClasses.get(sourceClass);
98 if (matchedSourceClasses.size() > 1 && matchedDestClasses.size() > 1) {
99 ambiguousMatches.put(
100 new ArrayList<ClassIdentity>(matchedSourceClasses),
101 new ArrayList<ClassIdentity>(matchedDestClasses)
102 );
103 }
104 }
105 return ambiguousMatches;
106 }
107
108 public int getNumAmbiguousSourceMatches() {
109 int num = 0;
110 for (Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry : getAmbiguousMatches().entrySet()) {
111 num += entry.getKey().size();
112 }
113 return num;
114 }
115
116 public int getNumAmbiguousDestMatches() {
117 int num = 0;
118 for (Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry : getAmbiguousMatches().entrySet()) {
119 num += entry.getValue().size();
120 }
121 return num;
122 }
123
124 public List<ClassIdentity> getUnmatchedSourceClasses() {
125 List<ClassIdentity> classes = Lists.newArrayList();
126 for (ClassIdentity sourceClass : getSourceClasses()) {
127 if (m_matchedDestClasses.get(sourceClass).isEmpty()) {
128 classes.add(sourceClass);
129 }
130 }
131 return classes;
132 }
133
134 public List<ClassIdentity> getUnmatchedDestClasses() {
135 return new ArrayList<ClassIdentity>(m_unmatchedDestClasses);
136 }
137
138 public Map<String,Map.Entry<ClassIdentity,List<ClassIdentity>>> getIndex() {
139 Map<String,Map.Entry<ClassIdentity,List<ClassIdentity>>> conversion = Maps.newHashMap();
140 for (Map.Entry<ClassIdentity,ClassIdentity> entry : getUniqueMatches().entrySet()) {
141 conversion.put(
142 entry.getKey().getClassEntry().getName(),
143 new AbstractMap.SimpleEntry<ClassIdentity,List<ClassIdentity>>(entry.getKey(), Arrays.asList(entry.getValue()))
144 );
145 }
146 for (Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry : getAmbiguousMatches().entrySet()) {
147 for (ClassIdentity sourceClass : entry.getKey()) {
148 conversion.put(
149 sourceClass.getClassEntry().getName(),
150 new AbstractMap.SimpleEntry<ClassIdentity,List<ClassIdentity>>(sourceClass, entry.getValue())
151 );
152 }
153 }
154 for (ClassIdentity sourceClass : getUnmatchedSourceClasses()) {
155 conversion.put(
156 sourceClass.getClassEntry().getName(),
157 new AbstractMap.SimpleEntry<ClassIdentity,List<ClassIdentity>>(sourceClass, getUnmatchedDestClasses())
158 );
159 }
160 return conversion;
161 }
162
163 @Override
164 public String toString() {
165 StringBuilder buf = new StringBuilder();
166 buf.append(String.format("%12s%8s%8s\n", "", "Source", "Dest"));
167 buf.append(String.format("%12s%8d%8d\n", "Classes", getSourceClasses().size(), getDestClasses().size()));
168 buf.append(String.format("%12s%8d%8d\n", "Unique", getUniqueMatches().size(), getUniqueMatches().size()));
169 buf.append(String.format("%12s%8d%8d\n", "Ambiguous", getNumAmbiguousSourceMatches(), getNumAmbiguousDestMatches()));
170 buf.append(String.format("%12s%8d%8d\n", "Unmatched", getUnmatchedSourceClasses().size(), getUnmatchedDestClasses().size()));
171 return buf.toString();
172 }
173}
diff --git a/src/cuchaz/enigma/convert/ClassNamer.java b/src/cuchaz/enigma/convert/ClassNamer.java
new file mode 100644
index 00000000..1b6e81c8
--- /dev/null
+++ b/src/cuchaz/enigma/convert/ClassNamer.java
@@ -0,0 +1,64 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.util.Map;
14
15import com.google.common.collect.BiMap;
16import com.google.common.collect.Maps;
17
18public class ClassNamer {
19
20 public interface SidedClassNamer {
21 String getName(String name);
22 }
23
24 private Map<String,String> m_sourceNames;
25 private Map<String,String> m_destNames;
26
27 public ClassNamer(BiMap<ClassIdentity,ClassIdentity> mappings) {
28 // convert the identity mappings to name maps
29 m_sourceNames = Maps.newHashMap();
30 m_destNames = Maps.newHashMap();
31 int i = 0;
32 for (Map.Entry<ClassIdentity,ClassIdentity> entry : mappings.entrySet()) {
33 String name = String.format("M%04d", i++);
34 m_sourceNames.put(entry.getKey().getClassEntry().getName(), name);
35 m_destNames.put(entry.getValue().getClassEntry().getName(), name);
36 }
37 }
38
39 public String getSourceName(String name) {
40 return m_sourceNames.get(name);
41 }
42
43 public String getDestName(String name) {
44 return m_destNames.get(name);
45 }
46
47 public SidedClassNamer getSourceNamer() {
48 return new SidedClassNamer() {
49 @Override
50 public String getName(String name) {
51 return getSourceName(name);
52 }
53 };
54 }
55
56 public SidedClassNamer getDestNamer() {
57 return new SidedClassNamer() {
58 @Override
59 public String getName(String name) {
60 return getDestName(name);
61 }
62 };
63 }
64}
diff --git a/src/cuchaz/enigma/gui/AboutDialog.java b/src/cuchaz/enigma/gui/AboutDialog.java
new file mode 100644
index 00000000..2476b564
--- /dev/null
+++ b/src/cuchaz/enigma/gui/AboutDialog.java
@@ -0,0 +1,86 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Color;
14import java.awt.Container;
15import java.awt.Cursor;
16import java.awt.FlowLayout;
17import java.awt.event.ActionEvent;
18import java.awt.event.ActionListener;
19import java.io.IOException;
20
21import javax.swing.JButton;
22import javax.swing.JFrame;
23import javax.swing.JLabel;
24import javax.swing.JPanel;
25import javax.swing.WindowConstants;
26
27import cuchaz.enigma.Constants;
28import cuchaz.enigma.Util;
29
30public class AboutDialog {
31
32 public static void show(JFrame parent) {
33 // init frame
34 final JFrame frame = new JFrame(Constants.Name + " - About");
35 final Container pane = frame.getContentPane();
36 pane.setLayout(new FlowLayout());
37
38 // load the content
39 try {
40 String html = Util.readResourceToString("/about.html");
41 html = String.format(html, Constants.Name, Constants.Version);
42 JLabel label = new JLabel(html);
43 label.setHorizontalAlignment(JLabel.CENTER);
44 pane.add(label);
45 } catch (IOException ex) {
46 throw new Error(ex);
47 }
48
49 // show the link
50 String html = "<html><a href=\"%s\">%s</a></html>";
51 html = String.format(html, Constants.Url, Constants.Url);
52 JButton link = new JButton(html);
53 link.addActionListener(new ActionListener() {
54 @Override
55 public void actionPerformed(ActionEvent event) {
56 Util.openUrl(Constants.Url);
57 }
58 });
59 link.setBorderPainted(false);
60 link.setOpaque(false);
61 link.setBackground(Color.WHITE);
62 link.setCursor(new Cursor(Cursor.HAND_CURSOR));
63 link.setFocusable(false);
64 JPanel linkPanel = new JPanel();
65 linkPanel.add(link);
66 pane.add(linkPanel);
67
68 // show ok button
69 JButton okButton = new JButton("Ok");
70 pane.add(okButton);
71 okButton.addActionListener(new ActionListener() {
72 @Override
73 public void actionPerformed(ActionEvent arg0) {
74 frame.dispose();
75 }
76 });
77
78 // show the frame
79 pane.doLayout();
80 frame.setSize(400, 220);
81 frame.setResizable(false);
82 frame.setLocationRelativeTo(parent);
83 frame.setVisible(true);
84 frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
85 }
86}
diff --git a/src/cuchaz/enigma/gui/BoxHighlightPainter.java b/src/cuchaz/enigma/gui/BoxHighlightPainter.java
new file mode 100644
index 00000000..db7c85b4
--- /dev/null
+++ b/src/cuchaz/enigma/gui/BoxHighlightPainter.java
@@ -0,0 +1,64 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Color;
14import java.awt.Graphics;
15import java.awt.Rectangle;
16import java.awt.Shape;
17
18import javax.swing.text.BadLocationException;
19import javax.swing.text.Highlighter;
20import javax.swing.text.JTextComponent;
21
22public abstract class BoxHighlightPainter implements Highlighter.HighlightPainter {
23
24 private Color m_fillColor;
25 private Color m_borderColor;
26
27 protected BoxHighlightPainter(Color fillColor, Color borderColor) {
28 m_fillColor = fillColor;
29 m_borderColor = borderColor;
30 }
31
32 @Override
33 public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) {
34 Rectangle bounds = getBounds(text, start, end);
35
36 // fill the area
37 if (m_fillColor != null) {
38 g.setColor(m_fillColor);
39 g.fillRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4);
40 }
41
42 // draw a box around the area
43 g.setColor(m_borderColor);
44 g.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4);
45 }
46
47 protected static Rectangle getBounds(JTextComponent text, int start, int end) {
48 try {
49 // determine the bounds of the text
50 Rectangle bounds = text.modelToView(start).union(text.modelToView(end));
51
52 // adjust the box so it looks nice
53 bounds.x -= 2;
54 bounds.width += 2;
55 bounds.y += 1;
56 bounds.height -= 2;
57
58 return bounds;
59 } catch (BadLocationException ex) {
60 // don't care... just return something
61 return new Rectangle(0, 0, 0, 0);
62 }
63 }
64}
diff --git a/src/cuchaz/enigma/gui/BrowserCaret.java b/src/cuchaz/enigma/gui/BrowserCaret.java
new file mode 100644
index 00000000..acee4833
--- /dev/null
+++ b/src/cuchaz/enigma/gui/BrowserCaret.java
@@ -0,0 +1,45 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Graphics;
14import java.awt.Shape;
15
16import javax.swing.text.DefaultCaret;
17import javax.swing.text.Highlighter;
18import javax.swing.text.JTextComponent;
19
20public class BrowserCaret extends DefaultCaret {
21
22 private static final long serialVersionUID = 1158977422507969940L;
23
24 private static final Highlighter.HighlightPainter m_selectionPainter = new Highlighter.HighlightPainter() {
25 @Override
26 public void paint(Graphics g, int p0, int p1, Shape bounds, JTextComponent c) {
27 // don't paint anything
28 }
29 };
30
31 @Override
32 public boolean isSelectionVisible() {
33 return false;
34 }
35
36 @Override
37 public boolean isVisible() {
38 return true;
39 }
40
41 @Override
42 public Highlighter.HighlightPainter getSelectionPainter() {
43 return m_selectionPainter;
44 }
45}
diff --git a/src/cuchaz/enigma/gui/ClassListCellRenderer.java b/src/cuchaz/enigma/gui/ClassListCellRenderer.java
new file mode 100644
index 00000000..d0f01e6a
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ClassListCellRenderer.java
@@ -0,0 +1,36 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Component;
14
15import javassist.bytecode.Descriptor;
16
17import javax.swing.DefaultListCellRenderer;
18import javax.swing.JLabel;
19import javax.swing.JList;
20import javax.swing.ListCellRenderer;
21
22public class ClassListCellRenderer implements ListCellRenderer<String> {
23
24 private DefaultListCellRenderer m_defaultRenderer;
25
26 public ClassListCellRenderer() {
27 m_defaultRenderer = new DefaultListCellRenderer();
28 }
29
30 @Override
31 public Component getListCellRendererComponent(JList<? extends String> list, String className, int index, boolean isSelected, boolean hasFocus) {
32 JLabel label = (JLabel)m_defaultRenderer.getListCellRendererComponent(list, className, index, isSelected, hasFocus);
33 label.setText(Descriptor.toJavaName(className));
34 return label;
35 }
36}
diff --git a/src/cuchaz/enigma/gui/ClassSelector.java b/src/cuchaz/enigma/gui/ClassSelector.java
new file mode 100644
index 00000000..654bfbed
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ClassSelector.java
@@ -0,0 +1,164 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.event.MouseAdapter;
14import java.awt.event.MouseEvent;
15import java.util.Collection;
16import java.util.Collections;
17import java.util.Comparator;
18import java.util.List;
19import java.util.Map;
20
21import javax.swing.JTree;
22import javax.swing.tree.DefaultMutableTreeNode;
23import javax.swing.tree.DefaultTreeModel;
24import javax.swing.tree.TreePath;
25
26import com.google.common.collect.ArrayListMultimap;
27import com.google.common.collect.Lists;
28import com.google.common.collect.Maps;
29import com.google.common.collect.Multimap;
30
31import cuchaz.enigma.mapping.ClassEntry;
32
33public class ClassSelector extends JTree {
34
35 private static final long serialVersionUID = -7632046902384775977L;
36
37 public interface ClassSelectionListener {
38 void onSelectClass(ClassEntry classEntry);
39 }
40
41 public static Comparator<ClassEntry> ObfuscatedClassEntryComparator;
42 public static Comparator<ClassEntry> DeobfuscatedClassEntryComparator;
43
44 static {
45 ObfuscatedClassEntryComparator = new Comparator<ClassEntry>() {
46 @Override
47 public int compare(ClassEntry a, ClassEntry b) {
48 if (a.getName().length() != b.getName().length()) {
49 return a.getName().length() - b.getName().length();
50 }
51 return a.getName().compareTo(b.getName());
52 }
53 };
54
55 DeobfuscatedClassEntryComparator = new Comparator<ClassEntry>() {
56 @Override
57 public int compare(ClassEntry a, ClassEntry b) {
58 return a.getName().compareTo(b.getName());
59 }
60 };
61 }
62
63 private ClassSelectionListener m_listener;
64 private Comparator<ClassEntry> m_comparator;
65
66 public ClassSelector(Comparator<ClassEntry> comparator) {
67 m_comparator = comparator;
68
69 // configure the tree control
70 setRootVisible(false);
71 setShowsRootHandles(false);
72 setModel(null);
73
74 // hook events
75 addMouseListener(new MouseAdapter() {
76 @Override
77 public void mouseClicked(MouseEvent event) {
78 if (m_listener != null && event.getClickCount() == 2) {
79 // get the selected node
80 TreePath path = getSelectionPath();
81 if (path != null && path.getLastPathComponent() instanceof ClassSelectorClassNode) {
82 ClassSelectorClassNode node = (ClassSelectorClassNode)path.getLastPathComponent();
83 m_listener.onSelectClass(node.getClassEntry());
84 }
85 }
86 }
87 });
88
89 // init defaults
90 m_listener = null;
91 }
92
93 public void setListener(ClassSelectionListener val) {
94 m_listener = val;
95 }
96
97 public void setClasses(Collection<ClassEntry> classEntries) {
98 if (classEntries == null) {
99 setModel(null);
100 return;
101 }
102
103 // build the package names
104 Map<String,ClassSelectorPackageNode> packages = Maps.newHashMap();
105 for (ClassEntry classEntry : classEntries) {
106 packages.put(classEntry.getPackageName(), null);
107 }
108
109 // sort the packages
110 List<String> sortedPackageNames = Lists.newArrayList(packages.keySet());
111 Collections.sort(sortedPackageNames, new Comparator<String>() {
112 @Override
113 public int compare(String a, String b) {
114 // I can never keep this rule straight when writing these damn things...
115 // a < b => -1, a == b => 0, a > b => +1
116
117 String[] aparts = a.split("/");
118 String[] bparts = b.split("/");
119 for (int i = 0; true; i++) {
120 if (i >= aparts.length) {
121 return -1;
122 } else if (i >= bparts.length) {
123 return 1;
124 }
125
126 int result = aparts[i].compareTo(bparts[i]);
127 if (result != 0) {
128 return result;
129 }
130 }
131 }
132 });
133
134 // create the root node and the package nodes
135 DefaultMutableTreeNode root = new DefaultMutableTreeNode();
136 for (String packageName : sortedPackageNames) {
137 ClassSelectorPackageNode node = new ClassSelectorPackageNode(packageName);
138 packages.put(packageName, node);
139 root.add(node);
140 }
141
142 // put the classes into packages
143 Multimap<String,ClassEntry> packagedClassEntries = ArrayListMultimap.create();
144 for (ClassEntry classEntry : classEntries) {
145 packagedClassEntries.put(classEntry.getPackageName(), classEntry);
146 }
147
148 // build the class nodes
149 for (String packageName : packagedClassEntries.keySet()) {
150 // sort the class entries
151 List<ClassEntry> classEntriesInPackage = Lists.newArrayList(packagedClassEntries.get(packageName));
152 Collections.sort(classEntriesInPackage, m_comparator);
153
154 // create the nodes in order
155 for (ClassEntry classEntry : classEntriesInPackage) {
156 ClassSelectorPackageNode node = packages.get(packageName);
157 node.add(new ClassSelectorClassNode(classEntry));
158 }
159 }
160
161 // finally, update the tree control
162 setModel(new DefaultTreeModel(root));
163 }
164}
diff --git a/src/cuchaz/enigma/gui/ClassSelectorClassNode.java b/src/cuchaz/enigma/gui/ClassSelectorClassNode.java
new file mode 100644
index 00000000..66e931b4
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ClassSelectorClassNode.java
@@ -0,0 +1,35 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import javax.swing.tree.DefaultMutableTreeNode;
14
15import cuchaz.enigma.mapping.ClassEntry;
16
17public class ClassSelectorClassNode extends DefaultMutableTreeNode {
18
19 private static final long serialVersionUID = -8956754339813257380L;
20
21 private ClassEntry m_classEntry;
22
23 public ClassSelectorClassNode(ClassEntry classEntry) {
24 m_classEntry = classEntry;
25 }
26
27 public ClassEntry getClassEntry() {
28 return m_classEntry;
29 }
30
31 @Override
32 public String toString() {
33 return m_classEntry.getSimpleName();
34 }
35}
diff --git a/src/cuchaz/enigma/gui/ClassSelectorPackageNode.java b/src/cuchaz/enigma/gui/ClassSelectorPackageNode.java
new file mode 100644
index 00000000..451d3809
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ClassSelectorPackageNode.java
@@ -0,0 +1,33 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import javax.swing.tree.DefaultMutableTreeNode;
14
15public class ClassSelectorPackageNode extends DefaultMutableTreeNode {
16
17 private static final long serialVersionUID = -3730868701219548043L;
18
19 private String m_packageName;
20
21 public ClassSelectorPackageNode(String packageName) {
22 m_packageName = packageName;
23 }
24
25 public String getPackageName() {
26 return m_packageName;
27 }
28
29 @Override
30 public String toString() {
31 return m_packageName;
32 }
33}
diff --git a/src/cuchaz/enigma/gui/CrashDialog.java b/src/cuchaz/enigma/gui/CrashDialog.java
new file mode 100644
index 00000000..360091ab
--- /dev/null
+++ b/src/cuchaz/enigma/gui/CrashDialog.java
@@ -0,0 +1,101 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.BorderLayout;
14import java.awt.Container;
15import java.awt.FlowLayout;
16import java.awt.event.ActionEvent;
17import java.awt.event.ActionListener;
18import java.io.PrintWriter;
19import java.io.StringWriter;
20
21import javax.swing.BorderFactory;
22import javax.swing.JButton;
23import javax.swing.JFrame;
24import javax.swing.JLabel;
25import javax.swing.JPanel;
26import javax.swing.JScrollPane;
27import javax.swing.JTextArea;
28import javax.swing.WindowConstants;
29
30import cuchaz.enigma.Constants;
31
32public class CrashDialog {
33
34 private static CrashDialog m_instance = null;
35
36 private JFrame m_frame;
37 private JTextArea m_text;
38
39 private CrashDialog(JFrame parent) {
40 // init frame
41 m_frame = new JFrame(Constants.Name + " - Crash Report");
42 final Container pane = m_frame.getContentPane();
43 pane.setLayout(new BorderLayout());
44
45 JLabel label = new JLabel(Constants.Name + " has crashed! =(");
46 label.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
47 pane.add(label, BorderLayout.NORTH);
48
49 // report panel
50 m_text = new JTextArea();
51 m_text.setTabSize(2);
52 pane.add(new JScrollPane(m_text), BorderLayout.CENTER);
53
54 // buttons panel
55 JPanel buttonsPanel = new JPanel();
56 FlowLayout buttonsLayout = new FlowLayout();
57 buttonsLayout.setAlignment(FlowLayout.RIGHT);
58 buttonsPanel.setLayout(buttonsLayout);
59 buttonsPanel.add(GuiTricks.unboldLabel(new JLabel("If you choose exit, you will lose any unsaved work.")));
60 JButton ignoreButton = new JButton("Ignore");
61 ignoreButton.addActionListener(new ActionListener() {
62 @Override
63 public void actionPerformed(ActionEvent event) {
64 // close (hide) the dialog
65 m_frame.setVisible(false);
66 }
67 });
68 buttonsPanel.add(ignoreButton);
69 JButton exitButton = new JButton("Exit");
70 exitButton.addActionListener(new ActionListener() {
71 @Override
72 public void actionPerformed(ActionEvent event) {
73 // exit enigma
74 System.exit(1);
75 }
76 });
77 buttonsPanel.add(exitButton);
78 pane.add(buttonsPanel, BorderLayout.SOUTH);
79
80 // show the frame
81 m_frame.setSize(600, 400);
82 m_frame.setLocationRelativeTo(parent);
83 m_frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
84 }
85
86 public static void init(JFrame parent) {
87 m_instance = new CrashDialog(parent);
88 }
89
90 public static void show(Throwable ex) {
91 // get the error report
92 StringWriter buf = new StringWriter();
93 ex.printStackTrace(new PrintWriter(buf));
94 String report = buf.toString();
95
96 // show it!
97 m_instance.m_text.setText(report);
98 m_instance.m_frame.doLayout();
99 m_instance.m_frame.setVisible(true);
100 }
101}
diff --git a/src/cuchaz/enigma/gui/DeobfuscatedHighlightPainter.java b/src/cuchaz/enigma/gui/DeobfuscatedHighlightPainter.java
new file mode 100644
index 00000000..26a31639
--- /dev/null
+++ b/src/cuchaz/enigma/gui/DeobfuscatedHighlightPainter.java
@@ -0,0 +1,21 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Color;
14
15public class DeobfuscatedHighlightPainter extends BoxHighlightPainter {
16
17 public DeobfuscatedHighlightPainter() {
18 // green ish
19 super(new Color(220, 255, 220), new Color(80, 160, 80));
20 }
21}
diff --git a/src/cuchaz/enigma/gui/Gui.java b/src/cuchaz/enigma/gui/Gui.java
new file mode 100644
index 00000000..ca39c42a
--- /dev/null
+++ b/src/cuchaz/enigma/gui/Gui.java
@@ -0,0 +1,1165 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.BorderLayout;
14import java.awt.Color;
15import java.awt.Container;
16import java.awt.Dimension;
17import java.awt.FlowLayout;
18import java.awt.GridLayout;
19import java.awt.Rectangle;
20import java.awt.event.ActionEvent;
21import java.awt.event.ActionListener;
22import java.awt.event.InputEvent;
23import java.awt.event.KeyAdapter;
24import java.awt.event.KeyEvent;
25import java.awt.event.MouseAdapter;
26import java.awt.event.MouseEvent;
27import java.awt.event.WindowAdapter;
28import java.awt.event.WindowEvent;
29import java.io.File;
30import java.io.IOException;
31import java.lang.Thread.UncaughtExceptionHandler;
32import java.util.Collection;
33import java.util.Collections;
34import java.util.List;
35import java.util.Vector;
36import java.util.jar.JarFile;
37
38import javax.swing.BorderFactory;
39import javax.swing.JEditorPane;
40import javax.swing.JFileChooser;
41import javax.swing.JFrame;
42import javax.swing.JLabel;
43import javax.swing.JList;
44import javax.swing.JMenu;
45import javax.swing.JMenuBar;
46import javax.swing.JMenuItem;
47import javax.swing.JOptionPane;
48import javax.swing.JPanel;
49import javax.swing.JPopupMenu;
50import javax.swing.JScrollPane;
51import javax.swing.JSplitPane;
52import javax.swing.JTabbedPane;
53import javax.swing.JTextField;
54import javax.swing.JTree;
55import javax.swing.KeyStroke;
56import javax.swing.ListSelectionModel;
57import javax.swing.SwingUtilities;
58import javax.swing.Timer;
59import javax.swing.WindowConstants;
60import javax.swing.event.CaretEvent;
61import javax.swing.event.CaretListener;
62import javax.swing.text.BadLocationException;
63import javax.swing.text.Highlighter;
64import javax.swing.tree.DefaultTreeModel;
65import javax.swing.tree.TreeNode;
66import javax.swing.tree.TreePath;
67
68import jsyntaxpane.DefaultSyntaxKit;
69
70import com.google.common.collect.Lists;
71
72import cuchaz.enigma.Constants;
73import cuchaz.enigma.analysis.BehaviorReferenceTreeNode;
74import cuchaz.enigma.analysis.ClassImplementationsTreeNode;
75import cuchaz.enigma.analysis.ClassInheritanceTreeNode;
76import cuchaz.enigma.analysis.EntryReference;
77import cuchaz.enigma.analysis.FieldReferenceTreeNode;
78import cuchaz.enigma.analysis.MethodImplementationsTreeNode;
79import cuchaz.enigma.analysis.MethodInheritanceTreeNode;
80import cuchaz.enigma.analysis.ReferenceTreeNode;
81import cuchaz.enigma.analysis.Token;
82import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener;
83import cuchaz.enigma.mapping.ArgumentEntry;
84import cuchaz.enigma.mapping.ClassEntry;
85import cuchaz.enigma.mapping.ConstructorEntry;
86import cuchaz.enigma.mapping.Entry;
87import cuchaz.enigma.mapping.FieldEntry;
88import cuchaz.enigma.mapping.IllegalNameException;
89import cuchaz.enigma.mapping.MappingParseException;
90import cuchaz.enigma.mapping.MethodEntry;
91import cuchaz.enigma.mapping.Signature;
92
93public class Gui {
94
95 private GuiController m_controller;
96
97 // controls
98 private JFrame m_frame;
99 private ClassSelector m_obfClasses;
100 private ClassSelector m_deobfClasses;
101 private JEditorPane m_editor;
102 private JPanel m_classesPanel;
103 private JSplitPane m_splitClasses;
104 private JPanel m_infoPanel;
105 private ObfuscatedHighlightPainter m_obfuscatedHighlightPainter;
106 private DeobfuscatedHighlightPainter m_deobfuscatedHighlightPainter;
107 private OtherHighlightPainter m_otherHighlightPainter;
108 private SelectionHighlightPainter m_selectionHighlightPainter;
109 private JTree m_inheritanceTree;
110 private JTree m_implementationsTree;
111 private JTree m_callsTree;
112 private JList<Token> m_tokens;
113 private JTabbedPane m_tabs;
114
115 // dynamic menu items
116 private JMenuItem m_closeJarMenu;
117 private JMenuItem m_openMappingsMenu;
118 private JMenuItem m_saveMappingsMenu;
119 private JMenuItem m_saveMappingsAsMenu;
120 private JMenuItem m_closeMappingsMenu;
121 private JMenuItem m_renameMenu;
122 private JMenuItem m_showInheritanceMenu;
123 private JMenuItem m_openEntryMenu;
124 private JMenuItem m_openPreviousMenu;
125 private JMenuItem m_showCallsMenu;
126 private JMenuItem m_showImplementationsMenu;
127 private JMenuItem m_toggleMappingMenu;
128 private JMenuItem m_exportSourceMenu;
129 private JMenuItem m_exportJarMenu;
130
131 // state
132 private EntryReference<Entry,Entry> m_reference;
133 private JFileChooser m_jarFileChooser;
134 private JFileChooser m_mappingsFileChooser;
135 private JFileChooser m_exportSourceFileChooser;
136 private JFileChooser m_exportJarFileChooser;
137
138 public Gui() {
139
140 // init frame
141 m_frame = new JFrame(Constants.Name);
142 final Container pane = m_frame.getContentPane();
143 pane.setLayout(new BorderLayout());
144
145 if (Boolean.parseBoolean(System.getProperty("enigma.catchExceptions", "true"))) {
146 // install a global exception handler to the event thread
147 CrashDialog.init(m_frame);
148 Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
149 @Override
150 public void uncaughtException(Thread thread, Throwable ex) {
151 ex.printStackTrace(System.err);
152 CrashDialog.show(ex);
153 }
154 });
155 }
156
157 m_controller = new GuiController(this);
158
159 // init file choosers
160 m_jarFileChooser = new JFileChooser();
161 m_mappingsFileChooser = new JFileChooser();
162 m_exportSourceFileChooser = new JFileChooser();
163 m_exportSourceFileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
164 m_exportJarFileChooser = new JFileChooser();
165
166 // init obfuscated classes list
167 m_obfClasses = new ClassSelector(ClassSelector.ObfuscatedClassEntryComparator);
168 m_obfClasses.setListener(new ClassSelectionListener() {
169 @Override
170 public void onSelectClass(ClassEntry classEntry) {
171 navigateTo(classEntry);
172 }
173 });
174 JScrollPane obfScroller = new JScrollPane(m_obfClasses);
175 JPanel obfPanel = new JPanel();
176 obfPanel.setLayout(new BorderLayout());
177 obfPanel.add(new JLabel("Obfuscated Classes"), BorderLayout.NORTH);
178 obfPanel.add(obfScroller, BorderLayout.CENTER);
179
180 // init deobfuscated classes list
181 m_deobfClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator);
182 m_deobfClasses.setListener(new ClassSelectionListener() {
183 @Override
184 public void onSelectClass(ClassEntry classEntry) {
185 navigateTo(classEntry);
186 }
187 });
188 JScrollPane deobfScroller = new JScrollPane(m_deobfClasses);
189 JPanel deobfPanel = new JPanel();
190 deobfPanel.setLayout(new BorderLayout());
191 deobfPanel.add(new JLabel("De-obfuscated Classes"), BorderLayout.NORTH);
192 deobfPanel.add(deobfScroller, BorderLayout.CENTER);
193
194 // set up classes panel (don't add the splitter yet)
195 m_splitClasses = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, obfPanel, deobfPanel);
196 m_splitClasses.setResizeWeight(0.3);
197 m_classesPanel = new JPanel();
198 m_classesPanel.setLayout(new BorderLayout());
199 m_classesPanel.setPreferredSize(new Dimension(250, 0));
200
201 // init info panel
202 m_infoPanel = new JPanel();
203 m_infoPanel.setLayout(new GridLayout(4, 1, 0, 0));
204 m_infoPanel.setPreferredSize(new Dimension(0, 100));
205 m_infoPanel.setBorder(BorderFactory.createTitledBorder("Identifier Info"));
206 clearReference();
207
208 // init editor
209 DefaultSyntaxKit.initKit();
210 m_obfuscatedHighlightPainter = new ObfuscatedHighlightPainter();
211 m_deobfuscatedHighlightPainter = new DeobfuscatedHighlightPainter();
212 m_otherHighlightPainter = new OtherHighlightPainter();
213 m_selectionHighlightPainter = new SelectionHighlightPainter();
214 m_editor = new JEditorPane();
215 m_editor.setEditable(false);
216 m_editor.setCaret(new BrowserCaret());
217 JScrollPane sourceScroller = new JScrollPane(m_editor);
218 m_editor.setContentType("text/java");
219 m_editor.addCaretListener(new CaretListener() {
220 @Override
221 public void caretUpdate(CaretEvent event) {
222 onCaretMove(event.getDot());
223 }
224 });
225 m_editor.addKeyListener(new KeyAdapter() {
226 @Override
227 public void keyPressed(KeyEvent event) {
228 switch (event.getKeyCode()) {
229 case KeyEvent.VK_R:
230 m_renameMenu.doClick();
231 break;
232
233 case KeyEvent.VK_I:
234 m_showInheritanceMenu.doClick();
235 break;
236
237 case KeyEvent.VK_M:
238 m_showImplementationsMenu.doClick();
239 break;
240
241 case KeyEvent.VK_N:
242 m_openEntryMenu.doClick();
243 break;
244
245 case KeyEvent.VK_P:
246 m_openPreviousMenu.doClick();
247 break;
248
249 case KeyEvent.VK_C:
250 m_showCallsMenu.doClick();
251 break;
252
253 case KeyEvent.VK_T:
254 m_toggleMappingMenu.doClick();
255 break;
256 }
257 }
258 });
259
260 // turn off token highlighting (it's wrong most of the time anyway...)
261 DefaultSyntaxKit kit = (DefaultSyntaxKit)m_editor.getEditorKit();
262 kit.toggleComponent(m_editor, "jsyntaxpane.components.TokenMarker");
263
264 // init editor popup menu
265 JPopupMenu popupMenu = new JPopupMenu();
266 m_editor.setComponentPopupMenu(popupMenu);
267 {
268 JMenuItem menu = new JMenuItem("Rename");
269 menu.addActionListener(new ActionListener() {
270 @Override
271 public void actionPerformed(ActionEvent event) {
272 startRename();
273 }
274 });
275 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, 0));
276 menu.setEnabled(false);
277 popupMenu.add(menu);
278 m_renameMenu = menu;
279 }
280 {
281 JMenuItem menu = new JMenuItem("Show Inheritance");
282 menu.addActionListener(new ActionListener() {
283 @Override
284 public void actionPerformed(ActionEvent event) {
285 showInheritance();
286 }
287 });
288 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, 0));
289 menu.setEnabled(false);
290 popupMenu.add(menu);
291 m_showInheritanceMenu = menu;
292 }
293 {
294 JMenuItem menu = new JMenuItem("Show Implementations");
295 menu.addActionListener(new ActionListener() {
296 @Override
297 public void actionPerformed(ActionEvent event) {
298 showImplementations();
299 }
300 });
301 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, 0));
302 menu.setEnabled(false);
303 popupMenu.add(menu);
304 m_showImplementationsMenu = menu;
305 }
306 {
307 JMenuItem menu = new JMenuItem("Show Calls");
308 menu.addActionListener(new ActionListener() {
309 @Override
310 public void actionPerformed(ActionEvent event) {
311 showCalls();
312 }
313 });
314 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, 0));
315 menu.setEnabled(false);
316 popupMenu.add(menu);
317 m_showCallsMenu = menu;
318 }
319 {
320 JMenuItem menu = new JMenuItem("Go to Declaration");
321 menu.addActionListener(new ActionListener() {
322 @Override
323 public void actionPerformed(ActionEvent event) {
324 navigateTo(m_reference.entry);
325 }
326 });
327 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, 0));
328 menu.setEnabled(false);
329 popupMenu.add(menu);
330 m_openEntryMenu = menu;
331 }
332 {
333 JMenuItem menu = new JMenuItem("Go to previous");
334 menu.addActionListener(new ActionListener() {
335 @Override
336 public void actionPerformed(ActionEvent event) {
337 m_controller.openPreviousReference();
338 }
339 });
340 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, 0));
341 menu.setEnabled(false);
342 popupMenu.add(menu);
343 m_openPreviousMenu = menu;
344 }
345 {
346 JMenuItem menu = new JMenuItem("Mark as deobfuscated");
347 menu.addActionListener(new ActionListener() {
348 @Override
349 public void actionPerformed(ActionEvent event) {
350 toggleMapping();
351 }
352 });
353 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, 0));
354 menu.setEnabled(false);
355 popupMenu.add(menu);
356 m_toggleMappingMenu = menu;
357 }
358
359 // init inheritance panel
360 m_inheritanceTree = new JTree();
361 m_inheritanceTree.setModel(null);
362 m_inheritanceTree.addMouseListener(new MouseAdapter() {
363 @Override
364 public void mouseClicked(MouseEvent event) {
365 if (event.getClickCount() == 2) {
366 // get the selected node
367 TreePath path = m_inheritanceTree.getSelectionPath();
368 if (path == null) {
369 return;
370 }
371
372 Object node = path.getLastPathComponent();
373 if (node instanceof ClassInheritanceTreeNode) {
374 ClassInheritanceTreeNode classNode = (ClassInheritanceTreeNode)node;
375 navigateTo(new ClassEntry(classNode.getObfClassName()));
376 } else if (node instanceof MethodInheritanceTreeNode) {
377 MethodInheritanceTreeNode methodNode = (MethodInheritanceTreeNode)node;
378 if (methodNode.isImplemented()) {
379 navigateTo(methodNode.getMethodEntry());
380 }
381 }
382 }
383 }
384 });
385 JPanel inheritancePanel = new JPanel();
386 inheritancePanel.setLayout(new BorderLayout());
387 inheritancePanel.add(new JScrollPane(m_inheritanceTree));
388
389 // init implementations panel
390 m_implementationsTree = new JTree();
391 m_implementationsTree.setModel(null);
392 m_implementationsTree.addMouseListener(new MouseAdapter() {
393 @Override
394 public void mouseClicked(MouseEvent event) {
395 if (event.getClickCount() == 2) {
396 // get the selected node
397 TreePath path = m_implementationsTree.getSelectionPath();
398 if (path == null) {
399 return;
400 }
401
402 Object node = path.getLastPathComponent();
403 if (node instanceof ClassImplementationsTreeNode) {
404 ClassImplementationsTreeNode classNode = (ClassImplementationsTreeNode)node;
405 navigateTo(classNode.getClassEntry());
406 } else if (node instanceof MethodImplementationsTreeNode) {
407 MethodImplementationsTreeNode methodNode = (MethodImplementationsTreeNode)node;
408 navigateTo(methodNode.getMethodEntry());
409 }
410 }
411 }
412 });
413 JPanel implementationsPanel = new JPanel();
414 implementationsPanel.setLayout(new BorderLayout());
415 implementationsPanel.add(new JScrollPane(m_implementationsTree));
416
417 // init call panel
418 m_callsTree = new JTree();
419 m_callsTree.setModel(null);
420 m_callsTree.addMouseListener(new MouseAdapter() {
421 @SuppressWarnings("unchecked")
422 @Override
423 public void mouseClicked(MouseEvent event) {
424 if (event.getClickCount() == 2) {
425 // get the selected node
426 TreePath path = m_callsTree.getSelectionPath();
427 if (path == null) {
428 return;
429 }
430
431 Object node = path.getLastPathComponent();
432 if (node instanceof ReferenceTreeNode) {
433 ReferenceTreeNode<Entry,Entry> referenceNode = ((ReferenceTreeNode<Entry,Entry>)node);
434 if (referenceNode.getReference() != null) {
435 navigateTo(referenceNode.getReference());
436 } else {
437 navigateTo(referenceNode.getEntry());
438 }
439 }
440 }
441 }
442 });
443 m_tokens = new JList<Token>();
444 m_tokens.setCellRenderer(new TokenListCellRenderer(m_controller));
445 m_tokens.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
446 m_tokens.setLayoutOrientation(JList.VERTICAL);
447 m_tokens.addMouseListener(new MouseAdapter() {
448 @Override
449 public void mouseClicked(MouseEvent event) {
450 if (event.getClickCount() == 2) {
451 Token selected = m_tokens.getSelectedValue();
452 if (selected != null) {
453 showToken(selected);
454 }
455 }
456 }
457 });
458 m_tokens.setPreferredSize(new Dimension(0, 200));
459 m_tokens.setMinimumSize(new Dimension(0, 200));
460 JSplitPane callPanel = new JSplitPane(
461 JSplitPane.VERTICAL_SPLIT,
462 true,
463 new JScrollPane(m_callsTree),
464 new JScrollPane(m_tokens)
465 );
466 callPanel.setResizeWeight(1); // let the top side take all the slack
467 callPanel.resetToPreferredSizes();
468
469 // layout controls
470 JPanel centerPanel = new JPanel();
471 centerPanel.setLayout(new BorderLayout());
472 centerPanel.add(m_infoPanel, BorderLayout.NORTH);
473 centerPanel.add(sourceScroller, BorderLayout.CENTER);
474 m_tabs = new JTabbedPane();
475 m_tabs.setPreferredSize(new Dimension(250, 0));
476 m_tabs.addTab("Inheritance", inheritancePanel);
477 m_tabs.addTab("Implementations", implementationsPanel);
478 m_tabs.addTab("Call Graph", callPanel);
479 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, centerPanel, m_tabs);
480 splitRight.setResizeWeight(1); // let the left side take all the slack
481 splitRight.resetToPreferredSizes();
482 JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, m_classesPanel, splitRight);
483 splitCenter.setResizeWeight(0); // let the right side take all the slack
484 pane.add(splitCenter, BorderLayout.CENTER);
485
486 // init menus
487 JMenuBar menuBar = new JMenuBar();
488 m_frame.setJMenuBar(menuBar);
489 {
490 JMenu menu = new JMenu("File");
491 menuBar.add(menu);
492 {
493 JMenuItem item = new JMenuItem("Open Jar...");
494 menu.add(item);
495 item.addActionListener(new ActionListener() {
496 @Override
497 public void actionPerformed(ActionEvent event) {
498 if (m_jarFileChooser.showOpenDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
499 // load the jar in a separate thread
500 new Thread() {
501 @Override
502 public void run() {
503 try {
504 m_controller.openJar(new JarFile(m_jarFileChooser.getSelectedFile()));
505 } catch (IOException ex) {
506 throw new Error(ex);
507 }
508 }
509 }.start();
510 }
511 }
512 });
513 }
514 {
515 JMenuItem item = new JMenuItem("Close Jar");
516 menu.add(item);
517 item.addActionListener(new ActionListener() {
518 @Override
519 public void actionPerformed(ActionEvent event) {
520 m_controller.closeJar();
521 }
522 });
523 m_closeJarMenu = item;
524 }
525 menu.addSeparator();
526 {
527 JMenuItem item = new JMenuItem("Open Mappings...");
528 menu.add(item);
529 item.addActionListener(new ActionListener() {
530 @Override
531 public void actionPerformed(ActionEvent event) {
532 if (m_mappingsFileChooser.showOpenDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
533 try {
534 m_controller.openMappings(m_mappingsFileChooser.getSelectedFile());
535 } catch (IOException ex) {
536 throw new Error(ex);
537 } catch (MappingParseException ex) {
538 JOptionPane.showMessageDialog(m_frame, ex.getMessage());
539 }
540 }
541 }
542 });
543 m_openMappingsMenu = item;
544 }
545 {
546 JMenuItem item = new JMenuItem("Save Mappings");
547 menu.add(item);
548 item.addActionListener(new ActionListener() {
549 @Override
550 public void actionPerformed(ActionEvent event) {
551 try {
552 m_controller.saveMappings(m_mappingsFileChooser.getSelectedFile());
553 } catch (IOException ex) {
554 throw new Error(ex);
555 }
556 }
557 });
558 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK));
559 m_saveMappingsMenu = item;
560 }
561 {
562 JMenuItem item = new JMenuItem("Save Mappings As...");
563 menu.add(item);
564 item.addActionListener(new ActionListener() {
565 @Override
566 public void actionPerformed(ActionEvent event) {
567 if (m_mappingsFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
568 try {
569 m_controller.saveMappings(m_mappingsFileChooser.getSelectedFile());
570 m_saveMappingsMenu.setEnabled(true);
571 } catch (IOException ex) {
572 throw new Error(ex);
573 }
574 }
575 }
576 });
577 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK));
578 m_saveMappingsAsMenu = item;
579 }
580 {
581 JMenuItem item = new JMenuItem("Close Mappings");
582 menu.add(item);
583 item.addActionListener(new ActionListener() {
584 @Override
585 public void actionPerformed(ActionEvent event) {
586 m_controller.closeMappings();
587 }
588 });
589 m_closeMappingsMenu = item;
590 }
591 menu.addSeparator();
592 {
593 JMenuItem item = new JMenuItem("Export Source...");
594 menu.add(item);
595 item.addActionListener(new ActionListener() {
596 @Override
597 public void actionPerformed(ActionEvent event) {
598 if (m_exportSourceFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
599 m_controller.exportSource(m_exportSourceFileChooser.getSelectedFile());
600 }
601 }
602 });
603 m_exportSourceMenu = item;
604 }
605 {
606 JMenuItem item = new JMenuItem("Export Jar...");
607 menu.add(item);
608 item.addActionListener(new ActionListener() {
609 @Override
610 public void actionPerformed(ActionEvent event) {
611 if (m_exportJarFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
612 m_controller.exportJar(m_exportJarFileChooser.getSelectedFile());
613 }
614 }
615 });
616 m_exportJarMenu = item;
617 }
618 menu.addSeparator();
619 {
620 JMenuItem item = new JMenuItem("Exit");
621 menu.add(item);
622 item.addActionListener(new ActionListener() {
623 @Override
624 public void actionPerformed(ActionEvent event) {
625 close();
626 }
627 });
628 }
629 }
630 {
631 JMenu menu = new JMenu("Help");
632 menuBar.add(menu);
633 {
634 JMenuItem item = new JMenuItem("About");
635 menu.add(item);
636 item.addActionListener(new ActionListener() {
637 @Override
638 public void actionPerformed(ActionEvent event) {
639 AboutDialog.show(m_frame);
640 }
641 });
642 }
643 }
644
645 // init state
646 onCloseJar();
647
648 m_frame.addWindowListener(new WindowAdapter() {
649 @Override
650 public void windowClosing(WindowEvent event) {
651 close();
652 }
653 });
654
655 // show the frame
656 pane.doLayout();
657 m_frame.setSize(1024, 576);
658 m_frame.setMinimumSize(new Dimension(640, 480));
659 m_frame.setVisible(true);
660 m_frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
661 }
662
663 public JFrame getFrame() {
664 return m_frame;
665 }
666
667 public GuiController getController() {
668 return m_controller;
669 }
670
671 public void onStartOpenJar() {
672 m_classesPanel.removeAll();
673 JPanel panel = new JPanel();
674 panel.setLayout(new FlowLayout());
675 panel.add(new JLabel("Loading..."));
676 m_classesPanel.add(panel);
677 redraw();
678 }
679
680 public void onFinishOpenJar(String jarName) {
681 // update gui
682 m_frame.setTitle(Constants.Name + " - " + jarName);
683 m_classesPanel.removeAll();
684 m_classesPanel.add(m_splitClasses);
685 setSource(null);
686
687 // update menu
688 m_closeJarMenu.setEnabled(true);
689 m_openMappingsMenu.setEnabled(true);
690 m_saveMappingsMenu.setEnabled(false);
691 m_saveMappingsAsMenu.setEnabled(true);
692 m_closeMappingsMenu.setEnabled(true);
693 m_exportSourceMenu.setEnabled(true);
694 m_exportJarMenu.setEnabled(true);
695
696 redraw();
697 }
698
699 public void onCloseJar() {
700 // update gui
701 m_frame.setTitle(Constants.Name);
702 setObfClasses(null);
703 setDeobfClasses(null);
704 setSource(null);
705 m_classesPanel.removeAll();
706
707 // update menu
708 m_closeJarMenu.setEnabled(false);
709 m_openMappingsMenu.setEnabled(false);
710 m_saveMappingsMenu.setEnabled(false);
711 m_saveMappingsAsMenu.setEnabled(false);
712 m_closeMappingsMenu.setEnabled(false);
713 m_exportSourceMenu.setEnabled(false);
714 m_exportJarMenu.setEnabled(false);
715
716 redraw();
717 }
718
719 public void setObfClasses(Collection<ClassEntry> obfClasses) {
720 m_obfClasses.setClasses(obfClasses);
721 }
722
723 public void setDeobfClasses(Collection<ClassEntry> deobfClasses) {
724 m_deobfClasses.setClasses(deobfClasses);
725 }
726
727 public void setMappingsFile(File file) {
728 m_mappingsFileChooser.setSelectedFile(file);
729 m_saveMappingsMenu.setEnabled(file != null);
730 }
731
732 public void setSource(String source) {
733 m_editor.getHighlighter().removeAllHighlights();
734 m_editor.setText(source);
735 }
736
737 public void showToken(final Token token) {
738 if (token == null) {
739 throw new IllegalArgumentException("Token cannot be null!");
740 }
741
742 // set the caret position to the token
743 m_editor.setCaretPosition(token.start);
744 m_editor.grabFocus();
745
746 try {
747 // make sure the token is visible in the scroll window
748 Rectangle start = m_editor.modelToView(token.start);
749 Rectangle end = m_editor.modelToView(token.end);
750 final Rectangle show = start.union(end);
751 show.grow(start.width * 10, start.height * 6);
752 SwingUtilities.invokeLater(new Runnable() {
753 @Override
754 public void run() {
755 m_editor.scrollRectToVisible(show);
756 }
757 });
758 } catch (BadLocationException ex) {
759 throw new Error(ex);
760 }
761
762 // highlight the token momentarily
763 final Timer timer = new Timer(200, new ActionListener() {
764 private int m_counter = 0;
765 private Object m_highlight = null;
766
767 @Override
768 public void actionPerformed(ActionEvent event) {
769 if (m_counter % 2 == 0) {
770 try {
771 m_highlight = m_editor.getHighlighter().addHighlight(token.start, token.end, m_selectionHighlightPainter);
772 } catch (BadLocationException ex) {
773 // don't care
774 }
775 } else if (m_highlight != null) {
776 m_editor.getHighlighter().removeHighlight(m_highlight);
777 }
778
779 if (m_counter++ > 6) {
780 Timer timer = (Timer)event.getSource();
781 timer.stop();
782 }
783 }
784 });
785 timer.start();
786
787 redraw();
788 }
789
790 public void showTokens(Collection<Token> tokens) {
791 Vector<Token> sortedTokens = new Vector<Token>(tokens);
792 Collections.sort(sortedTokens);
793 if (sortedTokens.size() > 1) {
794 // sort the tokens and update the tokens panel
795 m_tokens.setListData(sortedTokens);
796 m_tokens.setSelectedIndex(0);
797 } else {
798 m_tokens.setListData(new Vector<Token>());
799 }
800
801 // show the first token
802 showToken(sortedTokens.get(0));
803 }
804
805 public void setHighlightedTokens(Iterable<Token> obfuscatedTokens, Iterable<Token> deobfuscatedTokens, Iterable<Token> otherTokens) {
806
807 // remove any old highlighters
808 m_editor.getHighlighter().removeAllHighlights();
809
810 // color things based on the index
811 if (obfuscatedTokens != null) {
812 setHighlightedTokens(obfuscatedTokens, m_obfuscatedHighlightPainter);
813 }
814 if (deobfuscatedTokens != null) {
815 setHighlightedTokens(deobfuscatedTokens, m_deobfuscatedHighlightPainter);
816 }
817 if (otherTokens != null) {
818 setHighlightedTokens(otherTokens, m_otherHighlightPainter);
819 }
820
821 redraw();
822 }
823
824 private void setHighlightedTokens(Iterable<Token> tokens, Highlighter.HighlightPainter painter) {
825 for (Token token : tokens) {
826 try {
827 m_editor.getHighlighter().addHighlight(token.start, token.end, painter);
828 } catch (BadLocationException ex) {
829 throw new IllegalArgumentException(ex);
830 }
831 }
832 }
833
834 private void clearReference() {
835 m_infoPanel.removeAll();
836 JLabel label = new JLabel("No identifier selected");
837 GuiTricks.unboldLabel(label);
838 label.setHorizontalAlignment(JLabel.CENTER);
839 m_infoPanel.add(label);
840
841 redraw();
842 }
843
844 private void showReference(EntryReference<Entry,Entry> reference) {
845 if (reference == null) {
846 clearReference();
847 return;
848 }
849
850 m_reference = reference;
851
852 m_infoPanel.removeAll();
853 if (reference.entry instanceof ClassEntry) {
854 showClassEntry((ClassEntry)m_reference.entry);
855 } else if (m_reference.entry instanceof FieldEntry) {
856 showFieldEntry((FieldEntry)m_reference.entry);
857 } else if (m_reference.entry instanceof MethodEntry) {
858 showMethodEntry((MethodEntry)m_reference.entry);
859 } else if (m_reference.entry instanceof ConstructorEntry) {
860 showConstructorEntry((ConstructorEntry)m_reference.entry);
861 } else if (m_reference.entry instanceof ArgumentEntry) {
862 showArgumentEntry((ArgumentEntry)m_reference.entry);
863 } else {
864 throw new Error("Unknown entry type: " + m_reference.entry.getClass().getName());
865 }
866
867 redraw();
868 }
869
870 private void showClassEntry(ClassEntry entry) {
871 addNameValue(m_infoPanel, "Class", entry.getName());
872 }
873
874 private void showFieldEntry(FieldEntry entry) {
875 addNameValue(m_infoPanel, "Field", entry.getName());
876 addNameValue(m_infoPanel, "Class", entry.getClassEntry().getName());
877 }
878
879 private void showMethodEntry(MethodEntry entry) {
880 addNameValue(m_infoPanel, "Method", entry.getName());
881 addNameValue(m_infoPanel, "Class", entry.getClassEntry().getName());
882 addNameValue(m_infoPanel, "Signature", entry.getSignature().toString());
883 }
884
885 private void showConstructorEntry(ConstructorEntry entry) {
886 addNameValue(m_infoPanel, "Constructor", entry.getClassEntry().getName());
887 addNameValue(m_infoPanel, "Signature", entry.getSignature().toString());
888 }
889
890 private void showArgumentEntry(ArgumentEntry entry) {
891 addNameValue(m_infoPanel, "Argument", entry.getName());
892 addNameValue(m_infoPanel, "Class", entry.getClassEntry().getName());
893 addNameValue(m_infoPanel, "Method", entry.getBehaviorEntry().getName());
894 addNameValue(m_infoPanel, "Index", Integer.toString(entry.getIndex()));
895 }
896
897 private void addNameValue(JPanel container, String name, String value) {
898 JPanel panel = new JPanel();
899 panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0));
900 container.add(panel);
901
902 JLabel label = new JLabel(name + ":", JLabel.RIGHT);
903 label.setPreferredSize(new Dimension(100, label.getPreferredSize().height));
904 panel.add(label);
905
906 panel.add(GuiTricks.unboldLabel(new JLabel(value, JLabel.LEFT)));
907 }
908
909 private void onCaretMove(int pos) {
910
911 Token token = m_controller.getToken(pos);
912 boolean isToken = token != null;
913
914 m_reference = m_controller.getDeobfReference(token);
915 boolean isClassEntry = isToken && m_reference.entry instanceof ClassEntry;
916 boolean isFieldEntry = isToken && m_reference.entry instanceof FieldEntry;
917 boolean isMethodEntry = isToken && m_reference.entry instanceof MethodEntry;
918 boolean isConstructorEntry = isToken && m_reference.entry instanceof ConstructorEntry;
919 boolean isInJar = isToken && m_controller.entryIsInJar(m_reference.entry);
920 boolean isRenameable = isToken && m_controller.referenceIsRenameable(m_reference);
921
922 if (isToken) {
923 showReference(m_reference);
924 } else {
925 clearReference();
926 }
927
928 m_renameMenu.setEnabled(isRenameable && isToken);
929 m_showInheritanceMenu.setEnabled(isClassEntry || isMethodEntry || isConstructorEntry);
930 m_showImplementationsMenu.setEnabled(isClassEntry || isMethodEntry);
931 m_showCallsMenu.setEnabled(isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry);
932 m_openEntryMenu.setEnabled(isInJar && (isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry));
933 m_openPreviousMenu.setEnabled(m_controller.hasPreviousLocation());
934 m_toggleMappingMenu.setEnabled(isRenameable && isToken);
935
936 if (isToken && m_controller.entryHasDeobfuscatedName(m_reference.entry)) {
937 m_toggleMappingMenu.setText("Reset to obfuscated");
938 } else {
939 m_toggleMappingMenu.setText("Mark as deobfuscated");
940 }
941 }
942
943 private void navigateTo(Entry entry) {
944 if (!m_controller.entryIsInJar(entry)) {
945 // entry is not in the jar. Ignore it
946 return;
947 }
948 if (m_reference != null) {
949 m_controller.savePreviousReference(m_reference);
950 }
951 m_controller.openDeclaration(entry);
952 }
953
954 private void navigateTo(EntryReference<Entry,Entry> reference) {
955 if (!m_controller.entryIsInJar(reference.getLocationClassEntry())) {
956 // reference is not in the jar. Ignore it
957 return;
958 }
959 if (m_reference != null) {
960 m_controller.savePreviousReference(m_reference);
961 }
962 m_controller.openReference(reference);
963 }
964
965 private void startRename() {
966
967 // init the text box
968 final JTextField text = new JTextField();
969 text.setText(m_reference.getNamableName());
970 text.setPreferredSize(new Dimension(360, text.getPreferredSize().height));
971 text.addKeyListener(new KeyAdapter() {
972 @Override
973 public void keyPressed(KeyEvent event) {
974 switch (event.getKeyCode()) {
975 case KeyEvent.VK_ENTER:
976 finishRename(text, true);
977 break;
978
979 case KeyEvent.VK_ESCAPE:
980 finishRename(text, false);
981 break;
982 }
983 }
984 });
985
986 // find the label with the name and replace it with the text box
987 JPanel panel = (JPanel)m_infoPanel.getComponent(0);
988 panel.remove(panel.getComponentCount() - 1);
989 panel.add(text);
990 text.grabFocus();
991 text.selectAll();
992
993 redraw();
994 }
995
996 private void finishRename(JTextField text, boolean saveName) {
997 String newName = text.getText();
998 if (saveName && newName != null && newName.length() > 0) {
999 try {
1000 m_controller.rename(m_reference, newName);
1001 } catch (IllegalNameException ex) {
1002 text.setBorder(BorderFactory.createLineBorder(Color.red, 1));
1003 text.setToolTipText(ex.getReason());
1004 GuiTricks.showToolTipNow(text);
1005 }
1006 return;
1007 }
1008
1009 // abort the rename
1010 JPanel panel = (JPanel)m_infoPanel.getComponent(0);
1011 panel.remove(panel.getComponentCount() - 1);
1012 panel.add(GuiTricks.unboldLabel(new JLabel(m_reference.getNamableName(), JLabel.LEFT)));
1013
1014 m_editor.grabFocus();
1015
1016 redraw();
1017 }
1018
1019 private void showInheritance() {
1020
1021 if (m_reference == null) {
1022 return;
1023 }
1024
1025 m_inheritanceTree.setModel(null);
1026
1027 if (m_reference.entry instanceof ClassEntry) {
1028 // get the class inheritance
1029 ClassInheritanceTreeNode classNode = m_controller.getClassInheritance((ClassEntry)m_reference.entry);
1030
1031 // show the tree at the root
1032 TreePath path = getPathToRoot(classNode);
1033 m_inheritanceTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0)));
1034 m_inheritanceTree.expandPath(path);
1035 m_inheritanceTree.setSelectionRow(m_inheritanceTree.getRowForPath(path));
1036 } else if (m_reference.entry instanceof MethodEntry) {
1037 // get the method inheritance
1038 MethodInheritanceTreeNode classNode = m_controller.getMethodInheritance((MethodEntry)m_reference.entry);
1039
1040 // show the tree at the root
1041 TreePath path = getPathToRoot(classNode);
1042 m_inheritanceTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0)));
1043 m_inheritanceTree.expandPath(path);
1044 m_inheritanceTree.setSelectionRow(m_inheritanceTree.getRowForPath(path));
1045 }
1046
1047 m_tabs.setSelectedIndex(0);
1048 redraw();
1049 }
1050
1051 private void showImplementations() {
1052
1053 if (m_reference == null) {
1054 return;
1055 }
1056
1057 m_implementationsTree.setModel(null);
1058
1059 if (m_reference.entry instanceof ClassEntry) {
1060 // get the class implementations
1061 ClassImplementationsTreeNode node = m_controller.getClassImplementations((ClassEntry)m_reference.entry);
1062 if (node != null) {
1063 // show the tree at the root
1064 TreePath path = getPathToRoot(node);
1065 m_implementationsTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0)));
1066 m_implementationsTree.expandPath(path);
1067 m_implementationsTree.setSelectionRow(m_implementationsTree.getRowForPath(path));
1068 }
1069 } else if (m_reference.entry instanceof MethodEntry) {
1070 // get the method implementations
1071 MethodImplementationsTreeNode node = m_controller.getMethodImplementations((MethodEntry)m_reference.entry);
1072 if (node != null) {
1073 // show the tree at the root
1074 TreePath path = getPathToRoot(node);
1075 m_implementationsTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0)));
1076 m_implementationsTree.expandPath(path);
1077 m_implementationsTree.setSelectionRow(m_implementationsTree.getRowForPath(path));
1078 }
1079 }
1080
1081 m_tabs.setSelectedIndex(1);
1082 redraw();
1083 }
1084
1085 private void showCalls() {
1086
1087 if (m_reference == null) {
1088 return;
1089 }
1090
1091 if (m_reference.entry instanceof ClassEntry) {
1092 // look for calls to the default constructor
1093 // TODO: get a list of all the constructors and find calls to all of them
1094 BehaviorReferenceTreeNode node = m_controller.getMethodReferences(new ConstructorEntry((ClassEntry)m_reference.entry, new Signature("()V")));
1095 m_callsTree.setModel(new DefaultTreeModel(node));
1096 } else if (m_reference.entry instanceof FieldEntry) {
1097 FieldReferenceTreeNode node = m_controller.getFieldReferences((FieldEntry)m_reference.entry);
1098 m_callsTree.setModel(new DefaultTreeModel(node));
1099 } else if (m_reference.entry instanceof MethodEntry) {
1100 BehaviorReferenceTreeNode node = m_controller.getMethodReferences((MethodEntry)m_reference.entry);
1101 m_callsTree.setModel(new DefaultTreeModel(node));
1102 } else if (m_reference.entry instanceof ConstructorEntry) {
1103 BehaviorReferenceTreeNode node = m_controller.getMethodReferences((ConstructorEntry)m_reference.entry);
1104 m_callsTree.setModel(new DefaultTreeModel(node));
1105 }
1106
1107 m_tabs.setSelectedIndex(2);
1108 redraw();
1109 }
1110
1111 private void toggleMapping() {
1112 if (m_controller.entryHasDeobfuscatedName(m_reference.entry)) {
1113 m_controller.removeMapping(m_reference);
1114 } else {
1115 m_controller.markAsDeobfuscated(m_reference);
1116 }
1117 }
1118
1119 private TreePath getPathToRoot(TreeNode node) {
1120 List<TreeNode> nodes = Lists.newArrayList();
1121 TreeNode n = node;
1122 do {
1123 nodes.add(n);
1124 n = n.getParent();
1125 } while (n != null);
1126 Collections.reverse(nodes);
1127 return new TreePath(nodes.toArray());
1128 }
1129
1130 private void close() {
1131 if (!m_controller.isDirty()) {
1132 // everything is saved, we can exit safely
1133 m_frame.dispose();
1134 } else {
1135 // ask to save before closing
1136 String[] options = { "Save and exit", "Discard changes", "Cancel" };
1137 int response = JOptionPane.showOptionDialog(m_frame, "Your mappings have not been saved yet. Do you want to save?", "Save your changes?", JOptionPane.YES_NO_CANCEL_OPTION,
1138 JOptionPane.QUESTION_MESSAGE, null, options, options[2]);
1139 switch (response) {
1140 case JOptionPane.YES_OPTION: // save and exit
1141 if (m_mappingsFileChooser.getSelectedFile() != null || m_mappingsFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
1142 try {
1143 m_controller.saveMappings(m_mappingsFileChooser.getSelectedFile());
1144 m_frame.dispose();
1145 } catch (IOException ex) {
1146 throw new Error(ex);
1147 }
1148 }
1149 break;
1150
1151 case JOptionPane.NO_OPTION:
1152 // don't save, exit
1153 m_frame.dispose();
1154 break;
1155
1156 // cancel means do nothing
1157 }
1158 }
1159 }
1160
1161 private void redraw() {
1162 m_frame.validate();
1163 m_frame.repaint();
1164 }
1165}
diff --git a/src/cuchaz/enigma/gui/GuiController.java b/src/cuchaz/enigma/gui/GuiController.java
new file mode 100644
index 00000000..61fea9c0
--- /dev/null
+++ b/src/cuchaz/enigma/gui/GuiController.java
@@ -0,0 +1,355 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.io.File;
14import java.io.FileReader;
15import java.io.FileWriter;
16import java.io.IOException;
17import java.util.Collection;
18import java.util.Deque;
19import java.util.List;
20import java.util.jar.JarFile;
21
22import com.google.common.collect.Lists;
23import com.google.common.collect.Queues;
24import com.strobel.decompiler.languages.java.ast.CompilationUnit;
25
26import cuchaz.enigma.Deobfuscator;
27import cuchaz.enigma.Deobfuscator.ProgressListener;
28import cuchaz.enigma.analysis.BehaviorReferenceTreeNode;
29import cuchaz.enigma.analysis.ClassImplementationsTreeNode;
30import cuchaz.enigma.analysis.ClassInheritanceTreeNode;
31import cuchaz.enigma.analysis.EntryReference;
32import cuchaz.enigma.analysis.FieldReferenceTreeNode;
33import cuchaz.enigma.analysis.MethodImplementationsTreeNode;
34import cuchaz.enigma.analysis.MethodInheritanceTreeNode;
35import cuchaz.enigma.analysis.SourceIndex;
36import cuchaz.enigma.analysis.Token;
37import cuchaz.enigma.gui.ProgressDialog.ProgressRunnable;
38import cuchaz.enigma.mapping.BehaviorEntry;
39import cuchaz.enigma.mapping.ClassEntry;
40import cuchaz.enigma.mapping.Entry;
41import cuchaz.enigma.mapping.FieldEntry;
42import cuchaz.enigma.mapping.MappingParseException;
43import cuchaz.enigma.mapping.MappingsReader;
44import cuchaz.enigma.mapping.MappingsWriter;
45import cuchaz.enigma.mapping.MethodEntry;
46import cuchaz.enigma.mapping.TranslationDirection;
47
48public class GuiController {
49
50 private Deobfuscator m_deobfuscator;
51 private Gui m_gui;
52 private SourceIndex m_index;
53 private ClassEntry m_currentObfClass;
54 private boolean m_isDirty;
55 private Deque<EntryReference<Entry,Entry>> m_referenceStack;
56
57 public GuiController(Gui gui) {
58 m_gui = gui;
59 m_deobfuscator = null;
60 m_index = null;
61 m_currentObfClass = null;
62 m_isDirty = false;
63 m_referenceStack = Queues.newArrayDeque();
64 }
65
66 public boolean isDirty() {
67 return m_isDirty;
68 }
69
70 public void openJar(final JarFile jar) throws IOException {
71 m_gui.onStartOpenJar();
72 m_deobfuscator = new Deobfuscator(jar);
73 m_gui.onFinishOpenJar(m_deobfuscator.getJarName());
74 refreshClasses();
75 }
76
77 public void closeJar() {
78 m_deobfuscator = null;
79 m_gui.onCloseJar();
80 }
81
82 public void openMappings(File file) throws IOException, MappingParseException {
83 FileReader in = new FileReader(file);
84 m_deobfuscator.setMappings(new MappingsReader().read(in));
85 in.close();
86 m_isDirty = false;
87 m_gui.setMappingsFile(file);
88 refreshClasses();
89 refreshCurrentClass();
90 }
91
92 public void saveMappings(File file) throws IOException {
93 FileWriter out = new FileWriter(file);
94 new MappingsWriter().write(out, m_deobfuscator.getMappings());
95 out.close();
96 m_isDirty = false;
97 }
98
99 public void closeMappings() {
100 m_deobfuscator.setMappings(null);
101 m_gui.setMappingsFile(null);
102 refreshClasses();
103 refreshCurrentClass();
104 }
105
106 public void exportSource(final File dirOut) {
107 ProgressDialog.runInThread(m_gui.getFrame(), new ProgressRunnable() {
108 @Override
109 public void run(ProgressListener progress) throws Exception {
110 m_deobfuscator.writeSources(dirOut, progress);
111 }
112 });
113 }
114
115 public void exportJar(final File fileOut) {
116 ProgressDialog.runInThread(m_gui.getFrame(), new ProgressRunnable() {
117 @Override
118 public void run(ProgressListener progress) {
119 m_deobfuscator.writeJar(fileOut, progress);
120 }
121 });
122 }
123
124 public Token getToken(int pos) {
125 if (m_index == null) {
126 return null;
127 }
128 return m_index.getReferenceToken(pos);
129 }
130
131 public EntryReference<Entry,Entry> getDeobfReference(Token token) {
132 if (m_index == null) {
133 return null;
134 }
135 return m_index.getDeobfReference(token);
136 }
137
138 public ReadableToken getReadableToken(Token token) {
139 if (m_index == null) {
140 return null;
141 }
142 return new ReadableToken(
143 m_index.getLineNumber(token.start),
144 m_index.getColumnNumber(token.start),
145 m_index.getColumnNumber(token.end)
146 );
147 }
148
149 public boolean entryHasDeobfuscatedName(Entry deobfEntry) {
150 return m_deobfuscator.hasDeobfuscatedName(m_deobfuscator.obfuscateEntry(deobfEntry));
151 }
152
153 public boolean entryIsInJar(Entry deobfEntry) {
154 return m_deobfuscator.isObfuscatedIdentifier(m_deobfuscator.obfuscateEntry(deobfEntry));
155 }
156
157 public boolean referenceIsRenameable(EntryReference<Entry,Entry> deobfReference) {
158 return m_deobfuscator.isRenameable(m_deobfuscator.obfuscateReference(deobfReference));
159 }
160
161 public ClassInheritanceTreeNode getClassInheritance(ClassEntry deobfClassEntry) {
162 ClassEntry obfClassEntry = m_deobfuscator.obfuscateEntry(deobfClassEntry);
163 ClassInheritanceTreeNode rootNode = m_deobfuscator.getJarIndex().getClassInheritance(
164 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
165 obfClassEntry
166 );
167 return ClassInheritanceTreeNode.findNode(rootNode, obfClassEntry);
168 }
169
170 public ClassImplementationsTreeNode getClassImplementations(ClassEntry deobfClassEntry) {
171 ClassEntry obfClassEntry = m_deobfuscator.obfuscateEntry(deobfClassEntry);
172 return m_deobfuscator.getJarIndex().getClassImplementations(
173 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
174 obfClassEntry
175 );
176 }
177
178 public MethodInheritanceTreeNode getMethodInheritance(MethodEntry deobfMethodEntry) {
179 MethodEntry obfMethodEntry = m_deobfuscator.obfuscateEntry(deobfMethodEntry);
180 MethodInheritanceTreeNode rootNode = m_deobfuscator.getJarIndex().getMethodInheritance(
181 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
182 obfMethodEntry
183 );
184 return MethodInheritanceTreeNode.findNode(rootNode, obfMethodEntry);
185 }
186
187 public MethodImplementationsTreeNode getMethodImplementations(MethodEntry deobfMethodEntry) {
188 MethodEntry obfMethodEntry = m_deobfuscator.obfuscateEntry(deobfMethodEntry);
189 MethodImplementationsTreeNode rootNode = m_deobfuscator.getJarIndex().getMethodImplementations(
190 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
191 obfMethodEntry
192 );
193 if (rootNode == null) {
194 return null;
195 }
196 return MethodImplementationsTreeNode.findNode(rootNode, obfMethodEntry);
197 }
198
199 public FieldReferenceTreeNode getFieldReferences(FieldEntry deobfFieldEntry) {
200 FieldEntry obfFieldEntry = m_deobfuscator.obfuscateEntry(deobfFieldEntry);
201 FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(
202 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
203 obfFieldEntry
204 );
205 rootNode.load(m_deobfuscator.getJarIndex(), true);
206 return rootNode;
207 }
208
209 public BehaviorReferenceTreeNode getMethodReferences(BehaviorEntry deobfBehaviorEntry) {
210 BehaviorEntry obfBehaviorEntry = m_deobfuscator.obfuscateEntry(deobfBehaviorEntry);
211 BehaviorReferenceTreeNode rootNode = new BehaviorReferenceTreeNode(
212 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
213 obfBehaviorEntry
214 );
215 rootNode.load(m_deobfuscator.getJarIndex(), true);
216 return rootNode;
217 }
218
219 public void rename(EntryReference<Entry,Entry> deobfReference, String newName) {
220 EntryReference<Entry,Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference);
221 m_deobfuscator.rename(obfReference.getNameableEntry(), newName);
222 m_isDirty = true;
223 refreshClasses();
224 refreshCurrentClass(obfReference);
225 }
226
227 public void removeMapping(EntryReference<Entry,Entry> deobfReference) {
228 EntryReference<Entry,Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference);
229 m_deobfuscator.removeMapping(obfReference.getNameableEntry());
230 m_isDirty = true;
231 refreshClasses();
232 refreshCurrentClass(obfReference);
233 }
234
235 public void markAsDeobfuscated(EntryReference<Entry,Entry> deobfReference) {
236 EntryReference<Entry,Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference);
237 m_deobfuscator.markAsDeobfuscated(obfReference.getNameableEntry());
238 m_isDirty = true;
239 refreshClasses();
240 refreshCurrentClass(obfReference);
241 }
242
243 public void openDeclaration(Entry deobfEntry) {
244 if (deobfEntry == null) {
245 throw new IllegalArgumentException("Entry cannot be null!");
246 }
247 openReference(new EntryReference<Entry,Entry>(deobfEntry, deobfEntry.getName()));
248 }
249
250 public void openReference(EntryReference<Entry,Entry> deobfReference) {
251 if (deobfReference == null) {
252 throw new IllegalArgumentException("Reference cannot be null!");
253 }
254
255 // get the reference target class
256 EntryReference<Entry,Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference);
257 ClassEntry obfClassEntry = obfReference.getLocationClassEntry().getOuterClassEntry();
258 if (!m_deobfuscator.isObfuscatedIdentifier(obfClassEntry)) {
259 throw new IllegalArgumentException("Obfuscated class " + obfClassEntry + " was not found in the jar!");
260 }
261 if (m_currentObfClass == null || !m_currentObfClass.equals(obfClassEntry)) {
262 // deobfuscate the class, then navigate to the reference
263 m_currentObfClass = obfClassEntry;
264 deobfuscate(m_currentObfClass, obfReference);
265 } else {
266 showReference(obfReference);
267 }
268 }
269
270 private void showReference(EntryReference<Entry,Entry> obfReference) {
271 EntryReference<Entry,Entry> deobfReference = m_deobfuscator.deobfuscateReference(obfReference);
272 Collection<Token> tokens = m_index.getReferenceTokens(deobfReference);
273 if (tokens.isEmpty()) {
274 // DEBUG
275 System.err.println(String.format("WARNING: no tokens found for %s in %s", deobfReference, m_currentObfClass));
276 } else {
277 m_gui.showTokens(tokens);
278 }
279 }
280
281 public void savePreviousReference(EntryReference<Entry,Entry> deobfReference) {
282 m_referenceStack.push(m_deobfuscator.obfuscateReference(deobfReference));
283 }
284
285 public void openPreviousReference() {
286 if (hasPreviousLocation()) {
287 openReference(m_deobfuscator.deobfuscateReference(m_referenceStack.pop()));
288 }
289 }
290
291 public boolean hasPreviousLocation() {
292 return !m_referenceStack.isEmpty();
293 }
294
295 private void refreshClasses() {
296 List<ClassEntry> obfClasses = Lists.newArrayList();
297 List<ClassEntry> deobfClasses = Lists.newArrayList();
298 m_deobfuscator.getSeparatedClasses(obfClasses, deobfClasses);
299 m_gui.setObfClasses(obfClasses);
300 m_gui.setDeobfClasses(deobfClasses);
301 }
302
303 private void refreshCurrentClass() {
304 refreshCurrentClass(null);
305 }
306
307 private void refreshCurrentClass(EntryReference<Entry,Entry> obfReference) {
308 if (m_currentObfClass != null) {
309 deobfuscate(m_currentObfClass, obfReference);
310 }
311 }
312
313 private void deobfuscate(final ClassEntry classEntry, final EntryReference<Entry,Entry> obfReference) {
314
315 m_gui.setSource("(deobfuscating...)");
316
317 // run the deobfuscator in a separate thread so we don't block the GUI event queue
318 new Thread() {
319 @Override
320 public void run() {
321 // decompile,deobfuscate the bytecode
322 CompilationUnit sourceTree = m_deobfuscator.getSourceTree(classEntry.getClassName());
323 if (sourceTree == null) {
324 // decompilation of this class is not supported
325 m_gui.setSource("Unable to find class: " + classEntry);
326 return;
327 }
328 String source = m_deobfuscator.getSource(sourceTree);
329 m_index = m_deobfuscator.getSourceIndex(sourceTree, source);
330 m_gui.setSource(m_index.getSource());
331 if (obfReference != null) {
332 showReference(obfReference);
333 }
334
335 // set the highlighted tokens
336 List<Token> obfuscatedTokens = Lists.newArrayList();
337 List<Token> deobfuscatedTokens = Lists.newArrayList();
338 List<Token> otherTokens = Lists.newArrayList();
339 for (Token token : m_index.referenceTokens()) {
340 EntryReference<Entry,Entry> reference = m_index.getDeobfReference(token);
341 if (referenceIsRenameable(reference)) {
342 if (entryHasDeobfuscatedName(reference.getNameableEntry())) {
343 deobfuscatedTokens.add(token);
344 } else {
345 obfuscatedTokens.add(token);
346 }
347 } else {
348 otherTokens.add(token);
349 }
350 }
351 m_gui.setHighlightedTokens(obfuscatedTokens, deobfuscatedTokens, otherTokens);
352 }
353 }.start();
354 }
355}
diff --git a/src/cuchaz/enigma/gui/GuiTricks.java b/src/cuchaz/enigma/gui/GuiTricks.java
new file mode 100644
index 00000000..df9e2215
--- /dev/null
+++ b/src/cuchaz/enigma/gui/GuiTricks.java
@@ -0,0 +1,36 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Font;
14import java.awt.event.MouseEvent;
15
16import javax.swing.JComponent;
17import javax.swing.JLabel;
18import javax.swing.ToolTipManager;
19
20public class GuiTricks {
21
22 public static JLabel unboldLabel(JLabel label) {
23 Font font = label.getFont();
24 label.setFont(font.deriveFont(font.getStyle() & ~Font.BOLD));
25 return label;
26 }
27
28 public static void showToolTipNow(JComponent component) {
29 // HACKHACK: trick the tooltip manager into showing the tooltip right now
30 ToolTipManager manager = ToolTipManager.sharedInstance();
31 int oldDelay = manager.getInitialDelay();
32 manager.setInitialDelay(0);
33 manager.mouseMoved(new MouseEvent(component, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, 0, 0, 0, false));
34 manager.setInitialDelay(oldDelay);
35 }
36}
diff --git a/src/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java b/src/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java
new file mode 100644
index 00000000..177835f4
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java
@@ -0,0 +1,21 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Color;
14
15public class ObfuscatedHighlightPainter extends BoxHighlightPainter {
16
17 public ObfuscatedHighlightPainter() {
18 // red ish
19 super(new Color(255, 220, 220), new Color(160, 80, 80));
20 }
21}
diff --git a/src/cuchaz/enigma/gui/OtherHighlightPainter.java b/src/cuchaz/enigma/gui/OtherHighlightPainter.java
new file mode 100644
index 00000000..4e9c8709
--- /dev/null
+++ b/src/cuchaz/enigma/gui/OtherHighlightPainter.java
@@ -0,0 +1,21 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Color;
14
15public class OtherHighlightPainter extends BoxHighlightPainter {
16
17 public OtherHighlightPainter() {
18 // grey
19 super(null, new Color(180, 180, 180));
20 }
21}
diff --git a/src/cuchaz/enigma/gui/ProgressDialog.java b/src/cuchaz/enigma/gui/ProgressDialog.java
new file mode 100644
index 00000000..b864fdbf
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ProgressDialog.java
@@ -0,0 +1,105 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.BorderLayout;
14import java.awt.Container;
15import java.awt.Dimension;
16import java.awt.FlowLayout;
17
18import javax.swing.BorderFactory;
19import javax.swing.JFrame;
20import javax.swing.JLabel;
21import javax.swing.JPanel;
22import javax.swing.JProgressBar;
23import javax.swing.WindowConstants;
24
25import cuchaz.enigma.Constants;
26import cuchaz.enigma.Deobfuscator.ProgressListener;
27
28public class ProgressDialog implements ProgressListener, AutoCloseable {
29
30 private JFrame m_frame;
31 private JLabel m_title;
32 private JLabel m_text;
33 private JProgressBar m_progress;
34
35 public ProgressDialog(JFrame parent) {
36
37 // init frame
38 m_frame = new JFrame(Constants.Name + " - Operation in progress");
39 final Container pane = m_frame.getContentPane();
40 FlowLayout layout = new FlowLayout();
41 layout.setAlignment(FlowLayout.LEFT);
42 pane.setLayout(layout);
43
44 m_title = new JLabel();
45 pane.add(m_title);
46
47 // set up the progress bar
48 JPanel panel = new JPanel();
49 pane.add(panel);
50 panel.setLayout(new BorderLayout());
51 m_text = GuiTricks.unboldLabel(new JLabel());
52 m_progress = new JProgressBar();
53 m_text.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0));
54 panel.add(m_text, BorderLayout.NORTH);
55 panel.add(m_progress, BorderLayout.CENTER);
56 panel.setPreferredSize(new Dimension(360, 50));
57
58 // show the frame
59 pane.doLayout();
60 m_frame.setSize(400, 120);
61 m_frame.setResizable(false);
62 m_frame.setLocationRelativeTo(parent);
63 m_frame.setVisible(true);
64 m_frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
65 }
66
67 public void close() {
68 m_frame.dispose();
69 }
70
71 @Override
72 public void init(int totalWork, String title) {
73 m_title.setText(title);
74 m_progress.setMinimum(0);
75 m_progress.setMaximum(totalWork);
76 m_progress.setValue(0);
77 }
78
79 @Override
80 public void onProgress(int numDone, String message) {
81 m_text.setText(message);
82 m_progress.setValue(numDone);
83
84 // update the frame
85 m_frame.validate();
86 m_frame.repaint();
87 }
88
89 public static interface ProgressRunnable {
90 void run(ProgressListener listener) throws Exception;
91 }
92
93 public static void runInThread(final JFrame parent, final ProgressRunnable runnable) {
94 new Thread() {
95 @Override
96 public void run() {
97 try (ProgressDialog progress = new ProgressDialog(parent)) {
98 runnable.run(progress);
99 } catch (Exception ex) {
100 throw new Error(ex);
101 }
102 }
103 }.start();
104 }
105}
diff --git a/src/cuchaz/enigma/gui/ReadableToken.java b/src/cuchaz/enigma/gui/ReadableToken.java
new file mode 100644
index 00000000..66bcbc2a
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ReadableToken.java
@@ -0,0 +1,36 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13public class ReadableToken {
14
15 public int line;
16 public int startColumn;
17 public int endColumn;
18
19 public ReadableToken(int line, int startColumn, int endColumn) {
20 this.line = line;
21 this.startColumn = startColumn;
22 this.endColumn = endColumn;
23 }
24
25 @Override
26 public String toString() {
27 StringBuilder buf = new StringBuilder();
28 buf.append("line ");
29 buf.append(line);
30 buf.append(" columns ");
31 buf.append(startColumn);
32 buf.append("-");
33 buf.append(endColumn);
34 return buf.toString();
35 }
36}
diff --git a/src/cuchaz/enigma/gui/RenameListener.java b/src/cuchaz/enigma/gui/RenameListener.java
new file mode 100644
index 00000000..abeda0ce
--- /dev/null
+++ b/src/cuchaz/enigma/gui/RenameListener.java
@@ -0,0 +1,17 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import cuchaz.enigma.mapping.Entry;
14
15public interface RenameListener {
16 void rename(Entry obfEntry, String newName);
17}
diff --git a/src/cuchaz/enigma/gui/SelectionHighlightPainter.java b/src/cuchaz/enigma/gui/SelectionHighlightPainter.java
new file mode 100644
index 00000000..5e189d2e
--- /dev/null
+++ b/src/cuchaz/enigma/gui/SelectionHighlightPainter.java
@@ -0,0 +1,34 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.BasicStroke;
14import java.awt.Color;
15import java.awt.Graphics;
16import java.awt.Graphics2D;
17import java.awt.Rectangle;
18import java.awt.Shape;
19
20import javax.swing.text.Highlighter;
21import javax.swing.text.JTextComponent;
22
23public class SelectionHighlightPainter implements Highlighter.HighlightPainter {
24
25 @Override
26 public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) {
27 // draw a thick border
28 Graphics2D g2d = (Graphics2D)g;
29 Rectangle bounds = BoxHighlightPainter.getBounds(text, start, end);
30 g2d.setColor(Color.black);
31 g2d.setStroke(new BasicStroke(2.0f));
32 g2d.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4);
33 }
34}
diff --git a/src/cuchaz/enigma/gui/TokenListCellRenderer.java b/src/cuchaz/enigma/gui/TokenListCellRenderer.java
new file mode 100644
index 00000000..a49be37b
--- /dev/null
+++ b/src/cuchaz/enigma/gui/TokenListCellRenderer.java
@@ -0,0 +1,38 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Component;
14
15import javax.swing.DefaultListCellRenderer;
16import javax.swing.JLabel;
17import javax.swing.JList;
18import javax.swing.ListCellRenderer;
19
20import cuchaz.enigma.analysis.Token;
21
22public class TokenListCellRenderer implements ListCellRenderer<Token> {
23
24 private GuiController m_controller;
25 private DefaultListCellRenderer m_defaultRenderer;
26
27 public TokenListCellRenderer(GuiController controller) {
28 m_controller = controller;
29 m_defaultRenderer = new DefaultListCellRenderer();
30 }
31
32 @Override
33 public Component getListCellRendererComponent(JList<? extends Token> list, Token token, int index, boolean isSelected, boolean hasFocus) {
34 JLabel label = (JLabel)m_defaultRenderer.getListCellRendererComponent(list, token, index, isSelected, hasFocus);
35 label.setText(m_controller.getReadableToken(token).toString());
36 return label;
37 }
38}
diff --git a/src/cuchaz/enigma/mapping/ArgumentEntry.java b/src/cuchaz/enigma/mapping/ArgumentEntry.java
new file mode 100644
index 00000000..aa222652
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/ArgumentEntry.java
@@ -0,0 +1,116 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14
15import cuchaz.enigma.Util;
16
17public class ArgumentEntry implements Entry, Serializable {
18
19 private static final long serialVersionUID = 4472172468162696006L;
20
21 private BehaviorEntry m_behaviorEntry;
22 private int m_index;
23 private String m_name;
24
25 public ArgumentEntry(BehaviorEntry behaviorEntry, int index, String name) {
26 if (behaviorEntry == null) {
27 throw new IllegalArgumentException("Behavior cannot be null!");
28 }
29 if (index < 0) {
30 throw new IllegalArgumentException("Index must be non-negative!");
31 }
32 if (name == null) {
33 throw new IllegalArgumentException("Argument name cannot be null!");
34 }
35
36 m_behaviorEntry = behaviorEntry;
37 m_index = index;
38 m_name = name;
39 }
40
41 public ArgumentEntry(ArgumentEntry other) {
42 m_behaviorEntry = (BehaviorEntry)m_behaviorEntry.cloneToNewClass(getClassEntry());
43 m_index = other.m_index;
44 m_name = other.m_name;
45 }
46
47 public ArgumentEntry(ArgumentEntry other, String newClassName) {
48 m_behaviorEntry = (BehaviorEntry)other.m_behaviorEntry.cloneToNewClass(new ClassEntry(newClassName));
49 m_index = other.m_index;
50 m_name = other.m_name;
51 }
52
53 public BehaviorEntry getBehaviorEntry() {
54 return m_behaviorEntry;
55 }
56
57 public int getIndex() {
58 return m_index;
59 }
60
61 @Override
62 public String getName() {
63 return m_name;
64 }
65
66 @Override
67 public ClassEntry getClassEntry() {
68 return m_behaviorEntry.getClassEntry();
69 }
70
71 @Override
72 public String getClassName() {
73 return m_behaviorEntry.getClassName();
74 }
75
76 @Override
77 public ArgumentEntry cloneToNewClass(ClassEntry classEntry) {
78 return new ArgumentEntry(this, classEntry.getName());
79 }
80
81 public String getMethodName() {
82 return m_behaviorEntry.getName();
83 }
84
85 public Signature getMethodSignature() {
86 return m_behaviorEntry.getSignature();
87 }
88
89 @Override
90 public int hashCode() {
91 return Util.combineHashesOrdered(
92 m_behaviorEntry,
93 Integer.valueOf(m_index).hashCode(),
94 m_name.hashCode()
95 );
96 }
97
98 @Override
99 public boolean equals(Object other) {
100 if (other instanceof ArgumentEntry) {
101 return equals((ArgumentEntry)other);
102 }
103 return false;
104 }
105
106 public boolean equals(ArgumentEntry other) {
107 return m_behaviorEntry.equals(other.m_behaviorEntry)
108 && m_index == other.m_index
109 && m_name.equals(other.m_name);
110 }
111
112 @Override
113 public String toString() {
114 return m_behaviorEntry.toString() + "(" + m_index + ":" + m_name + ")";
115 }
116}
diff --git a/src/cuchaz/enigma/mapping/ArgumentMapping.java b/src/cuchaz/enigma/mapping/ArgumentMapping.java
new file mode 100644
index 00000000..f4d8e774
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/ArgumentMapping.java
@@ -0,0 +1,44 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14
15public class ArgumentMapping implements Serializable, Comparable<ArgumentMapping> {
16
17 private static final long serialVersionUID = 8610742471440861315L;
18
19 private int m_index;
20 private String m_name;
21
22 // NOTE: this argument order is important for the MethodReader/MethodWriter
23 public ArgumentMapping(int index, String name) {
24 m_index = index;
25 m_name = NameValidator.validateArgumentName(name);
26 }
27
28 public int getIndex() {
29 return m_index;
30 }
31
32 public String getName() {
33 return m_name;
34 }
35
36 public void setName(String val) {
37 m_name = NameValidator.validateArgumentName(val);
38 }
39
40 @Override
41 public int compareTo(ArgumentMapping other) {
42 return Integer.compare(m_index, other.m_index);
43 }
44}
diff --git a/src/cuchaz/enigma/mapping/BehaviorEntry.java b/src/cuchaz/enigma/mapping/BehaviorEntry.java
new file mode 100644
index 00000000..535788f2
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/BehaviorEntry.java
@@ -0,0 +1,15 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13public interface BehaviorEntry extends Entry {
14 Signature getSignature();
15}
diff --git a/src/cuchaz/enigma/mapping/BehaviorEntryFactory.java b/src/cuchaz/enigma/mapping/BehaviorEntryFactory.java
new file mode 100644
index 00000000..61e501b7
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/BehaviorEntryFactory.java
@@ -0,0 +1,57 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import javassist.CtBehavior;
14import javassist.CtConstructor;
15import javassist.CtMethod;
16import javassist.bytecode.Descriptor;
17
18public class BehaviorEntryFactory {
19
20 public static BehaviorEntry create(String className, String name, String signature) {
21 return create(new ClassEntry(className), name, signature);
22 }
23
24 public static BehaviorEntry create(ClassEntry classEntry, String name, String signature) {
25 if (name.equals("<init>")) {
26 return new ConstructorEntry(classEntry, new Signature(signature));
27 } else if (name.equals("<clinit>")) {
28 return new ConstructorEntry(classEntry);
29 } else {
30 return new MethodEntry(classEntry, name, new Signature(signature));
31 }
32 }
33
34 public static BehaviorEntry create(CtBehavior behavior) {
35 String className = Descriptor.toJvmName(behavior.getDeclaringClass().getName());
36 if (behavior instanceof CtMethod) {
37 return create(className, behavior.getName(), behavior.getSignature());
38 } else if (behavior instanceof CtConstructor) {
39 CtConstructor constructor = (CtConstructor)behavior;
40 if (constructor.isClassInitializer()) {
41 return create(className, "<clinit>", null);
42 } else {
43 return create(className, "<init>", constructor.getSignature());
44 }
45 } else {
46 throw new IllegalArgumentException("Unable to create BehaviorEntry from " + behavior);
47 }
48 }
49
50 public static BehaviorEntry createObf(ClassEntry classEntry, MethodMapping methodMapping) {
51 return create(classEntry, methodMapping.getObfName(), methodMapping.getObfSignature().toString());
52 }
53
54 public static BehaviorEntry createDeobf(ClassEntry classEntry, MethodMapping methodMapping) {
55 return create(classEntry, methodMapping.getDeobfName(), methodMapping.getObfSignature().toString());
56 }
57}
diff --git a/src/cuchaz/enigma/mapping/ClassEntry.java b/src/cuchaz/enigma/mapping/ClassEntry.java
new file mode 100644
index 00000000..cf410012
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/ClassEntry.java
@@ -0,0 +1,123 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14
15public class ClassEntry implements Entry, Serializable {
16
17 private static final long serialVersionUID = 4235460580973955811L;
18
19 private String m_name;
20
21 public ClassEntry(String className) {
22 if (className == null) {
23 throw new IllegalArgumentException("Class name cannot be null!");
24 }
25 if (className.indexOf('.') >= 0) {
26 throw new IllegalArgumentException("Class name must be in JVM format. ie, path/to/package/class$inner : " + className);
27 }
28
29 m_name = className;
30
31 if (isInnerClass() && getInnerClassName().indexOf('/') >= 0) {
32 throw new IllegalArgumentException("Inner class must not have a package: " + className);
33 }
34 }
35
36 public ClassEntry(ClassEntry other) {
37 m_name = other.m_name;
38 }
39
40 @Override
41 public String getName() {
42 return m_name;
43 }
44
45 @Override
46 public String getClassName() {
47 return m_name;
48 }
49
50 @Override
51 public ClassEntry getClassEntry() {
52 return this;
53 }
54
55 @Override
56 public ClassEntry cloneToNewClass(ClassEntry classEntry) {
57 return classEntry;
58 }
59
60 @Override
61 public int hashCode() {
62 return m_name.hashCode();
63 }
64
65 @Override
66 public boolean equals(Object other) {
67 if (other instanceof ClassEntry) {
68 return equals((ClassEntry)other);
69 }
70 return false;
71 }
72
73 public boolean equals(ClassEntry other) {
74 return m_name.equals(other.m_name);
75 }
76
77 @Override
78 public String toString() {
79 return m_name;
80 }
81
82 public boolean isInnerClass() {
83 return m_name.lastIndexOf('$') >= 0;
84 }
85
86 public String getOuterClassName() {
87 if (isInnerClass()) {
88 return m_name.substring(0, m_name.lastIndexOf('$'));
89 }
90 return m_name;
91 }
92
93 public String getInnerClassName() {
94 if (!isInnerClass()) {
95 throw new Error("This is not an inner class!");
96 }
97 return m_name.substring(m_name.lastIndexOf('$') + 1);
98 }
99
100 public ClassEntry getOuterClassEntry() {
101 return new ClassEntry(getOuterClassName());
102 }
103
104 public boolean isInDefaultPackage() {
105 return m_name.indexOf('/') < 0;
106 }
107
108 public String getPackageName() {
109 int pos = m_name.lastIndexOf('/');
110 if (pos > 0) {
111 return m_name.substring(0, pos);
112 }
113 return null;
114 }
115
116 public String getSimpleName() {
117 int pos = m_name.lastIndexOf('/');
118 if (pos > 0) {
119 return m_name.substring(pos + 1);
120 }
121 return m_name;
122 }
123}
diff --git a/src/cuchaz/enigma/mapping/ClassMapping.java b/src/cuchaz/enigma/mapping/ClassMapping.java
new file mode 100644
index 00000000..e2c3d56f
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/ClassMapping.java
@@ -0,0 +1,405 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14import java.util.ArrayList;
15import java.util.Map;
16
17import com.google.common.collect.Maps;
18
19public class ClassMapping implements Serializable, Comparable<ClassMapping> {
20
21 private static final long serialVersionUID = -5148491146902340107L;
22
23 private String m_obfName;
24 private String m_deobfName;
25 private Map<String,ClassMapping> m_innerClassesByObf;
26 private Map<String,ClassMapping> m_innerClassesByDeobf;
27 private Map<String,FieldMapping> m_fieldsByObf;
28 private Map<String,FieldMapping> m_fieldsByDeobf;
29 private Map<String,MethodMapping> m_methodsByObf;
30 private Map<String,MethodMapping> m_methodsByDeobf;
31
32 public ClassMapping(String obfName) {
33 this(obfName, null);
34 }
35
36 public ClassMapping(String obfName, String deobfName) {
37 m_obfName = obfName;
38 m_deobfName = NameValidator.validateClassName(deobfName, false);
39 m_innerClassesByObf = Maps.newHashMap();
40 m_innerClassesByDeobf = Maps.newHashMap();
41 m_fieldsByObf = Maps.newHashMap();
42 m_fieldsByDeobf = Maps.newHashMap();
43 m_methodsByObf = Maps.newHashMap();
44 m_methodsByDeobf = Maps.newHashMap();
45 }
46
47 public String getObfName() {
48 return m_obfName;
49 }
50
51 public String getDeobfName() {
52 return m_deobfName;
53 }
54
55 public void setDeobfName(String val) {
56 m_deobfName = NameValidator.validateClassName(val, false);
57 }
58
59 //// INNER CLASSES ////////
60
61 public Iterable<ClassMapping> innerClasses() {
62 assert (m_innerClassesByObf.size() >= m_innerClassesByDeobf.size());
63 return m_innerClassesByObf.values();
64 }
65
66 public void addInnerClassMapping(ClassMapping classMapping) {
67 assert (isSimpleClassName(classMapping.getObfName()));
68 boolean obfWasAdded = m_innerClassesByObf.put(classMapping.getObfName(), classMapping) == null;
69 assert (obfWasAdded);
70 if (classMapping.getDeobfName() != null) {
71 assert (isSimpleClassName(classMapping.getDeobfName()));
72 boolean deobfWasAdded = m_innerClassesByDeobf.put(classMapping.getDeobfName(), classMapping) == null;
73 assert (deobfWasAdded);
74 }
75 }
76
77 public void removeInnerClassMapping(ClassMapping classMapping) {
78 boolean obfWasRemoved = m_innerClassesByObf.remove(classMapping.getObfName()) != null;
79 assert (obfWasRemoved);
80 if (classMapping.getDeobfName() != null) {
81 boolean deobfWasRemoved = m_innerClassesByDeobf.remove(classMapping.getDeobfName()) != null;
82 assert (deobfWasRemoved);
83 }
84 }
85
86 public ClassMapping getOrCreateInnerClass(String obfName) {
87 assert (isSimpleClassName(obfName));
88 ClassMapping classMapping = m_innerClassesByObf.get(obfName);
89 if (classMapping == null) {
90 classMapping = new ClassMapping(obfName);
91 boolean wasAdded = m_innerClassesByObf.put(obfName, classMapping) == null;
92 assert (wasAdded);
93 }
94 return classMapping;
95 }
96
97 public ClassMapping getInnerClassByObf(String obfName) {
98 assert (isSimpleClassName(obfName));
99 return m_innerClassesByObf.get(obfName);
100 }
101
102 public ClassMapping getInnerClassByDeobf(String deobfName) {
103 assert (isSimpleClassName(deobfName));
104 return m_innerClassesByDeobf.get(deobfName);
105 }
106
107 public ClassMapping getInnerClassByDeobfThenObf(String name) {
108 ClassMapping classMapping = getInnerClassByDeobf(name);
109 if (classMapping == null) {
110 classMapping = getInnerClassByObf(name);
111 }
112 return classMapping;
113 }
114
115 public String getObfInnerClassName(String deobfName) {
116 assert (isSimpleClassName(deobfName));
117 ClassMapping classMapping = m_innerClassesByDeobf.get(deobfName);
118 if (classMapping != null) {
119 return classMapping.getObfName();
120 }
121 return null;
122 }
123
124 public String getDeobfInnerClassName(String obfName) {
125 assert (isSimpleClassName(obfName));
126 ClassMapping classMapping = m_innerClassesByObf.get(obfName);
127 if (classMapping != null) {
128 return classMapping.getDeobfName();
129 }
130 return null;
131 }
132
133 public void setInnerClassName(String obfName, String deobfName) {
134 assert (isSimpleClassName(obfName));
135 ClassMapping classMapping = getOrCreateInnerClass(obfName);
136 if (classMapping.getDeobfName() != null) {
137 boolean wasRemoved = m_innerClassesByDeobf.remove(classMapping.getDeobfName()) != null;
138 assert (wasRemoved);
139 }
140 classMapping.setDeobfName(deobfName);
141 if (deobfName != null) {
142 assert (isSimpleClassName(deobfName));
143 boolean wasAdded = m_innerClassesByDeobf.put(deobfName, classMapping) == null;
144 assert (wasAdded);
145 }
146 }
147
148 //// FIELDS ////////
149
150 public Iterable<FieldMapping> fields() {
151 assert (m_fieldsByObf.size() == m_fieldsByDeobf.size());
152 return m_fieldsByObf.values();
153 }
154
155 public boolean containsObfField(String obfName) {
156 return m_fieldsByObf.containsKey(obfName);
157 }
158
159 public boolean containsDeobfField(String deobfName) {
160 return m_fieldsByDeobf.containsKey(deobfName);
161 }
162
163 public void addFieldMapping(FieldMapping fieldMapping) {
164 if (m_fieldsByObf.containsKey(fieldMapping.getObfName())) {
165 throw new Error("Already have mapping for " + m_obfName + "." + fieldMapping.getObfName());
166 }
167 if (m_fieldsByDeobf.containsKey(fieldMapping.getDeobfName())) {
168 throw new Error("Already have mapping for " + m_deobfName + "." + fieldMapping.getDeobfName());
169 }
170 boolean obfWasAdded = m_fieldsByObf.put(fieldMapping.getObfName(), fieldMapping) == null;
171 assert (obfWasAdded);
172 boolean deobfWasAdded = m_fieldsByDeobf.put(fieldMapping.getDeobfName(), fieldMapping) == null;
173 assert (deobfWasAdded);
174 assert (m_fieldsByObf.size() == m_fieldsByDeobf.size());
175 }
176
177 public void removeFieldMapping(FieldMapping fieldMapping) {
178 boolean obfWasRemoved = m_fieldsByObf.remove(fieldMapping.getObfName()) != null;
179 assert (obfWasRemoved);
180 if (fieldMapping.getDeobfName() != null) {
181 boolean deobfWasRemoved = m_fieldsByDeobf.remove(fieldMapping.getDeobfName()) != null;
182 assert (deobfWasRemoved);
183 }
184 }
185
186 public FieldMapping getFieldByObf(String obfName) {
187 return m_fieldsByObf.get(obfName);
188 }
189
190 public FieldMapping getFieldByDeobf(String deobfName) {
191 return m_fieldsByDeobf.get(deobfName);
192 }
193
194 public String getObfFieldName(String deobfName) {
195 FieldMapping fieldMapping = m_fieldsByDeobf.get(deobfName);
196 if (fieldMapping != null) {
197 return fieldMapping.getObfName();
198 }
199 return null;
200 }
201
202 public String getDeobfFieldName(String obfName) {
203 FieldMapping fieldMapping = m_fieldsByObf.get(obfName);
204 if (fieldMapping != null) {
205 return fieldMapping.getDeobfName();
206 }
207 return null;
208 }
209
210 public void setFieldName(String obfName, String deobfName) {
211 FieldMapping fieldMapping = m_fieldsByObf.get(obfName);
212 if (fieldMapping == null) {
213 fieldMapping = new FieldMapping(obfName, deobfName);
214 boolean obfWasAdded = m_fieldsByObf.put(obfName, fieldMapping) == null;
215 assert (obfWasAdded);
216 } else {
217 boolean wasRemoved = m_fieldsByDeobf.remove(fieldMapping.getDeobfName()) != null;
218 assert (wasRemoved);
219 }
220 fieldMapping.setDeobfName(deobfName);
221 if (deobfName != null) {
222 boolean wasAdded = m_fieldsByDeobf.put(deobfName, fieldMapping) == null;
223 assert (wasAdded);
224 }
225 }
226
227 //// METHODS ////////
228
229 public Iterable<MethodMapping> methods() {
230 assert (m_methodsByObf.size() >= m_methodsByDeobf.size());
231 return m_methodsByObf.values();
232 }
233
234 public boolean containsObfMethod(String obfName, Signature obfSignature) {
235 return m_methodsByObf.containsKey(getMethodKey(obfName, obfSignature));
236 }
237
238 public boolean containsDeobfMethod(String deobfName, Signature deobfSignature) {
239 return m_methodsByDeobf.containsKey(getMethodKey(deobfName, deobfSignature));
240 }
241
242 public void addMethodMapping(MethodMapping methodMapping) {
243 String obfKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature());
244 if (m_methodsByObf.containsKey(obfKey)) {
245 throw new Error("Already have mapping for " + m_obfName + "." + obfKey);
246 }
247 boolean wasAdded = m_methodsByObf.put(obfKey, methodMapping) == null;
248 assert (wasAdded);
249 if (methodMapping.getDeobfName() != null) {
250 String deobfKey = getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature());
251 if (m_methodsByDeobf.containsKey(deobfKey)) {
252 throw new Error("Already have mapping for " + m_deobfName + "." + deobfKey);
253 }
254 boolean deobfWasAdded = m_methodsByDeobf.put(deobfKey, methodMapping) == null;
255 assert (deobfWasAdded);
256 }
257 assert (m_methodsByObf.size() >= m_methodsByDeobf.size());
258 }
259
260 public void removeMethodMapping(MethodMapping methodMapping) {
261 boolean obfWasRemoved = m_methodsByObf.remove(getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature())) != null;
262 assert (obfWasRemoved);
263 if (methodMapping.getDeobfName() != null) {
264 boolean deobfWasRemoved = m_methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature())) != null;
265 assert (deobfWasRemoved);
266 }
267 }
268
269 public MethodMapping getMethodByObf(String obfName, Signature signature) {
270 return m_methodsByObf.get(getMethodKey(obfName, signature));
271 }
272
273 public MethodMapping getMethodByDeobf(String deobfName, Signature signature) {
274 return m_methodsByDeobf.get(getMethodKey(deobfName, signature));
275 }
276
277 private String getMethodKey(String name, Signature signature) {
278 if (name == null) {
279 throw new IllegalArgumentException("name cannot be null!");
280 }
281 if (signature == null) {
282 throw new IllegalArgumentException("signature cannot be null!");
283 }
284 return name + signature;
285 }
286
287 public void setMethodName(String obfName, Signature obfSignature, String deobfName) {
288 MethodMapping methodMapping = m_methodsByObf.get(getMethodKey(obfName, obfSignature));
289 if (methodMapping == null) {
290 methodMapping = createMethodMapping(obfName, obfSignature);
291 } else if (methodMapping.getDeobfName() != null) {
292 boolean wasRemoved = m_methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature())) != null;
293 assert (wasRemoved);
294 }
295 methodMapping.setDeobfName(deobfName);
296 if (deobfName != null) {
297 boolean wasAdded = m_methodsByDeobf.put(getMethodKey(deobfName, obfSignature), methodMapping) == null;
298 assert (wasAdded);
299 }
300 }
301
302 //// ARGUMENTS ////////
303
304 public void setArgumentName(String obfMethodName, Signature obfMethodSignature, int argumentIndex, String argumentName) {
305 MethodMapping methodMapping = m_methodsByObf.get(getMethodKey(obfMethodName, obfMethodSignature));
306 if (methodMapping == null) {
307 methodMapping = createMethodMapping(obfMethodName, obfMethodSignature);
308 }
309 methodMapping.setArgumentName(argumentIndex, argumentName);
310 }
311
312 public void removeArgumentName(String obfMethodName, Signature obfMethodSignature, int argumentIndex) {
313 m_methodsByObf.get(getMethodKey(obfMethodName, obfMethodSignature)).removeArgumentName(argumentIndex);
314 }
315
316 private MethodMapping createMethodMapping(String obfName, Signature obfSignature) {
317 MethodMapping methodMapping = new MethodMapping(obfName, obfSignature);
318 boolean wasAdded = m_methodsByObf.put(getMethodKey(obfName, obfSignature), methodMapping) == null;
319 assert (wasAdded);
320 return methodMapping;
321 }
322
323 @Override
324 public String toString() {
325 StringBuilder buf = new StringBuilder();
326 buf.append(m_obfName);
327 buf.append(" <-> ");
328 buf.append(m_deobfName);
329 buf.append("\n");
330 buf.append("Fields:\n");
331 for (FieldMapping fieldMapping : fields()) {
332 buf.append("\t");
333 buf.append(fieldMapping.getObfName());
334 buf.append(" <-> ");
335 buf.append(fieldMapping.getDeobfName());
336 buf.append("\n");
337 }
338 buf.append("Methods:\n");
339 for (MethodMapping methodMapping : m_methodsByObf.values()) {
340 buf.append(methodMapping.toString());
341 buf.append("\n");
342 }
343 buf.append("Inner Classes:\n");
344 for (ClassMapping classMapping : m_innerClassesByObf.values()) {
345 buf.append("\t");
346 buf.append(classMapping.getObfName());
347 buf.append(" <-> ");
348 buf.append(classMapping.getDeobfName());
349 buf.append("\n");
350 }
351 return buf.toString();
352 }
353
354 @Override
355 public int compareTo(ClassMapping other) {
356 // sort by a, b, c, ... aa, ab, etc
357 if (m_obfName.length() != other.m_obfName.length()) {
358 return m_obfName.length() - other.m_obfName.length();
359 }
360 return m_obfName.compareTo(other.m_obfName);
361 }
362
363 public boolean renameObfClass(String oldObfClassName, String newObfClassName) {
364
365 // rename inner classes
366 for (ClassMapping innerClassMapping : new ArrayList<ClassMapping>(m_innerClassesByObf.values())) {
367 if (innerClassMapping.renameObfClass(oldObfClassName, newObfClassName)) {
368 boolean wasRemoved = m_innerClassesByObf.remove(oldObfClassName) != null;
369 assert (wasRemoved);
370 boolean wasAdded = m_innerClassesByObf.put(newObfClassName, innerClassMapping) == null;
371 assert (wasAdded);
372 }
373 }
374
375 // rename method signatures
376 for (MethodMapping methodMapping : new ArrayList<MethodMapping>(m_methodsByObf.values())) {
377 String oldMethodKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature());
378 if (methodMapping.renameObfClass(oldObfClassName, newObfClassName)) {
379 boolean wasRemoved = m_methodsByObf.remove(oldMethodKey) != null;
380 assert (wasRemoved);
381 boolean wasAdded = m_methodsByObf.put(getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()), methodMapping) == null;
382 assert (wasAdded);
383 }
384 }
385
386 if (m_obfName.equals(oldObfClassName)) {
387 // rename this class
388 m_obfName = newObfClassName;
389 return true;
390 }
391 return false;
392 }
393
394 public boolean containsArgument(BehaviorEntry obfBehaviorEntry, String name) {
395 MethodMapping methodMapping = m_methodsByObf.get(getMethodKey(obfBehaviorEntry.getName(), obfBehaviorEntry.getSignature()));
396 if (methodMapping != null) {
397 return methodMapping.containsArgument(name);
398 }
399 return false;
400 }
401
402 public static boolean isSimpleClassName(String name) {
403 return name.indexOf('/') < 0 && name.indexOf('$') < 0;
404 }
405}
diff --git a/src/cuchaz/enigma/mapping/ClassNameReplacer.java b/src/cuchaz/enigma/mapping/ClassNameReplacer.java
new file mode 100644
index 00000000..bf984fd3
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/ClassNameReplacer.java
@@ -0,0 +1,5 @@
1package cuchaz.enigma.mapping;
2
3public interface ClassNameReplacer {
4 String replace(String className);
5}
diff --git a/src/cuchaz/enigma/mapping/ConstructorEntry.java b/src/cuchaz/enigma/mapping/ConstructorEntry.java
new file mode 100644
index 00000000..5f3760f3
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/ConstructorEntry.java
@@ -0,0 +1,116 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14
15import cuchaz.enigma.Util;
16
17public class ConstructorEntry implements BehaviorEntry, Serializable {
18
19 private static final long serialVersionUID = -868346075317366758L;
20
21 private ClassEntry m_classEntry;
22 private Signature m_signature;
23
24 public ConstructorEntry(ClassEntry classEntry) {
25 this(classEntry, null);
26 }
27
28 public ConstructorEntry(ClassEntry classEntry, Signature signature) {
29 if (classEntry == null) {
30 throw new IllegalArgumentException("Class cannot be null!");
31 }
32
33 m_classEntry = classEntry;
34 m_signature = signature;
35 }
36
37 public ConstructorEntry(ConstructorEntry other) {
38 m_classEntry = new ClassEntry(other.m_classEntry);
39 m_signature = other.m_signature;
40 }
41
42 public ConstructorEntry(ConstructorEntry other, String newClassName) {
43 m_classEntry = new ClassEntry(newClassName);
44 m_signature = other.m_signature;
45 }
46
47 @Override
48 public ClassEntry getClassEntry() {
49 return m_classEntry;
50 }
51
52 @Override
53 public String getName() {
54 if (isStatic()) {
55 return "<clinit>";
56 }
57 return "<init>";
58 }
59
60 public boolean isStatic() {
61 return m_signature == null;
62 }
63
64 @Override
65 public Signature getSignature() {
66 return m_signature;
67 }
68
69 @Override
70 public String getClassName() {
71 return m_classEntry.getName();
72 }
73
74 @Override
75 public ConstructorEntry cloneToNewClass(ClassEntry classEntry) {
76 return new ConstructorEntry(this, classEntry.getName());
77 }
78
79 @Override
80 public int hashCode() {
81 if (isStatic()) {
82 return Util.combineHashesOrdered(m_classEntry);
83 } else {
84 return Util.combineHashesOrdered(m_classEntry, m_signature);
85 }
86 }
87
88 @Override
89 public boolean equals(Object other) {
90 if (other instanceof ConstructorEntry) {
91 return equals((ConstructorEntry)other);
92 }
93 return false;
94 }
95
96 public boolean equals(ConstructorEntry other) {
97 if (isStatic() != other.isStatic()) {
98 return false;
99 }
100
101 if (isStatic()) {
102 return m_classEntry.equals(other.m_classEntry);
103 } else {
104 return m_classEntry.equals(other.m_classEntry) && m_signature.equals(other.m_signature);
105 }
106 }
107
108 @Override
109 public String toString() {
110 if (isStatic()) {
111 return m_classEntry.getName() + "." + getName();
112 } else {
113 return m_classEntry.getName() + "." + getName() + m_signature;
114 }
115 }
116}
diff --git a/src/cuchaz/enigma/mapping/Entry.java b/src/cuchaz/enigma/mapping/Entry.java
new file mode 100644
index 00000000..39e1507d
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/Entry.java
@@ -0,0 +1,18 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13public interface Entry {
14 String getName();
15 String getClassName();
16 ClassEntry getClassEntry();
17 Entry cloneToNewClass(ClassEntry classEntry);
18}
diff --git a/src/cuchaz/enigma/mapping/EntryPair.java b/src/cuchaz/enigma/mapping/EntryPair.java
new file mode 100644
index 00000000..60411c40
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/EntryPair.java
@@ -0,0 +1,22 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13public class EntryPair<T extends Entry> {
14
15 public T obf;
16 public T deobf;
17
18 public EntryPair(T obf, T deobf) {
19 this.obf = obf;
20 this.deobf = deobf;
21 }
22}
diff --git a/src/cuchaz/enigma/mapping/FieldEntry.java b/src/cuchaz/enigma/mapping/FieldEntry.java
new file mode 100644
index 00000000..6cc9eb78
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/FieldEntry.java
@@ -0,0 +1,88 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14
15import cuchaz.enigma.Util;
16
17public class FieldEntry implements Entry, Serializable {
18
19 private static final long serialVersionUID = 3004663582802885451L;
20
21 private ClassEntry m_classEntry;
22 private String m_name;
23
24 // NOTE: this argument order is important for the MethodReader/MethodWriter
25 public FieldEntry(ClassEntry classEntry, String name) {
26 if (classEntry == null) {
27 throw new IllegalArgumentException("Class cannot be null!");
28 }
29 if (name == null) {
30 throw new IllegalArgumentException("Field name cannot be null!");
31 }
32
33 m_classEntry = classEntry;
34 m_name = name;
35 }
36
37 public FieldEntry(FieldEntry other) {
38 m_classEntry = new ClassEntry(other.m_classEntry);
39 m_name = other.m_name;
40 }
41
42 public FieldEntry(FieldEntry other, String newClassName) {
43 m_classEntry = new ClassEntry(newClassName);
44 m_name = other.m_name;
45 }
46
47 @Override
48 public ClassEntry getClassEntry() {
49 return m_classEntry;
50 }
51
52 @Override
53 public String getName() {
54 return m_name;
55 }
56
57 @Override
58 public String getClassName() {
59 return m_classEntry.getName();
60 }
61
62 @Override
63 public FieldEntry cloneToNewClass(ClassEntry classEntry) {
64 return new FieldEntry(this, classEntry.getName());
65 }
66
67 @Override
68 public int hashCode() {
69 return Util.combineHashesOrdered(m_classEntry, m_name);
70 }
71
72 @Override
73 public boolean equals(Object other) {
74 if (other instanceof FieldEntry) {
75 return equals((FieldEntry)other);
76 }
77 return false;
78 }
79
80 public boolean equals(FieldEntry other) {
81 return m_classEntry.equals(other.m_classEntry) && m_name.equals(other.m_name);
82 }
83
84 @Override
85 public String toString() {
86 return m_classEntry.getName() + "." + m_name;
87 }
88}
diff --git a/src/cuchaz/enigma/mapping/FieldMapping.java b/src/cuchaz/enigma/mapping/FieldMapping.java
new file mode 100644
index 00000000..5f5c270d
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/FieldMapping.java
@@ -0,0 +1,43 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14
15public class FieldMapping implements Serializable, Comparable<FieldMapping> {
16
17 private static final long serialVersionUID = 8610742471440861315L;
18
19 private String m_obfName;
20 private String m_deobfName;
21
22 public FieldMapping(String obfName, String deobfName) {
23 m_obfName = obfName;
24 m_deobfName = NameValidator.validateFieldName(deobfName);
25 }
26
27 public String getObfName() {
28 return m_obfName;
29 }
30
31 public String getDeobfName() {
32 return m_deobfName;
33 }
34
35 public void setDeobfName(String val) {
36 m_deobfName = NameValidator.validateFieldName(val);
37 }
38
39 @Override
40 public int compareTo(FieldMapping other) {
41 return m_obfName.compareTo(other.m_obfName);
42 }
43}
diff --git a/src/cuchaz/enigma/mapping/IllegalNameException.java b/src/cuchaz/enigma/mapping/IllegalNameException.java
new file mode 100644
index 00000000..aacaf3b0
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/IllegalNameException.java
@@ -0,0 +1,44 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13public class IllegalNameException extends RuntimeException {
14
15 private static final long serialVersionUID = -2279910052561114323L;
16
17 private String m_name;
18 private String m_reason;
19
20 public IllegalNameException(String name) {
21 this(name, null);
22 }
23
24 public IllegalNameException(String name, String reason) {
25 m_name = name;
26 m_reason = reason;
27 }
28
29 public String getReason() {
30 return m_reason;
31 }
32
33 @Override
34 public String getMessage() {
35 StringBuilder buf = new StringBuilder();
36 buf.append("Illegal name: ");
37 buf.append(m_name);
38 if (m_reason != null) {
39 buf.append(" because ");
40 buf.append(m_reason);
41 }
42 return buf.toString();
43 }
44}
diff --git a/src/cuchaz/enigma/mapping/JavassistUtil.java b/src/cuchaz/enigma/mapping/JavassistUtil.java
new file mode 100644
index 00000000..0c446c4a
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/JavassistUtil.java
@@ -0,0 +1,83 @@
1package cuchaz.enigma.mapping;
2
3import javassist.CtBehavior;
4import javassist.CtClass;
5import javassist.CtConstructor;
6import javassist.CtField;
7import javassist.CtMethod;
8import javassist.bytecode.Descriptor;
9import javassist.expr.ConstructorCall;
10import javassist.expr.FieldAccess;
11import javassist.expr.MethodCall;
12import javassist.expr.NewExpr;
13
14public class JavassistUtil {
15
16 public static ClassEntry getClassEntry(CtClass c) {
17 return new ClassEntry(Descriptor.toJvmName(c.getName()));
18 }
19
20 public static ClassEntry getSuperclassEntry(CtClass c) {
21 return new ClassEntry(Descriptor.toJvmName(c.getClassFile().getSuperclass()));
22 }
23
24 public static MethodEntry getMethodEntry(CtMethod method) {
25 return new MethodEntry(
26 getClassEntry(method.getDeclaringClass()),
27 method.getName(),
28 new Signature(method.getMethodInfo().getDescriptor())
29 );
30 }
31
32 public static MethodEntry getMethodEntry(MethodCall call) {
33 return new MethodEntry(
34 new ClassEntry(Descriptor.toJvmName(call.getClassName())),
35 call.getMethodName(),
36 new Signature(call.getSignature())
37 );
38 }
39
40 public static ConstructorEntry getConstructorEntry(CtConstructor constructor) {
41 return new ConstructorEntry(
42 getClassEntry(constructor.getDeclaringClass()),
43 new Signature(constructor.getMethodInfo().getDescriptor())
44 );
45 }
46
47 public static ConstructorEntry getConstructorEntry(ConstructorCall call) {
48 return new ConstructorEntry(
49 new ClassEntry(Descriptor.toJvmName(call.getClassName())),
50 new Signature(call.getSignature())
51 );
52 }
53
54 public static ConstructorEntry getConstructorEntry(NewExpr call) {
55 return new ConstructorEntry(
56 new ClassEntry(Descriptor.toJvmName(call.getClassName())),
57 new Signature(call.getSignature())
58 );
59 }
60
61 public static BehaviorEntry getBehaviorEntry(CtBehavior behavior) {
62 if (behavior instanceof CtMethod) {
63 return getMethodEntry((CtMethod)behavior);
64 } else if (behavior instanceof CtConstructor) {
65 return getConstructorEntry((CtConstructor)behavior);
66 }
67 throw new Error("behavior is neither Method nor Constructor!");
68 }
69
70 public static FieldEntry getFieldEntry(CtField field) {
71 return new FieldEntry(
72 getClassEntry(field.getDeclaringClass()),
73 field.getName()
74 );
75 }
76
77 public static FieldEntry getFieldEntry(FieldAccess call) {
78 return new FieldEntry(
79 new ClassEntry(Descriptor.toJvmName(call.getClassName())),
80 call.getFieldName()
81 );
82 }
83}
diff --git a/src/cuchaz/enigma/mapping/MappingParseException.java b/src/cuchaz/enigma/mapping/MappingParseException.java
new file mode 100644
index 00000000..1974c222
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MappingParseException.java
@@ -0,0 +1,29 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13public class MappingParseException extends Exception {
14
15 private static final long serialVersionUID = -5487280332892507236L;
16
17 private int m_line;
18 private String m_message;
19
20 public MappingParseException(int line, String message) {
21 m_line = line;
22 m_message = message;
23 }
24
25 @Override
26 public String getMessage() {
27 return "Line " + m_line + ": " + m_message;
28 }
29}
diff --git a/src/cuchaz/enigma/mapping/Mappings.java b/src/cuchaz/enigma/mapping/Mappings.java
new file mode 100644
index 00000000..57d80013
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/Mappings.java
@@ -0,0 +1,188 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14import java.util.ArrayList;
15import java.util.Collection;
16import java.util.Map;
17import java.util.Set;
18
19import com.google.common.collect.Maps;
20import com.google.common.collect.Sets;
21
22import cuchaz.enigma.analysis.TranslationIndex;
23
24public class Mappings implements Serializable {
25
26 private static final long serialVersionUID = 4649790259460259026L;
27
28 protected Map<String,ClassMapping> m_classesByObf;
29 protected Map<String,ClassMapping> m_classesByDeobf;
30
31 public Mappings() {
32 m_classesByObf = Maps.newHashMap();
33 m_classesByDeobf = Maps.newHashMap();
34 }
35
36 public Mappings(Iterable<ClassMapping> classes) {
37 this();
38
39 for (ClassMapping classMapping : classes) {
40 m_classesByObf.put(classMapping.getObfName(), classMapping);
41 if (classMapping.getDeobfName() != null) {
42 m_classesByDeobf.put(classMapping.getDeobfName(), classMapping);
43 }
44 }
45 }
46
47 public Collection<ClassMapping> classes() {
48 assert (m_classesByObf.size() >= m_classesByDeobf.size());
49 return m_classesByObf.values();
50 }
51
52 public void addClassMapping(ClassMapping classMapping) {
53 if (m_classesByObf.containsKey(classMapping.getObfName())) {
54 throw new Error("Already have mapping for " + classMapping.getObfName());
55 }
56 boolean obfWasAdded = m_classesByObf.put(classMapping.getObfName(), classMapping) == null;
57 assert (obfWasAdded);
58 if (classMapping.getDeobfName() != null) {
59 if (m_classesByDeobf.containsKey(classMapping.getDeobfName())) {
60 throw new Error("Already have mapping for " + classMapping.getDeobfName());
61 }
62 boolean deobfWasAdded = m_classesByDeobf.put(classMapping.getDeobfName(), classMapping) == null;
63 assert (deobfWasAdded);
64 }
65 }
66
67 public void removeClassMapping(ClassMapping classMapping) {
68 boolean obfWasRemoved = m_classesByObf.remove(classMapping.getObfName()) != null;
69 assert (obfWasRemoved);
70 if (classMapping.getDeobfName() != null) {
71 boolean deobfWasRemoved = m_classesByDeobf.remove(classMapping.getDeobfName()) != null;
72 assert (deobfWasRemoved);
73 }
74 }
75
76 public ClassMapping getClassByObf(ClassEntry entry) {
77 return getClassByObf(entry.getName());
78 }
79
80 public ClassMapping getClassByObf(String obfName) {
81 return m_classesByObf.get(obfName);
82 }
83
84 public ClassMapping getClassByDeobf(ClassEntry entry) {
85 return getClassByDeobf(entry.getName());
86 }
87
88 public ClassMapping getClassByDeobf(String deobfName) {
89 return m_classesByDeobf.get(deobfName);
90 }
91
92 public Translator getTranslator(TranslationDirection direction, TranslationIndex index) {
93 switch (direction) {
94 case Deobfuscating:
95
96 return new Translator(direction, m_classesByObf, index);
97
98 case Obfuscating:
99
100 // fill in the missing deobf class entries with obf entries
101 Map<String,ClassMapping> classes = Maps.newHashMap();
102 for (ClassMapping classMapping : classes()) {
103 if (classMapping.getDeobfName() != null) {
104 classes.put(classMapping.getDeobfName(), classMapping);
105 } else {
106 classes.put(classMapping.getObfName(), classMapping);
107 }
108 }
109
110 // translate the translation index
111 // NOTE: this isn't actually recursive
112 TranslationIndex deobfIndex = new TranslationIndex(index, getTranslator(TranslationDirection.Deobfuscating, index));
113
114 return new Translator(direction, classes, deobfIndex);
115
116 default:
117 throw new Error("Invalid translation direction!");
118 }
119 }
120
121 @Override
122 public String toString() {
123 StringBuilder buf = new StringBuilder();
124 for (ClassMapping classMapping : m_classesByObf.values()) {
125 buf.append(classMapping.toString());
126 buf.append("\n");
127 }
128 return buf.toString();
129 }
130
131 public void renameObfClass(String oldObfName, String newObfName) {
132 for (ClassMapping classMapping : new ArrayList<ClassMapping>(classes())) {
133 if (classMapping.renameObfClass(oldObfName, newObfName)) {
134 boolean wasRemoved = m_classesByObf.remove(oldObfName) != null;
135 assert (wasRemoved);
136 boolean wasAdded = m_classesByObf.put(newObfName, classMapping) == null;
137 assert (wasAdded);
138 }
139 }
140 }
141
142 public Set<String> getAllObfClassNames() {
143 final Set<String> classNames = Sets.newHashSet();
144 for (ClassMapping classMapping : classes()) {
145
146 // add the class name
147 classNames.add(classMapping.getObfName());
148
149 // add classes from method signatures
150 for (MethodMapping methodMapping : classMapping.methods()) {
151 for (Type type : methodMapping.getObfSignature().types()) {
152 if (type.hasClass()) {
153 classNames.add(type.getClassEntry().getClassName());
154 }
155 }
156 }
157 }
158 return classNames;
159 }
160
161 public boolean containsDeobfClass(String deobfName) {
162 return m_classesByDeobf.containsKey(deobfName);
163 }
164
165 public boolean containsDeobfField(ClassEntry obfClassEntry, String deobfName) {
166 ClassMapping classMapping = m_classesByObf.get(obfClassEntry.getName());
167 if (classMapping != null) {
168 return classMapping.containsDeobfField(deobfName);
169 }
170 return false;
171 }
172
173 public boolean containsDeobfMethod(ClassEntry obfClassEntry, String deobfName, Signature deobfSignature) {
174 ClassMapping classMapping = m_classesByObf.get(obfClassEntry.getName());
175 if (classMapping != null) {
176 return classMapping.containsDeobfMethod(deobfName, deobfSignature);
177 }
178 return false;
179 }
180
181 public boolean containsArgument(BehaviorEntry obfBehaviorEntry, String name) {
182 ClassMapping classMapping = m_classesByObf.get(obfBehaviorEntry.getClassName());
183 if (classMapping != null) {
184 return classMapping.containsArgument(obfBehaviorEntry, name);
185 }
186 return false;
187 }
188}
diff --git a/src/cuchaz/enigma/mapping/MappingsReader.java b/src/cuchaz/enigma/mapping/MappingsReader.java
new file mode 100644
index 00000000..adf460e6
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MappingsReader.java
@@ -0,0 +1,175 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.BufferedReader;
14import java.io.IOException;
15import java.io.Reader;
16import java.util.Deque;
17
18import com.google.common.collect.Queues;
19
20import cuchaz.enigma.Constants;
21
22public class MappingsReader {
23
24 public Mappings read(Reader in) throws IOException, MappingParseException {
25 return read(new BufferedReader(in));
26 }
27
28 public Mappings read(BufferedReader in) throws IOException, MappingParseException {
29 Mappings mappings = new Mappings();
30 Deque<Object> mappingStack = Queues.newArrayDeque();
31
32 int lineNumber = 0;
33 String line = null;
34 while ( (line = in.readLine()) != null) {
35 lineNumber++;
36
37 // strip comments
38 int commentPos = line.indexOf('#');
39 if (commentPos >= 0) {
40 line = line.substring(0, commentPos);
41 }
42
43 // skip blank lines
44 if (line.trim().length() <= 0) {
45 continue;
46 }
47
48 // get the indent of this line
49 int indent = 0;
50 for (int i = 0; i < line.length(); i++) {
51 if (line.charAt(i) != '\t') {
52 break;
53 }
54 indent++;
55 }
56
57 // handle stack pops
58 while (indent < mappingStack.size()) {
59 mappingStack.pop();
60 }
61
62 String[] parts = line.trim().split("\\s");
63 try {
64 // read the first token
65 String token = parts[0];
66
67 if (token.equalsIgnoreCase("CLASS")) {
68 ClassMapping classMapping;
69 if (indent == 0) {
70 // outer class
71 classMapping = readClass(parts, false);
72 mappings.addClassMapping(classMapping);
73 } else if (indent == 1) {
74 // inner class
75 if (! (mappingStack.getFirst() instanceof ClassMapping)) {
76 throw new MappingParseException(lineNumber, "Unexpected CLASS entry here!");
77 }
78
79 classMapping = readClass(parts, true);
80 ((ClassMapping)mappingStack.getFirst()).addInnerClassMapping(classMapping);
81 } else {
82 throw new MappingParseException(lineNumber, "Unexpected CLASS entry nesting!");
83 }
84 mappingStack.push(classMapping);
85 } else if (token.equalsIgnoreCase("FIELD")) {
86 if (mappingStack.isEmpty() || ! (mappingStack.getFirst() instanceof ClassMapping)) {
87 throw new MappingParseException(lineNumber, "Unexpected FIELD entry here!");
88 }
89 ((ClassMapping)mappingStack.getFirst()).addFieldMapping(readField(parts));
90 } else if (token.equalsIgnoreCase("METHOD")) {
91 if (mappingStack.isEmpty() || ! (mappingStack.getFirst() instanceof ClassMapping)) {
92 throw new MappingParseException(lineNumber, "Unexpected METHOD entry here!");
93 }
94 MethodMapping methodMapping = readMethod(parts);
95 ((ClassMapping)mappingStack.getFirst()).addMethodMapping(methodMapping);
96 mappingStack.push(methodMapping);
97 } else if (token.equalsIgnoreCase("ARG")) {
98 if (mappingStack.isEmpty() || ! (mappingStack.getFirst() instanceof MethodMapping)) {
99 throw new MappingParseException(lineNumber, "Unexpected ARG entry here!");
100 }
101 ((MethodMapping)mappingStack.getFirst()).addArgumentMapping(readArgument(parts));
102 }
103 } catch (ArrayIndexOutOfBoundsException | NumberFormatException ex) {
104 throw new MappingParseException(lineNumber, "Malformed line!");
105 }
106 }
107
108 return mappings;
109 }
110
111 private ArgumentMapping readArgument(String[] parts) {
112 return new ArgumentMapping(Integer.parseInt(parts[1]), parts[2]);
113 }
114
115 private ClassMapping readClass(String[] parts, boolean makeSimple) {
116 if (parts.length == 2) {
117 String obfName = processName(parts[1], makeSimple);
118 return new ClassMapping(obfName);
119 } else {
120 String obfName = processName(parts[1], makeSimple);
121 String deobfName = processName(parts[2], makeSimple);
122 return new ClassMapping(obfName, deobfName);
123 }
124 }
125
126 private String processName(String name, boolean makeSimple) {
127 if (makeSimple) {
128 return new ClassEntry(name).getSimpleName();
129 } else {
130 return moveClassOutOfDefaultPackage(name, Constants.NonePackage);
131 }
132 }
133
134 private String moveClassOutOfDefaultPackage(String className, String newPackageName) {
135 ClassEntry classEntry = new ClassEntry(className);
136 if (classEntry.isInDefaultPackage()) {
137 return newPackageName + "/" + classEntry.getName();
138 }
139 return className;
140 }
141
142 private FieldMapping readField(String[] parts) {
143 return new FieldMapping(parts[1], parts[2]);
144 }
145
146 private MethodMapping readMethod(String[] parts) {
147 if (parts.length == 3) {
148 String obfName = parts[1];
149 Signature obfSignature = moveSignatureOutOfDefaultPackage(new Signature(parts[2]), Constants.NonePackage);
150 return new MethodMapping(obfName, obfSignature);
151 } else {
152 String obfName = parts[1];
153 String deobfName = parts[2];
154 Signature obfSignature = moveSignatureOutOfDefaultPackage(new Signature(parts[3]), Constants.NonePackage);
155 if (obfName.equals(deobfName)) {
156 return new MethodMapping(obfName, obfSignature);
157 } else {
158 return new MethodMapping(obfName, obfSignature, deobfName);
159 }
160 }
161 }
162
163 private Signature moveSignatureOutOfDefaultPackage(Signature signature, final String newPackageName) {
164 return new Signature(signature, new ClassNameReplacer() {
165 @Override
166 public String replace(String className) {
167 ClassEntry classEntry = new ClassEntry(className);
168 if (classEntry.isInDefaultPackage()) {
169 return newPackageName + "/" + className;
170 }
171 return null;
172 }
173 });
174 }
175}
diff --git a/src/cuchaz/enigma/mapping/MappingsRenamer.java b/src/cuchaz/enigma/mapping/MappingsRenamer.java
new file mode 100644
index 00000000..0a41c2b8
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MappingsRenamer.java
@@ -0,0 +1,237 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.IOException;
14import java.io.ObjectOutputStream;
15import java.io.OutputStream;
16import java.util.Set;
17import java.util.zip.GZIPOutputStream;
18
19import cuchaz.enigma.Constants;
20import cuchaz.enigma.analysis.JarIndex;
21
22public class MappingsRenamer {
23
24 private JarIndex m_index;
25 private Mappings m_mappings;
26
27 public MappingsRenamer(JarIndex index, Mappings mappings) {
28 m_index = index;
29 m_mappings = mappings;
30 }
31
32 public void setClassName(ClassEntry obf, String deobfName) {
33 deobfName = NameValidator.validateClassName(deobfName, !obf.isInnerClass());
34 ClassEntry targetEntry = new ClassEntry(deobfName);
35 if (m_mappings.containsDeobfClass(deobfName) || m_index.containsObfClass(targetEntry)) {
36 throw new IllegalNameException(deobfName, "There is already a class with that name");
37 }
38
39 ClassMapping classMapping = getOrCreateClassMapping(obf);
40
41 if (obf.isInnerClass()) {
42 classMapping.setInnerClassName(obf.getInnerClassName(), deobfName);
43 } else {
44 if (classMapping.getDeobfName() != null) {
45 boolean wasRemoved = m_mappings.m_classesByDeobf.remove(classMapping.getDeobfName()) != null;
46 assert (wasRemoved);
47 }
48 classMapping.setDeobfName(deobfName);
49 boolean wasAdded = m_mappings.m_classesByDeobf.put(deobfName, classMapping) == null;
50 assert (wasAdded);
51 }
52 }
53
54 public void removeClassMapping(ClassEntry obf) {
55 ClassMapping classMapping = getClassMapping(obf);
56 if (obf.isInnerClass()) {
57 classMapping.setInnerClassName(obf.getName(), null);
58 } else {
59 boolean wasRemoved = m_mappings.m_classesByDeobf.remove(classMapping.getDeobfName()) != null;
60 assert (wasRemoved);
61 classMapping.setDeobfName(null);
62 }
63 }
64
65 public void markClassAsDeobfuscated(ClassEntry obf) {
66 ClassMapping classMapping = getOrCreateClassMapping(obf);
67 if (obf.isInnerClass()) {
68 String innerClassName = Constants.NonePackage + "/" + obf.getInnerClassName();
69 classMapping.setInnerClassName(innerClassName, innerClassName);
70 } else {
71 classMapping.setDeobfName(obf.getName());
72 boolean wasAdded = m_mappings.m_classesByDeobf.put(obf.getName(), classMapping) == null;
73 assert (wasAdded);
74 }
75 }
76
77 public void setFieldName(FieldEntry obf, String deobfName) {
78 deobfName = NameValidator.validateFieldName(deobfName);
79 FieldEntry targetEntry = new FieldEntry(obf.getClassEntry(), deobfName);
80 if (m_mappings.containsDeobfField(obf.getClassEntry(), deobfName) || m_index.containsObfField(targetEntry)) {
81 throw new IllegalNameException(deobfName, "There is already a field with that name");
82 }
83
84 ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry());
85 classMapping.setFieldName(obf.getName(), deobfName);
86 }
87
88 public void removeFieldMapping(FieldEntry obf) {
89 ClassMapping classMapping = getClassMappingOrInnerClassMapping(obf.getClassEntry());
90 classMapping.setFieldName(obf.getName(), null);
91 }
92
93 public void markFieldAsDeobfuscated(FieldEntry obf) {
94 ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry());
95 classMapping.setFieldName(obf.getName(), obf.getName());
96 }
97
98 public void setMethodTreeName(MethodEntry obf, String deobfName) {
99 Set<MethodEntry> implementations = m_index.getRelatedMethodImplementations(obf);
100
101 deobfName = NameValidator.validateMethodName(deobfName);
102 for (MethodEntry entry : implementations) {
103 Signature deobfSignature = m_mappings.getTranslator(TranslationDirection.Deobfuscating, m_index.getTranslationIndex()).translateSignature(obf.getSignature());
104 MethodEntry targetEntry = new MethodEntry(entry.getClassEntry(), deobfName, deobfSignature);
105 if (m_mappings.containsDeobfMethod(entry.getClassEntry(), deobfName, entry.getSignature()) || m_index.containsObfBehavior(targetEntry)) {
106 String deobfClassName = m_mappings.getTranslator(TranslationDirection.Deobfuscating, m_index.getTranslationIndex()).translateClass(entry.getClassName());
107 throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName);
108 }
109 }
110
111 for (MethodEntry entry : implementations) {
112 setMethodName(entry, deobfName);
113 }
114 }
115
116 public void setMethodName(MethodEntry obf, String deobfName) {
117 deobfName = NameValidator.validateMethodName(deobfName);
118 MethodEntry targetEntry = new MethodEntry(obf.getClassEntry(), deobfName, obf.getSignature());
119 if (m_mappings.containsDeobfMethod(obf.getClassEntry(), deobfName, obf.getSignature()) || m_index.containsObfBehavior(targetEntry)) {
120 String deobfClassName = m_mappings.getTranslator(TranslationDirection.Deobfuscating, m_index.getTranslationIndex()).translateClass(obf.getClassName());
121 throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName);
122 }
123
124 ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry());
125 classMapping.setMethodName(obf.getName(), obf.getSignature(), deobfName);
126 }
127
128 public void removeMethodTreeMapping(MethodEntry obf) {
129 for (MethodEntry implementation : m_index.getRelatedMethodImplementations(obf)) {
130 removeMethodMapping(implementation);
131 }
132 }
133
134 public void removeMethodMapping(MethodEntry obf) {
135 ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry());
136 classMapping.setMethodName(obf.getName(), obf.getSignature(), null);
137 }
138
139 public void markMethodTreeAsDeobfuscated(MethodEntry obf) {
140 for (MethodEntry implementation : m_index.getRelatedMethodImplementations(obf)) {
141 markMethodAsDeobfuscated(implementation);
142 }
143 }
144
145 public void markMethodAsDeobfuscated(MethodEntry obf) {
146 ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry());
147 classMapping.setMethodName(obf.getName(), obf.getSignature(), obf.getName());
148 }
149
150 public void setArgumentName(ArgumentEntry obf, String deobfName) {
151 deobfName = NameValidator.validateArgumentName(deobfName);
152 // NOTE: don't need to check arguments for name collisions with names determined by Procyon
153 if (m_mappings.containsArgument(obf.getBehaviorEntry(), deobfName)) {
154 throw new IllegalNameException(deobfName, "There is already an argument with that name");
155 }
156
157 ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry());
158 classMapping.setArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex(), deobfName);
159 }
160
161 public void removeArgumentMapping(ArgumentEntry obf) {
162 ClassMapping classMapping = getClassMappingOrInnerClassMapping(obf.getClassEntry());
163 classMapping.removeArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex());
164 }
165
166 public void markArgumentAsDeobfuscated(ArgumentEntry obf) {
167 ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry());
168 classMapping.setArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex(), obf.getName());
169 }
170
171 public boolean moveFieldToObfClass(ClassMapping classMapping, FieldMapping fieldMapping, ClassEntry obfClass) {
172 classMapping.removeFieldMapping(fieldMapping);
173 ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass);
174 if (!targetClassMapping.containsObfField(fieldMapping.getObfName())) {
175 if (!targetClassMapping.containsDeobfField(fieldMapping.getDeobfName())) {
176 targetClassMapping.addFieldMapping(fieldMapping);
177 return true;
178 } else {
179 System.err.println("WARNING: deobf field was already there: " + obfClass + "." + fieldMapping.getDeobfName());
180 }
181 }
182 return false;
183 }
184
185 public boolean moveMethodToObfClass(ClassMapping classMapping, MethodMapping methodMapping, ClassEntry obfClass) {
186 classMapping.removeMethodMapping(methodMapping);
187 ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass);
188 if (!targetClassMapping.containsObfMethod(methodMapping.getObfName(), methodMapping.getObfSignature())) {
189 if (!targetClassMapping.containsDeobfMethod(methodMapping.getDeobfName(), methodMapping.getObfSignature())) {
190 targetClassMapping.addMethodMapping(methodMapping);
191 return true;
192 } else {
193 System.err.println("WARNING: deobf method was already there: " + obfClass + "." + methodMapping.getDeobfName() + methodMapping.getObfSignature());
194 }
195 }
196 return false;
197 }
198
199 public void write(OutputStream out) throws IOException {
200 // TEMP: just use the object output for now. We can find a more efficient storage format later
201 GZIPOutputStream gzipout = new GZIPOutputStream(out);
202 ObjectOutputStream oout = new ObjectOutputStream(gzipout);
203 oout.writeObject(this);
204 gzipout.finish();
205 }
206
207 private ClassMapping getClassMapping(ClassEntry obfClassEntry) {
208 return m_mappings.m_classesByObf.get(obfClassEntry.getOuterClassName());
209 }
210
211 private ClassMapping getOrCreateClassMapping(ClassEntry obfClassEntry) {
212 String obfClassName = obfClassEntry.getOuterClassName();
213 ClassMapping classMapping = m_mappings.m_classesByObf.get(obfClassName);
214 if (classMapping == null) {
215 classMapping = new ClassMapping(obfClassName);
216 boolean obfWasAdded = m_mappings.m_classesByObf.put(classMapping.getObfName(), classMapping) == null;
217 assert (obfWasAdded);
218 }
219 return classMapping;
220 }
221
222 private ClassMapping getClassMappingOrInnerClassMapping(ClassEntry obfClassEntry) {
223 ClassMapping classMapping = getClassMapping(obfClassEntry);
224 if (obfClassEntry.isInDefaultPackage()) {
225 classMapping = classMapping.getInnerClassByObf(obfClassEntry.getInnerClassName());
226 }
227 return classMapping;
228 }
229
230 private ClassMapping getOrCreateClassMappingOrInnerClassMapping(ClassEntry obfClassEntry) {
231 ClassMapping classMapping = getOrCreateClassMapping(obfClassEntry);
232 if (obfClassEntry.isInnerClass()) {
233 classMapping = classMapping.getOrCreateInnerClass(obfClassEntry.getInnerClassName());
234 }
235 return classMapping;
236 }
237}
diff --git a/src/cuchaz/enigma/mapping/MappingsWriter.java b/src/cuchaz/enigma/mapping/MappingsWriter.java
new file mode 100644
index 00000000..5ac409fc
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MappingsWriter.java
@@ -0,0 +1,88 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.IOException;
14import java.io.PrintWriter;
15import java.io.Writer;
16import java.util.ArrayList;
17import java.util.Collections;
18import java.util.List;
19
20public class MappingsWriter {
21
22 public void write(Writer out, Mappings mappings) throws IOException {
23 write(new PrintWriter(out), mappings);
24 }
25
26 public void write(PrintWriter out, Mappings mappings) throws IOException {
27 for (ClassMapping classMapping : sorted(mappings.classes())) {
28 write(out, classMapping, 0);
29 }
30 }
31
32 private void write(PrintWriter out, ClassMapping classMapping, int depth) throws IOException {
33 if (classMapping.getDeobfName() == null) {
34 out.format("%sCLASS %s\n", getIndent(depth), classMapping.getObfName());
35 } else {
36 out.format("%sCLASS %s %s\n", getIndent(depth), classMapping.getObfName(), classMapping.getDeobfName());
37 }
38
39 for (ClassMapping innerClassMapping : sorted(classMapping.innerClasses())) {
40 write(out, innerClassMapping, depth + 1);
41 }
42
43 for (FieldMapping fieldMapping : sorted(classMapping.fields())) {
44 write(out, fieldMapping, depth + 1);
45 }
46
47 for (MethodMapping methodMapping : sorted(classMapping.methods())) {
48 write(out, methodMapping, depth + 1);
49 }
50 }
51
52 private void write(PrintWriter out, FieldMapping fieldMapping, int depth) throws IOException {
53 out.format("%sFIELD %s %s\n", getIndent(depth), fieldMapping.getObfName(), fieldMapping.getDeobfName());
54 }
55
56 private void write(PrintWriter out, MethodMapping methodMapping, int depth) throws IOException {
57 if (methodMapping.getDeobfName() == null) {
58 out.format("%sMETHOD %s %s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getObfSignature());
59 } else {
60 out.format("%sMETHOD %s %s %s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getDeobfName(), methodMapping.getObfSignature());
61 }
62
63 for (ArgumentMapping argumentMapping : sorted(methodMapping.arguments())) {
64 write(out, argumentMapping, depth + 1);
65 }
66 }
67
68 private void write(PrintWriter out, ArgumentMapping argumentMapping, int depth) throws IOException {
69 out.format("%sARG %d %s\n", getIndent(depth), argumentMapping.getIndex(), argumentMapping.getName());
70 }
71
72 private <T extends Comparable<T>> List<T> sorted(Iterable<T> classes) {
73 List<T> out = new ArrayList<T>();
74 for (T t : classes) {
75 out.add(t);
76 }
77 Collections.sort(out);
78 return out;
79 }
80
81 private String getIndent(int depth) {
82 StringBuilder buf = new StringBuilder();
83 for (int i = 0; i < depth; i++) {
84 buf.append("\t");
85 }
86 return buf.toString();
87 }
88}
diff --git a/src/cuchaz/enigma/mapping/MethodEntry.java b/src/cuchaz/enigma/mapping/MethodEntry.java
new file mode 100644
index 00000000..057e02b9
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MethodEntry.java
@@ -0,0 +1,104 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14
15import cuchaz.enigma.Util;
16
17public class MethodEntry implements BehaviorEntry, Serializable {
18
19 private static final long serialVersionUID = 4770915224467247458L;
20
21 private ClassEntry m_classEntry;
22 private String m_name;
23 private Signature m_signature;
24
25 public MethodEntry(ClassEntry classEntry, String name, Signature signature) {
26 if (classEntry == null) {
27 throw new IllegalArgumentException("Class cannot be null!");
28 }
29 if (name == null) {
30 throw new IllegalArgumentException("Method name cannot be null!");
31 }
32 if (signature == null) {
33 throw new IllegalArgumentException("Method signature cannot be null!");
34 }
35 if (name.startsWith("<")) {
36 throw new IllegalArgumentException("Don't use MethodEntry for a constructor!");
37 }
38
39 m_classEntry = classEntry;
40 m_name = name;
41 m_signature = signature;
42 }
43
44 public MethodEntry(MethodEntry other) {
45 m_classEntry = new ClassEntry(other.m_classEntry);
46 m_name = other.m_name;
47 m_signature = other.m_signature;
48 }
49
50 public MethodEntry(MethodEntry other, String newClassName) {
51 m_classEntry = new ClassEntry(newClassName);
52 m_name = other.m_name;
53 m_signature = other.m_signature;
54 }
55
56 @Override
57 public ClassEntry getClassEntry() {
58 return m_classEntry;
59 }
60
61 @Override
62 public String getName() {
63 return m_name;
64 }
65
66 @Override
67 public Signature getSignature() {
68 return m_signature;
69 }
70
71 @Override
72 public String getClassName() {
73 return m_classEntry.getName();
74 }
75
76 @Override
77 public MethodEntry cloneToNewClass(ClassEntry classEntry) {
78 return new MethodEntry(this, classEntry.getName());
79 }
80
81 @Override
82 public int hashCode() {
83 return Util.combineHashesOrdered(m_classEntry, m_name, m_signature);
84 }
85
86 @Override
87 public boolean equals(Object other) {
88 if (other instanceof MethodEntry) {
89 return equals((MethodEntry)other);
90 }
91 return false;
92 }
93
94 public boolean equals(MethodEntry other) {
95 return m_classEntry.equals(other.m_classEntry)
96 && m_name.equals(other.m_name)
97 && m_signature.equals(other.m_signature);
98 }
99
100 @Override
101 public String toString() {
102 return m_classEntry.getName() + "." + m_name + m_signature;
103 }
104}
diff --git a/src/cuchaz/enigma/mapping/MethodMapping.java b/src/cuchaz/enigma/mapping/MethodMapping.java
new file mode 100644
index 00000000..1704428a
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MethodMapping.java
@@ -0,0 +1,161 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14import java.util.Map;
15import java.util.TreeMap;
16
17public class MethodMapping implements Serializable, Comparable<MethodMapping> {
18
19 private static final long serialVersionUID = -4409570216084263978L;
20
21 private String m_obfName;
22 private String m_deobfName;
23 private Signature m_obfSignature;
24 private Map<Integer,ArgumentMapping> m_arguments;
25
26 public MethodMapping(String obfName, Signature obfSignature) {
27 this(obfName, obfSignature, null);
28 }
29
30 public MethodMapping(String obfName, Signature obfSignature, String deobfName) {
31 if (obfName == null) {
32 throw new IllegalArgumentException("obf name cannot be null!");
33 }
34 if (obfSignature == null) {
35 throw new IllegalArgumentException("obf signature cannot be null!");
36 }
37 m_obfName = obfName;
38 m_deobfName = NameValidator.validateMethodName(deobfName);
39 m_obfSignature = obfSignature;
40 m_arguments = new TreeMap<Integer,ArgumentMapping>();
41 }
42
43 public String getObfName() {
44 return m_obfName;
45 }
46
47 public String getDeobfName() {
48 return m_deobfName;
49 }
50
51 public void setDeobfName(String val) {
52 m_deobfName = NameValidator.validateMethodName(val);
53 }
54
55 public Signature getObfSignature() {
56 return m_obfSignature;
57 }
58
59 public Iterable<ArgumentMapping> arguments() {
60 return m_arguments.values();
61 }
62
63 public boolean isConstructor() {
64 return m_obfName.startsWith("<");
65 }
66
67 public void addArgumentMapping(ArgumentMapping argumentMapping) {
68 boolean wasAdded = m_arguments.put(argumentMapping.getIndex(), argumentMapping) == null;
69 assert (wasAdded);
70 }
71
72 public String getObfArgumentName(int index) {
73 ArgumentMapping argumentMapping = m_arguments.get(index);
74 if (argumentMapping != null) {
75 return argumentMapping.getName();
76 }
77
78 return null;
79 }
80
81 public String getDeobfArgumentName(int index) {
82 ArgumentMapping argumentMapping = m_arguments.get(index);
83 if (argumentMapping != null) {
84 return argumentMapping.getName();
85 }
86
87 return null;
88 }
89
90 public void setArgumentName(int index, String name) {
91 ArgumentMapping argumentMapping = m_arguments.get(index);
92 if (argumentMapping == null) {
93 argumentMapping = new ArgumentMapping(index, name);
94 boolean wasAdded = m_arguments.put(index, argumentMapping) == null;
95 assert (wasAdded);
96 } else {
97 argumentMapping.setName(name);
98 }
99 }
100
101 public void removeArgumentName(int index) {
102 boolean wasRemoved = m_arguments.remove(index) != null;
103 assert (wasRemoved);
104 }
105
106 @Override
107 public String toString() {
108 StringBuilder buf = new StringBuilder();
109 buf.append("\t");
110 buf.append(m_obfName);
111 buf.append(" <-> ");
112 buf.append(m_deobfName);
113 buf.append("\n");
114 buf.append("\t");
115 buf.append(m_obfSignature);
116 buf.append("\n");
117 buf.append("\tArguments:\n");
118 for (ArgumentMapping argumentMapping : m_arguments.values()) {
119 buf.append("\t\t");
120 buf.append(argumentMapping.getIndex());
121 buf.append(" -> ");
122 buf.append(argumentMapping.getName());
123 buf.append("\n");
124 }
125 return buf.toString();
126 }
127
128 @Override
129 public int compareTo(MethodMapping other) {
130 return (m_obfName + m_obfSignature).compareTo(other.m_obfName + other.m_obfSignature);
131 }
132
133 public boolean renameObfClass(final String oldObfClassName, final String newObfClassName) {
134
135 // rename obf classes in the signature
136 Signature newSignature = new Signature(m_obfSignature, new ClassNameReplacer() {
137 @Override
138 public String replace(String className) {
139 if (className.equals(oldObfClassName)) {
140 return newObfClassName;
141 }
142 return null;
143 }
144 });
145
146 if (!newSignature.equals(m_obfSignature)) {
147 m_obfSignature = newSignature;
148 return true;
149 }
150 return false;
151 }
152
153 public boolean containsArgument(String name) {
154 for (ArgumentMapping argumentMapping : m_arguments.values()) {
155 if (argumentMapping.getName().equals(name)) {
156 return true;
157 }
158 }
159 return false;
160 }
161}
diff --git a/src/cuchaz/enigma/mapping/NameValidator.java b/src/cuchaz/enigma/mapping/NameValidator.java
new file mode 100644
index 00000000..35a17f90
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/NameValidator.java
@@ -0,0 +1,80 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.util.Arrays;
14import java.util.List;
15import java.util.regex.Pattern;
16
17import javassist.bytecode.Descriptor;
18
19public class NameValidator {
20
21 private static final Pattern IdentifierPattern;
22 private static final Pattern ClassPattern;
23 private static final List<String> ReservedWords = Arrays.asList(
24 "abstract", "continue", "for", "new", "switch", "assert", "default", "goto", "package", "synchronized",
25 "boolean", "do", "if", "private", "this", "break", "double", "implements", "protected", "throw", "byte",
26 "else", "import", "public", "throws", "case", "enum", "instanceof", "return", "transient", "catch",
27 "extends", "int", "short", "try", "char", "final", "interface", "static", "void", "class", "finally",
28 "long", "strictfp", "volatile", "const", "float", "native", "super", "while"
29 );
30
31 static {
32
33 // java allows all kinds of weird characters...
34 StringBuilder startChars = new StringBuilder();
35 StringBuilder partChars = new StringBuilder();
36 for (int i = Character.MIN_CODE_POINT; i <= Character.MAX_CODE_POINT; i++) {
37 if (Character.isJavaIdentifierStart(i)) {
38 startChars.appendCodePoint(i);
39 }
40 if (Character.isJavaIdentifierPart(i)) {
41 partChars.appendCodePoint(i);
42 }
43 }
44
45 String identifierRegex = "[A-Za-z_<][A-Za-z0-9_>]*";
46 IdentifierPattern = Pattern.compile(identifierRegex);
47 ClassPattern = Pattern.compile(String.format("^(%s(\\.|/))*(%s)$", identifierRegex, identifierRegex));
48 }
49
50 public static String validateClassName(String name, boolean packageRequired) {
51 if (name == null) {
52 return null;
53 }
54 if (!ClassPattern.matcher(name).matches() || ReservedWords.contains(name)) {
55 throw new IllegalNameException(name, "This doesn't look like a legal class name");
56 }
57 if (packageRequired && new ClassEntry(name).getPackageName() == null) {
58 throw new IllegalNameException(name, "Class must be in a package");
59 }
60 return Descriptor.toJvmName(name);
61 }
62
63 public static String validateFieldName(String name) {
64 if (name == null) {
65 return null;
66 }
67 if (!IdentifierPattern.matcher(name).matches() || ReservedWords.contains(name)) {
68 throw new IllegalNameException(name, "This doesn't look like a legal identifier");
69 }
70 return name;
71 }
72
73 public static String validateMethodName(String name) {
74 return validateFieldName(name);
75 }
76
77 public static String validateArgumentName(String name) {
78 return validateFieldName(name);
79 }
80}
diff --git a/src/cuchaz/enigma/mapping/Signature.java b/src/cuchaz/enigma/mapping/Signature.java
new file mode 100644
index 00000000..ff7f8070
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/Signature.java
@@ -0,0 +1,109 @@
1package cuchaz.enigma.mapping;
2
3import java.util.List;
4
5import com.beust.jcommander.internal.Lists;
6
7import cuchaz.enigma.Util;
8
9public class Signature {
10
11 private List<Type> m_argumentTypes;
12 private Type m_returnType;
13
14 public Signature(String signature) {
15 try {
16 m_argumentTypes = Lists.newArrayList();
17 int i=0;
18 while (i<signature.length()) {
19 char c = signature.charAt(i);
20 if (c == '(') {
21 assert(m_argumentTypes.isEmpty());
22 assert(m_returnType == null);
23 i++;
24 } else if (c == ')') {
25 i++;
26 break;
27 } else {
28 String type = Type.parseFirst(signature.substring(i));
29 m_argumentTypes.add(new Type(type));
30 i += type.length();
31 }
32 }
33 m_returnType = new Type(Type.parseFirst(signature.substring(i)));
34 } catch (Exception ex) {
35 throw new IllegalArgumentException("Unable to parse signature: " + signature, ex);
36 }
37 }
38
39 public Signature(Signature other, ClassNameReplacer replacer) {
40 m_argumentTypes = Lists.newArrayList(other.m_argumentTypes);
41 for (int i=0; i<m_argumentTypes.size(); i++) {
42 m_argumentTypes.set(i, new Type(m_argumentTypes.get(i), replacer));
43 }
44 m_returnType = new Type(other.m_returnType, replacer);
45 }
46
47 public List<Type> getArgumentTypes() {
48 return m_argumentTypes;
49 }
50
51 public Type getReturnType() {
52 return m_returnType;
53 }
54
55 @Override
56 public String toString() {
57 StringBuilder buf = new StringBuilder();
58 buf.append("(");
59 for (Type type : m_argumentTypes) {
60 buf.append(type.toString());
61 }
62 buf.append(")");
63 buf.append(m_returnType.toString());
64 return buf.toString();
65 }
66
67 public Iterable<Type> types() {
68 List<Type> types = Lists.newArrayList();
69 types.addAll(m_argumentTypes);
70 types.add(m_returnType);
71 return types;
72 }
73
74 public Iterable<ClassEntry> classes() {
75 List<ClassEntry> out = Lists.newArrayList();
76 for (Type type : types()) {
77 if (type.isClass()) {
78 out.add(type.getClassEntry());
79 }
80 }
81 return out;
82 }
83
84 @Override
85 public boolean equals(Object other) {
86 if (other instanceof Signature) {
87 return equals((Signature)other);
88 }
89 return false;
90 }
91
92 public boolean equals(Signature other) {
93 return m_argumentTypes.equals(other.m_argumentTypes) && m_returnType.equals(other.m_returnType);
94 }
95
96 @Override
97 public int hashCode() {
98 return Util.combineHashesOrdered(m_argumentTypes.hashCode(), m_returnType.hashCode());
99 }
100
101 public boolean hasClass(ClassEntry classEntry) {
102 for (Type type : types()) {
103 if (type.hasClass() && type.getClassEntry().equals(classEntry)) {
104 return true;
105 }
106 }
107 return false;
108 }
109}
diff --git a/src/cuchaz/enigma/mapping/SignatureUpdater.java b/src/cuchaz/enigma/mapping/SignatureUpdater.java
new file mode 100644
index 00000000..3477cd56
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/SignatureUpdater.java
@@ -0,0 +1,94 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.IOException;
14import java.io.StringReader;
15import java.util.List;
16
17import com.google.common.collect.Lists;
18
19public class SignatureUpdater {
20
21 public interface ClassNameUpdater {
22 String update(String className);
23 }
24
25 public static String update(String signature, ClassNameUpdater updater) {
26 try {
27 StringBuilder buf = new StringBuilder();
28
29 // read the signature character-by-character
30 StringReader reader = new StringReader(signature);
31 int i = -1;
32 while ( (i = reader.read()) != -1) {
33 char c = (char)i;
34
35 // does this character start a class name?
36 if (c == 'L') {
37 // update the class name and add it to the buffer
38 buf.append('L');
39 String className = readClass(reader);
40 if (className == null) {
41 throw new IllegalArgumentException("Malformed signature: " + signature);
42 }
43 buf.append(updater.update(className));
44 buf.append(';');
45 } else {
46 // copy the character into the buffer
47 buf.append(c);
48 }
49 }
50
51 return buf.toString();
52 } catch (IOException ex) {
53 // I'm pretty sure a StringReader will never throw one of these
54 throw new Error(ex);
55 }
56 }
57
58 private static String readClass(StringReader reader) throws IOException {
59 // read all the characters in the buffer until we hit a ';'
60 // remember to treat generics correctly
61 StringBuilder buf = new StringBuilder();
62 int depth = 0;
63 int i = -1;
64 while ( (i = reader.read()) != -1) {
65 char c = (char)i;
66
67 if (c == '<') {
68 depth++;
69 } else if (c == '>') {
70 depth--;
71 } else if (depth == 0) {
72 if (c == ';') {
73 return buf.toString();
74 } else {
75 buf.append(c);
76 }
77 }
78 }
79
80 return null;
81 }
82
83 public static List<String> getClasses(String signature) {
84 final List<String> classNames = Lists.newArrayList();
85 update(signature, new ClassNameUpdater() {
86 @Override
87 public String update(String className) {
88 classNames.add(className);
89 return className;
90 }
91 });
92 return classNames;
93 }
94}
diff --git a/src/cuchaz/enigma/mapping/TranslationDirection.java b/src/cuchaz/enigma/mapping/TranslationDirection.java
new file mode 100644
index 00000000..d1b14cd5
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/TranslationDirection.java
@@ -0,0 +1,29 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13public enum TranslationDirection {
14
15 Deobfuscating {
16 @Override
17 public <T> T choose(T deobfChoice, T obfChoice) {
18 return deobfChoice;
19 }
20 },
21 Obfuscating {
22 @Override
23 public <T> T choose(T deobfChoice, T obfChoice) {
24 return obfChoice;
25 }
26 };
27
28 public abstract <T> T choose(T deobfChoice, T obfChoice);
29}
diff --git a/src/cuchaz/enigma/mapping/Translator.java b/src/cuchaz/enigma/mapping/Translator.java
new file mode 100644
index 00000000..5eba18ce
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/Translator.java
@@ -0,0 +1,239 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.util.Map;
14
15import com.google.common.collect.Maps;
16
17import cuchaz.enigma.analysis.TranslationIndex;
18
19public class Translator {
20
21 private TranslationDirection m_direction;
22 private Map<String,ClassMapping> m_classes;
23 private TranslationIndex m_index;
24
25 public Translator() {
26 m_direction = null;
27 m_classes = Maps.newHashMap();
28 }
29
30 public Translator(TranslationDirection direction, Map<String,ClassMapping> classes, TranslationIndex index) {
31 m_direction = direction;
32 m_classes = classes;
33 m_index = index;
34 }
35
36 @SuppressWarnings("unchecked")
37 public <T extends Entry> T translateEntry(T entry) {
38 if (entry instanceof ClassEntry) {
39 return (T)translateEntry((ClassEntry)entry);
40 } else if (entry instanceof FieldEntry) {
41 return (T)translateEntry((FieldEntry)entry);
42 } else if (entry instanceof MethodEntry) {
43 return (T)translateEntry((MethodEntry)entry);
44 } else if (entry instanceof ConstructorEntry) {
45 return (T)translateEntry((ConstructorEntry)entry);
46 } else if (entry instanceof ArgumentEntry) {
47 return (T)translateEntry((ArgumentEntry)entry);
48 } else {
49 throw new Error("Unknown entry type: " + entry.getClass().getName());
50 }
51 }
52
53 public String translateClass(String className) {
54 return translate(new ClassEntry(className));
55 }
56
57 public String translate(ClassEntry in) {
58 ClassMapping classMapping = m_classes.get(in.getOuterClassName());
59 if (classMapping != null) {
60 if (in.isInnerClass()) {
61 // translate the inner class
62 String translatedInnerClassName = m_direction.choose(
63 classMapping.getDeobfInnerClassName(in.getInnerClassName()),
64 classMapping.getObfInnerClassName(in.getInnerClassName())
65 );
66 if (translatedInnerClassName != null) {
67 // try to translate the outer name
68 String translatedOuterClassName = m_direction.choose(classMapping.getDeobfName(), classMapping.getObfName());
69 if (translatedOuterClassName != null) {
70 return translatedOuterClassName + "$" + translatedInnerClassName;
71 } else {
72 return in.getOuterClassName() + "$" + translatedInnerClassName;
73 }
74 }
75 } else {
76 // just return outer
77 return m_direction.choose(classMapping.getDeobfName(), classMapping.getObfName());
78 }
79 }
80 return null;
81 }
82
83 public ClassEntry translateEntry(ClassEntry in) {
84
85 // can we translate the inner class?
86 String name = translate(in);
87 if (name != null) {
88 return new ClassEntry(name);
89 }
90
91 if (in.isInnerClass()) {
92
93 // guess not. just translate the outer class name then
94 String outerClassName = translate(in.getOuterClassEntry());
95 if (outerClassName != null) {
96 return new ClassEntry(outerClassName + "$" + in.getInnerClassName());
97 }
98 }
99
100 return in;
101 }
102
103 public String translate(FieldEntry in) {
104
105 // resolve the class entry
106 ClassEntry resolvedClassEntry = m_index.resolveEntryClass(in);
107 if (resolvedClassEntry != null) {
108
109 // look for the class
110 ClassMapping classMapping = findClassMapping(resolvedClassEntry);
111 if (classMapping != null) {
112
113 // look for the field
114 String translatedName = m_direction.choose(
115 classMapping.getDeobfFieldName(in.getName()),
116 classMapping.getObfFieldName(in.getName())
117 );
118 if (translatedName != null) {
119 return translatedName;
120 }
121 }
122 }
123 return null;
124 }
125
126 public FieldEntry translateEntry(FieldEntry in) {
127 String name = translate(in);
128 if (name == null) {
129 name = in.getName();
130 }
131 return new FieldEntry(translateEntry(in.getClassEntry()), name);
132 }
133
134 public String translate(MethodEntry in) {
135
136 // resolve the class entry
137 ClassEntry resolvedClassEntry = m_index.resolveEntryClass(in);
138 if (resolvedClassEntry != null) {
139
140 // look for class
141 ClassMapping classMapping = findClassMapping(resolvedClassEntry);
142 if (classMapping != null) {
143
144 // look for the method
145 MethodMapping methodMapping = m_direction.choose(
146 classMapping.getMethodByObf(in.getName(), in.getSignature()),
147 classMapping.getMethodByDeobf(in.getName(), translateSignature(in.getSignature()))
148 );
149 if (methodMapping != null) {
150 return m_direction.choose(methodMapping.getDeobfName(), methodMapping.getObfName());
151 }
152 }
153 }
154 return null;
155 }
156
157 public MethodEntry translateEntry(MethodEntry in) {
158 String name = translate(in);
159 if (name == null) {
160 name = in.getName();
161 }
162 return new MethodEntry(translateEntry(in.getClassEntry()), name, translateSignature(in.getSignature()));
163 }
164
165 public ConstructorEntry translateEntry(ConstructorEntry in) {
166 if (in.isStatic()) {
167 return new ConstructorEntry(translateEntry(in.getClassEntry()));
168 } else {
169 return new ConstructorEntry(translateEntry(in.getClassEntry()), translateSignature(in.getSignature()));
170 }
171 }
172
173 public BehaviorEntry translateEntry(BehaviorEntry in) {
174 if (in instanceof MethodEntry) {
175 return translateEntry((MethodEntry)in);
176 } else if (in instanceof ConstructorEntry) {
177 return translateEntry((ConstructorEntry)in);
178 }
179 throw new Error("Wrong entry type!");
180 }
181
182 public String translate(ArgumentEntry in) {
183
184 // look for the class
185 ClassMapping classMapping = findClassMapping(in.getClassEntry());
186 if (classMapping != null) {
187
188 // look for the method
189 MethodMapping methodMapping = m_direction.choose(
190 classMapping.getMethodByObf(in.getMethodName(), in.getMethodSignature()),
191 classMapping.getMethodByDeobf(in.getMethodName(), translateSignature(in.getMethodSignature()))
192 );
193 if (methodMapping != null) {
194 return m_direction.choose(
195 methodMapping.getDeobfArgumentName(in.getIndex()),
196 methodMapping.getObfArgumentName(in.getIndex())
197 );
198 }
199 }
200 return null;
201 }
202
203 public ArgumentEntry translateEntry(ArgumentEntry in) {
204 String name = translate(in);
205 if (name == null) {
206 name = in.getName();
207 }
208 return new ArgumentEntry(translateEntry(in.getBehaviorEntry()), in.getIndex(), name);
209 }
210
211 public Type translateType(Type type) {
212 return new Type(type, new ClassNameReplacer() {
213 @Override
214 public String replace(String className) {
215 return translateClass(className);
216 }
217 });
218 }
219
220 public Signature translateSignature(Signature signature) {
221 return new Signature(signature, new ClassNameReplacer() {
222 @Override
223 public String replace(String className) {
224 return translateClass(className);
225 }
226 });
227 }
228
229 private ClassMapping findClassMapping(ClassEntry classEntry) {
230 ClassMapping classMapping = m_classes.get(classEntry.getOuterClassName());
231 if (classMapping != null && classEntry.isInnerClass()) {
232 classMapping = m_direction.choose(
233 classMapping.getInnerClassByObf(classEntry.getInnerClassName()),
234 classMapping.getInnerClassByDeobfThenObf(classEntry.getInnerClassName())
235 );
236 }
237 return classMapping;
238 }
239}
diff --git a/src/cuchaz/enigma/mapping/Type.java b/src/cuchaz/enigma/mapping/Type.java
new file mode 100644
index 00000000..9f5d52f0
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/Type.java
@@ -0,0 +1,218 @@
1package cuchaz.enigma.mapping;
2
3import java.util.Map;
4
5import com.google.common.collect.Maps;
6
7public class Type {
8
9 public enum Primitive {
10 Byte('B'),
11 Character('C'),
12 Short('S'),
13 Integer('I'),
14 Long('J'),
15 Float('F'),
16 Double('D'),
17 Boolean('Z');
18
19 private static final Map<Character,Primitive> m_lookup;
20
21 static {
22 m_lookup = Maps.newTreeMap();
23 for (Primitive val : values()) {
24 m_lookup.put(val.getCode(), val);
25 }
26 }
27
28 public static Primitive get(char code) {
29 return m_lookup.get(code);
30 }
31
32 private char m_code;
33
34 private Primitive(char code) {
35 m_code = code;
36 }
37
38 public char getCode() {
39 return m_code;
40 }
41 }
42
43 public static String parseFirst(String in) {
44
45 if (in == null || in.length() <= 0) {
46 throw new IllegalArgumentException("No type to parse, input is empty!");
47 }
48
49 // read one type from the input
50
51 char c = in.charAt(0);
52
53 // first check for void
54 if (c == 'V') {
55 return "V";
56 }
57
58 // then check for primitives
59 Primitive primitive = Primitive.get(c);
60 if (primitive != null) {
61 return in.substring(0, 1);
62 }
63
64 // then check for classes
65 if (c == 'L') {
66 return readClass(in);
67 }
68
69 // then check for arrays
70 int dim = countArrayDimension(in);
71 if (dim > 0) {
72 String arrayType = Type.parseFirst(in.substring(dim));
73 return in.substring(0, dim + arrayType.length());
74 }
75
76 throw new IllegalArgumentException("don't know how to parse: " + in);
77 }
78
79 private String m_name;
80
81 public Type(String name) {
82 m_name = name;
83 }
84
85 public Type(ClassEntry classEntry) {
86 m_name = "L" + classEntry.getClassName() + ";";
87 }
88
89 public Type(Type type, ClassNameReplacer replacer) {
90 m_name = type.m_name;
91 if (type.isClass()) {
92 String replacedName = replacer.replace(type.getClassEntry().getClassName());
93 if (replacedName != null) {
94 m_name = "L" + replacedName + ";";
95 }
96 } else if (type.isArray() && type.hasClass()) {
97 String replacedName = replacer.replace(type.getClassEntry().getClassName());
98 if (replacedName != null) {
99 m_name = Type.getArrayPrefix(type.getArrayDimension()) + "L" + replacedName + ";";
100 }
101 }
102 }
103
104 @Override
105 public String toString() {
106 return m_name;
107 }
108
109 public boolean isVoid() {
110 return m_name.length() == 1 && m_name.charAt(0) == 'V';
111 }
112
113 public boolean isPrimitive() {
114 return m_name.length() == 1 && Primitive.get(m_name.charAt(0)) != null;
115 }
116
117 public Primitive getPrimitive() {
118 if (!isPrimitive()) {
119 throw new IllegalStateException("not a primitive");
120 }
121 return Primitive.get(m_name.charAt(0));
122 }
123
124 public boolean isClass() {
125 return m_name.charAt(0) == 'L' && m_name.charAt(m_name.length() - 1) == ';';
126 }
127
128 public ClassEntry getClassEntry() {
129 if (isClass()) {
130 String name = m_name.substring(1, m_name.length() - 1);
131
132 int pos = name.indexOf('<');
133 if (pos >= 0) {
134 // remove the parameters from the class name
135 name = name.substring(0, pos);
136 }
137
138 return new ClassEntry(name);
139
140 } else if (isArray() && getArrayType().isClass()) {
141 return getArrayType().getClassEntry();
142 } else {
143 throw new IllegalStateException("type doesn't have a class");
144 }
145 }
146
147 public boolean isArray() {
148 return m_name.charAt(0) == '[';
149 }
150
151 public int getArrayDimension() {
152 if (!isArray()) {
153 throw new IllegalStateException("not an array");
154 }
155 return countArrayDimension(m_name);
156 }
157
158 public Type getArrayType() {
159 if (!isArray()) {
160 throw new IllegalStateException("not an array");
161 }
162 return new Type(m_name.substring(getArrayDimension(), m_name.length()));
163 }
164
165 private static String getArrayPrefix(int dimension) {
166 StringBuilder buf = new StringBuilder();
167 for (int i=0; i<dimension; i++) {
168 buf.append("[");
169 }
170 return buf.toString();
171 }
172
173 public boolean hasClass() {
174 return isClass() || (isArray() && getArrayType().hasClass());
175 }
176
177 @Override
178 public boolean equals(Object other) {
179 if (other instanceof Type) {
180 return equals((Type)other);
181 }
182 return false;
183 }
184
185 public boolean equals(Type other) {
186 return m_name.equals(other.m_name);
187 }
188
189 public int hashCode() {
190 return m_name.hashCode();
191 }
192
193 private static int countArrayDimension(String in) {
194 int i=0;
195 for(; i < in.length() && in.charAt(i) == '['; i++);
196 return i;
197 }
198
199 private static String readClass(String in) {
200 // read all the characters in the buffer until we hit a ';'
201 // remember to treat parameters correctly
202 StringBuilder buf = new StringBuilder();
203 int depth = 0;
204 for (int i=0; i<in.length(); i++) {
205 char c = in.charAt(i);
206 buf.append(c);
207
208 if (c == '<') {
209 depth++;
210 } else if (c == '>') {
211 depth--;
212 } else if (depth == 0 && c == ';') {
213 return buf.toString();
214 }
215 }
216 return null;
217 }
218}