summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Thog2017-05-16 00:24:29 +0200
committerGravatar Thog2017-05-16 00:24:29 +0200
commitb280104d2f926ab74772cef2bf1602663cefa312 (patch)
treed130c86a30ec5df37b3a9c4bab576e971ae2e664
parentAdd offset for Enum constructor arguments (Fix #58) (diff)
downloadenigma-b280104d2f926ab74772cef2bf1602663cefa312.tar.gz
enigma-b280104d2f926ab74772cef2bf1602663cefa312.tar.xz
enigma-b280104d2f926ab74772cef2bf1602663cefa312.zip
Remove the converter + some reorganization
-rw-r--r--build.gradle1
-rw-r--r--proguard-build.conf1
-rw-r--r--src/main/java/cuchaz/enigma/ConvertMain.java357
-rw-r--r--src/main/java/cuchaz/enigma/TranslatingTypeLoader.java19
-rw-r--r--src/main/java/cuchaz/enigma/analysis/BridgeMarker.java10
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/translators/ClassTranslator.java (renamed from src/main/java/cuchaz/enigma/bytecode/ClassTranslator.java)38
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/translators/InnerClassWriter.java (renamed from src/main/java/cuchaz/enigma/bytecode/InnerClassWriter.java)27
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableTranslator.java (renamed from src/main/java/cuchaz/enigma/bytecode/LocalVariableRenamer.java)30
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/translators/MethodParameterTranslator.java (renamed from src/main/java/cuchaz/enigma/bytecode/MethodParameterWriter.java)15
-rw-r--r--src/main/java/cuchaz/enigma/convert/ClassForest.java59
-rw-r--r--src/main/java/cuchaz/enigma/convert/ClassIdentifier.java54
-rw-r--r--src/main/java/cuchaz/enigma/convert/ClassIdentity.java439
-rw-r--r--src/main/java/cuchaz/enigma/convert/ClassMatch.java83
-rw-r--r--src/main/java/cuchaz/enigma/convert/ClassMatches.java158
-rw-r--r--src/main/java/cuchaz/enigma/convert/ClassMatching.java153
-rw-r--r--src/main/java/cuchaz/enigma/convert/ClassNamer.java56
-rw-r--r--src/main/java/cuchaz/enigma/convert/FieldMatches.java150
-rw-r--r--src/main/java/cuchaz/enigma/convert/MappingsConverter.java711
-rw-r--r--src/main/java/cuchaz/enigma/convert/MatchesReader.java105
-rw-r--r--src/main/java/cuchaz/enigma/convert/MatchesWriter.java123
-rw-r--r--src/main/java/cuchaz/enigma/convert/MemberMatches.java179
-rw-r--r--src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java536
-rw-r--r--src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java435
23 files changed, 55 insertions, 3684 deletions
diff --git a/build.gradle b/build.gradle
index f3da0946..ce19a4c7 100644
--- a/build.gradle
+++ b/build.gradle
@@ -154,7 +154,6 @@ task('libJar', type: Jar, dependsOn: classes) {
154 // Main classes + inner classes (keep CommandMain) 154 // Main classes + inner classes (keep CommandMain)
155 exclude 'cuchaz/enigma/Main.class' 155 exclude 'cuchaz/enigma/Main.class'
156 exclude 'cuchaz/enigma/Main.class' 156 exclude 'cuchaz/enigma/Main.class'
157 exclude 'cuchaz/enigma/ConvertMain*.class'
158 } 157 }
159} 158}
160 159
diff --git a/proguard-build.conf b/proguard-build.conf
index 195b84db..117180e6 100644
--- a/proguard-build.conf
+++ b/proguard-build.conf
@@ -4,5 +4,4 @@
4-dontwarn 4-dontwarn
5-keep class cuchaz.enigma.Main { static void main(java.lang.String[]); } 5-keep class cuchaz.enigma.Main { static void main(java.lang.String[]); }
6-keep class cuchaz.enigma.CommandMain { static void main(java.lang.String[]); } 6-keep class cuchaz.enigma.CommandMain { static void main(java.lang.String[]); }
7-keep class cuchaz.enigma.ConvertMain { static void main(java.lang.String[]); }
8-keep class de.sciss.syntaxpane.** { *; } \ No newline at end of file 7-keep class de.sciss.syntaxpane.** { *; } \ No newline at end of file
diff --git a/src/main/java/cuchaz/enigma/ConvertMain.java b/src/main/java/cuchaz/enigma/ConvertMain.java
deleted file mode 100644
index 3d58f57c..00000000
--- a/src/main/java/cuchaz/enigma/ConvertMain.java
+++ /dev/null
@@ -1,357 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma;
13
14import cuchaz.enigma.convert.*;
15import cuchaz.enigma.gui.ClassMatchingGui;
16import cuchaz.enigma.gui.MemberMatchingGui;
17import cuchaz.enigma.mapping.*;
18import cuchaz.enigma.throwables.MappingConflict;
19import cuchaz.enigma.throwables.MappingParseException;
20
21import java.io.File;
22import java.io.IOException;
23import java.util.jar.JarFile;
24
25public class ConvertMain {
26
27 public static void main(String[] args)
28 throws IOException, MappingParseException {
29 try {
30 //Get all are args
31 String JarOld = getArg(args, 1, "Path to Old Jar", true);
32 String JarNew = getArg(args, 2, "Path to New Jar", true);
33 String OldMappings = getArg(args, 3, "Path to old .mappings file", true);
34 String NewMappings = getArg(args, 4, "Path to new .mappings file", true);
35 String ClassMatches = getArg(args, 5, "Path to Class .matches file", true);
36 String FieldMatches = getArg(args, 6, "Path to Field .matches file", true);
37 String MethodMatches = getArg(args, 7, "Path to Method .matches file", true);
38 //OldJar
39 JarFile sourceJar = new JarFile(new File(JarOld));
40 //NewJar
41 JarFile destJar = new JarFile(new File(JarNew));
42 //Get the mapping files
43 File inMappingsFile = new File(OldMappings);
44 File outMappingsFile = new File(NewMappings);
45 Mappings mappings = new MappingsEnigmaReader().read(inMappingsFile);
46 //Make the Match Files..
47 File classMatchesFile = new File(ClassMatches);
48 File fieldMatchesFile = new File(FieldMatches);
49 File methodMatchesFile = new File(MethodMatches);
50
51 String command = getArg(args, 0, "command", true);
52
53 if (command.equalsIgnoreCase("computeClassMatches")) {
54 computeClassMatches(classMatchesFile, sourceJar, destJar, mappings);
55 convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile);
56 } else if (command.equalsIgnoreCase("editClassMatches")) {
57 editClasssMatches(classMatchesFile, sourceJar, destJar, mappings);
58 convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile);
59 } else if (command.equalsIgnoreCase("computeFieldMatches")) {
60 computeFieldMatches(fieldMatchesFile, destJar, outMappingsFile, classMatchesFile);
61 convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile);
62 } else if (command.equalsIgnoreCase("editFieldMatches")) {
63 editFieldMatches(sourceJar, destJar, outMappingsFile, mappings, classMatchesFile, fieldMatchesFile);
64 convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile);
65 } else if (command.equalsIgnoreCase("computeMethodMatches")) {
66 computeMethodMatches(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile, methodMatchesFile);
67 convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile, methodMatchesFile);
68 } else if (command.equalsIgnoreCase("editMethodMatches")) {
69 editMethodMatches(sourceJar, destJar, outMappingsFile, mappings, classMatchesFile, methodMatchesFile);
70 convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile, methodMatchesFile);
71 } else if (command.equalsIgnoreCase("convertMappings")) {
72 convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile, methodMatchesFile);
73 }
74 } catch (MappingConflict ex) {
75 System.out.println(ex.getMessage());
76 ex.printStackTrace();
77 } catch (IllegalArgumentException ex) {
78 System.out.println(ex.getMessage());
79 printHelp();
80 }
81 }
82
83 private static void printHelp() {
84 System.out.println(String.format("%s - %s", Constants.NAME, Constants.VERSION));
85 System.out.println("Usage:");
86 System.out.println("\tjava -cp enigma.jar cuchaz.enigma.ConvertMain <command> <old-jar> <new-jar> <old-mappings> <new-mappings> <class-matches> <field-matches> <method-matches>");
87 System.out.println("\tWhere <command> is one of:");
88 System.out.println("\t\tcomputeClassMatches");
89 System.out.println("\t\teditClassMatches");
90 System.out.println("\t\tcomputeFieldMatches");
91 System.out.println("\t\teditFieldMatches");
92 System.out.println("\t\teditMethodMatches");
93 System.out.println("\t\tconvertMappings");
94 System.out.println("\tWhere <old-jar> is the already mapped jar.");
95 System.out.println("\tWhere <new-jar> is the unmapped jar.");
96 System.out.println("\tWhere <old-mappings> is the path to the mappings for the old jar.");
97 System.out.println("\tWhere <new-mappings> is the new mappings. (Where you want to save them and there name)");
98 System.out.println("\tWhere <class-matches> is the class matches file.");
99 System.out.println("\tWhere <field-matches> is the field matches file.");
100 System.out.println("\tWhere <method-matches> is the method matches file.");
101 }
102
103 //Copy of getArg from CommandMain.... Should make a utils class.
104 private static String getArg(String[] args, int i, String name, boolean required) {
105 if (i >= args.length) {
106 if (required) {
107 throw new IllegalArgumentException(name + " is required");
108 } else {
109 return null;
110 }
111 }
112 return args[i];
113 }
114
115 private static void computeClassMatches(File classMatchesFile, JarFile sourceJar, JarFile destJar, Mappings mappings)
116 throws IOException {
117 ClassMatches classMatches = MappingsConverter.computeClassMatches(sourceJar, destJar, mappings);
118 MatchesWriter.writeClasses(classMatches, classMatchesFile);
119 System.out.println("Wrote:\n\t" + classMatchesFile.getAbsolutePath());
120 }
121
122 private static void editClasssMatches(final File classMatchesFile, JarFile sourceJar, JarFile destJar, Mappings mappings)
123 throws IOException {
124 System.out.println("Reading class matches...");
125 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
126 Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
127 deobfuscators.source.setMappings(mappings);
128 System.out.println("Starting GUI...");
129 new ClassMatchingGui(classMatches, deobfuscators.source, deobfuscators.dest).setSaveListener(matches ->
130 {
131 try {
132 MatchesWriter.writeClasses(matches, classMatchesFile);
133 } catch (IOException ex) {
134 throw new Error(ex);
135 }
136 });
137 }
138
139 @SuppressWarnings("unused")
140 private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchesFile)
141 throws IOException, MappingConflict {
142 System.out.println("Reading class matches...");
143 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
144 Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
145 deobfuscators.source.setMappings(mappings);
146
147 Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.dest);
148 new MappingsEnigmaWriter().write(outMappingsFile, newMappings, true);
149 System.out.println("Write converted mappings to: " + outMappingsFile.getAbsolutePath());
150 }
151
152 private static void computeFieldMatches(File memberMatchesFile, JarFile destJar, File destMappingsFile, File classMatchesFile)
153 throws IOException, MappingParseException {
154
155 System.out.println("Reading class matches...");
156 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
157 System.out.println("Reading mappings...");
158 Mappings destMappings = new MappingsEnigmaReader().read(destMappingsFile);
159 System.out.println("Indexing dest jar...");
160 Deobfuscator destDeobfuscator = new Deobfuscator(destJar);
161
162 System.out.println("Writing matches...");
163
164 // get the matched and unmatched mappings
165 MemberMatches<FieldEntry> fieldMatches = MappingsConverter.computeMemberMatches(
166 destDeobfuscator,
167 destMappings,
168 classMatches,
169 MappingsConverter.getFieldDoer()
170 );
171
172 MatchesWriter.writeMembers(fieldMatches, memberMatchesFile);
173 System.out.println("Wrote:\n\t" + memberMatchesFile.getAbsolutePath());
174 }
175
176 private static void editFieldMatches(JarFile sourceJar, JarFile destJar, File destMappingsFile, Mappings sourceMappings, File classMatchesFile, final File fieldMatchesFile)
177 throws IOException, MappingParseException {
178
179 System.out.println("Reading matches...");
180 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
181 MemberMatches<FieldEntry> fieldMatches = MatchesReader.readMembers(fieldMatchesFile);
182
183 // prep deobfuscators
184 Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
185 deobfuscators.source.setMappings(sourceMappings);
186 Mappings destMappings = new MappingsEnigmaReader().read(destMappingsFile);
187 MappingsChecker checker = new MappingsChecker(deobfuscators.dest.getJarIndex());
188 checker.dropBrokenMappings(destMappings);
189 deobfuscators.dest.setMappings(destMappings);
190
191 new MemberMatchingGui<>(classMatches, fieldMatches, deobfuscators.source, deobfuscators.dest).setSaveListener(
192 matches ->
193 {
194 try {
195 MatchesWriter.writeMembers(matches, fieldMatchesFile);
196 } catch (IOException ex) {
197 throw new Error(ex);
198 }
199 });
200 }
201
202 @SuppressWarnings("unused")
203 private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchesFile, File fieldMatchesFile)
204 throws IOException, MappingConflict {
205
206 System.out.println("Reading matches...");
207 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
208 MemberMatches<FieldEntry> fieldMatches = MatchesReader.readMembers(fieldMatchesFile);
209
210 Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
211 deobfuscators.source.setMappings(mappings);
212
213 // apply matches
214 Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.dest);
215 MappingsConverter.applyMemberMatches(newMappings, classMatches, fieldMatches, MappingsConverter.getFieldDoer());
216
217 // write out the converted mappings
218
219 new MappingsEnigmaWriter().write(outMappingsFile, newMappings, true);
220 System.out.println("Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath());
221 }
222
223 private static void computeMethodMatches(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings sourceMappings, File classMatchesFile, File fieldMatchesFile, File methodMatchesFile)
224 throws IOException, MappingParseException {
225
226 System.out.println("Reading class matches...");
227 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
228 System.out.println("Reading dest mappings...");
229 Mappings destMappings = new MappingsEnigmaReader().read(outMappingsFile);
230 System.out.println("Indexing dest jar...");
231 Deobfuscator destDeobfuscator = new Deobfuscator(destJar);
232 System.out.println("Indexing source jar...");
233 Deobfuscator sourceDeobfuscator = new Deobfuscator(sourceJar);
234
235 System.out.println("Writing method matches...");
236
237 // get the matched and unmatched mappings
238 MemberMatches<BehaviorEntry> methodMatches = MappingsConverter.computeMethodsMatches(
239 destDeobfuscator,
240 destMappings,
241 sourceDeobfuscator,
242 sourceMappings,
243 classMatches,
244 MappingsConverter.getMethodDoer()
245 );
246
247 MatchesWriter.writeMembers(methodMatches, methodMatchesFile);
248 System.out.println("Wrote:\n\t" + methodMatchesFile.getAbsolutePath());
249 }
250
251 private static void editMethodMatches(JarFile sourceJar, JarFile destJar, File destMappingsFile, Mappings sourceMappings, File classMatchesFile, final File methodMatchesFile)
252 throws IOException, MappingParseException {
253
254 System.out.println("Reading matches...");
255 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
256 MemberMatches<BehaviorEntry> methodMatches = MatchesReader.readMembers(methodMatchesFile);
257
258 // prep deobfuscators
259 Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
260 deobfuscators.source.setMappings(sourceMappings);
261 Mappings destMappings = new MappingsEnigmaReader().read(destMappingsFile);
262 MappingsChecker checker = new MappingsChecker(deobfuscators.dest.getJarIndex());
263 checker.dropBrokenMappings(destMappings);
264 deobfuscators.dest.setMappings(destMappings);
265
266 new MemberMatchingGui<>(classMatches, methodMatches, deobfuscators.source, deobfuscators.dest).setSaveListener(
267 matches ->
268 {
269 try {
270 MatchesWriter.writeMembers(matches, methodMatchesFile);
271 } catch (IOException ex) {
272 throw new Error(ex);
273 }
274 });
275 }
276
277 private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchesFile, File fieldMatchesFile, File methodMatchesFile)
278 throws IOException, MappingConflict {
279
280 System.out.println("Reading matches...");
281 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
282 MemberMatches<FieldEntry> fieldMatches = MatchesReader.readMembers(fieldMatchesFile);
283 MemberMatches<BehaviorEntry> methodMatches = MatchesReader.readMembers(methodMatchesFile);
284
285 Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
286 deobfuscators.source.setMappings(mappings);
287
288 // apply matches
289 Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.dest);
290 MappingsConverter.applyMemberMatches(newMappings, classMatches, fieldMatches, MappingsConverter.getFieldDoer());
291 MappingsConverter.applyMemberMatches(newMappings, classMatches, methodMatches, MappingsConverter.getMethodDoer());
292
293 // check the final mappings
294 MappingsChecker checker = new MappingsChecker(deobfuscators.dest.getJarIndex());
295 checker.dropBrokenMappings(newMappings);
296
297 for (java.util.Map.Entry<ClassEntry, ClassMapping> mapping : checker.getDroppedClassMappings().entrySet()) {
298 System.out.println("WARNING: Broken class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")");
299 }
300 for (java.util.Map.Entry<ClassEntry, ClassMapping> mapping : checker.getDroppedInnerClassMappings().entrySet()) {
301 System.out.println("WARNING: Broken inner class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")");
302 }
303 for (java.util.Map.Entry<FieldEntry, FieldMapping> mapping : checker.getDroppedFieldMappings().entrySet()) {
304 System.out.println("WARNING: Broken field entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")");
305 }
306 for (java.util.Map.Entry<BehaviorEntry, MethodMapping> mapping : checker.getDroppedMethodMappings().entrySet()) {
307 System.out.println("WARNING: Broken behavior entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")");
308 }
309
310 // write out the converted mappings
311 new MappingsEnigmaWriter().write(outMappingsFile, newMappings, true);
312 System.out.println("Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath());
313 }
314
315 private static class Deobfuscators {
316
317 public Deobfuscator source;
318 public Deobfuscator dest;
319
320 public Deobfuscators(JarFile sourceJar, JarFile destJar) {
321 System.out.println("Indexing source jar...");
322 IndexerThread sourceIndexer = new IndexerThread(sourceJar);
323 sourceIndexer.start();
324 System.out.println("Indexing dest jar...");
325 IndexerThread destIndexer = new IndexerThread(destJar);
326 destIndexer.start();
327 sourceIndexer.joinOrBail();
328 destIndexer.joinOrBail();
329 source = sourceIndexer.deobfuscator;
330 dest = destIndexer.deobfuscator;
331 }
332 }
333
334 private static class IndexerThread extends Thread {
335
336 public Deobfuscator deobfuscator;
337 private JarFile jarFile;
338
339 public IndexerThread(JarFile jarFile) {
340 this.jarFile = jarFile;
341 deobfuscator = null;
342 }
343
344 public void joinOrBail() {
345 try {
346 join();
347 } catch (InterruptedException ex) {
348 throw new Error(ex);
349 }
350 }
351
352 @Override
353 public void run() {
354 deobfuscator = new Deobfuscator(jarFile);
355 }
356 }
357} \ No newline at end of file
diff --git a/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java b/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java
index 7304f722..2a2041a0 100644
--- a/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java
+++ b/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java
@@ -18,10 +18,10 @@ import com.strobel.assembler.metadata.ClasspathTypeLoader;
18import com.strobel.assembler.metadata.ITypeLoader; 18import com.strobel.assembler.metadata.ITypeLoader;
19import cuchaz.enigma.analysis.BridgeMarker; 19import cuchaz.enigma.analysis.BridgeMarker;
20import cuchaz.enigma.analysis.JarIndex; 20import cuchaz.enigma.analysis.JarIndex;
21import cuchaz.enigma.bytecode.ClassTranslator; 21import cuchaz.enigma.bytecode.translators.ClassTranslator;
22import cuchaz.enigma.bytecode.InnerClassWriter; 22import cuchaz.enigma.bytecode.translators.InnerClassWriter;
23import cuchaz.enigma.bytecode.LocalVariableRenamer; 23import cuchaz.enigma.bytecode.translators.LocalVariableTranslator;
24import cuchaz.enigma.bytecode.MethodParameterWriter; 24import cuchaz.enigma.bytecode.translators.MethodParameterTranslator;
25import cuchaz.enigma.mapping.ClassEntry; 25import cuchaz.enigma.mapping.ClassEntry;
26import cuchaz.enigma.mapping.Translator; 26import cuchaz.enigma.mapping.Translator;
27import javassist.*; 27import javassist.*;
@@ -51,6 +51,7 @@ public class TranslatingTypeLoader implements ITypeLoader {
51 this.deobfuscatingTranslator = deobfuscatingTranslator; 51 this.deobfuscatingTranslator = deobfuscatingTranslator;
52 this.cache = Maps.newHashMap(); 52 this.cache = Maps.newHashMap();
53 this.defaultTypeLoader = new ClasspathTypeLoader(); 53 this.defaultTypeLoader = new ClasspathTypeLoader();
54
54 } 55 }
55 56
56 public void clearCache() { 57 public void clearCache() {
@@ -200,7 +201,7 @@ public class TranslatingTypeLoader implements ITypeLoader {
200 throws IOException, NotFoundException, CannotCompileException { 201 throws IOException, NotFoundException, CannotCompileException {
201 202
202 // reconstruct inner classes 203 // reconstruct inner classes
203 new InnerClassWriter(this.jarIndex, this.deobfuscatingTranslator).write(c); 204 InnerClassWriter.write(jarIndex, c);
204 205
205 // re-get the javassist handle since we changed class names 206 // re-get the javassist handle since we changed class names
206 ClassEntry obfClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); 207 ClassEntry obfClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
@@ -213,10 +214,10 @@ public class TranslatingTypeLoader implements ITypeLoader {
213 assertClassName(c, obfClassEntry); 214 assertClassName(c, obfClassEntry);
214 215
215 // do all kinds of deobfuscating transformations on the class 216 // do all kinds of deobfuscating transformations on the class
216 new BridgeMarker(this.jarIndex).markBridges(c); 217 BridgeMarker.markBridges(this.jarIndex, c);
217 new MethodParameterWriter(this.deobfuscatingTranslator).writeMethodArguments(c); 218 MethodParameterTranslator.translate(this.deobfuscatingTranslator, c);
218 new LocalVariableRenamer(this.deobfuscatingTranslator).rename(c); 219 LocalVariableTranslator.translate(this.deobfuscatingTranslator, c);
219 new ClassTranslator(this.deobfuscatingTranslator).translate(c); 220 ClassTranslator.translate(this.deobfuscatingTranslator, c);
220 221
221 return c; 222 return c;
222 } 223 }
diff --git a/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java b/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java
index 81e750c1..a2f1f909 100644
--- a/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java
+++ b/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java
@@ -19,19 +19,13 @@ import javassist.bytecode.AccessFlag;
19 19
20public class BridgeMarker { 20public class BridgeMarker {
21 21
22 private JarIndex jarIndex; 22 public static void markBridges(JarIndex jarIndex, CtClass c) {
23
24 public BridgeMarker(JarIndex jarIndex) {
25 this.jarIndex = jarIndex;
26 }
27
28 public void markBridges(CtClass c) {
29 23
30 for (CtMethod method : c.getDeclaredMethods()) { 24 for (CtMethod method : c.getDeclaredMethods()) {
31 MethodEntry methodEntry = EntryFactory.getMethodEntry(method); 25 MethodEntry methodEntry = EntryFactory.getMethodEntry(method);
32 26
33 // is this a bridge method? 27 // is this a bridge method?
34 MethodEntry bridgedMethodEntry = this.jarIndex.getBridgedMethod(methodEntry); 28 MethodEntry bridgedMethodEntry = jarIndex.getBridgedMethod(methodEntry);
35 if (bridgedMethodEntry != null) { 29 if (bridgedMethodEntry != null) {
36 30
37 // it's a bridge method! add the bridge flag 31 // it's a bridge method! add the bridge flag
diff --git a/src/main/java/cuchaz/enigma/bytecode/ClassTranslator.java b/src/main/java/cuchaz/enigma/bytecode/translators/ClassTranslator.java
index 1ebf6561..4ac5a8b0 100644
--- a/src/main/java/cuchaz/enigma/bytecode/ClassTranslator.java
+++ b/src/main/java/cuchaz/enigma/bytecode/translators/ClassTranslator.java
@@ -9,8 +9,10 @@
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11 11
12package cuchaz.enigma.bytecode; 12package cuchaz.enigma.bytecode.translators;
13 13
14import cuchaz.enigma.bytecode.ClassRenamer;
15import cuchaz.enigma.bytecode.ConstPoolEditor;
14import cuchaz.enigma.mapping.*; 16import cuchaz.enigma.mapping.*;
15import javassist.CtBehavior; 17import javassist.CtBehavior;
16import javassist.CtClass; 18import javassist.CtClass;
@@ -20,13 +22,7 @@ import javassist.bytecode.*;
20 22
21public class ClassTranslator { 23public class ClassTranslator {
22 24
23 private Translator translator; 25 public static void translate(Translator translator, CtClass c) {
24
25 public ClassTranslator(Translator translator) {
26 this.translator = translator;
27 }
28
29 public void translate(CtClass c) {
30 26
31 // NOTE: the order of these translations is very important 27 // NOTE: the order of these translations is very important
32 28
@@ -44,7 +40,7 @@ public class ClassTranslator {
44 constants.getFieldrefName(i), 40 constants.getFieldrefName(i),
45 constants.getFieldrefType(i) 41 constants.getFieldrefType(i)
46 ); 42 );
47 FieldEntry translatedEntry = this.translator.translateEntry(entry); 43 FieldEntry translatedEntry = translator.translateEntry(entry);
48 if (!entry.equals(translatedEntry)) { 44 if (!entry.equals(translatedEntry)) {
49 editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedEntry.getType().toString()); 45 editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedEntry.getType().toString());
50 } 46 }
@@ -60,7 +56,7 @@ public class ClassTranslator {
60 editor.getMemberrefName(i), 56 editor.getMemberrefName(i),
61 editor.getMemberrefType(i) 57 editor.getMemberrefType(i)
62 ); 58 );
63 BehaviorEntry translatedEntry = this.translator.translateEntry(entry); 59 BehaviorEntry translatedEntry = translator.translateEntry(entry);
64 if (!entry.equals(translatedEntry)) { 60 if (!entry.equals(translatedEntry)) {
65 editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedEntry.getSignature().toString()); 61 editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedEntry.getSignature().toString());
66 } 62 }
@@ -72,7 +68,7 @@ public class ClassTranslator {
72 } 68 }
73 69
74 ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); 70 ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
75 Mappings.EntryModifier modifier = this.translator.getModifier(classEntry); 71 Mappings.EntryModifier modifier = translator.getModifier(classEntry);
76 if (modifier != null && modifier != Mappings.EntryModifier.UNCHANGED) 72 if (modifier != null && modifier != Mappings.EntryModifier.UNCHANGED)
77 ClassRenamer.applyModifier(c, modifier); 73 ClassRenamer.applyModifier(c, modifier);
78 74
@@ -81,8 +77,8 @@ public class ClassTranslator {
81 77
82 // translate the name 78 // translate the name
83 FieldEntry entry = EntryFactory.getFieldEntry(field); 79 FieldEntry entry = EntryFactory.getFieldEntry(field);
84 String translatedName = this.translator.translate(entry); 80 String translatedName = translator.translate(entry);
85 modifier = this.translator.getModifier(entry); 81 modifier = translator.getModifier(entry);
86 if (modifier != null && modifier != Mappings.EntryModifier.UNCHANGED) 82 if (modifier != null && modifier != Mappings.EntryModifier.UNCHANGED)
87 ClassRenamer.applyModifier(field, modifier); 83 ClassRenamer.applyModifier(field, modifier);
88 84
@@ -91,7 +87,7 @@ public class ClassTranslator {
91 } 87 }
92 88
93 // translate the type 89 // translate the type
94 Type translatedType = this.translator.translateType(entry.getType()); 90 Type translatedType = translator.translateType(entry.getType());
95 field.getFieldInfo().setDescriptor(translatedType.toString()); 91 field.getFieldInfo().setDescriptor(translatedType.toString());
96 } 92 }
97 93
@@ -100,7 +96,7 @@ public class ClassTranslator {
100 96
101 BehaviorEntry entry = EntryFactory.getBehaviorEntry(behavior); 97 BehaviorEntry entry = EntryFactory.getBehaviorEntry(behavior);
102 98
103 modifier = this.translator.getModifier(entry); 99 modifier = translator.getModifier(entry);
104 if (modifier != null && modifier != Mappings.EntryModifier.UNCHANGED) 100 if (modifier != null && modifier != Mappings.EntryModifier.UNCHANGED)
105 ClassRenamer.applyModifier(behavior, modifier); 101 ClassRenamer.applyModifier(behavior, modifier);
106 102
@@ -108,7 +104,7 @@ public class ClassTranslator {
108 CtMethod method = (CtMethod) behavior; 104 CtMethod method = (CtMethod) behavior;
109 105
110 // translate the name 106 // translate the name
111 String translatedName = this.translator.translate(entry); 107 String translatedName = translator.translate(entry);
112 if (translatedName != null) { 108 if (translatedName != null) {
113 method.setName(translatedName); 109 method.setName(translatedName);
114 } 110 }
@@ -116,7 +112,7 @@ public class ClassTranslator {
116 112
117 if (entry.getSignature() != null) { 113 if (entry.getSignature() != null) {
118 // translate the signature 114 // translate the signature
119 Signature translatedSignature = this.translator.translateSignature(entry.getSignature()); 115 Signature translatedSignature = translator.translateSignature(entry.getSignature());
120 behavior.getMethodInfo().setDescriptor(translatedSignature.toString()); 116 behavior.getMethodInfo().setDescriptor(translatedSignature.toString());
121 } 117 }
122 } 118 }
@@ -127,7 +123,7 @@ public class ClassTranslator {
127 123
128 if (enclosingMethodAttr.methodIndex() == 0) { 124 if (enclosingMethodAttr.methodIndex() == 0) {
129 BehaviorEntry obfBehaviorEntry = EntryFactory.getBehaviorEntry(Descriptor.toJvmName(enclosingMethodAttr.className())); 125 BehaviorEntry obfBehaviorEntry = EntryFactory.getBehaviorEntry(Descriptor.toJvmName(enclosingMethodAttr.className()));
130 BehaviorEntry deobfBehaviorEntry = this.translator.translateEntry(obfBehaviorEntry); 126 BehaviorEntry deobfBehaviorEntry = translator.translateEntry(obfBehaviorEntry);
131 c.getClassFile().addAttribute(new EnclosingMethodAttribute( 127 c.getClassFile().addAttribute(new EnclosingMethodAttribute(
132 constants, 128 constants,
133 deobfBehaviorEntry.getClassName() 129 deobfBehaviorEntry.getClassName()
@@ -138,7 +134,7 @@ public class ClassTranslator {
138 enclosingMethodAttr.methodName(), 134 enclosingMethodAttr.methodName(),
139 enclosingMethodAttr.methodDescriptor() 135 enclosingMethodAttr.methodDescriptor()
140 ); 136 );
141 BehaviorEntry deobfBehaviorEntry = this.translator.translateEntry(obfBehaviorEntry); 137 BehaviorEntry deobfBehaviorEntry = translator.translateEntry(obfBehaviorEntry);
142 c.getClassFile().addAttribute(new EnclosingMethodAttribute( 138 c.getClassFile().addAttribute(new EnclosingMethodAttribute(
143 constants, 139 constants,
144 deobfBehaviorEntry.getClassName(), 140 deobfBehaviorEntry.getClassName(),
@@ -150,10 +146,10 @@ public class ClassTranslator {
150 146
151 // translate all the class names referenced in the code 147 // translate all the class names referenced in the code
152 // the above code only changed method/field/reference names and types, but not the rest of the class references 148 // the above code only changed method/field/reference names and types, but not the rest of the class references
153 ClassRenamer.renameClasses(c, this.translator); 149 ClassRenamer.renameClasses(c, translator);
154 150
155 // translate the source file attribute too 151 // translate the source file attribute too
156 ClassEntry deobfClassEntry = this.translator.translateEntry(classEntry); 152 ClassEntry deobfClassEntry = translator.translateEntry(classEntry);
157 if (deobfClassEntry != null) { 153 if (deobfClassEntry != null) {
158 String sourceFile = Descriptor.toJvmName(deobfClassEntry.getOutermostClassEntry().getSimpleName()) + ".java"; 154 String sourceFile = Descriptor.toJvmName(deobfClassEntry.getOutermostClassEntry().getSimpleName()) + ".java";
159 c.getClassFile().addAttribute(new SourceFileAttribute(constants, sourceFile)); 155 c.getClassFile().addAttribute(new SourceFileAttribute(constants, sourceFile));
diff --git a/src/main/java/cuchaz/enigma/bytecode/InnerClassWriter.java b/src/main/java/cuchaz/enigma/bytecode/translators/InnerClassWriter.java
index f1c3dd77..0e359386 100644
--- a/src/main/java/cuchaz/enigma/bytecode/InnerClassWriter.java
+++ b/src/main/java/cuchaz/enigma/bytecode/translators/InnerClassWriter.java
@@ -9,10 +9,11 @@
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11 11
12package cuchaz.enigma.bytecode; 12package cuchaz.enigma.bytecode.translators;
13 13
14import com.google.common.collect.Lists; 14import com.google.common.collect.Lists;
15import cuchaz.enigma.analysis.JarIndex; 15import cuchaz.enigma.analysis.JarIndex;
16import cuchaz.enigma.bytecode.ClassRenamer;
16import cuchaz.enigma.mapping.*; 17import cuchaz.enigma.mapping.*;
17import javassist.ClassPool; 18import javassist.ClassPool;
18import javassist.CtClass; 19import javassist.CtClass;
@@ -24,14 +25,6 @@ import java.util.List;
24 25
25public class InnerClassWriter { 26public class InnerClassWriter {
26 27
27 private JarIndex index;
28 private Translator deobfuscatorTranslator;
29
30 public InnerClassWriter(JarIndex index, Translator deobfuscatorTranslator) {
31 this.index = index;
32 this.deobfuscatorTranslator = deobfuscatorTranslator;
33 }
34
35 // FIXME: modifier is not applied to inner class 28 // FIXME: modifier is not applied to inner class
36 public static void changeModifier(CtClass c, InnerClassesAttribute attr, Translator translator) { 29 public static void changeModifier(CtClass c, InnerClassesAttribute attr, Translator translator) {
37 ClassPool pool = c.getClassPool(); 30 ClassPool pool = c.getClassPool();
@@ -52,7 +45,7 @@ public class InnerClassWriter {
52 } 45 }
53 } 46 }
54 47
55 public void write(CtClass c) { 48 public static void write(JarIndex index, CtClass c) {
56 49
57 // don't change anything if there's already an attribute there 50 // don't change anything if there's already an attribute there
58 InnerClassesAttribute oldAttr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag); 51 InnerClassesAttribute oldAttr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag);
@@ -62,7 +55,7 @@ public class InnerClassWriter {
62 } 55 }
63 56
64 ClassEntry obfClassEntry = EntryFactory.getClassEntry(c); 57 ClassEntry obfClassEntry = EntryFactory.getClassEntry(c);
65 List<ClassEntry> obfClassChain = this.index.getObfClassChain(obfClassEntry); 58 List<ClassEntry> obfClassChain = index.getObfClassChain(obfClassEntry);
66 59
67 boolean isInnerClass = obfClassChain.size() > 1; 60 boolean isInnerClass = obfClassChain.size() > 1;
68 if (isInnerClass) { 61 if (isInnerClass) {
@@ -70,7 +63,7 @@ public class InnerClassWriter {
70 // it's an inner class, rename it to the fully qualified name 63 // it's an inner class, rename it to the fully qualified name
71 c.setName(obfClassEntry.buildClassEntry(obfClassChain).getName()); 64 c.setName(obfClassEntry.buildClassEntry(obfClassChain).getName());
72 65
73 BehaviorEntry caller = this.index.getAnonymousClassCaller(obfClassEntry); 66 BehaviorEntry caller = index.getAnonymousClassCaller(obfClassEntry);
74 if (caller != null) { 67 if (caller != null) {
75 68
76 // write the enclosing method attribute 69 // write the enclosing method attribute
@@ -83,7 +76,7 @@ public class InnerClassWriter {
83 } 76 }
84 77
85 // does this class have any inner classes? 78 // does this class have any inner classes?
86 Collection<ClassEntry> obfInnerClassEntries = this.index.getInnerClasses(obfClassEntry); 79 Collection<ClassEntry> obfInnerClassEntries = index.getInnerClasses(obfClassEntry);
87 80
88 if (isInnerClass || !obfInnerClassEntries.isEmpty()) { 81 if (isInnerClass || !obfInnerClassEntries.isEmpty()) {
89 82
@@ -94,7 +87,7 @@ public class InnerClassWriter {
94 // write the ancestry, but not the outermost class 87 // write the ancestry, but not the outermost class
95 for (int i = 1; i < obfClassChain.size(); i++) { 88 for (int i = 1; i < obfClassChain.size(); i++) {
96 ClassEntry obfInnerClassEntry = obfClassChain.get(i); 89 ClassEntry obfInnerClassEntry = obfClassChain.get(i);
97 writeInnerClass(attr, obfClassChain, obfInnerClassEntry); 90 writeInnerClass(index, attr, obfClassChain, obfInnerClassEntry);
98 91
99 // update references to use the fully qualified inner class name 92 // update references to use the fully qualified inner class name
100 c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(obfClassChain).getName()); 93 c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(obfClassChain).getName());
@@ -107,7 +100,7 @@ public class InnerClassWriter {
107 List<ClassEntry> extendedObfClassChain = Lists.newArrayList(obfClassChain); 100 List<ClassEntry> extendedObfClassChain = Lists.newArrayList(obfClassChain);
108 extendedObfClassChain.add(obfInnerClassEntry); 101 extendedObfClassChain.add(obfInnerClassEntry);
109 102
110 writeInnerClass(attr, extendedObfClassChain, obfInnerClassEntry); 103 writeInnerClass(index, attr, extendedObfClassChain, obfInnerClassEntry);
111 104
112 // update references to use the fully qualified inner class name 105 // update references to use the fully qualified inner class name
113 c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(extendedObfClassChain).getName()); 106 c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(extendedObfClassChain).getName());
@@ -115,7 +108,7 @@ public class InnerClassWriter {
115 } 108 }
116 } 109 }
117 110
118 private void writeInnerClass(InnerClassesAttribute attr, List<ClassEntry> obfClassChain, ClassEntry obfClassEntry) { 111 private static void writeInnerClass(JarIndex index, InnerClassesAttribute attr, List<ClassEntry> obfClassChain, ClassEntry obfClassEntry) {
119 112
120 // get the new inner class name 113 // get the new inner class name
121 ClassEntry obfInnerClassEntry = obfClassEntry.buildClassEntry(obfClassChain); 114 ClassEntry obfInnerClassEntry = obfClassEntry.buildClassEntry(obfClassChain);
@@ -131,7 +124,7 @@ public class InnerClassWriter {
131 int innerClassNameIndex = 0; 124 int innerClassNameIndex = 0;
132 int accessFlags = AccessFlag.PUBLIC; 125 int accessFlags = AccessFlag.PUBLIC;
133 // TODO: need to figure out if we can put static or not 126 // TODO: need to figure out if we can put static or not
134 if (!this.index.isAnonymousClass(obfClassEntry)) { 127 if (!index.isAnonymousClass(obfClassEntry)) {
135 innerClassNameIndex = constPool.addUtf8Info(obfInnerClassEntry.getInnermostClassName()); 128 innerClassNameIndex = constPool.addUtf8Info(obfInnerClassEntry.getInnermostClassName());
136 } 129 }
137 130
diff --git a/src/main/java/cuchaz/enigma/bytecode/LocalVariableRenamer.java b/src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableTranslator.java
index 878e30a7..51b3d2df 100644
--- a/src/main/java/cuchaz/enigma/bytecode/LocalVariableRenamer.java
+++ b/src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableTranslator.java
@@ -9,22 +9,16 @@
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11 11
12package cuchaz.enigma.bytecode; 12package cuchaz.enigma.bytecode.translators;
13 13
14import cuchaz.enigma.mapping.*; 14import cuchaz.enigma.mapping.*;
15import javassist.CtBehavior; 15import javassist.CtBehavior;
16import javassist.CtClass; 16import javassist.CtClass;
17import javassist.bytecode.*; 17import javassist.bytecode.*;
18 18
19public class LocalVariableRenamer { 19public class LocalVariableTranslator {
20 20
21 private Translator translator; 21 public static void translate(Translator translator, CtClass c) {
22
23 public LocalVariableRenamer(Translator translator) {
24 this.translator = translator;
25 }
26
27 public void rename(CtClass c) {
28 for (CtBehavior behavior : c.getDeclaredBehaviors()) { 22 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
29 23
30 // if there's a local variable table, just rename everything to v1, v2, v3, ... for now 24 // if there's a local variable table, just rename everything to v1, v2, v3, ... for now
@@ -38,7 +32,7 @@ public class LocalVariableRenamer {
38 32
39 LocalVariableAttribute table = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag); 33 LocalVariableAttribute table = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
40 if (table != null) { 34 if (table != null) {
41 renameLVT(behaviorEntry, constants, table, c); 35 renameLVT(translator, behaviorEntry, constants, table, c);
42 } 36 }
43 37
44 LocalVariableTypeAttribute typeTable = (LocalVariableTypeAttribute) codeAttribute.getAttribute(LocalVariableAttribute.typeTag); 38 LocalVariableTypeAttribute typeTable = (LocalVariableTypeAttribute) codeAttribute.getAttribute(LocalVariableAttribute.typeTag);
@@ -50,7 +44,7 @@ public class LocalVariableRenamer {
50 44
51 // DEBUG 45 // DEBUG
52 @SuppressWarnings("unused") 46 @SuppressWarnings("unused")
53 private void dumpTable(LocalVariableAttribute table) { 47 private static void dumpTable(LocalVariableAttribute table) {
54 for (int i = 0; i < table.tableLength(); i++) { 48 for (int i = 0; i < table.tableLength(); i++) {
55 System.out.println(String.format("\t%d (%d): %s %s", 49 System.out.println(String.format("\t%d (%d): %s %s",
56 i, table.index(i), table.variableName(i), table.descriptor(i) 50 i, table.index(i), table.variableName(i), table.descriptor(i)
@@ -58,7 +52,7 @@ public class LocalVariableRenamer {
58 } 52 }
59 } 53 }
60 54
61 private void renameLVT(BehaviorEntry behaviorEntry, ConstPool constants, LocalVariableAttribute table, CtClass ctClass) { 55 private static void renameLVT(Translator translator, BehaviorEntry behaviorEntry, ConstPool constants, LocalVariableAttribute table, CtClass ctClass) {
62 56
63 // skip empty tables 57 // skip empty tables
64 if (table.tableLength() <= 0) { 58 if (table.tableLength() <= 0) {
@@ -94,9 +88,7 @@ public class LocalVariableRenamer {
94 int argi = i - starti; 88 int argi = i - starti;
95 if (ctClass.isEnum()) 89 if (ctClass.isEnum())
96 argi += 2; 90 argi += 2;
97 if (behaviorEntry.getClassEntry().getName().contains("ahd") && behaviorEntry instanceof ConstructorEntry) 91 String argName = translator.translate(new ArgumentEntry(behaviorEntry, argi, ""));
98 System.out.println(behaviorEntry.getClassEntry() + " " + i);
99 String argName = this.translator.translate(new ArgumentEntry(behaviorEntry, argi, ""));
100 if (argName == null) { 92 if (argName == null) {
101 int argIndex = isNestedClassConstructor ? argi + 1 : argi; 93 int argIndex = isNestedClassConstructor ? argi + 1 : argi;
102 if (ctClass.isEnum()) 94 if (ctClass.isEnum())
@@ -110,7 +102,7 @@ public class LocalVariableRenamer {
110 // List types would require this whole block again, so just go with aListx 102 // List types would require this whole block again, so just go with aListx
111 argName = "aList" + (argIndex + 1); 103 argName = "aList" + (argIndex + 1);
112 } else if (argType.isClass()) { 104 } else if (argType.isClass()) {
113 ClassEntry argClsTrans = this.translator.translateEntry(argType.getClassEntry()); 105 ClassEntry argClsTrans = translator.translateEntry(argType.getClassEntry());
114 argName = "a" + argClsTrans.getSimpleName().replace("$", "") + (argIndex + 1); 106 argName = "a" + argClsTrans.getSimpleName().replace("$", "") + (argIndex + 1);
115 } else { 107 } else {
116 argName = "a" + (argIndex + 1); 108 argName = "a" + (argIndex + 1);
@@ -127,19 +119,19 @@ public class LocalVariableRenamer {
127 } 119 }
128 } 120 }
129 121
130 private void renameLVTT(LocalVariableTypeAttribute typeTable, LocalVariableAttribute table) { 122 private static void renameLVTT(LocalVariableTypeAttribute typeTable, LocalVariableAttribute table) {
131 // rename args to the same names as in the LVT 123 // rename args to the same names as in the LVT
132 for (int i = 0; i < typeTable.tableLength(); i++) { 124 for (int i = 0; i < typeTable.tableLength(); i++) {
133 renameVariable(typeTable, i, getNameIndex(table, typeTable.index(i))); 125 renameVariable(typeTable, i, getNameIndex(table, typeTable.index(i)));
134 } 126 }
135 } 127 }
136 128
137 private void renameVariable(LocalVariableAttribute table, int i, int stringId) { 129 private static void renameVariable(LocalVariableAttribute table, int i, int stringId) {
138 // based off of LocalVariableAttribute.nameIndex() 130 // based off of LocalVariableAttribute.nameIndex()
139 ByteArray.write16bit(stringId, table.get(), i * 10 + 6); 131 ByteArray.write16bit(stringId, table.get(), i * 10 + 6);
140 } 132 }
141 133
142 private int getNameIndex(LocalVariableAttribute table, int index) { 134 private static int getNameIndex(LocalVariableAttribute table, int index) {
143 for (int i = 0; i < table.tableLength(); i++) { 135 for (int i = 0; i < table.tableLength(); i++) {
144 if (table.index(i) == index) { 136 if (table.index(i) == index) {
145 return table.nameIndex(i); 137 return table.nameIndex(i);
diff --git a/src/main/java/cuchaz/enigma/bytecode/MethodParameterWriter.java b/src/main/java/cuchaz/enigma/bytecode/translators/MethodParameterTranslator.java
index d63572e9..4e632b94 100644
--- a/src/main/java/cuchaz/enigma/bytecode/MethodParameterWriter.java
+++ b/src/main/java/cuchaz/enigma/bytecode/translators/MethodParameterTranslator.java
@@ -9,8 +9,9 @@
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11 11
12package cuchaz.enigma.bytecode; 12package cuchaz.enigma.bytecode.translators;
13 13
14import cuchaz.enigma.bytecode.MethodParametersAttribute;
14import cuchaz.enigma.mapping.*; 15import cuchaz.enigma.mapping.*;
15import javassist.CtBehavior; 16import javassist.CtBehavior;
16import javassist.CtClass; 17import javassist.CtClass;
@@ -20,15 +21,9 @@ import javassist.bytecode.LocalVariableAttribute;
20import java.util.ArrayList; 21import java.util.ArrayList;
21import java.util.List; 22import java.util.List;
22 23
23public class MethodParameterWriter { 24public class MethodParameterTranslator {
24 25
25 private Translator translator; 26 public static void translate(Translator translator, CtClass c) {
26
27 public MethodParameterWriter(Translator translator) {
28 this.translator = translator;
29 }
30
31 public void writeMethodArguments(CtClass c) {
32 27
33 // Procyon will read method arguments from the "MethodParameters" attribute, so write those 28 // Procyon will read method arguments from the "MethodParameters" attribute, so write those
34 for (CtBehavior behavior : c.getDeclaredBehaviors()) { 29 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
@@ -57,7 +52,7 @@ public class MethodParameterWriter {
57 // get the list of argument names 52 // get the list of argument names
58 List<String> names = new ArrayList<>(numParams); 53 List<String> names = new ArrayList<>(numParams);
59 for (int i = 0; i < numParams; i++) { 54 for (int i = 0; i < numParams; i++) {
60 names.add(this.translator.translate(new ArgumentEntry(behaviorEntry, i, ""))); 55 names.add(translator.translate(new ArgumentEntry(behaviorEntry, i, "")));
61 } 56 }
62 57
63 // save the mappings to the class 58 // save the mappings to the class
diff --git a/src/main/java/cuchaz/enigma/convert/ClassForest.java b/src/main/java/cuchaz/enigma/convert/ClassForest.java
deleted file mode 100644
index 4542fb33..00000000
--- a/src/main/java/cuchaz/enigma/convert/ClassForest.java
+++ /dev/null
@@ -1,59 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.convert;
13
14import com.google.common.collect.HashMultimap;
15import com.google.common.collect.Multimap;
16import cuchaz.enigma.mapping.ClassEntry;
17
18import java.util.Collection;
19
20public class ClassForest {
21
22 private ClassIdentifier identifier;
23 private Multimap<ClassIdentity, ClassEntry> forest;
24
25 public ClassForest(ClassIdentifier identifier) {
26 this.identifier = identifier;
27 this.forest = HashMultimap.create();
28 }
29
30 public void addAll(Iterable<ClassEntry> entries) {
31 for (ClassEntry entry : entries) {
32 add(entry);
33 }
34 }
35
36 public void add(ClassEntry entry) {
37 try {
38 this.forest.put(this.identifier.identify(entry), entry);
39 } catch (ClassNotFoundException ex) {
40 throw new Error("Unable to find class " + entry.getName());
41 }
42 }
43
44 public Collection<ClassIdentity> identities() {
45 return this.forest.keySet();
46 }
47
48 public Collection<ClassEntry> classes() {
49 return this.forest.values();
50 }
51
52 public Collection<ClassEntry> getClasses(ClassIdentity identity) {
53 return this.forest.get(identity);
54 }
55
56 public boolean containsIdentity(ClassIdentity identity) {
57 return this.forest.containsKey(identity);
58 }
59}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassIdentifier.java b/src/main/java/cuchaz/enigma/convert/ClassIdentifier.java
deleted file mode 100644
index 0a72073c..00000000
--- a/src/main/java/cuchaz/enigma/convert/ClassIdentifier.java
+++ /dev/null
@@ -1,54 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.convert;
13
14import com.google.common.collect.Maps;
15import cuchaz.enigma.TranslatingTypeLoader;
16import cuchaz.enigma.analysis.JarIndex;
17import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
18import cuchaz.enigma.mapping.ClassEntry;
19import cuchaz.enigma.mapping.Translator;
20import javassist.CtClass;
21
22import java.util.Map;
23import java.util.jar.JarFile;
24
25public class ClassIdentifier {
26
27 private JarIndex index;
28 private SidedClassNamer namer;
29 private boolean useReferences;
30 private TranslatingTypeLoader loader;
31 private Map<ClassEntry, ClassIdentity> cache;
32
33 public ClassIdentifier(JarFile jar, JarIndex index, SidedClassNamer namer, boolean useReferences) {
34 this.index = index;
35 this.namer = namer;
36 this.useReferences = useReferences;
37 this.loader = new TranslatingTypeLoader(jar, index, new Translator(), new Translator());
38 this.cache = Maps.newHashMap();
39 }
40
41 public ClassIdentity identify(ClassEntry classEntry)
42 throws ClassNotFoundException {
43 ClassIdentity identity = this.cache.get(classEntry);
44 if (identity == null) {
45 CtClass c = this.loader.loadClass(classEntry.getName());
46 if (c == null) {
47 throw new ClassNotFoundException(classEntry.getName());
48 }
49 identity = new ClassIdentity(c, this.namer, this.index, this.useReferences);
50 this.cache.put(classEntry, identity);
51 }
52 return identity;
53 }
54}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassIdentity.java b/src/main/java/cuchaz/enigma/convert/ClassIdentity.java
deleted file mode 100644
index a395b755..00000000
--- a/src/main/java/cuchaz/enigma/convert/ClassIdentity.java
+++ /dev/null
@@ -1,439 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.convert;
13
14import com.google.common.collect.*;
15import cuchaz.enigma.analysis.ClassImplementationsTreeNode;
16import cuchaz.enigma.analysis.EntryReference;
17import cuchaz.enigma.analysis.JarIndex;
18import cuchaz.enigma.bytecode.ConstPoolEditor;
19import cuchaz.enigma.bytecode.InfoType;
20import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor;
21import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
22import cuchaz.enigma.mapping.*;
23import cuchaz.enigma.utils.Utils;
24import javassist.*;
25import javassist.bytecode.*;
26import javassist.expr.*;
27
28import java.io.UnsupportedEncodingException;
29import java.security.MessageDigest;
30import java.security.NoSuchAlgorithmException;
31import java.util.Enumeration;
32import java.util.List;
33import java.util.Map;
34import java.util.Set;
35
36public class ClassIdentity {
37
38 private ClassEntry classEntry;
39 private SidedClassNamer namer;
40 private final ClassNameReplacer classNameReplacer = new ClassNameReplacer() {
41
42 private Map<String, String> classNames = Maps.newHashMap();
43
44 @Override
45 public String replace(String className) {
46
47 // classes not in the none package can be passed through
48 ClassEntry classEntry = new ClassEntry(className);
49 if (classEntry.getPackageName() != null) {
50 return className;
51 }
52
53 // is this class ourself?
54 if (className.equals(classEntry.getName())) {
55 return "CSelf";
56 }
57
58 // try the namer
59 if (namer != null) {
60 String newName = namer.getName(className);
61 if (newName != null) {
62 return newName;
63 }
64 }
65
66 // otherwise, use local naming
67 if (!classNames.containsKey(className)) {
68 classNames.put(className, getNewClassName());
69 }
70 return classNames.get(className);
71 }
72
73 private String getNewClassName() {
74 return String.format("C%03d", classNames.size());
75 }
76 };
77 private Multiset<String> fields;
78 private Multiset<String> methods;
79 private Multiset<String> constructors;
80 private String staticInitializer;
81 private String extendz;
82 private Multiset<String> implementz;
83 private Set<String> stringLiterals;
84 private Multiset<String> implementations;
85 private Multiset<String> references;
86 private String outer;
87
88 public ClassIdentity(CtClass c, SidedClassNamer namer, JarIndex index, boolean useReferences) {
89 this.namer = namer;
90
91 // stuff from the bytecode
92
93 this.classEntry = EntryFactory.getClassEntry(c);
94 this.fields = HashMultiset.create();
95 for (CtField field : c.getDeclaredFields()) {
96 this.fields.add(scrubType(field.getSignature()));
97 }
98 this.methods = HashMultiset.create();
99 for (CtMethod method : c.getDeclaredMethods()) {
100 this.methods.add(scrubSignature(method.getSignature()) + "0x" + getBehaviorSignature(method));
101 }
102 this.constructors = HashMultiset.create();
103 for (CtConstructor constructor : c.getDeclaredConstructors()) {
104 this.constructors.add(scrubSignature(constructor.getSignature()) + "0x" + getBehaviorSignature(constructor));
105 }
106 this.staticInitializer = "";
107 if (c.getClassInitializer() != null) {
108 this.staticInitializer = getBehaviorSignature(c.getClassInitializer());
109 }
110 this.extendz = "";
111 if (c.getClassFile().getSuperclass() != null) {
112 this.extendz = scrubClassName(Descriptor.toJvmName(c.getClassFile().getSuperclass()));
113 }
114 this.implementz = HashMultiset.create();
115 for (String interfaceName : c.getClassFile().getInterfaces()) {
116 this.implementz.add(scrubClassName(Descriptor.toJvmName(interfaceName)));
117 }
118
119 this.stringLiterals = Sets.newHashSet();
120 ConstPool constants = c.getClassFile().getConstPool();
121 for (int i = 1; i < constants.getSize(); i++) {
122 if (constants.getTag(i) == ConstPool.CONST_String) {
123 this.stringLiterals.add(constants.getStringInfo(i));
124 }
125 }
126
127 // stuff from the jar index
128
129 this.implementations = HashMultiset.create();
130 ClassImplementationsTreeNode implementationsNode = index.getClassImplementations(null, this.classEntry);
131 if (implementationsNode != null) {
132 @SuppressWarnings("unchecked")
133 Enumeration<ClassImplementationsTreeNode> implementations = implementationsNode.children();
134 while (implementations.hasMoreElements()) {
135 ClassImplementationsTreeNode node = implementations.nextElement();
136 this.implementations.add(scrubClassName(node.getClassEntry().getName()));
137 }
138 }
139
140 this.references = HashMultiset.create();
141 if (useReferences) {
142 for (CtField field : c.getDeclaredFields()) {
143 FieldEntry fieldEntry = EntryFactory.getFieldEntry(field);
144 index.getFieldReferences(fieldEntry).forEach(this::addReference);
145 }
146 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
147 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
148 index.getBehaviorReferences(behaviorEntry).forEach(this::addReference);
149 }
150 }
151
152 this.outer = null;
153 if (this.classEntry.isInnerClass()) {
154 this.outer = this.classEntry.getOuterClassName();
155 }
156 }
157
158 private void addReference(EntryReference<? extends Entry, BehaviorEntry> reference) {
159 if (reference.context.getSignature() != null) {
160 this.references.add(String.format("%s_%s",
161 scrubClassName(reference.context.getClassName()),
162 scrubSignature(reference.context.getSignature())
163 ));
164 } else {
165 this.references.add(String.format("%s_<clinit>",
166 scrubClassName(reference.context.getClassName())
167 ));
168 }
169 }
170
171 public ClassEntry getClassEntry() {
172 return this.classEntry;
173 }
174
175 @Override
176 public String toString() {
177 StringBuilder buf = new StringBuilder();
178 buf.append("class: ");
179 buf.append(this.classEntry.getName());
180 buf.append(" ");
181 buf.append(hashCode());
182 buf.append("\n");
183 for (String field : this.fields) {
184 buf.append("\tfield ");
185 buf.append(field);
186 buf.append("\n");
187 }
188 for (String method : this.methods) {
189 buf.append("\tmethod ");
190 buf.append(method);
191 buf.append("\n");
192 }
193 for (String constructor : this.constructors) {
194 buf.append("\tconstructor ");
195 buf.append(constructor);
196 buf.append("\n");
197 }
198 if (!this.staticInitializer.isEmpty()) {
199 buf.append("\tinitializer ");
200 buf.append(this.staticInitializer);
201 buf.append("\n");
202 }
203 if (!this.extendz.isEmpty()) {
204 buf.append("\textends ");
205 buf.append(this.extendz);
206 buf.append("\n");
207 }
208 for (String interfaceName : this.implementz) {
209 buf.append("\timplements ");
210 buf.append(interfaceName);
211 buf.append("\n");
212 }
213 for (String implementation : this.implementations) {
214 buf.append("\timplemented by ");
215 buf.append(implementation);
216 buf.append("\n");
217 }
218 for (String reference : this.references) {
219 buf.append("\treference ");
220 buf.append(reference);
221 buf.append("\n");
222 }
223 buf.append("\touter ");
224 buf.append(this.outer);
225 buf.append("\n");
226 return buf.toString();
227 }
228
229 private String scrubClassName(String className) {
230 return classNameReplacer.replace(className);
231 }
232
233 private String scrubType(String typeName) {
234 return scrubType(new Type(typeName)).toString();
235 }
236
237 private Type scrubType(Type type) {
238 if (type.hasClass()) {
239 return new Type(type, classNameReplacer);
240 } else {
241 return type;
242 }
243 }
244
245 private String scrubSignature(String signature) {
246 return scrubSignature(new Signature(signature)).toString();
247 }
248
249 private Signature scrubSignature(Signature signature) {
250 return new Signature(signature, classNameReplacer);
251 }
252
253 private boolean isClassMatchedUniquely(String className) {
254 return this.namer != null && this.namer.getName(Descriptor.toJvmName(className)) != null;
255 }
256
257 private String getBehaviorSignature(CtBehavior behavior) {
258 try {
259 // does this method have an implementation?
260 if (behavior.getMethodInfo().getCodeAttribute() == null) {
261 return "(none)";
262 }
263
264 // compute the hash from the opcodes
265 ConstPool constants = behavior.getMethodInfo().getConstPool();
266 final MessageDigest digest = MessageDigest.getInstance("MD5");
267 CodeIterator iter = behavior.getMethodInfo().getCodeAttribute().iterator();
268 while (iter.hasNext()) {
269 int pos = iter.next();
270
271 // update the hash with the opcode
272 int opcode = iter.byteAt(pos);
273 digest.update((byte) opcode);
274 int constIndex;
275 switch (opcode) {
276 case Opcode.LDC:
277 constIndex = iter.byteAt(pos + 1);
278 updateHashWithConstant(digest, constants, constIndex);
279 break;
280
281 case Opcode.LDC_W:
282 case Opcode.LDC2_W:
283 constIndex = (iter.byteAt(pos + 1) << 8) | iter.byteAt(pos + 2);
284 updateHashWithConstant(digest, constants, constIndex);
285 break;
286 default:
287 break;
288 }
289 }
290
291 // update hash with method and field accesses
292 behavior.instrument(new ExprEditor() {
293 @Override
294 public void edit(MethodCall call) {
295 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName())));
296 updateHashWithString(digest, scrubSignature(call.getSignature()));
297 if (isClassMatchedUniquely(call.getClassName())) {
298 updateHashWithString(digest, call.getMethodName());
299 }
300 }
301
302 @Override
303 public void edit(FieldAccess access) {
304 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(access.getClassName())));
305 updateHashWithString(digest, scrubType(access.getSignature()));
306 if (isClassMatchedUniquely(access.getClassName())) {
307 updateHashWithString(digest, access.getFieldName());
308 }
309 }
310
311 @Override
312 public void edit(ConstructorCall call) {
313 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName())));
314 updateHashWithString(digest, scrubSignature(call.getSignature()));
315 }
316
317 @Override
318 public void edit(NewExpr expr) {
319 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(expr.getClassName())));
320 }
321 });
322
323 // convert the hash to a hex string
324 return toHex(digest.digest());
325 } catch (BadBytecode | NoSuchAlgorithmException | CannotCompileException ex) {
326 throw new Error(ex);
327 }
328 }
329
330 private void updateHashWithConstant(MessageDigest digest, ConstPool constants, int index) {
331 ConstPoolEditor editor = new ConstPoolEditor(constants);
332 ConstInfoAccessor item = editor.getItem(index);
333 if (item.getType() == InfoType.StringInfo) {
334 updateHashWithString(digest, constants.getStringInfo(index));
335 }
336 // TODO: other constants
337 }
338
339 private void updateHashWithString(MessageDigest digest, String val) {
340 try {
341 digest.update(val.getBytes("UTF8"));
342 } catch (UnsupportedEncodingException ex) {
343 throw new Error(ex);
344 }
345 }
346
347 private String toHex(byte[] bytes) {
348 // function taken from:
349 // http://stackoverflow.com/questions/9655181/convert-from-byte-array-to-hex-string-in-java
350 final char[] hexArray = "0123456789ABCDEF".toCharArray();
351 char[] hexChars = new char[bytes.length * 2];
352 for (int j = 0; j < bytes.length; j++) {
353 int v = bytes[j] & 0xFF;
354 hexChars[j * 2] = hexArray[v >>> 4];
355 hexChars[j * 2 + 1] = hexArray[v & 0x0F];
356 }
357 return new String(hexChars);
358 }
359
360 @Override
361 public boolean equals(Object other) {
362 return other instanceof ClassIdentity && equals((ClassIdentity) other);
363 }
364
365 public boolean equals(ClassIdentity other) {
366 return this.fields.equals(other.fields)
367 && this.methods.equals(other.methods)
368 && this.constructors.equals(other.constructors)
369 && this.staticInitializer.equals(other.staticInitializer)
370 && this.extendz.equals(other.extendz)
371 && this.implementz.equals(other.implementz)
372 && this.implementations.equals(other.implementations)
373 && this.references.equals(other.references);
374 }
375
376 @Override
377 public int hashCode() {
378 List<Object> objs = Lists.newArrayList();
379 objs.addAll(this.fields);
380 objs.addAll(this.methods);
381 objs.addAll(this.constructors);
382 objs.add(this.staticInitializer);
383 objs.add(this.extendz);
384 objs.addAll(this.implementz);
385 objs.addAll(this.implementations);
386 objs.addAll(this.references);
387 return Utils.combineHashesOrdered(objs);
388 }
389
390 public int getMatchScore(ClassIdentity other) {
391 return 2 * getNumMatches(this.extendz, other.extendz)
392 + 2 * getNumMatches(this.outer, other.outer)
393 + 2 * getNumMatches(this.implementz, other.implementz)
394 + getNumMatches(this.stringLiterals, other.stringLiterals)
395 + getNumMatches(this.fields, other.fields)
396 + getNumMatches(this.methods, other.methods)
397 + getNumMatches(this.constructors, other.constructors);
398 }
399
400 public int getMaxMatchScore() {
401 return 2 + 2 + 2 * this.implementz.size() + this.stringLiterals.size() + this.fields.size() + this.methods.size() + this.constructors.size();
402 }
403
404 public boolean matches(CtClass c) {
405 // just compare declaration counts
406 return this.fields.size() == c.getDeclaredFields().length
407 && this.methods.size() == c.getDeclaredMethods().length
408 && this.constructors.size() == c.getDeclaredConstructors().length;
409 }
410
411 private int getNumMatches(Set<String> a, Set<String> b) {
412 int numMatches = 0;
413 for (String val : a) {
414 if (b.contains(val)) {
415 numMatches++;
416 }
417 }
418 return numMatches;
419 }
420
421 private int getNumMatches(Multiset<String> a, Multiset<String> b) {
422 int numMatches = 0;
423 for (String val : a) {
424 if (b.contains(val)) {
425 numMatches++;
426 }
427 }
428 return numMatches;
429 }
430
431 private int getNumMatches(String a, String b) {
432 if (a == null && b == null) {
433 return 1;
434 } else if (a != null && b != null && a.equals(b)) {
435 return 1;
436 }
437 return 0;
438 }
439}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassMatch.java b/src/main/java/cuchaz/enigma/convert/ClassMatch.java
deleted file mode 100644
index bb3e4f43..00000000
--- a/src/main/java/cuchaz/enigma/convert/ClassMatch.java
+++ /dev/null
@@ -1,83 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.convert;
13
14import com.google.common.collect.Sets;
15import cuchaz.enigma.mapping.ClassEntry;
16import cuchaz.enigma.utils.Utils;
17
18import java.util.Collection;
19import java.util.Set;
20
21public class ClassMatch {
22
23 public Set<ClassEntry> sourceClasses;
24 public Set<ClassEntry> destClasses;
25
26 public ClassMatch(Collection<ClassEntry> sourceClasses, Collection<ClassEntry> destClasses) {
27 this.sourceClasses = Sets.newHashSet(sourceClasses);
28 this.destClasses = Sets.newHashSet(destClasses);
29 }
30
31 public ClassMatch(ClassEntry sourceClass, ClassEntry destClass) {
32 sourceClasses = Sets.newHashSet();
33 if (sourceClass != null) {
34 sourceClasses.add(sourceClass);
35 }
36 destClasses = Sets.newHashSet();
37 if (destClass != null) {
38 destClasses.add(destClass);
39 }
40 }
41
42 public boolean isMatched() {
43 return !sourceClasses.isEmpty() && !destClasses.isEmpty();
44 }
45
46 public boolean isAmbiguous() {
47 return sourceClasses.size() > 1 || destClasses.size() > 1;
48 }
49
50 public ClassEntry getUniqueSource() {
51 if (sourceClasses.size() != 1) {
52 throw new IllegalStateException("Match has ambiguous source!");
53 }
54 return sourceClasses.iterator().next();
55 }
56
57 public ClassEntry getUniqueDest() {
58 if (destClasses.size() != 1) {
59 throw new IllegalStateException("Match has ambiguous source!");
60 }
61 return destClasses.iterator().next();
62 }
63
64 public Set<ClassEntry> intersectSourceClasses(Set<ClassEntry> classes) {
65 Set<ClassEntry> intersection = Sets.newHashSet(sourceClasses);
66 intersection.retainAll(classes);
67 return intersection;
68 }
69
70 @Override
71 public int hashCode() {
72 return Utils.combineHashesOrdered(sourceClasses, destClasses);
73 }
74
75 @Override
76 public boolean equals(Object other) {
77 return other instanceof ClassMatch && equals((ClassMatch) other);
78 }
79
80 public boolean equals(ClassMatch other) {
81 return this.sourceClasses.equals(other.sourceClasses) && this.destClasses.equals(other.destClasses);
82 }
83}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassMatches.java b/src/main/java/cuchaz/enigma/convert/ClassMatches.java
deleted file mode 100644
index db2c550f..00000000
--- a/src/main/java/cuchaz/enigma/convert/ClassMatches.java
+++ /dev/null
@@ -1,158 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.convert;
13
14import com.google.common.collect.BiMap;
15import com.google.common.collect.HashBiMap;
16import com.google.common.collect.Maps;
17import com.google.common.collect.Sets;
18import cuchaz.enigma.mapping.ClassEntry;
19
20import java.util.*;
21
22public class ClassMatches implements Iterable<ClassMatch> {
23
24 private Collection<ClassMatch> matches;
25 private Map<ClassEntry, ClassMatch> matchesBySource;
26 private Map<ClassEntry, ClassMatch> matchesByDest;
27 private BiMap<ClassEntry, ClassEntry> uniqueMatches;
28 private Map<ClassEntry, ClassMatch> ambiguousMatchesBySource;
29 private Map<ClassEntry, ClassMatch> ambiguousMatchesByDest;
30 private Set<ClassEntry> unmatchedSourceClasses;
31 private Set<ClassEntry> unmatchedDestClasses;
32
33 public ClassMatches() {
34 this(new ArrayList<>());
35 }
36
37 public ClassMatches(Collection<ClassMatch> matches) {
38 this.matches = matches;
39 matchesBySource = Maps.newHashMap();
40 matchesByDest = Maps.newHashMap();
41 uniqueMatches = HashBiMap.create();
42 ambiguousMatchesBySource = Maps.newHashMap();
43 ambiguousMatchesByDest = Maps.newHashMap();
44 unmatchedSourceClasses = Sets.newHashSet();
45 unmatchedDestClasses = Sets.newHashSet();
46
47 for (ClassMatch match : matches) {
48 indexMatch(match);
49 }
50 }
51
52 public void add(ClassMatch match) {
53 matches.add(match);
54 indexMatch(match);
55 }
56
57 public void remove(ClassMatch match) {
58 for (ClassEntry sourceClass : match.sourceClasses) {
59 matchesBySource.remove(sourceClass);
60 uniqueMatches.remove(sourceClass);
61 ambiguousMatchesBySource.remove(sourceClass);
62 unmatchedSourceClasses.remove(sourceClass);
63 }
64 for (ClassEntry destClass : match.destClasses) {
65 matchesByDest.remove(destClass);
66 uniqueMatches.inverse().remove(destClass);
67 ambiguousMatchesByDest.remove(destClass);
68 unmatchedDestClasses.remove(destClass);
69 }
70 matches.remove(match);
71 }
72
73 public int size() {
74 return matches.size();
75 }
76
77 @Override
78 public Iterator<ClassMatch> iterator() {
79 return matches.iterator();
80 }
81
82 private void indexMatch(ClassMatch match) {
83 if (!match.isMatched()) {
84 // unmatched
85 unmatchedSourceClasses.addAll(match.sourceClasses);
86 unmatchedDestClasses.addAll(match.destClasses);
87 } else {
88 if (match.isAmbiguous()) {
89 // ambiguously matched
90 for (ClassEntry entry : match.sourceClasses) {
91 ambiguousMatchesBySource.put(entry, match);
92 }
93 for (ClassEntry entry : match.destClasses) {
94 ambiguousMatchesByDest.put(entry, match);
95 }
96 } else {
97 // uniquely matched
98 uniqueMatches.put(match.getUniqueSource(), match.getUniqueDest());
99 }
100 }
101 for (ClassEntry entry : match.sourceClasses) {
102 matchesBySource.put(entry, match);
103 }
104 for (ClassEntry entry : match.destClasses) {
105 matchesByDest.put(entry, match);
106 }
107 }
108
109 public BiMap<ClassEntry, ClassEntry> getUniqueMatches() {
110 return uniqueMatches;
111 }
112
113 public Set<ClassEntry> getUnmatchedSourceClasses() {
114 return unmatchedSourceClasses;
115 }
116
117 public Set<ClassEntry> getUnmatchedDestClasses() {
118 return unmatchedDestClasses;
119 }
120
121 public Set<ClassEntry> getAmbiguouslyMatchedSourceClasses() {
122 return ambiguousMatchesBySource.keySet();
123 }
124
125 public ClassMatch getAmbiguousMatchBySource(ClassEntry sourceClass) {
126 return ambiguousMatchesBySource.get(sourceClass);
127 }
128
129 public ClassMatch getMatchBySource(ClassEntry sourceClass) {
130 return matchesBySource.get(sourceClass);
131 }
132
133 public ClassMatch getMatchByDest(ClassEntry destClass) {
134 return matchesByDest.get(destClass);
135 }
136
137 public void removeSource(ClassEntry sourceClass) {
138 ClassMatch match = matchesBySource.get(sourceClass);
139 if (match != null) {
140 remove(match);
141 match.sourceClasses.remove(sourceClass);
142 if (!match.sourceClasses.isEmpty() || !match.destClasses.isEmpty()) {
143 add(match);
144 }
145 }
146 }
147
148 public void removeDest(ClassEntry destClass) {
149 ClassMatch match = matchesByDest.get(destClass);
150 if (match != null) {
151 remove(match);
152 match.destClasses.remove(destClass);
153 if (!match.sourceClasses.isEmpty() || !match.destClasses.isEmpty()) {
154 add(match);
155 }
156 }
157 }
158}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassMatching.java b/src/main/java/cuchaz/enigma/convert/ClassMatching.java
deleted file mode 100644
index f0f27cf5..00000000
--- a/src/main/java/cuchaz/enigma/convert/ClassMatching.java
+++ /dev/null
@@ -1,153 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.convert;
13
14import com.google.common.collect.BiMap;
15import com.google.common.collect.HashBiMap;
16import com.google.common.collect.Lists;
17import com.google.common.collect.Sets;
18import cuchaz.enigma.mapping.ClassEntry;
19
20import java.util.ArrayList;
21import java.util.Collection;
22import java.util.List;
23import java.util.Map.Entry;
24import java.util.Set;
25
26public class ClassMatching {
27
28 private ClassForest sourceClasses;
29 private ClassForest destClasses;
30 private BiMap<ClassEntry, ClassEntry> knownMatches;
31
32 public ClassMatching(ClassIdentifier sourceIdentifier, ClassIdentifier destIdentifier) {
33 sourceClasses = new ClassForest(sourceIdentifier);
34 destClasses = new ClassForest(destIdentifier);
35 knownMatches = HashBiMap.create();
36 }
37
38 public void addKnownMatches(BiMap<ClassEntry, ClassEntry> knownMatches) {
39 this.knownMatches.putAll(knownMatches);
40 }
41
42 public void match(Iterable<ClassEntry> sourceClasses, Iterable<ClassEntry> destClasses) {
43 for (ClassEntry sourceClass : sourceClasses) {
44 if (!knownMatches.containsKey(sourceClass)) {
45 this.sourceClasses.add(sourceClass);
46 }
47 }
48 for (ClassEntry destClass : destClasses) {
49 if (!knownMatches.containsValue(destClass)) {
50 this.destClasses.add(destClass);
51 }
52 }
53 }
54
55 public Collection<ClassMatch> matches() {
56 List<ClassMatch> matches = Lists.newArrayList();
57 for (Entry<ClassEntry, ClassEntry> entry : knownMatches.entrySet()) {
58 matches.add(new ClassMatch(
59 entry.getKey(),
60 entry.getValue()
61 ));
62 }
63 for (ClassIdentity identity : sourceClasses.identities()) {
64 matches.add(new ClassMatch(
65 sourceClasses.getClasses(identity),
66 destClasses.getClasses(identity)
67 ));
68 }
69 for (ClassIdentity identity : destClasses.identities()) {
70 if (!sourceClasses.containsIdentity(identity)) {
71 matches.add(new ClassMatch(
72 new ArrayList<>(),
73 destClasses.getClasses(identity)
74 ));
75 }
76 }
77 return matches;
78 }
79
80 public Collection<ClassEntry> sourceClasses() {
81 Set<ClassEntry> classes = Sets.newHashSet();
82 for (ClassMatch match : matches()) {
83 classes.addAll(match.sourceClasses);
84 }
85 return classes;
86 }
87
88 public Collection<ClassEntry> destClasses() {
89 Set<ClassEntry> classes = Sets.newHashSet();
90 for (ClassMatch match : matches()) {
91 classes.addAll(match.destClasses);
92 }
93 return classes;
94 }
95
96 public BiMap<ClassEntry, ClassEntry> uniqueMatches() {
97 BiMap<ClassEntry, ClassEntry> uniqueMatches = HashBiMap.create();
98 for (ClassMatch match : matches()) {
99 if (match.isMatched() && !match.isAmbiguous()) {
100 uniqueMatches.put(match.getUniqueSource(), match.getUniqueDest());
101 }
102 }
103 return uniqueMatches;
104 }
105
106 public Collection<ClassMatch> ambiguousMatches() {
107 List<ClassMatch> ambiguousMatches = Lists.newArrayList();
108 for (ClassMatch match : matches()) {
109 if (match.isMatched() && match.isAmbiguous()) {
110 ambiguousMatches.add(match);
111 }
112 }
113 return ambiguousMatches;
114 }
115
116 public Collection<ClassEntry> unmatchedSourceClasses() {
117 List<ClassEntry> classes = Lists.newArrayList();
118 for (ClassMatch match : matches()) {
119 if (!match.isMatched() && !match.sourceClasses.isEmpty()) {
120 classes.addAll(match.sourceClasses);
121 }
122 }
123 return classes;
124 }
125
126 public Collection<ClassEntry> unmatchedDestClasses() {
127 List<ClassEntry> classes = Lists.newArrayList();
128 for (ClassMatch match : matches()) {
129 if (!match.isMatched() && !match.destClasses.isEmpty()) {
130 classes.addAll(match.destClasses);
131 }
132 }
133 return classes;
134 }
135
136 @Override
137 public String toString() {
138
139 // count the ambiguous classes
140 int numAmbiguousSource = 0;
141 int numAmbiguousDest = 0;
142 for (ClassMatch match : ambiguousMatches()) {
143 numAmbiguousSource += match.sourceClasses.size();
144 numAmbiguousDest += match.destClasses.size();
145 }
146
147 return String.format("%20s%8s%8s\n", "", "Source", "Dest") + String
148 .format("%20s%8d%8d\n", "Classes", sourceClasses().size(), destClasses().size()) + String
149 .format("%20s%8d%8d\n", "Uniquely matched", uniqueMatches().size(), uniqueMatches().size()) + String
150 .format("%20s%8d%8d\n", "Ambiguously matched", numAmbiguousSource, numAmbiguousDest) + String
151 .format("%20s%8d%8d\n", "Unmatched", unmatchedSourceClasses().size(), unmatchedDestClasses().size());
152 }
153}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassNamer.java b/src/main/java/cuchaz/enigma/convert/ClassNamer.java
deleted file mode 100644
index e5902c43..00000000
--- a/src/main/java/cuchaz/enigma/convert/ClassNamer.java
+++ /dev/null
@@ -1,56 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.convert;
13
14import com.google.common.collect.BiMap;
15import com.google.common.collect.Maps;
16import cuchaz.enigma.mapping.ClassEntry;
17
18import java.util.Map;
19
20public class ClassNamer {
21
22 private Map<String, String> sourceNames;
23 private Map<String, String> destNames;
24
25 public ClassNamer(BiMap<ClassEntry, ClassEntry> mappings) {
26 // convert the identity mappings to name maps
27 this.sourceNames = Maps.newHashMap();
28 this.destNames = Maps.newHashMap();
29 int i = 0;
30 for (Map.Entry<ClassEntry, ClassEntry> entry : mappings.entrySet()) {
31 String name = String.format("M%04d", i++);
32 this.sourceNames.put(entry.getKey().getName(), name);
33 this.destNames.put(entry.getValue().getName(), name);
34 }
35 }
36
37 public String getSourceName(String name) {
38 return this.sourceNames.get(name);
39 }
40
41 public String getDestName(String name) {
42 return this.destNames.get(name);
43 }
44
45 public SidedClassNamer getSourceNamer() {
46 return this::getSourceName;
47 }
48
49 public SidedClassNamer getDestNamer() {
50 return this::getDestName;
51 }
52
53 public interface SidedClassNamer {
54 String getName(String name);
55 }
56}
diff --git a/src/main/java/cuchaz/enigma/convert/FieldMatches.java b/src/main/java/cuchaz/enigma/convert/FieldMatches.java
deleted file mode 100644
index a528b276..00000000
--- a/src/main/java/cuchaz/enigma/convert/FieldMatches.java
+++ /dev/null
@@ -1,150 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.convert;
13
14import com.google.common.collect.*;
15import cuchaz.enigma.mapping.ClassEntry;
16import cuchaz.enigma.mapping.FieldEntry;
17
18import java.util.Collection;
19import java.util.Set;
20
21public class FieldMatches {
22
23 private BiMap<FieldEntry, FieldEntry> matches;
24 private Multimap<ClassEntry, FieldEntry> matchedSourceFields;
25 private Multimap<ClassEntry, FieldEntry> unmatchedSourceFields;
26 private Multimap<ClassEntry, FieldEntry> unmatchedDestFields;
27 private Multimap<ClassEntry, FieldEntry> unmatchableSourceFields;
28
29 public FieldMatches() {
30 matches = HashBiMap.create();
31 matchedSourceFields = HashMultimap.create();
32 unmatchedSourceFields = HashMultimap.create();
33 unmatchedDestFields = HashMultimap.create();
34 unmatchableSourceFields = HashMultimap.create();
35 }
36
37 public void addMatch(FieldEntry srcField, FieldEntry destField) {
38 boolean wasAdded = matches.put(srcField, destField) == null;
39 assert (wasAdded);
40 wasAdded = matchedSourceFields.put(srcField.getClassEntry(), srcField);
41 assert (wasAdded);
42 }
43
44 public void addUnmatchedSourceField(FieldEntry fieldEntry) {
45 boolean wasAdded = unmatchedSourceFields.put(fieldEntry.getClassEntry(), fieldEntry);
46 assert (wasAdded);
47 }
48
49 public void addUnmatchedSourceFields(Iterable<FieldEntry> fieldEntries) {
50 for (FieldEntry fieldEntry : fieldEntries) {
51 addUnmatchedSourceField(fieldEntry);
52 }
53 }
54
55 public void addUnmatchedDestField(FieldEntry fieldEntry) {
56 boolean wasAdded = unmatchedDestFields.put(fieldEntry.getClassEntry(), fieldEntry);
57 assert (wasAdded);
58 }
59
60 public void addUnmatchedDestFields(Iterable<FieldEntry> fieldEntries) {
61 for (FieldEntry fieldEntry : fieldEntries) {
62 addUnmatchedDestField(fieldEntry);
63 }
64 }
65
66 public void addUnmatchableSourceField(FieldEntry sourceField) {
67 boolean wasAdded = unmatchableSourceFields.put(sourceField.getClassEntry(), sourceField);
68 assert (wasAdded);
69 }
70
71 public Set<ClassEntry> getSourceClassesWithUnmatchedFields() {
72 return unmatchedSourceFields.keySet();
73 }
74
75 public Collection<ClassEntry> getSourceClassesWithoutUnmatchedFields() {
76 Set<ClassEntry> out = Sets.newHashSet();
77 out.addAll(matchedSourceFields.keySet());
78 out.removeAll(unmatchedSourceFields.keySet());
79 return out;
80 }
81
82 public Collection<FieldEntry> getUnmatchedSourceFields() {
83 return unmatchedSourceFields.values();
84 }
85
86 public Collection<FieldEntry> getUnmatchedSourceFields(ClassEntry sourceClass) {
87 return unmatchedSourceFields.get(sourceClass);
88 }
89
90 public Collection<FieldEntry> getUnmatchedDestFields() {
91 return unmatchedDestFields.values();
92 }
93
94 public Collection<FieldEntry> getUnmatchedDestFields(ClassEntry destClass) {
95 return unmatchedDestFields.get(destClass);
96 }
97
98 public Collection<FieldEntry> getUnmatchableSourceFields() {
99 return unmatchableSourceFields.values();
100 }
101
102 public boolean hasSource(FieldEntry fieldEntry) {
103 return matches.containsKey(fieldEntry) || unmatchedSourceFields.containsValue(fieldEntry);
104 }
105
106 public boolean hasDest(FieldEntry fieldEntry) {
107 return matches.containsValue(fieldEntry) || unmatchedDestFields.containsValue(fieldEntry);
108 }
109
110 public BiMap<FieldEntry, FieldEntry> matches() {
111 return matches;
112 }
113
114 public boolean isMatchedSourceField(FieldEntry sourceField) {
115 return matches.containsKey(sourceField);
116 }
117
118 public boolean isMatchedDestField(FieldEntry destField) {
119 return matches.containsValue(destField);
120 }
121
122 public void makeMatch(FieldEntry sourceField, FieldEntry destField) {
123 boolean wasRemoved = unmatchedSourceFields.remove(sourceField.getClassEntry(), sourceField);
124 assert (wasRemoved);
125 wasRemoved = unmatchedDestFields.remove(destField.getClassEntry(), destField);
126 assert (wasRemoved);
127 addMatch(sourceField, destField);
128 }
129
130 public boolean isMatched(FieldEntry sourceField, FieldEntry destField) {
131 FieldEntry match = matches.get(sourceField);
132 return match != null && match.equals(destField);
133 }
134
135 public void unmakeMatch(FieldEntry sourceField, FieldEntry destField) {
136 boolean wasRemoved = matches.remove(sourceField) != null;
137 assert (wasRemoved);
138 wasRemoved = matchedSourceFields.remove(sourceField.getClassEntry(), sourceField);
139 assert (wasRemoved);
140 addUnmatchedSourceField(sourceField);
141 addUnmatchedDestField(destField);
142 }
143
144 public void makeSourceUnmatchable(FieldEntry sourceField) {
145 assert (!isMatchedSourceField(sourceField));
146 boolean wasRemoved = unmatchedSourceFields.remove(sourceField.getClassEntry(), sourceField);
147 assert (wasRemoved);
148 addUnmatchableSourceField(sourceField);
149 }
150}
diff --git a/src/main/java/cuchaz/enigma/convert/MappingsConverter.java b/src/main/java/cuchaz/enigma/convert/MappingsConverter.java
deleted file mode 100644
index fa3e9362..00000000
--- a/src/main/java/cuchaz/enigma/convert/MappingsConverter.java
+++ /dev/null
@@ -1,711 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.convert;
13
14import com.google.common.collect.*;
15import cuchaz.enigma.Deobfuscator;
16import cuchaz.enigma.TranslatingTypeLoader;
17import cuchaz.enigma.analysis.JarIndex;
18import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
19import cuchaz.enigma.mapping.*;
20import cuchaz.enigma.throwables.MappingConflict;
21import javassist.CtClass;
22import javassist.CtMethod;
23import javassist.NotFoundException;
24import javassist.bytecode.BadBytecode;
25import javassist.bytecode.CodeAttribute;
26import javassist.bytecode.CodeIterator;
27
28import java.util.*;
29import java.util.jar.JarFile;
30
31public class MappingsConverter {
32
33 public static ClassMatches computeClassMatches(JarFile sourceJar, JarFile destJar, Mappings mappings) {
34
35 // index jars
36 System.out.println("Indexing source jar...");
37 JarIndex sourceIndex = new JarIndex();
38 sourceIndex.indexJar(sourceJar, false);
39 System.out.println("Indexing dest jar...");
40 JarIndex destIndex = new JarIndex();
41 destIndex.indexJar(destJar, false);
42
43 // compute the matching
44 ClassMatching matching = computeMatching(sourceJar, sourceIndex, destJar, destIndex, null);
45 return new ClassMatches(matching.matches());
46 }
47
48 public static ClassMatching computeMatching(JarFile sourceJar, JarIndex sourceIndex, JarFile destJar, JarIndex destIndex, BiMap<ClassEntry, ClassEntry> knownMatches) {
49
50 System.out.println("Iteratively matching classes");
51
52 ClassMatching lastMatching = null;
53 int round = 0;
54 SidedClassNamer sourceNamer = null;
55 SidedClassNamer destNamer = null;
56 for (boolean useReferences : Arrays.asList(false, true)) {
57
58 int numUniqueMatchesLastTime = 0;
59 if (lastMatching != null) {
60 numUniqueMatchesLastTime = lastMatching.uniqueMatches().size();
61 }
62
63 while (true) {
64
65 System.out.println("Round " + (++round) + "...");
66
67 // init the matching with identity settings
68 ClassMatching matching = new ClassMatching(
69 new ClassIdentifier(sourceJar, sourceIndex, sourceNamer, useReferences),
70 new ClassIdentifier(destJar, destIndex, destNamer, useReferences)
71 );
72
73 if (knownMatches != null) {
74 matching.addKnownMatches(knownMatches);
75 }
76
77 if (lastMatching == null) {
78 // search all classes
79 matching.match(sourceIndex.getObfClassEntries(), destIndex.getObfClassEntries());
80 } else {
81 // we already know about these matches from last time
82 matching.addKnownMatches(lastMatching.uniqueMatches());
83
84 // search unmatched and ambiguously-matched classes
85 matching.match(lastMatching.unmatchedSourceClasses(), lastMatching.unmatchedDestClasses());
86 for (ClassMatch match : lastMatching.ambiguousMatches()) {
87 matching.match(match.sourceClasses, match.destClasses);
88 }
89 }
90 System.out.println(matching);
91 BiMap<ClassEntry, ClassEntry> uniqueMatches = matching.uniqueMatches();
92
93 // did we match anything new this time?
94 if (uniqueMatches.size() > numUniqueMatchesLastTime) {
95 numUniqueMatchesLastTime = uniqueMatches.size();
96 lastMatching = matching;
97 } else {
98 break;
99 }
100
101 // update the namers
102 ClassNamer namer = new ClassNamer(uniqueMatches);
103 sourceNamer = namer.getSourceNamer();
104 destNamer = namer.getDestNamer();
105 }
106 }
107
108 return lastMatching;
109 }
110
111 public static Mappings newMappings(ClassMatches matches, Mappings oldMappings, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator)
112 throws MappingConflict {
113 // sort the unique matches by size of inner class chain
114 Multimap<Integer, java.util.Map.Entry<ClassEntry, ClassEntry>> matchesByDestChainSize = HashMultimap.create();
115 for (java.util.Map.Entry<ClassEntry, ClassEntry> match : matches.getUniqueMatches().entrySet()) {
116 int chainSize = destDeobfuscator.getJarIndex().getObfClassChain(match.getValue()).size();
117 matchesByDestChainSize.put(chainSize, match);
118 }
119
120 // build the mappings (in order of small-to-large inner chains)
121 Mappings newMappings = new Mappings();
122 List<Integer> chainSizes = Lists.newArrayList(matchesByDestChainSize.keySet());
123 Collections.sort(chainSizes);
124 for (int chainSize : chainSizes) {
125 for (java.util.Map.Entry<ClassEntry, ClassEntry> match : matchesByDestChainSize.get(chainSize)) {
126 // get class info
127 ClassEntry obfSourceClassEntry = match.getKey();
128 ClassEntry obfDestClassEntry = match.getValue();
129 List<ClassEntry> destClassChain = destDeobfuscator.getJarIndex().getObfClassChain(obfDestClassEntry);
130
131 ClassMapping sourceMapping;
132 if (obfSourceClassEntry.isInnerClass()) {
133 List<ClassMapping> srcClassChain = sourceDeobfuscator.getMappings().getClassMappingChain(obfSourceClassEntry);
134 sourceMapping = srcClassChain.get(srcClassChain.size() - 1);
135 } else {
136 sourceMapping = sourceDeobfuscator.getMappings().getClassByObf(obfSourceClassEntry);
137 }
138
139 if (sourceMapping == null) {
140 // if this class was never deobfuscated, don't try to match it
141 continue;
142 }
143
144 // find out where to make the dest class mapping
145 if (destClassChain.size() == 1) {
146 // not an inner class, add directly to mappings
147 newMappings.addClassMapping(migrateClassMapping(obfDestClassEntry, sourceMapping, matches, false));
148 } else {
149 // inner class, find the outer class mapping
150 ClassMapping destMapping = null;
151 for (int i = 0; i < destClassChain.size() - 1; i++) {
152 ClassEntry destChainClassEntry = destClassChain.get(i);
153 if (destMapping == null) {
154 destMapping = newMappings.getClassByObf(destChainClassEntry);
155 if (destMapping == null) {
156 destMapping = new ClassMapping(destChainClassEntry.getName());
157 newMappings.addClassMapping(destMapping);
158 }
159 } else {
160 destMapping = destMapping.getInnerClassByObfSimple(destChainClassEntry.getInnermostClassName());
161 if (destMapping == null) {
162 destMapping = new ClassMapping(destChainClassEntry.getName());
163 destMapping.addInnerClassMapping(destMapping);
164 }
165 }
166 }
167 destMapping.addInnerClassMapping(migrateClassMapping(obfDestClassEntry, sourceMapping, matches, true));
168 }
169 }
170 }
171 return newMappings;
172 }
173
174 private static ClassMapping migrateClassMapping(ClassEntry newObfClass, ClassMapping oldClassMapping, final ClassMatches matches, boolean useSimpleName) {
175
176 ClassNameReplacer replacer = className ->
177 {
178 ClassEntry newClassEntry = matches.getUniqueMatches().get(new ClassEntry(className));
179 if (newClassEntry != null) {
180 return newClassEntry.getName();
181 }
182 return null;
183 };
184
185 ClassMapping newClassMapping;
186 String deobfName = oldClassMapping.getDeobfName();
187 if (deobfName != null) {
188 if (useSimpleName) {
189 deobfName = new ClassEntry(deobfName).getSimpleName();
190 }
191 newClassMapping = new ClassMapping(newObfClass.getName(), deobfName);
192 } else {
193 newClassMapping = new ClassMapping(newObfClass.getName());
194 }
195
196 // migrate fields
197 for (FieldMapping oldFieldMapping : oldClassMapping.fields()) {
198 if (canMigrate(oldFieldMapping.getObfType(), matches)) {
199 newClassMapping.addFieldMapping(new FieldMapping(oldFieldMapping, replacer));
200 } else {
201 System.out.println(String.format("Can't map field, dropping: %s.%s %s",
202 oldClassMapping.getDeobfName(),
203 oldFieldMapping.getDeobfName(),
204 oldFieldMapping.getObfType()
205 ));
206 }
207 }
208
209 // migrate methods
210 for (MethodMapping oldMethodMapping : oldClassMapping.methods()) {
211 if (canMigrate(oldMethodMapping.getObfSignature(), matches)) {
212 newClassMapping.addMethodMapping(new MethodMapping(oldMethodMapping, replacer));
213 } else {
214 System.out.println(String.format("Can't map method, dropping: %s.%s %s",
215 oldClassMapping.getDeobfName(),
216 oldMethodMapping.getDeobfName(),
217 oldMethodMapping.getObfSignature()
218 ));
219 }
220 }
221
222 return newClassMapping;
223 }
224
225 private static boolean canMigrate(Signature oldObfSignature, ClassMatches classMatches) {
226 for (Type oldObfType : oldObfSignature.types()) {
227 if (!canMigrate(oldObfType, classMatches)) {
228 return false;
229 }
230 }
231 return true;
232 }
233
234 private static boolean canMigrate(Type oldObfType, ClassMatches classMatches) {
235
236 // non classes can be migrated
237 if (!oldObfType.hasClass()) {
238 return true;
239 }
240
241 // non obfuscated classes can be migrated
242 ClassEntry classEntry = oldObfType.getClassEntry();
243 if (classEntry.getPackageName() != null) {
244 return true;
245 }
246
247 // obfuscated classes with mappings can be migrated
248 return classMatches.getUniqueMatches().containsKey(classEntry);
249 }
250
251 public static void convertMappings(Mappings mappings, BiMap<ClassEntry, ClassEntry> changes) {
252
253 // sort the changes so classes are renamed in the correct order
254 // ie. if we have the mappings a->b, b->c, we have to apply b->c before a->b
255 LinkedHashMap<ClassEntry, ClassEntry> sortedChanges = Maps.newLinkedHashMap();
256 int numChangesLeft = changes.size();
257 while (!changes.isEmpty()) {
258 Iterator<Map.Entry<ClassEntry, ClassEntry>> iter = changes.entrySet().iterator();
259 while (iter.hasNext()) {
260 Map.Entry<ClassEntry, ClassEntry> change = iter.next();
261 if (changes.containsKey(change.getValue())) {
262 sortedChanges.put(change.getKey(), change.getValue());
263 iter.remove();
264 }
265 }
266
267 // did we remove any changes?
268 if (numChangesLeft - changes.size() > 0) {
269 // keep going
270 numChangesLeft = changes.size();
271 } else {
272 // can't sort anymore. There must be a loop
273 break;
274 }
275 }
276 if (!changes.isEmpty()) {
277 throw new Error("Unable to sort class changes! There must be a cycle.");
278 }
279
280 // convert the mappings in the correct class order
281 for (Map.Entry<ClassEntry, ClassEntry> entry : sortedChanges.entrySet()) {
282 mappings.renameObfClass(entry.getKey().getName(), entry.getValue().getName());
283 }
284 }
285
286 public static Doer<FieldEntry> getFieldDoer() {
287 return new Doer<FieldEntry>() {
288
289 @Override
290 public Collection<FieldEntry> getDroppedEntries(MappingsChecker checker) {
291 return checker.getDroppedFieldMappings().keySet();
292 }
293
294 @Override
295 public Collection<FieldEntry> getObfEntries(JarIndex jarIndex) {
296 return jarIndex.getObfFieldEntries();
297 }
298
299 @Override
300 public Collection<? extends MemberMapping<FieldEntry>> getMappings(ClassMapping destClassMapping) {
301 return (Collection<? extends MemberMapping<FieldEntry>>) destClassMapping.fields();
302 }
303
304 @Override
305 public Set<FieldEntry> filterEntries(Collection<FieldEntry> obfDestFields, FieldEntry obfSourceField, ClassMatches classMatches) {
306 Set<FieldEntry> out = Sets.newHashSet();
307 for (FieldEntry obfDestField : obfDestFields) {
308 Type translatedDestType = translate(obfDestField.getType(), classMatches.getUniqueMatches().inverse());
309 if (translatedDestType.equals(obfSourceField.getType())) {
310 out.add(obfDestField);
311 }
312 }
313 return out;
314 }
315
316 @Override
317 public void setUpdateObfMember(ClassMapping classMapping, MemberMapping<FieldEntry> memberMapping, FieldEntry newField) {
318 FieldMapping fieldMapping = (FieldMapping) memberMapping;
319 classMapping.setFieldObfNameAndType(fieldMapping.getObfName(), fieldMapping.getObfType(), newField.getName(), newField.getType());
320 }
321
322 @Override
323 public boolean hasObfMember(ClassMapping classMapping, FieldEntry obfField) {
324 return classMapping.getFieldByObf(obfField.getName(), obfField.getType()) != null;
325 }
326
327 @Override
328 public void removeMemberByObf(ClassMapping classMapping, FieldEntry obfField) {
329 classMapping.removeFieldMapping(classMapping.getFieldByObf(obfField.getName(), obfField.getType()));
330 }
331 };
332 }
333
334 public static Doer<BehaviorEntry> getMethodDoer() {
335 return new Doer<BehaviorEntry>() {
336
337 @Override
338 public Collection<BehaviorEntry> getDroppedEntries(MappingsChecker checker) {
339 return checker.getDroppedMethodMappings().keySet();
340 }
341
342 @Override
343 public Collection<BehaviorEntry> getObfEntries(JarIndex jarIndex) {
344 return jarIndex.getObfBehaviorEntries();
345 }
346
347 @Override
348 public Collection<? extends MemberMapping<BehaviorEntry>> getMappings(ClassMapping destClassMapping) {
349 return (Collection<? extends MemberMapping<BehaviorEntry>>) destClassMapping.methods();
350 }
351
352 @Override
353 public Set<BehaviorEntry> filterEntries(Collection<BehaviorEntry> obfDestFields, BehaviorEntry obfSourceField, ClassMatches classMatches) {
354 Set<BehaviorEntry> out = Sets.newHashSet();
355 for (BehaviorEntry obfDestField : obfDestFields) {
356 // Try to translate the signature
357 Signature translatedDestSignature = translate(obfDestField.getSignature(), classMatches.getUniqueMatches().inverse());
358 if (translatedDestSignature != null && obfSourceField.getSignature() != null && translatedDestSignature.equals(obfSourceField.getSignature()))
359 out.add(obfDestField);
360 }
361 return out;
362 }
363
364 @Override
365 public void setUpdateObfMember(ClassMapping classMapping, MemberMapping<BehaviorEntry> memberMapping, BehaviorEntry newBehavior) {
366 MethodMapping methodMapping = (MethodMapping) memberMapping;
367 classMapping.setMethodObfNameAndSignature(methodMapping.getObfName(), methodMapping.getObfSignature(), newBehavior.getName(), newBehavior.getSignature());
368 }
369
370 @Override
371 public boolean hasObfMember(ClassMapping classMapping, BehaviorEntry obfBehavior) {
372 return classMapping.getMethodByObf(obfBehavior.getName(), obfBehavior.getSignature()) != null;
373 }
374
375 @Override
376 public void removeMemberByObf(ClassMapping classMapping, BehaviorEntry obfBehavior) {
377 classMapping.removeMethodMapping(classMapping.getMethodByObf(obfBehavior.getName(), obfBehavior.getSignature()));
378 }
379 };
380 }
381
382 public static int compareMethodByteCode(CodeIterator sourceIt, CodeIterator destIt) {
383 int sourcePos = 0;
384 int destPos = 0;
385 while (sourceIt.hasNext() && destIt.hasNext()) {
386 try {
387 sourcePos = sourceIt.next();
388 destPos = destIt.next();
389 if (sourceIt.byteAt(sourcePos) != destIt.byteAt(destPos))
390 return sourcePos;
391 } catch (BadBytecode badBytecode) {
392 // Ignore bad bytecode (it might be a little bit dangerous...)
393 }
394 }
395 if (sourcePos < destPos)
396 return sourcePos;
397 else if (destPos < sourcePos)
398 return destPos;
399 return sourcePos;
400 }
401
402 public static BehaviorEntry compareMethods(CtClass destCtClass, CtClass sourceCtClass, BehaviorEntry obfSourceEntry,
403 Set<BehaviorEntry> obfDestEntries) {
404 try {
405 // Get the source method with Javassist
406 CtMethod sourceCtClassMethod = sourceCtClass.getMethod(obfSourceEntry.getName(), obfSourceEntry.getSignature().toString());
407 CodeAttribute sourceAttribute = sourceCtClassMethod.getMethodInfo().getCodeAttribute();
408
409 // Empty method body, ignore!
410 if (sourceAttribute == null)
411 return null;
412 for (BehaviorEntry desEntry : obfDestEntries) {
413 try {
414 CtMethod destCtClassMethod = destCtClass
415 .getMethod(desEntry.getName(), desEntry.getSignature().toString());
416 CodeAttribute destAttribute = destCtClassMethod.getMethodInfo().getCodeAttribute();
417
418 // Ignore empty body methods
419 if (destAttribute == null)
420 continue;
421 CodeIterator destIterator = destAttribute.iterator();
422 int maxPos = compareMethodByteCode(sourceAttribute.iterator(), destIterator);
423
424 // The bytecode is identical to the original method, assuming that the method is correct!
425 if (sourceAttribute.getCodeLength() == (maxPos + 1) && maxPos > 1)
426 return desEntry;
427 } catch (NotFoundException e) {
428 e.printStackTrace();
429 }
430 }
431 } catch (NotFoundException e) {
432 e.printStackTrace();
433 return null;
434 }
435 return null;
436 }
437
438 public static MemberMatches<BehaviorEntry> computeMethodsMatches(Deobfuscator destDeobfuscator,
439 Mappings destMappings,
440 Deobfuscator sourceDeobfuscator,
441 Mappings sourceMappings,
442 ClassMatches classMatches,
443 Doer<BehaviorEntry> doer) {
444
445 MemberMatches<BehaviorEntry> memberMatches = new MemberMatches<>();
446
447 // unmatched source fields are easy
448 MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex());
449 checker.dropBrokenMappings(destMappings);
450 for (BehaviorEntry destObfEntry : doer.getDroppedEntries(checker)) {
451 BehaviorEntry srcObfEntry = translate(destObfEntry, classMatches.getUniqueMatches().inverse());
452 memberMatches.addUnmatchedSourceEntry(srcObfEntry);
453 }
454
455 // get matched fields (anything that's left after the checks/drops is matched(
456 for (ClassMapping classMapping : destMappings.classes())
457 collectMatchedFields(memberMatches, classMapping, classMatches, doer);
458
459 // get unmatched dest fields
460 doer.getObfEntries(destDeobfuscator.getJarIndex()).stream()
461 .filter(destEntry -> !memberMatches.isMatchedDestEntry(destEntry))
462 .forEach(memberMatches::addUnmatchedDestEntry);
463
464 // Apply mappings to deobfuscator
465
466 // Create type loader
467 TranslatingTypeLoader destTypeLoader = destDeobfuscator.createTypeLoader();
468 TranslatingTypeLoader sourceTypeLoader = sourceDeobfuscator.createTypeLoader();
469
470 System.out.println("Automatching " + memberMatches.getUnmatchedSourceEntries().size() + " unmatched source entries...");
471
472 // go through the unmatched source fields and try to pick out the easy matches
473 for (ClassEntry obfSourceClass : Lists.newArrayList(memberMatches.getSourceClassesWithUnmatchedEntries())) {
474 for (BehaviorEntry obfSourceEntry : Lists.newArrayList(memberMatches.getUnmatchedSourceEntries(obfSourceClass))) {
475
476 // get the possible dest matches
477 ClassEntry obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass);
478
479 // filter by type/signature
480 Set<BehaviorEntry> obfDestEntries = doer.filterEntries(memberMatches.getUnmatchedDestEntries(obfDestClass), obfSourceEntry, classMatches);
481
482 if (obfDestEntries.size() == 1) {
483 // make the easy match
484 memberMatches.makeMatch(obfSourceEntry, obfDestEntries.iterator().next());
485 } else if (obfDestEntries.isEmpty()) {
486 // no match is possible =(
487 memberMatches.makeSourceUnmatchable(obfSourceEntry, null);
488 } else {
489 // Multiple matches! Scan methods instructions
490 CtClass destCtClass = destTypeLoader.loadClass(obfDestClass.getClassName());
491 CtClass sourceCtClass = sourceTypeLoader.loadClass(obfSourceClass.getClassName());
492 BehaviorEntry match = compareMethods(destCtClass, sourceCtClass, obfSourceEntry, obfDestEntries);
493 // the method match correctly, match it on the member mapping!
494 if (match != null)
495 memberMatches.makeMatch(obfSourceEntry, match);
496 }
497 }
498 }
499
500 System.out.println(String.format("Ended up with %d ambiguous and %d unmatchable source entries",
501 memberMatches.getUnmatchedSourceEntries().size(),
502 memberMatches.getUnmatchableSourceEntries().size()
503 ));
504
505 return memberMatches;
506 }
507
508 public static <T extends Entry> MemberMatches<T> computeMemberMatches(Deobfuscator destDeobfuscator, Mappings destMappings, ClassMatches classMatches, Doer<T> doer) {
509
510 MemberMatches<T> memberMatches = new MemberMatches<>();
511
512 // unmatched source fields are easy
513 MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex());
514 checker.dropBrokenMappings(destMappings);
515 for (T destObfEntry : doer.getDroppedEntries(checker)) {
516 T srcObfEntry = translate(destObfEntry, classMatches.getUniqueMatches().inverse());
517 memberMatches.addUnmatchedSourceEntry(srcObfEntry);
518 }
519
520 // get matched fields (anything that's left after the checks/drops is matched(
521 for (ClassMapping classMapping : destMappings.classes()) {
522 collectMatchedFields(memberMatches, classMapping, classMatches, doer);
523 }
524
525 // get unmatched dest fields
526 for (T destEntry : doer.getObfEntries(destDeobfuscator.getJarIndex())) {
527 if (!memberMatches.isMatchedDestEntry(destEntry)) {
528 memberMatches.addUnmatchedDestEntry(destEntry);
529 }
530 }
531
532 System.out.println("Automatching " + memberMatches.getUnmatchedSourceEntries().size() + " unmatched source entries...");
533
534 // go through the unmatched source fields and try to pick out the easy matches
535 for (ClassEntry obfSourceClass : Lists.newArrayList(memberMatches.getSourceClassesWithUnmatchedEntries())) {
536 for (T obfSourceEntry : Lists.newArrayList(memberMatches.getUnmatchedSourceEntries(obfSourceClass))) {
537
538 // get the possible dest matches
539 ClassEntry obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass);
540
541 // filter by type/signature
542 Set<T> obfDestEntries = doer.filterEntries(memberMatches.getUnmatchedDestEntries(obfDestClass), obfSourceEntry, classMatches);
543
544 if (obfDestEntries.size() == 1) {
545 // make the easy match
546 memberMatches.makeMatch(obfSourceEntry, obfDestEntries.iterator().next());
547 } else if (obfDestEntries.isEmpty()) {
548 // no match is possible =(
549 memberMatches.makeSourceUnmatchable(obfSourceEntry, null);
550 }
551 }
552 }
553
554 System.out.println(String.format("Ended up with %d ambiguous and %d unmatchable source entries",
555 memberMatches.getUnmatchedSourceEntries().size(),
556 memberMatches.getUnmatchableSourceEntries().size()
557 ));
558
559 return memberMatches;
560 }
561
562 private static <T extends Entry> void collectMatchedFields(MemberMatches<T> memberMatches, ClassMapping destClassMapping, ClassMatches classMatches, Doer<T> doer) {
563
564 // get the fields for this class
565 for (MemberMapping<T> destEntryMapping : doer.getMappings(destClassMapping)) {
566 T destObfField = destEntryMapping.getObfEntry(destClassMapping.getObfEntry());
567 T srcObfField = translate(destObfField, classMatches.getUniqueMatches().inverse());
568 memberMatches.addMatch(srcObfField, destObfField);
569 }
570
571 // recurse
572 for (ClassMapping destInnerClassMapping : destClassMapping.innerClasses()) {
573 collectMatchedFields(memberMatches, destInnerClassMapping, classMatches, doer);
574 }
575 }
576
577 @SuppressWarnings("unchecked")
578 private static <T extends Entry> T translate(T in, BiMap<ClassEntry, ClassEntry> map) {
579 if (in instanceof FieldEntry) {
580 return (T) new FieldEntry(
581 map.get(in.getClassEntry()),
582 in.getName(),
583 translate(((FieldEntry) in).getType(), map)
584 );
585 } else if (in instanceof MethodEntry) {
586 return (T) new MethodEntry(
587 map.get(in.getClassEntry()),
588 in.getName(),
589 translate(((MethodEntry) in).getSignature(), map)
590 );
591 } else if (in instanceof ConstructorEntry) {
592 return (T) new ConstructorEntry(
593 map.get(in.getClassEntry()),
594 translate(((ConstructorEntry) in).getSignature(), map)
595 );
596 }
597 throw new Error("Unhandled entry type: " + in.getClass());
598 }
599
600 private static Type translate(Type type, final BiMap<ClassEntry, ClassEntry> map) {
601 return new Type(type, inClassName ->
602 {
603 ClassEntry outClassEntry = map.get(new ClassEntry(inClassName));
604 if (outClassEntry == null) {
605 return null;
606 }
607 return outClassEntry.getName();
608 });
609 }
610
611 private static Signature translate(Signature signature, final BiMap<ClassEntry, ClassEntry> map) {
612 if (signature == null) {
613 return null;
614 }
615 return new Signature(signature, inClassName ->
616 {
617 ClassEntry outClassEntry = map.get(new ClassEntry(inClassName));
618 if (outClassEntry == null) {
619 return null;
620 }
621 return outClassEntry.getName();
622 });
623 }
624
625 public static <T extends Entry> void applyMemberMatches(Mappings mappings, ClassMatches classMatches, MemberMatches<T> memberMatches, Doer<T> doer) {
626 for (ClassMapping classMapping : mappings.classes()) {
627 applyMemberMatches(classMapping, classMatches, memberMatches, doer);
628 }
629 }
630
631 private static <T extends Entry> void applyMemberMatches(ClassMapping classMapping, ClassMatches classMatches, MemberMatches<T> memberMatches, Doer<T> doer) {
632
633 // get the classes
634 ClassEntry obfDestClass = new ClassEntry(classMapping.getObfFullName());
635
636 // make a map of all the renames we need to make
637 Map<T, T> renames = Maps.newHashMap();
638 for (MemberMapping<T> memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) {
639 T obfOldDestEntry = memberMapping.getObfEntry(obfDestClass);
640 T obfSourceEntry = getSourceEntryFromDestMapping(memberMapping, obfDestClass, classMatches);
641
642 // but drop the unmatchable things
643 if (memberMatches.isUnmatchableSourceEntry(obfSourceEntry)) {
644 doer.removeMemberByObf(classMapping, obfOldDestEntry);
645 continue;
646 }
647
648 T obfNewDestEntry = memberMatches.matches().get(obfSourceEntry);
649 if (obfNewDestEntry != null && !obfOldDestEntry.getName().equals(obfNewDestEntry.getName())) {
650 renames.put(obfOldDestEntry, obfNewDestEntry);
651 }
652 }
653
654 if (!renames.isEmpty()) {
655
656 // apply to this class (should never need more than n passes)
657 int numRenamesAppliedThisRound;
658 do {
659 numRenamesAppliedThisRound = 0;
660
661 for (MemberMapping<T> memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) {
662 T obfOldDestEntry = memberMapping.getObfEntry(obfDestClass);
663 T obfNewDestEntry = renames.get(obfOldDestEntry);
664 if (obfNewDestEntry != null) {
665 // make sure this rename won't cause a collision
666 // otherwise, save it for the next round and try again next time
667 if (!doer.hasObfMember(classMapping, obfNewDestEntry)) {
668 doer.setUpdateObfMember(classMapping, memberMapping, obfNewDestEntry);
669 renames.remove(obfOldDestEntry);
670 numRenamesAppliedThisRound++;
671 }
672 }
673 }
674 } while (numRenamesAppliedThisRound > 0);
675
676 if (!renames.isEmpty()) {
677 System.err.println(String.format("WARNING: Couldn't apply all the renames for class %s. %d renames left.",
678 classMapping.getObfFullName(), renames.size()
679 ));
680 for (Map.Entry<T, T> entry : renames.entrySet()) {
681 System.err.println(String.format("\t%s -> %s", entry.getKey().getName(), entry.getValue().getName()));
682 }
683 }
684 }
685
686 // recurse
687 for (ClassMapping innerClassMapping : classMapping.innerClasses()) {
688 applyMemberMatches(innerClassMapping, classMatches, memberMatches, doer);
689 }
690 }
691
692 private static <T extends Entry> T getSourceEntryFromDestMapping(MemberMapping<T> destMemberMapping, ClassEntry obfDestClass, ClassMatches classMatches) {
693 return translate(destMemberMapping.getObfEntry(obfDestClass), classMatches.getUniqueMatches().inverse());
694 }
695
696 public interface Doer<T extends Entry> {
697 Collection<T> getDroppedEntries(MappingsChecker checker);
698
699 Collection<T> getObfEntries(JarIndex jarIndex);
700
701 Collection<? extends MemberMapping<T>> getMappings(ClassMapping destClassMapping);
702
703 Set<T> filterEntries(Collection<T> obfEntries, T obfSourceEntry, ClassMatches classMatches);
704
705 void setUpdateObfMember(ClassMapping classMapping, MemberMapping<T> memberMapping, T newEntry);
706
707 boolean hasObfMember(ClassMapping classMapping, T obfEntry);
708
709 void removeMemberByObf(ClassMapping classMapping, T obfEntry);
710 }
711}
diff --git a/src/main/java/cuchaz/enigma/convert/MatchesReader.java b/src/main/java/cuchaz/enigma/convert/MatchesReader.java
deleted file mode 100644
index 1cf50fa4..00000000
--- a/src/main/java/cuchaz/enigma/convert/MatchesReader.java
+++ /dev/null
@@ -1,105 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.convert;
13
14import com.google.common.collect.Lists;
15import cuchaz.enigma.mapping.*;
16
17import java.io.*;
18import java.nio.charset.Charset;
19import java.util.Collection;
20import java.util.List;
21
22public class MatchesReader {
23
24 public static ClassMatches readClasses(File file)
25 throws IOException {
26 try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8")))) {
27 ClassMatches matches = new ClassMatches();
28 String line;
29 while ((line = in.readLine()) != null) {
30 matches.add(readClassMatch(line));
31 }
32 return matches;
33 }
34 }
35
36 private static ClassMatch readClassMatch(String line) {
37 String[] sides = line.split(":", 2);
38 return new ClassMatch(readClasses(sides[0]), readClasses(sides[1]));
39 }
40
41 private static Collection<ClassEntry> readClasses(String in) {
42 List<ClassEntry> entries = Lists.newArrayList();
43 for (String className : in.split(",")) {
44 className = className.trim();
45 if (!className.isEmpty()) {
46 entries.add(new ClassEntry(className));
47 }
48 }
49 return entries;
50 }
51
52 public static <T extends Entry> MemberMatches<T> readMembers(File file)
53 throws IOException {
54 try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8")))) {
55 MemberMatches<T> matches = new MemberMatches<>();
56 String line;
57 while ((line = in.readLine()) != null) {
58 readMemberMatch(matches, line);
59 }
60 return matches;
61 }
62 }
63
64 private static <T extends Entry> void readMemberMatch(MemberMatches<T> matches, String line) {
65 if (line.startsWith("!")) {
66 T source = readEntry(line.substring(1));
67 matches.addUnmatchableSourceEntry(source);
68 } else {
69 String[] parts = line.split(":", 2);
70 T source = readEntry(parts[0]);
71 T dest = readEntry(parts[1]);
72 if (source != null && dest != null) {
73 matches.addMatch(source, dest);
74 } else if (source != null) {
75 matches.addUnmatchedSourceEntry(source);
76 } else if (dest != null) {
77 matches.addUnmatchedDestEntry(dest);
78 }
79 }
80 }
81
82 @SuppressWarnings("unchecked")
83 private static <T extends Entry> T readEntry(String in) {
84 if (in.length() <= 0) {
85 return null;
86 }
87 String[] parts = in.split(" ");
88 if (parts.length == 3 && parts[2].indexOf('(') < 0) {
89 return (T) new FieldEntry(
90 new ClassEntry(parts[0]),
91 parts[1],
92 new Type(parts[2])
93 );
94 } else {
95 assert (parts.length == 2 || parts.length == 3);
96 if (parts.length == 2) {
97 return (T) EntryFactory.getBehaviorEntry(parts[0], parts[1]);
98 } else if (parts.length == 3) {
99 return (T) EntryFactory.getBehaviorEntry(parts[0], parts[1], parts[2]);
100 } else {
101 throw new Error("Malformed behavior entry: " + in);
102 }
103 }
104 }
105}
diff --git a/src/main/java/cuchaz/enigma/convert/MatchesWriter.java b/src/main/java/cuchaz/enigma/convert/MatchesWriter.java
deleted file mode 100644
index 8fe73265..00000000
--- a/src/main/java/cuchaz/enigma/convert/MatchesWriter.java
+++ /dev/null
@@ -1,123 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.convert;
13
14import cuchaz.enigma.mapping.BehaviorEntry;
15import cuchaz.enigma.mapping.ClassEntry;
16import cuchaz.enigma.mapping.Entry;
17import cuchaz.enigma.mapping.FieldEntry;
18
19import java.io.File;
20import java.io.FileOutputStream;
21import java.io.IOException;
22import java.io.OutputStreamWriter;
23import java.nio.charset.Charset;
24import java.util.Map;
25
26public class MatchesWriter {
27
28 public static void writeClasses(ClassMatches matches, File file)
29 throws IOException {
30 try (OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(file), Charset.forName("UTF-8"))) {
31 for (ClassMatch match : matches) {
32 writeClassMatch(out, match);
33 }
34 }
35 }
36
37 private static void writeClassMatch(OutputStreamWriter out, ClassMatch match)
38 throws IOException {
39 writeClasses(out, match.sourceClasses);
40 out.write(":");
41 writeClasses(out, match.destClasses);
42 out.write("\n");
43 }
44
45 private static void writeClasses(OutputStreamWriter out, Iterable<ClassEntry> classes)
46 throws IOException {
47 boolean isFirst = true;
48 for (ClassEntry entry : classes) {
49 if (isFirst) {
50 isFirst = false;
51 } else {
52 out.write(",");
53 }
54 out.write(entry.toString());
55 }
56 }
57
58 public static <T extends Entry> void writeMembers(MemberMatches<T> matches, File file)
59 throws IOException {
60 try (OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(file), Charset.forName("UTF-8"))) {
61 for (Map.Entry<T, T> match : matches.matches().entrySet()) {
62 writeMemberMatch(out, match.getKey(), match.getValue());
63 }
64 for (T entry : matches.getUnmatchedSourceEntries()) {
65 writeMemberMatch(out, entry, null);
66 }
67 for (T entry : matches.getUnmatchedDestEntries()) {
68 writeMemberMatch(out, null, entry);
69 }
70 for (T entry : matches.getUnmatchableSourceEntries()) {
71 writeUnmatchableEntry(out, entry);
72 }
73 }
74 }
75
76 private static <T extends Entry> void writeMemberMatch(OutputStreamWriter out, T source, T dest)
77 throws IOException {
78 if (source != null) {
79 writeEntry(out, source);
80 }
81 out.write(":");
82 if (dest != null) {
83 writeEntry(out, dest);
84 }
85 out.write("\n");
86 }
87
88 private static <T extends Entry> void writeUnmatchableEntry(OutputStreamWriter out, T entry)
89 throws IOException {
90 out.write("!");
91 writeEntry(out, entry);
92 out.write("\n");
93 }
94
95 private static <T extends Entry> void writeEntry(OutputStreamWriter out, T entry)
96 throws IOException {
97 if (entry instanceof FieldEntry) {
98 writeField(out, (FieldEntry) entry);
99 } else if (entry instanceof BehaviorEntry) {
100 writeBehavior(out, (BehaviorEntry) entry);
101 }
102 }
103
104 private static void writeField(OutputStreamWriter out, FieldEntry fieldEntry)
105 throws IOException {
106 out.write(fieldEntry.getClassName());
107 out.write(" ");
108 out.write(fieldEntry.getName());
109 out.write(" ");
110 out.write(fieldEntry.getType().toString());
111 }
112
113 private static void writeBehavior(OutputStreamWriter out, BehaviorEntry behaviorEntry)
114 throws IOException {
115 out.write(behaviorEntry.getClassName());
116 out.write(" ");
117 out.write(behaviorEntry.getName());
118 out.write(" ");
119 if (behaviorEntry.getSignature() != null) {
120 out.write(behaviorEntry.getSignature().toString());
121 }
122 }
123}
diff --git a/src/main/java/cuchaz/enigma/convert/MemberMatches.java b/src/main/java/cuchaz/enigma/convert/MemberMatches.java
deleted file mode 100644
index bd743115..00000000
--- a/src/main/java/cuchaz/enigma/convert/MemberMatches.java
+++ /dev/null
@@ -1,179 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.convert;
13
14import com.google.common.collect.*;
15import cuchaz.enigma.Deobfuscator;
16import cuchaz.enigma.mapping.ClassEntry;
17import cuchaz.enigma.mapping.Entry;
18
19import java.util.Collection;
20import java.util.Set;
21
22public class MemberMatches<T extends Entry> {
23
24 private BiMap<T, T> matches;
25 private Multimap<ClassEntry, T> matchedSourceEntries;
26 private Multimap<ClassEntry, T> unmatchedSourceEntries;
27 private Multimap<ClassEntry, T> unmatchedDestEntries;
28 private Multimap<ClassEntry, T> unmatchableSourceEntries;
29
30 public MemberMatches() {
31 matches = HashBiMap.create();
32 matchedSourceEntries = HashMultimap.create();
33 unmatchedSourceEntries = HashMultimap.create();
34 unmatchedDestEntries = HashMultimap.create();
35 unmatchableSourceEntries = HashMultimap.create();
36 }
37
38 public void addMatch(T srcEntry, T destEntry) {
39 boolean wasAdded = matches.put(srcEntry, destEntry) == null;
40 assert (wasAdded);
41 wasAdded = matchedSourceEntries.put(srcEntry.getClassEntry(), srcEntry);
42 assert (wasAdded);
43 }
44
45 public void addUnmatchedSourceEntry(T sourceEntry) {
46 boolean wasAdded = unmatchedSourceEntries.put(sourceEntry.getClassEntry(), sourceEntry);
47 assert (wasAdded);
48 }
49
50 public void addUnmatchedSourceEntries(Iterable<T> sourceEntries) {
51 for (T sourceEntry : sourceEntries) {
52 addUnmatchedSourceEntry(sourceEntry);
53 }
54 }
55
56 public void addUnmatchedDestEntry(T destEntry) {
57 if (destEntry.getName().equals("<clinit>") || destEntry.getName().equals("<init>"))
58 return;
59 boolean wasAdded = unmatchedDestEntries.put(destEntry.getClassEntry(), destEntry);
60 assert (wasAdded);
61 }
62
63 public void addUnmatchedDestEntries(Iterable<T> destEntriesntries) {
64 for (T entry : destEntriesntries) {
65 addUnmatchedDestEntry(entry);
66 }
67 }
68
69 public void addUnmatchableSourceEntry(T sourceEntry) {
70 boolean wasAdded = unmatchableSourceEntries.put(sourceEntry.getClassEntry(), sourceEntry);
71 assert (wasAdded);
72 }
73
74 public Set<ClassEntry> getSourceClassesWithUnmatchedEntries() {
75 return unmatchedSourceEntries.keySet();
76 }
77
78 public Collection<ClassEntry> getSourceClassesWithoutUnmatchedEntries() {
79 Set<ClassEntry> out = Sets.newHashSet();
80 out.addAll(matchedSourceEntries.keySet());
81 out.removeAll(unmatchedSourceEntries.keySet());
82 return out;
83 }
84
85 public Collection<T> getUnmatchedSourceEntries() {
86 return unmatchedSourceEntries.values();
87 }
88
89 public Collection<T> getUnmatchedSourceEntries(ClassEntry sourceClass) {
90 return unmatchedSourceEntries.get(sourceClass);
91 }
92
93 public Collection<T> getUnmatchedDestEntries() {
94 return unmatchedDestEntries.values();
95 }
96
97 public Collection<T> getUnmatchedDestEntries(ClassEntry destClass) {
98 return unmatchedDestEntries.get(destClass);
99 }
100
101 public Collection<T> getUnmatchableSourceEntries() {
102 return unmatchableSourceEntries.values();
103 }
104
105 public boolean hasSource(T sourceEntry) {
106 return matches.containsKey(sourceEntry) || unmatchedSourceEntries.containsValue(sourceEntry);
107 }
108
109 public boolean hasDest(T destEntry) {
110 return matches.containsValue(destEntry) || unmatchedDestEntries.containsValue(destEntry);
111 }
112
113 public BiMap<T, T> matches() {
114 return matches;
115 }
116
117 public boolean isMatchedSourceEntry(T sourceEntry) {
118 return matches.containsKey(sourceEntry);
119 }
120
121 public boolean isMatchedDestEntry(T destEntry) {
122 return matches.containsValue(destEntry);
123 }
124
125 public boolean isUnmatchableSourceEntry(T sourceEntry) {
126 return unmatchableSourceEntries.containsEntry(sourceEntry.getClassEntry(), sourceEntry);
127 }
128
129 public void makeMatch(T sourceEntry, T destEntry) {
130 makeMatch(sourceEntry, destEntry, null, null);
131 }
132
133 public void makeMatch(T sourceEntry, T destEntry, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
134 if (sourceDeobfuscator != null && destDeobfuscator != null) {
135 makeMatch(sourceEntry, destEntry);
136 sourceEntry = (T) sourceEntry.cloneToNewClass(sourceDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(sourceEntry, true));
137 destEntry = (T) destEntry.cloneToNewClass(destDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(destEntry, true));
138 }
139 boolean wasRemoved = unmatchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry);
140 assert (wasRemoved);
141 wasRemoved = unmatchedDestEntries.remove(destEntry.getClassEntry(), destEntry);
142 assert (wasRemoved);
143 addMatch(sourceEntry, destEntry);
144 }
145
146 public boolean isMatched(T sourceEntry, T destEntry) {
147 T match = matches.get(sourceEntry);
148 return match != null && match.equals(destEntry);
149 }
150
151 public void unmakeMatch(T sourceEntry, T destEntry, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
152 if (sourceDeobfuscator != null && destDeobfuscator != null) {
153 unmakeMatch(sourceEntry, destEntry, null, null);
154 sourceEntry = (T) sourceEntry.cloneToNewClass(
155 sourceDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(sourceEntry, true));
156 destEntry = (T) destEntry.cloneToNewClass(
157 destDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(destEntry, true));
158 }
159
160 boolean wasRemoved = matches.remove(sourceEntry) != null;
161 assert (wasRemoved);
162 wasRemoved = matchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry);
163 assert (wasRemoved);
164 addUnmatchedSourceEntry(sourceEntry);
165 addUnmatchedDestEntry(destEntry);
166 }
167
168 public void makeSourceUnmatchable(T sourceEntry, Deobfuscator sourceDeobfuscator) {
169 if (sourceDeobfuscator != null) {
170 makeSourceUnmatchable(sourceEntry, null);
171 sourceEntry = (T) sourceEntry.cloneToNewClass(
172 sourceDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(sourceEntry, true));
173 }
174 assert (!isMatchedSourceEntry(sourceEntry));
175 boolean wasRemoved = unmatchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry);
176 assert (wasRemoved);
177 addUnmatchableSourceEntry(sourceEntry);
178 }
179}
diff --git a/src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java b/src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java
deleted file mode 100644
index 833a5340..00000000
--- a/src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java
+++ /dev/null
@@ -1,536 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.gui;
13
14import com.google.common.collect.BiMap;
15import com.google.common.collect.Lists;
16import com.google.common.collect.Maps;
17import cuchaz.enigma.Constants;
18import cuchaz.enigma.Deobfuscator;
19import cuchaz.enigma.convert.*;
20import cuchaz.enigma.gui.node.ClassSelectorClassNode;
21import cuchaz.enigma.gui.node.ClassSelectorPackageNode;
22import cuchaz.enigma.mapping.ClassEntry;
23import cuchaz.enigma.mapping.Mappings;
24import cuchaz.enigma.mapping.MappingsChecker;
25import cuchaz.enigma.throwables.MappingConflict;
26import de.sciss.syntaxpane.DefaultSyntaxKit;
27
28import javax.swing.*;
29import java.awt.*;
30import java.awt.event.ActionListener;
31import java.util.Collection;
32import java.util.List;
33import java.util.Map;
34
35public class ClassMatchingGui {
36
37 // controls
38 private JFrame frame;
39 private ClassSelector sourceClasses;
40 private ClassSelector destClasses;
41 private CodeReader sourceReader;
42 private CodeReader destReader;
43 private JLabel sourceClassLabel;
44 private JLabel destClassLabel;
45 private JButton matchButton;
46 private Map<SourceType, JRadioButton> sourceTypeButtons;
47 private JCheckBox advanceCheck;
48 private JCheckBox top10Matches;
49 private ClassMatches classMatches;
50 private Deobfuscator sourceDeobfuscator;
51 private Deobfuscator destDeobfuscator;
52 private ClassEntry sourceClass;
53 private ClassEntry destClass;
54 private SourceType sourceType;
55 private SaveListener saveListener;
56
57 public ClassMatchingGui(ClassMatches matches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
58
59 classMatches = matches;
60 this.sourceDeobfuscator = sourceDeobfuscator;
61 this.destDeobfuscator = destDeobfuscator;
62
63 // init frame
64 frame = new JFrame(Constants.NAME + " - Class Matcher");
65 final Container pane = frame.getContentPane();
66 pane.setLayout(new BorderLayout());
67
68 // init source side
69 JPanel sourcePanel = new JPanel();
70 sourcePanel.setLayout(new BoxLayout(sourcePanel, BoxLayout.PAGE_AXIS));
71 sourcePanel.setPreferredSize(new Dimension(200, 0));
72 pane.add(sourcePanel, BorderLayout.WEST);
73 sourcePanel.add(new JLabel("Source Classes"));
74
75 // init source type radios
76 JPanel sourceTypePanel = new JPanel();
77 sourcePanel.add(sourceTypePanel);
78 sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS));
79 ActionListener sourceTypeListener = event -> setSourceType(SourceType.valueOf(event.getActionCommand()));
80 ButtonGroup sourceTypeButtons = new ButtonGroup();
81 this.sourceTypeButtons = Maps.newHashMap();
82 for (SourceType sourceType : SourceType.values()) {
83 JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons);
84 this.sourceTypeButtons.put(sourceType, button);
85 sourceTypePanel.add(button);
86 }
87
88 sourceClasses = new ClassSelector(null, ClassSelector.DEOBF_CLASS_COMPARATOR, false);
89 sourceClasses.setSelectionListener(this::setSourceClass);
90 JScrollPane sourceScroller = new JScrollPane(sourceClasses);
91 sourcePanel.add(sourceScroller);
92
93 // init dest side
94 JPanel destPanel = new JPanel();
95 destPanel.setLayout(new BoxLayout(destPanel, BoxLayout.PAGE_AXIS));
96 destPanel.setPreferredSize(new Dimension(200, 0));
97 pane.add(destPanel, BorderLayout.WEST);
98 destPanel.add(new JLabel("Destination Classes"));
99
100 top10Matches = new JCheckBox("Show only top 10 matches");
101 destPanel.add(top10Matches);
102 top10Matches.addActionListener(event -> toggleTop10Matches());
103
104 destClasses = new ClassSelector(null, ClassSelector.DEOBF_CLASS_COMPARATOR, false);
105 destClasses.setSelectionListener(this::setDestClass);
106 JScrollPane destScroller = new JScrollPane(destClasses);
107 destPanel.add(destScroller);
108
109 JButton autoMatchButton = new JButton("AutoMatch");
110 autoMatchButton.addActionListener(event -> autoMatch());
111 destPanel.add(autoMatchButton);
112
113 // init source panels
114 DefaultSyntaxKit.initKit();
115 sourceReader = new CodeReader();
116 destReader = new CodeReader();
117
118 // init all the splits
119 JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, sourcePanel, new JScrollPane(
120 sourceReader));
121 splitLeft.setResizeWeight(0); // let the right side take all the slack
122 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(destReader), destPanel);
123 splitRight.setResizeWeight(1); // let the left side take all the slack
124 JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, splitLeft, splitRight);
125 splitCenter.setResizeWeight(0.5); // resize 50:50
126 pane.add(splitCenter, BorderLayout.CENTER);
127 splitCenter.resetToPreferredSizes();
128
129 // init bottom panel
130 JPanel bottomPanel = new JPanel();
131 bottomPanel.setLayout(new FlowLayout());
132
133 sourceClassLabel = new JLabel();
134 sourceClassLabel.setHorizontalAlignment(SwingConstants.RIGHT);
135 destClassLabel = new JLabel();
136 destClassLabel.setHorizontalAlignment(SwingConstants.LEFT);
137
138 matchButton = new JButton();
139
140 advanceCheck = new JCheckBox("Advance to next likely match");
141 advanceCheck.addActionListener(event -> {
142 if (advanceCheck.isSelected()) {
143 advance();
144 }
145 });
146
147 bottomPanel.add(sourceClassLabel);
148 bottomPanel.add(matchButton);
149 bottomPanel.add(destClassLabel);
150 bottomPanel.add(advanceCheck);
151 pane.add(bottomPanel, BorderLayout.SOUTH);
152
153 // show the frame
154 pane.doLayout();
155 frame.setSize(1024, 576);
156 frame.setMinimumSize(new Dimension(640, 480));
157 frame.setVisible(true);
158 frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
159
160 // init state
161 updateDestMappings();
162 setSourceType(SourceType.getDefault());
163 updateMatchButton();
164 saveListener = null;
165 }
166
167 public void setSaveListener(SaveListener val) {
168 saveListener = val;
169 }
170
171 private void updateDestMappings() {
172 try {
173 Mappings newMappings = MappingsConverter.newMappings(classMatches,
174 sourceDeobfuscator.getMappings(), sourceDeobfuscator, destDeobfuscator
175 );
176
177 // look for dropped mappings
178 MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex());
179 checker.dropBrokenMappings(newMappings);
180
181 // count them
182 int numDroppedFields = checker.getDroppedFieldMappings().size();
183 int numDroppedMethods = checker.getDroppedMethodMappings().size();
184 System.out.println(String.format(
185 "%d mappings from matched classes don't match the dest jar:\n\t%5d fields\n\t%5d methods",
186 numDroppedFields + numDroppedMethods,
187 numDroppedFields,
188 numDroppedMethods
189 ));
190
191 destDeobfuscator.setMappings(newMappings);
192 } catch (MappingConflict ex) {
193 System.out.println(ex.getMessage());
194 ex.printStackTrace();
195 }
196 }
197
198 protected void setSourceType(SourceType val) {
199
200 // show the source classes
201 sourceType = val;
202 sourceClasses.setClasses(deobfuscateClasses(sourceType.getSourceClasses(classMatches), sourceDeobfuscator));
203
204 // update counts
205 for (SourceType sourceType : SourceType.values()) {
206 sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)",
207 sourceType.name(),
208 sourceType.getSourceClasses(classMatches).size()
209 ));
210 }
211 }
212
213 private Collection<ClassEntry> deobfuscateClasses(Collection<ClassEntry> in, Deobfuscator deobfuscator) {
214 List<ClassEntry> out = Lists.newArrayList();
215 for (ClassEntry entry : in) {
216
217 ClassEntry deobf = deobfuscator.deobfuscateEntry(entry);
218
219 // make sure we preserve any scores
220 if (entry instanceof ScoredClassEntry) {
221 deobf = new ScoredClassEntry(deobf, ((ScoredClassEntry) entry).getScore());
222 }
223
224 out.add(deobf);
225 }
226 return out;
227 }
228
229 protected void setSourceClass(ClassEntry classEntry) {
230
231 Runnable onGetDestClasses = null;
232 if (advanceCheck.isSelected()) {
233 onGetDestClasses = this::pickBestDestClass;
234 }
235
236 setSourceClass(classEntry, onGetDestClasses);
237 }
238
239 protected void setSourceClass(ClassEntry classEntry, final Runnable onGetDestClasses) {
240
241 // update the current source class
242 sourceClass = classEntry;
243 sourceClassLabel.setText(sourceClass != null ? sourceClass.getName() : "");
244
245 if (sourceClass != null) {
246
247 // show the dest class(es)
248 ClassMatch match = classMatches.getMatchBySource(sourceDeobfuscator.obfuscateEntry(sourceClass));
249 assert (match != null);
250 if (match.destClasses.isEmpty()) {
251
252 destClasses.setClasses(null);
253
254 // run in a separate thread to keep ui responsive
255 new Thread(() ->
256 {
257 destClasses.setClasses(deobfuscateClasses(getLikelyMatches(sourceClass), destDeobfuscator));
258 destClasses.expandAll();
259
260 if (onGetDestClasses != null) {
261 onGetDestClasses.run();
262 }
263 }).start();
264
265 } else {
266
267 destClasses.setClasses(deobfuscateClasses(match.destClasses, destDeobfuscator));
268 destClasses.expandAll();
269
270 if (onGetDestClasses != null) {
271 onGetDestClasses.run();
272 }
273 }
274 }
275
276 setDestClass(null);
277 sourceReader.decompileClass(
278 sourceClass, sourceDeobfuscator, () -> sourceReader.navigateToClassDeclaration(sourceClass));
279
280 updateMatchButton();
281 }
282
283 private Collection<ClassEntry> getLikelyMatches(ClassEntry sourceClass) {
284
285 ClassEntry obfSourceClass = sourceDeobfuscator.obfuscateEntry(sourceClass);
286
287 // set up identifiers
288 ClassNamer namer = new ClassNamer(classMatches.getUniqueMatches());
289 ClassIdentifier sourceIdentifier = new ClassIdentifier(
290 sourceDeobfuscator.getJar(), sourceDeobfuscator.getJarIndex(),
291 namer.getSourceNamer(), true
292 );
293 ClassIdentifier destIdentifier = new ClassIdentifier(
294 destDeobfuscator.getJar(), destDeobfuscator.getJarIndex(),
295 namer.getDestNamer(), true
296 );
297
298 try {
299
300 // rank all the unmatched dest classes against the source class
301 ClassIdentity sourceIdentity = sourceIdentifier.identify(obfSourceClass);
302 List<ClassEntry> scoredDestClasses = Lists.newArrayList();
303 for (ClassEntry unmatchedDestClass : classMatches.getUnmatchedDestClasses()) {
304 ClassIdentity destIdentity = destIdentifier.identify(unmatchedDestClass);
305 float score = 100.0f * (sourceIdentity.getMatchScore(destIdentity) + destIdentity.getMatchScore(sourceIdentity))
306 / (sourceIdentity.getMaxMatchScore() + destIdentity.getMaxMatchScore());
307 scoredDestClasses.add(new ScoredClassEntry(unmatchedDestClass, score));
308 }
309
310 if (top10Matches.isSelected() && scoredDestClasses.size() > 10) {
311 scoredDestClasses.sort((a, b) ->
312 {
313 ScoredClassEntry sa = (ScoredClassEntry) a;
314 ScoredClassEntry sb = (ScoredClassEntry) b;
315 return -Float.compare(sa.getScore(), sb.getScore());
316 });
317 scoredDestClasses = scoredDestClasses.subList(0, 10);
318 }
319
320 return scoredDestClasses;
321
322 } catch (ClassNotFoundException ex) {
323 throw new Error("Unable to find class " + ex.getMessage());
324 }
325 }
326
327 protected void setDestClass(ClassEntry classEntry) {
328
329 // update the current source class
330 destClass = classEntry;
331 destClassLabel.setText(destClass != null ? destClass.getName() : "");
332
333 destReader.decompileClass(destClass, destDeobfuscator, () -> destReader.navigateToClassDeclaration(destClass));
334
335 updateMatchButton();
336 }
337
338 private void updateMatchButton() {
339
340 ClassEntry obfSource = sourceDeobfuscator.obfuscateEntry(sourceClass);
341 ClassEntry obfDest = destDeobfuscator.obfuscateEntry(destClass);
342
343 BiMap<ClassEntry, ClassEntry> uniqueMatches = classMatches.getUniqueMatches();
344 boolean twoSelected = sourceClass != null && destClass != null;
345 boolean isMatched = uniqueMatches.containsKey(obfSource) && uniqueMatches.containsValue(obfDest);
346 boolean canMatch = !uniqueMatches.containsKey(obfSource) && !uniqueMatches.containsValue(obfDest);
347
348 GuiTricks.deactivateButton(matchButton);
349 if (twoSelected) {
350 if (isMatched) {
351 GuiTricks.activateButton(matchButton, "Unmatch", event -> onUnmatchClick());
352 } else if (canMatch) {
353 GuiTricks.activateButton(matchButton, "Match", event -> onMatchClick());
354 }
355 }
356 }
357
358 private void onMatchClick() {
359 // precondition: source and dest classes are set correctly
360
361 ClassEntry obfSource = sourceDeobfuscator.obfuscateEntry(sourceClass);
362 ClassEntry obfDest = destDeobfuscator.obfuscateEntry(destClass);
363
364 // remove the classes from their match
365 classMatches.removeSource(obfSource);
366 classMatches.removeDest(obfDest);
367
368 // add them as matched classes
369 classMatches.add(new ClassMatch(obfSource, obfDest));
370
371 ClassEntry nextClass = null;
372 if (advanceCheck.isSelected()) {
373 nextClass = sourceClasses.getNextClass(sourceClass);
374 }
375
376 save();
377 updateMatches();
378
379 if (nextClass != null) {
380 advance(nextClass);
381 }
382 }
383
384 private void onUnmatchClick() {
385 // precondition: source and dest classes are set to a unique match
386
387 ClassEntry obfSource = sourceDeobfuscator.obfuscateEntry(sourceClass);
388
389 // remove the source to break the match, then add the source back as unmatched
390 classMatches.removeSource(obfSource);
391 classMatches.add(new ClassMatch(obfSource, null));
392
393 save();
394 updateMatches();
395 }
396
397 private void updateMatches() {
398 updateDestMappings();
399 setDestClass(null);
400 destClasses.setClasses(null);
401 updateMatchButton();
402
403 // remember where we were in the source tree
404 String packageName = sourceClasses.getSelectedPackage();
405
406 setSourceType(sourceType);
407
408 sourceClasses.expandPackage(packageName);
409 }
410
411 private void save() {
412 if (saveListener != null) {
413 saveListener.save(classMatches);
414 }
415 }
416
417 private void autoMatch() {
418
419 System.out.println("Automatching...");
420
421 // compute a new matching
422 ClassMatching matching = MappingsConverter.computeMatching(
423 sourceDeobfuscator.getJar(), sourceDeobfuscator.getJarIndex(),
424 destDeobfuscator.getJar(), destDeobfuscator.getJarIndex(),
425 classMatches.getUniqueMatches()
426 );
427 ClassMatches newMatches = new ClassMatches(matching.matches());
428 System.out.println(String.format("Automatch found %d new matches",
429 newMatches.getUniqueMatches().size() - classMatches.getUniqueMatches().size()
430 ));
431
432 // update the current matches
433 classMatches = newMatches;
434 save();
435 updateMatches();
436 }
437
438 private void advance() {
439 advance(null);
440 }
441
442 private void advance(ClassEntry sourceClass) {
443
444 // make sure we have a source class
445 if (sourceClass == null) {
446 sourceClass = sourceClasses.getSelectedClass();
447 if (sourceClass != null) {
448 sourceClass = sourceClasses.getNextClass(sourceClass);
449 } else {
450 sourceClass = sourceClasses.getFirstClass();
451 }
452 }
453
454 // set the source class
455 setSourceClass(sourceClass, this::pickBestDestClass);
456 sourceClasses.setSelectionClass(sourceClass);
457 }
458
459 private void pickBestDestClass() {
460
461 // then, pick the best dest class
462 ClassEntry firstClass = null;
463 ScoredClassEntry bestDestClass = null;
464 for (ClassSelectorPackageNode packageNode : destClasses.packageNodes()) {
465 for (ClassSelectorClassNode classNode : destClasses.classNodes(packageNode)) {
466 if (firstClass == null) {
467 firstClass = classNode.getClassEntry();
468 }
469 if (classNode.getClassEntry() instanceof ScoredClassEntry) {
470 ScoredClassEntry scoredClass = (ScoredClassEntry) classNode.getClassEntry();
471 if (bestDestClass == null || bestDestClass.getScore() < scoredClass.getScore()) {
472 bestDestClass = scoredClass;
473 }
474 }
475 }
476 }
477
478 // pick the entry to show
479 ClassEntry destClass = null;
480 if (bestDestClass != null) {
481 destClass = bestDestClass;
482 } else if (firstClass != null) {
483 destClass = firstClass;
484 }
485
486 setDestClass(destClass);
487 destClasses.setSelectionClass(destClass);
488 }
489
490 private void toggleTop10Matches() {
491 if (sourceClass != null) {
492 destClasses.clearSelection();
493 destClasses.setClasses(deobfuscateClasses(getLikelyMatches(sourceClass), destDeobfuscator));
494 destClasses.expandAll();
495 }
496 }
497
498 private enum SourceType {
499 Matched {
500 @Override
501 public Collection<ClassEntry> getSourceClasses(ClassMatches matches) {
502 return matches.getUniqueMatches().keySet();
503 }
504 },
505 Unmatched {
506 @Override
507 public Collection<ClassEntry> getSourceClasses(ClassMatches matches) {
508 return matches.getUnmatchedSourceClasses();
509 }
510 },
511 Ambiguous {
512 @Override
513 public Collection<ClassEntry> getSourceClasses(ClassMatches matches) {
514 return matches.getAmbiguouslyMatchedSourceClasses();
515 }
516 };
517
518 public static SourceType getDefault() {
519 return values()[0];
520 }
521
522 public JRadioButton newRadio(ActionListener listener, ButtonGroup group) {
523 JRadioButton button = new JRadioButton(name(), this == getDefault());
524 button.setActionCommand(name());
525 button.addActionListener(listener);
526 group.add(button);
527 return button;
528 }
529
530 public abstract Collection<ClassEntry> getSourceClasses(ClassMatches matches);
531 }
532
533 public interface SaveListener {
534 void save(ClassMatches matches);
535 }
536}
diff --git a/src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java b/src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java
deleted file mode 100644
index fe6a3b0e..00000000
--- a/src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java
+++ /dev/null
@@ -1,435 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.gui;
13
14import com.google.common.collect.Lists;
15import com.google.common.collect.Maps;
16import cuchaz.enigma.Constants;
17import cuchaz.enigma.Deobfuscator;
18import cuchaz.enigma.analysis.SourceIndex;
19import cuchaz.enigma.analysis.Token;
20import cuchaz.enigma.convert.ClassMatches;
21import cuchaz.enigma.convert.MemberMatches;
22import cuchaz.enigma.gui.highlight.DeobfuscatedHighlightPainter;
23import cuchaz.enigma.gui.highlight.ObfuscatedHighlightPainter;
24import cuchaz.enigma.mapping.ClassEntry;
25import cuchaz.enigma.mapping.Entry;
26import de.sciss.syntaxpane.DefaultSyntaxKit;
27
28import javax.swing.*;
29import javax.swing.text.Highlighter.HighlightPainter;
30import java.awt.*;
31import java.awt.event.ActionListener;
32import java.awt.event.KeyAdapter;
33import java.awt.event.KeyEvent;
34import java.util.Collection;
35import java.util.List;
36import java.util.Map;
37
38public class MemberMatchingGui<T extends Entry> {
39
40 // controls
41 private JFrame frame;
42 private Map<SourceType, JRadioButton> sourceTypeButtons;
43 private ClassSelector sourceClasses;
44 private CodeReader sourceReader;
45 private CodeReader destReader;
46 private JButton matchButton;
47 private JButton unmatchableButton;
48 private JLabel sourceLabel;
49 private JLabel destLabel;
50 private HighlightPainter unmatchedHighlightPainter;
51 private HighlightPainter matchedHighlightPainter;
52 private ClassMatches classMatches;
53 private MemberMatches<T> memberMatches;
54 private Deobfuscator sourceDeobfuscator;
55 private Deobfuscator destDeobfuscator;
56 private SaveListener<T> saveListener;
57 private SourceType sourceType;
58 private ClassEntry obfSourceClass;
59 private ClassEntry obfDestClass;
60 private T obfSourceEntry;
61 private T obfDestEntry;
62
63 public MemberMatchingGui(ClassMatches classMatches, MemberMatches<T> fieldMatches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
64
65 this.classMatches = classMatches;
66 memberMatches = fieldMatches;
67 this.sourceDeobfuscator = sourceDeobfuscator;
68 this.destDeobfuscator = destDeobfuscator;
69
70 // init frame
71 frame = new JFrame(Constants.NAME + " - Member Matcher");
72 final Container pane = frame.getContentPane();
73 pane.setLayout(new BorderLayout());
74
75 // init classes side
76 JPanel classesPanel = new JPanel();
77 classesPanel.setLayout(new BoxLayout(classesPanel, BoxLayout.PAGE_AXIS));
78 classesPanel.setPreferredSize(new Dimension(200, 0));
79 pane.add(classesPanel, BorderLayout.WEST);
80 classesPanel.add(new JLabel("Classes"));
81
82 // init source type radios
83 JPanel sourceTypePanel = new JPanel();
84 classesPanel.add(sourceTypePanel);
85 sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS));
86 ActionListener sourceTypeListener = event -> setSourceType(SourceType.valueOf(event.getActionCommand()));
87 ButtonGroup sourceTypeButtons = new ButtonGroup();
88 this.sourceTypeButtons = Maps.newHashMap();
89 for (SourceType sourceType : SourceType.values()) {
90 JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons);
91 this.sourceTypeButtons.put(sourceType, button);
92 sourceTypePanel.add(button);
93 }
94
95 sourceClasses = new ClassSelector(null, ClassSelector.DEOBF_CLASS_COMPARATOR, false);
96 sourceClasses.setSelectionListener(this::setSourceClass);
97 JScrollPane sourceScroller = new JScrollPane(sourceClasses);
98 classesPanel.add(sourceScroller);
99
100 // init readers
101 DefaultSyntaxKit.initKit();
102 sourceReader = new CodeReader();
103 sourceReader.setSelectionListener(reference ->
104 {
105 if (reference != null) {
106 onSelectSource(reference.entry);
107 } else {
108 onSelectSource(null);
109 }
110 });
111 destReader = new CodeReader();
112 destReader.setSelectionListener(reference ->
113 {
114 if (reference != null) {
115 onSelectDest(reference.entry);
116 } else {
117 onSelectDest(null);
118 }
119 });
120
121 // add key bindings
122 KeyAdapter keyListener = new KeyAdapter() {
123 @Override
124 public void keyPressed(KeyEvent event) {
125 if (event.getKeyCode() == KeyEvent.VK_M)
126 matchButton.doClick();
127 }
128 };
129 sourceReader.addKeyListener(keyListener);
130 destReader.addKeyListener(keyListener);
131
132 // init all the splits
133 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(sourceReader), new JScrollPane(
134 destReader));
135 splitRight.setResizeWeight(0.5); // resize 50:50
136 JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, classesPanel, splitRight);
137 splitLeft.setResizeWeight(0); // let the right side take all the slack
138 pane.add(splitLeft, BorderLayout.CENTER);
139 splitLeft.resetToPreferredSizes();
140
141 // init bottom panel
142 JPanel bottomPanel = new JPanel();
143 bottomPanel.setLayout(new FlowLayout());
144 pane.add(bottomPanel, BorderLayout.SOUTH);
145
146 matchButton = new JButton();
147 unmatchableButton = new JButton();
148
149 sourceLabel = new JLabel();
150 bottomPanel.add(sourceLabel);
151 bottomPanel.add(matchButton);
152 bottomPanel.add(unmatchableButton);
153 destLabel = new JLabel();
154 bottomPanel.add(destLabel);
155
156 // show the frame
157 pane.doLayout();
158 frame.setSize(1024, 576);
159 frame.setMinimumSize(new Dimension(640, 480));
160 frame.setVisible(true);
161 frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
162
163 unmatchedHighlightPainter = new ObfuscatedHighlightPainter();
164 matchedHighlightPainter = new DeobfuscatedHighlightPainter();
165
166 // init state
167 saveListener = null;
168 obfSourceClass = null;
169 obfDestClass = null;
170 obfSourceEntry = null;
171 obfDestEntry = null;
172 setSourceType(SourceType.getDefault());
173 updateButtons();
174 }
175
176 protected void setSourceType(SourceType val) {
177 sourceType = val;
178 updateSourceClasses();
179 }
180
181 public void setSaveListener(SaveListener<T> val) {
182 saveListener = val;
183 }
184
185 private void updateSourceClasses() {
186
187 String selectedPackage = sourceClasses.getSelectedPackage();
188
189 List<ClassEntry> deobfClassEntries = Lists.newArrayList();
190 for (ClassEntry entry : sourceType.getObfSourceClasses(memberMatches)) {
191 deobfClassEntries.add(sourceDeobfuscator.deobfuscateEntry(entry));
192 }
193 sourceClasses.setClasses(deobfClassEntries);
194
195 if (selectedPackage != null) {
196 sourceClasses.expandPackage(selectedPackage);
197 }
198
199 for (SourceType sourceType : SourceType.values()) {
200 sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)",
201 sourceType.name(), sourceType.getObfSourceClasses(memberMatches).size()
202 ));
203 }
204 }
205
206 protected void setSourceClass(ClassEntry sourceClass) {
207
208 obfSourceClass = sourceDeobfuscator.obfuscateEntry(sourceClass);
209 obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass);
210 if (obfDestClass == null) {
211 throw new Error("No matching dest class for source class: " + obfSourceClass);
212 }
213
214 sourceReader.decompileClass(obfSourceClass, sourceDeobfuscator, false, this::updateSourceHighlights);
215 destReader.decompileClass(obfDestClass, destDeobfuscator, false, this::updateDestHighlights);
216 }
217
218 protected void updateSourceHighlights() {
219 highlightEntries(sourceReader, sourceDeobfuscator, memberMatches.matches().keySet(), memberMatches.getUnmatchedSourceEntries());
220 }
221
222 protected void updateDestHighlights() {
223 highlightEntries(destReader, destDeobfuscator, memberMatches.matches().values(), memberMatches.getUnmatchedDestEntries());
224 }
225
226 private void highlightEntries(CodeReader reader, Deobfuscator deobfuscator, Collection<T> obfMatchedEntries, Collection<T> obfUnmatchedEntries) {
227 reader.clearHighlights();
228 // matched fields
229 updateHighlighted(obfMatchedEntries, deobfuscator, reader, matchedHighlightPainter);
230 // unmatched fields
231 updateHighlighted(obfUnmatchedEntries, deobfuscator, reader, unmatchedHighlightPainter);
232 }
233
234 private void updateHighlighted(Collection<T> entries, Deobfuscator deobfuscator, CodeReader reader, HighlightPainter painter) {
235 SourceIndex index = reader.getSourceIndex();
236 for (T obfT : entries) {
237 T deobfT = deobfuscator.deobfuscateEntry(obfT);
238 Token token = index.getDeclarationToken(deobfT);
239 if (token != null) {
240 reader.setHighlightedToken(token, painter);
241 }
242 }
243 }
244
245 private boolean isSelectionMatched() {
246 return obfSourceEntry != null && obfDestEntry != null
247 && memberMatches.isMatched(obfSourceEntry, obfDestEntry);
248 }
249
250 protected void onSelectSource(Entry source) {
251
252 // start with no selection
253 if (isSelectionMatched()) {
254 setDest(null);
255 }
256 setSource(null);
257
258 // then look for a valid source selection
259 if (source != null) {
260
261 // this looks really scary, but it's actually ok
262 // Deobfuscator.obfuscateEntry can handle all implementations of Entry
263 // and MemberMatches.hasSource() will only pass entries that actually match T
264 @SuppressWarnings("unchecked")
265 T sourceEntry = (T) source;
266
267 T obfSourceEntry = sourceDeobfuscator.obfuscateEntry(sourceEntry);
268 if (memberMatches.hasSource(obfSourceEntry)) {
269 setSource(obfSourceEntry);
270
271 // look for a matched dest too
272 T obfDestEntry = memberMatches.matches().get(obfSourceEntry);
273 if (obfDestEntry != null) {
274 setDest(obfDestEntry);
275 }
276 }
277 }
278
279 updateButtons();
280 }
281
282 protected void onSelectDest(Entry dest) {
283
284 // start with no selection
285 if (isSelectionMatched()) {
286 setSource(null);
287 }
288 setDest(null);
289
290 // then look for a valid dest selection
291 if (dest != null) {
292
293 // this looks really scary, but it's actually ok
294 // Deobfuscator.obfuscateEntry can handle all implementations of Entry
295 // and MemberMatches.hasSource() will only pass entries that actually match T
296 @SuppressWarnings("unchecked")
297 T destEntry = (T) dest;
298
299 T obfDestEntry = destDeobfuscator.obfuscateEntry(destEntry);
300 if (memberMatches.hasDest(obfDestEntry)) {
301 setDest(obfDestEntry);
302
303 // look for a matched source too
304 T obfSourceEntry = memberMatches.matches().inverse().get(obfDestEntry);
305 if (obfSourceEntry != null) {
306 setSource(obfSourceEntry);
307 }
308 }
309 }
310
311 updateButtons();
312 }
313
314 private void setSource(T obfEntry) {
315 if (obfEntry == null) {
316 obfSourceEntry = null;
317 sourceLabel.setText("");
318 } else {
319 obfSourceEntry = obfEntry;
320 sourceLabel.setText(getEntryLabel(obfEntry, sourceDeobfuscator));
321 }
322 }
323
324 private void setDest(T obfEntry) {
325 if (obfEntry == null) {
326 obfDestEntry = null;
327 destLabel.setText("");
328 } else {
329 obfDestEntry = obfEntry;
330 destLabel.setText(getEntryLabel(obfEntry, destDeobfuscator));
331 }
332 }
333
334 private String getEntryLabel(T obfEntry, Deobfuscator deobfuscator) {
335 // show obfuscated and deobfuscated names, but no types/signatures
336 T deobfEntry = deobfuscator.deobfuscateEntry(obfEntry);
337 return String.format("%s (%s)", deobfEntry.getName(), obfEntry.getName());
338 }
339
340 private void updateButtons() {
341
342 GuiTricks.deactivateButton(matchButton);
343 GuiTricks.deactivateButton(unmatchableButton);
344
345 if (obfSourceEntry != null && obfDestEntry != null) {
346 if (memberMatches.isMatched(obfSourceEntry, obfDestEntry))
347 GuiTricks.activateButton(matchButton, "Unmatch", event -> unmatch());
348 else if (!memberMatches.isMatchedSourceEntry(obfSourceEntry) && !memberMatches.isMatchedDestEntry(
349 obfDestEntry))
350 GuiTricks.activateButton(matchButton, "Match", event -> match());
351 } else if (obfSourceEntry != null)
352 GuiTricks.activateButton(unmatchableButton, "Set Unmatchable", event -> unmatchable());
353 }
354
355 protected void match() {
356
357 // update the field matches
358 memberMatches.makeMatch(obfSourceEntry, obfDestEntry, sourceDeobfuscator, destDeobfuscator);
359 save();
360
361 // update the ui
362 onSelectSource(null);
363 onSelectDest(null);
364 updateSourceHighlights();
365 updateDestHighlights();
366 updateSourceClasses();
367 }
368
369 protected void unmatch() {
370
371 // update the field matches
372 memberMatches.unmakeMatch(obfSourceEntry, obfDestEntry, sourceDeobfuscator, destDeobfuscator);
373 save();
374
375 // update the ui
376 onSelectSource(null);
377 onSelectDest(null);
378 updateSourceHighlights();
379 updateDestHighlights();
380 updateSourceClasses();
381 }
382
383 protected void unmatchable() {
384
385 // update the field matches
386 memberMatches.makeSourceUnmatchable(obfSourceEntry, sourceDeobfuscator);
387 save();
388
389 // update the ui
390 onSelectSource(null);
391 onSelectDest(null);
392 updateSourceHighlights();
393 updateDestHighlights();
394 updateSourceClasses();
395 }
396
397 private void save() {
398 if (saveListener != null) {
399 saveListener.save(memberMatches);
400 }
401 }
402
403 private enum SourceType {
404 Matched {
405 @Override
406 public <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches) {
407 return matches.getSourceClassesWithoutUnmatchedEntries();
408 }
409 },
410 Unmatched {
411 @Override
412 public <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches) {
413 return matches.getSourceClassesWithUnmatchedEntries();
414 }
415 };
416
417 public static SourceType getDefault() {
418 return values()[0];
419 }
420
421 public JRadioButton newRadio(ActionListener listener, ButtonGroup group) {
422 JRadioButton button = new JRadioButton(name(), this == getDefault());
423 button.setActionCommand(name());
424 button.addActionListener(listener);
425 group.add(button);
426 return button;
427 }
428
429 public abstract <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches);
430 }
431
432 public interface SaveListener<T extends Entry> {
433 void save(MemberMatches<T> matches);
434 }
435}