summaryrefslogtreecommitdiff
path: root/src/main/java
diff options
context:
space:
mode:
authorGravatar asiekierka2016-08-17 18:35:12 +0200
committerGravatar asiekierka2016-08-17 18:35:12 +0200
commit5540c815de36e316d0749ce2163f12c61895b327 (patch)
tree2b30d5ae98735ee7cba7d1c0087c51d68ed3ebf9 /src/main/java
parentRevert "Removed util" (diff)
downloadenigma-5540c815de36e316d0749ce2163f12c61895b327.tar.gz
enigma-5540c815de36e316d0749ce2163f12c61895b327.tar.xz
enigma-5540c815de36e316d0749ce2163f12c61895b327.zip
Revert "Removed unused methods"
This reverts commit 1742190f784d0d62e7cc869eebafdfe1927e448f.
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/cuchaz/enigma/ConvertMain.java361
-rw-r--r--src/main/java/cuchaz/enigma/Deobfuscator.java4
-rw-r--r--src/main/java/cuchaz/enigma/TranslatingTypeLoader.java22
-rw-r--r--src/main/java/cuchaz/enigma/Util.java31
-rw-r--r--src/main/java/cuchaz/enigma/analysis/Access.java4
-rw-r--r--src/main/java/cuchaz/enigma/analysis/BridgeMarker.java2
-rw-r--r--src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java17
-rw-r--r--src/main/java/cuchaz/enigma/analysis/EntryRenamer.java53
-rw-r--r--src/main/java/cuchaz/enigma/analysis/JarIndex.java16
-rw-r--r--src/main/java/cuchaz/enigma/analysis/SourceIndex.java8
-rw-r--r--src/main/java/cuchaz/enigma/analysis/TranslationIndex.java7
-rw-r--r--src/main/java/cuchaz/enigma/analysis/TreeDumpVisitor.java441
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/CheckCastIterator.java117
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/ClassRenamer.java10
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/ConstPoolEditor.java122
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java4
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java42
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java4
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java4
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java4
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java4
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java4
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java4
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java28
-rw-r--r--src/main/java/cuchaz/enigma/convert/ClassForest.java60
-rw-r--r--src/main/java/cuchaz/enigma/convert/ClassIdentifier.java56
-rw-r--r--src/main/java/cuchaz/enigma/convert/ClassIdentity.java441
-rw-r--r--src/main/java/cuchaz/enigma/convert/ClassMatch.java84
-rw-r--r--src/main/java/cuchaz/enigma/convert/ClassMatches.java159
-rw-r--r--src/main/java/cuchaz/enigma/convert/ClassMatching.java155
-rw-r--r--src/main/java/cuchaz/enigma/convert/ClassNamer.java56
-rw-r--r--src/main/java/cuchaz/enigma/convert/FieldMatches.java151
-rw-r--r--src/main/java/cuchaz/enigma/convert/MappingsConverter.java583
-rw-r--r--src/main/java/cuchaz/enigma/convert/MatchesReader.java109
-rw-r--r--src/main/java/cuchaz/enigma/convert/MatchesWriter.java121
-rw-r--r--src/main/java/cuchaz/enigma/convert/MemberMatches.java155
-rw-r--r--src/main/java/cuchaz/enigma/gui/BrowserCaret.java2
-rw-r--r--src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java546
-rw-r--r--src/main/java/cuchaz/enigma/gui/ClassSelector.java120
-rw-r--r--src/main/java/cuchaz/enigma/gui/CodeReader.java223
-rw-r--r--src/main/java/cuchaz/enigma/gui/Gui.java2
-rw-r--r--src/main/java/cuchaz/enigma/gui/GuiTricks.java56
-rw-r--r--src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java490
-rw-r--r--src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java30
-rw-r--r--src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java4
-rw-r--r--src/main/java/cuchaz/enigma/mapping/ArgumentEntry.java6
-rw-r--r--src/main/java/cuchaz/enigma/mapping/ArgumentMapping.java5
-rw-r--r--src/main/java/cuchaz/enigma/mapping/ClassMapping.java87
-rw-r--r--src/main/java/cuchaz/enigma/mapping/EntryFactory.java20
-rw-r--r--src/main/java/cuchaz/enigma/mapping/EntryPair.java22
-rw-r--r--src/main/java/cuchaz/enigma/mapping/FieldMapping.java39
-rw-r--r--src/main/java/cuchaz/enigma/mapping/Mappings.java44
-rw-r--r--src/main/java/cuchaz/enigma/mapping/MappingsEnigmaReader.java6
-rw-r--r--src/main/java/cuchaz/enigma/mapping/MappingsRenamer.java40
-rw-r--r--src/main/java/cuchaz/enigma/mapping/MemberMapping.java2
-rw-r--r--src/main/java/cuchaz/enigma/mapping/MethodMapping.java50
-rw-r--r--src/main/java/cuchaz/enigma/mapping/NameValidator.java11
-rw-r--r--src/main/java/cuchaz/enigma/mapping/SignatureUpdater.java91
-rw-r--r--src/main/java/cuchaz/enigma/mapping/Translator.java8
-rw-r--r--src/main/java/cuchaz/enigma/utils/Utils.java43
60 files changed, 5340 insertions, 50 deletions
diff --git a/src/main/java/cuchaz/enigma/ConvertMain.java b/src/main/java/cuchaz/enigma/ConvertMain.java
new file mode 100644
index 00000000..4c6be7fd
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/ConvertMain.java
@@ -0,0 +1,361 @@
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 ******************************************************************************/
11package cuchaz.enigma;
12
13import java.io.File;
14import java.io.IOException;
15import java.util.jar.JarFile;
16
17import cuchaz.enigma.convert.*;
18import cuchaz.enigma.gui.ClassMatchingGui;
19import cuchaz.enigma.gui.MemberMatchingGui;
20import cuchaz.enigma.mapping.*;
21import cuchaz.enigma.throwables.MappingConflict;
22import cuchaz.enigma.throwables.MappingParseException;
23
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(methodMatchesFile, destJar, outMappingsFile, classMatchesFile);
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(new ClassMatchingGui.SaveListener() {
130 @Override
131 public void save(ClassMatches matches) {
132 try {
133 MatchesWriter.writeClasses(matches, classMatchesFile);
134 } catch (IOException ex) {
135 throw new Error(ex);
136 }
137 }
138 });
139 }
140
141 @SuppressWarnings("unused")
142 private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchesFile)
143 throws IOException, MappingConflict {
144 System.out.println("Reading class matches...");
145 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
146 Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
147 deobfuscators.source.setMappings(mappings);
148
149 Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.dest);
150 new MappingsEnigmaWriter().write(outMappingsFile, newMappings, true);
151 System.out.println("Write converted mappings to: " + outMappingsFile.getAbsolutePath());
152 }
153
154 private static void computeFieldMatches(File memberMatchesFile, JarFile destJar, File destMappingsFile, File classMatchesFile)
155 throws IOException, MappingParseException {
156
157 System.out.println("Reading class matches...");
158 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
159 System.out.println("Reading mappings...");
160 Mappings destMappings = new MappingsEnigmaReader().read(destMappingsFile);
161 System.out.println("Indexing dest jar...");
162 Deobfuscator destDeobfuscator = new Deobfuscator(destJar);
163
164 System.out.println("Writing matches...");
165
166 // get the matched and unmatched mappings
167 MemberMatches<FieldEntry> fieldMatches = MappingsConverter.computeMemberMatches(
168 destDeobfuscator,
169 destMappings,
170 classMatches,
171 MappingsConverter.getFieldDoer()
172 );
173
174 MatchesWriter.writeMembers(fieldMatches, memberMatchesFile);
175 System.out.println("Wrote:\n\t" + memberMatchesFile.getAbsolutePath());
176 }
177
178 private static void editFieldMatches(JarFile sourceJar, JarFile destJar, File destMappingsFile, Mappings sourceMappings, File classMatchesFile, final File fieldMatchesFile)
179 throws IOException, MappingParseException {
180
181 System.out.println("Reading matches...");
182 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
183 MemberMatches<FieldEntry> fieldMatches = MatchesReader.readMembers(fieldMatchesFile);
184
185 // prep deobfuscators
186 Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
187 deobfuscators.source.setMappings(sourceMappings);
188 Mappings destMappings = new MappingsEnigmaReader().read(destMappingsFile);
189 MappingsChecker checker = new MappingsChecker(deobfuscators.dest.getJarIndex());
190 checker.dropBrokenMappings(destMappings);
191 deobfuscators.dest.setMappings(destMappings);
192
193 new MemberMatchingGui<>(classMatches, fieldMatches, deobfuscators.source, deobfuscators.dest).setSaveListener(new MemberMatchingGui.SaveListener<FieldEntry>() {
194 @Override
195 public void save(MemberMatches<FieldEntry> matches) {
196 try {
197 MatchesWriter.writeMembers(matches, fieldMatchesFile);
198 } catch (IOException ex) {
199 throw new Error(ex);
200 }
201 }
202 });
203 }
204
205 @SuppressWarnings("unused")
206 private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchesFile, File fieldMatchesFile)
207 throws IOException, MappingConflict {
208
209 System.out.println("Reading matches...");
210 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
211 MemberMatches<FieldEntry> fieldMatches = MatchesReader.readMembers(fieldMatchesFile);
212
213 Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
214 deobfuscators.source.setMappings(mappings);
215
216 // apply matches
217 Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.dest);
218 MappingsConverter.applyMemberMatches(newMappings, classMatches, fieldMatches, MappingsConverter.getFieldDoer());
219
220 // write out the converted mappings
221
222 new MappingsEnigmaWriter().write(outMappingsFile, newMappings, true);
223 System.out.println("Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath());
224 }
225
226
227 private static void computeMethodMatches(File methodMatchesFile, JarFile destJar, File destMappingsFile, File classMatchesFile)
228 throws IOException, MappingParseException {
229
230 System.out.println("Reading class matches...");
231 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
232 System.out.println("Reading mappings...");
233 Mappings destMappings = new MappingsEnigmaReader().read(destMappingsFile);
234 System.out.println("Indexing dest jar...");
235 Deobfuscator destDeobfuscator = new Deobfuscator(destJar);
236
237 System.out.println("Writing method matches...");
238
239 // get the matched and unmatched mappings
240 MemberMatches<BehaviorEntry> methodMatches = MappingsConverter.computeMemberMatches(
241 destDeobfuscator,
242 destMappings,
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(new MemberMatchingGui.SaveListener<BehaviorEntry>() {
267 @Override
268 public void save(MemberMatches<BehaviorEntry> matches) {
269 try {
270 MatchesWriter.writeMembers(matches, methodMatchesFile);
271 } catch (IOException ex) {
272 throw new Error(ex);
273 }
274 }
275 });
276 }
277
278 private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchesFile, File fieldMatchesFile, File methodMatchesFile)
279 throws IOException, MappingConflict {
280
281 System.out.println("Reading matches...");
282 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
283 MemberMatches<FieldEntry> fieldMatches = MatchesReader.readMembers(fieldMatchesFile);
284 MemberMatches<BehaviorEntry> methodMatches = MatchesReader.readMembers(methodMatchesFile);
285
286 Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
287 deobfuscators.source.setMappings(mappings);
288
289 // apply matches
290 Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.dest);
291 MappingsConverter.applyMemberMatches(newMappings, classMatches, fieldMatches, MappingsConverter.getFieldDoer());
292 MappingsConverter.applyMemberMatches(newMappings, classMatches, methodMatches, MappingsConverter.getMethodDoer());
293
294 // check the final mappings
295 MappingsChecker checker = new MappingsChecker(deobfuscators.dest.getJarIndex());
296 checker.dropBrokenMappings(newMappings);
297
298 for (java.util.Map.Entry<ClassEntry, ClassMapping> mapping : checker.getDroppedClassMappings().entrySet()) {
299 System.out.println("WARNING: Broken class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")");
300 }
301 for (java.util.Map.Entry<ClassEntry, ClassMapping> mapping : checker.getDroppedInnerClassMappings().entrySet()) {
302 System.out.println("WARNING: Broken inner class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")");
303 }
304 for (java.util.Map.Entry<FieldEntry, FieldMapping> mapping : checker.getDroppedFieldMappings().entrySet()) {
305 System.out.println("WARNING: Broken field entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")");
306 }
307 for (java.util.Map.Entry<BehaviorEntry, MethodMapping> mapping : checker.getDroppedMethodMappings().entrySet()) {
308 System.out.println("WARNING: Broken behavior entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")");
309 }
310
311 //TODO Fix
312 // write out the converted mappings
313// try (FileWriter out = new FileWriter(outMappingsFile)) {
314// new MappingsWriter().write(out, newMappings);
315// }
316 System.out.println("Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath());
317 }
318
319 private static class Deobfuscators {
320
321 public Deobfuscator source;
322 public Deobfuscator dest;
323
324 public Deobfuscators(JarFile sourceJar, JarFile destJar) {
325 System.out.println("Indexing source jar...");
326 IndexerThread sourceIndexer = new IndexerThread(sourceJar);
327 sourceIndexer.start();
328 System.out.println("Indexing dest jar...");
329 IndexerThread destIndexer = new IndexerThread(destJar);
330 destIndexer.start();
331 sourceIndexer.joinOrBail();
332 destIndexer.joinOrBail();
333 source = sourceIndexer.deobfuscator;
334 dest = destIndexer.deobfuscator;
335 }
336 }
337
338 private static class IndexerThread extends Thread {
339
340 private JarFile m_jarFile;
341 public Deobfuscator deobfuscator;
342
343 public IndexerThread(JarFile jarFile) {
344 m_jarFile = jarFile;
345 deobfuscator = null;
346 }
347
348 public void joinOrBail() {
349 try {
350 join();
351 } catch (InterruptedException ex) {
352 throw new Error(ex);
353 }
354 }
355
356 @Override
357 public void run() {
358 deobfuscator = new Deobfuscator(m_jarFile);
359 }
360 }
361} \ No newline at end of file
diff --git a/src/main/java/cuchaz/enigma/Deobfuscator.java b/src/main/java/cuchaz/enigma/Deobfuscator.java
index 4d9e6556..b2f361e9 100644
--- a/src/main/java/cuchaz/enigma/Deobfuscator.java
+++ b/src/main/java/cuchaz/enigma/Deobfuscator.java
@@ -78,6 +78,10 @@ public class Deobfuscator {
78 setMappings(new Mappings()); 78 setMappings(new Mappings());
79 } 79 }
80 80
81 public JarFile getJar() {
82 return this.jar;
83 }
84
81 public String getJarName() { 85 public String getJarName() {
82 return this.jar.getName(); 86 return this.jar.getName();
83 } 87 }
diff --git a/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java b/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java
index b3e5226e..8dd075ec 100644
--- a/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java
+++ b/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java
@@ -52,6 +52,10 @@ public class TranslatingTypeLoader implements ITypeLoader {
52 this.defaultTypeLoader = new ClasspathTypeLoader(); 52 this.defaultTypeLoader = new ClasspathTypeLoader();
53 } 53 }
54 54
55 public void clearCache() {
56 this.cache.clear();
57 }
58
55 @Override 59 @Override
56 public boolean tryLoadType(String className, Buffer out) { 60 public boolean tryLoadType(String className, Buffer out) {
57 61
@@ -76,6 +80,24 @@ public class TranslatingTypeLoader implements ITypeLoader {
76 return true; 80 return true;
77 } 81 }
78 82
83 public CtClass loadClass(String deobfClassName) {
84
85 byte[] data = loadType(deobfClassName);
86 if (data == null) {
87 return null;
88 }
89
90 // return a javassist handle for the class
91 String javaClassFileName = Descriptor.toJavaName(deobfClassName);
92 ClassPool classPool = new ClassPool();
93 classPool.insertClassPath(new ByteArrayClassPath(javaClassFileName, data));
94 try {
95 return classPool.get(javaClassFileName);
96 } catch (NotFoundException ex) {
97 throw new Error(ex);
98 }
99 }
100
79 private byte[] loadType(String className) { 101 private byte[] loadType(String className) {
80 102
81 // NOTE: don't know if class name is obf or deobf 103 // NOTE: don't know if class name is obf or deobf
diff --git a/src/main/java/cuchaz/enigma/Util.java b/src/main/java/cuchaz/enigma/Util.java
index 9445b2b4..1bcdb9ea 100644
--- a/src/main/java/cuchaz/enigma/Util.java
+++ b/src/main/java/cuchaz/enigma/Util.java
@@ -41,6 +41,27 @@ public class Util {
41 return result; 41 return result;
42 } 42 }
43 43
44 public static void closeQuietly(Closeable closeable) {
45 if (closeable != null) {
46 try {
47 closeable.close();
48 } catch (IOException ex) {
49 // just ignore any further exceptions
50 }
51 }
52 }
53
54 public static void closeQuietly(JarFile jarFile) {
55 // silly library should implement Closeable...
56 if (jarFile != null) {
57 try {
58 jarFile.close();
59 } catch (IOException ex) {
60 // just ignore any further exceptions
61 }
62 }
63 }
64
44 public static String readStreamToString(InputStream in) throws IOException { 65 public static String readStreamToString(InputStream in) throws IOException {
45 return CharStreams.toString(new InputStreamReader(in, "UTF-8")); 66 return CharStreams.toString(new InputStreamReader(in, "UTF-8"));
46 } 67 }
@@ -65,4 +86,14 @@ public class Util {
65 } 86 }
66 } 87 }
67 } 88 }
89
90 public static void writeClass(CtClass c) {
91 String name = Descriptor.toJavaName(c.getName());
92 File file = new File(name + ".class");
93 try (FileOutputStream out = new FileOutputStream(file)) {
94 out.write(c.toBytecode());
95 } catch (IOException | CannotCompileException ex) {
96 throw new Error(ex);
97 }
98 }
68} 99}
diff --git a/src/main/java/cuchaz/enigma/analysis/Access.java b/src/main/java/cuchaz/enigma/analysis/Access.java
index ec5ac1ec..877327f1 100644
--- a/src/main/java/cuchaz/enigma/analysis/Access.java
+++ b/src/main/java/cuchaz/enigma/analysis/Access.java
@@ -30,7 +30,9 @@ public enum Access {
30 } 30 }
31 31
32 public static Access get(int modifiers) { 32 public static Access get(int modifiers) {
33 if (Modifier.isProtected(modifiers)) { 33 if (Modifier.isPublic(modifiers)) {
34 return Public;
35 } else if (Modifier.isProtected(modifiers)) {
34 return Protected; 36 return Protected;
35 } else if (Modifier.isPrivate(modifiers)) { 37 } else if (Modifier.isPrivate(modifiers)) {
36 return Private; 38 return Private;
diff --git a/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java b/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java
index d6db11c0..cd185846 100644
--- a/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java
+++ b/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java
@@ -18,7 +18,7 @@ import javassist.bytecode.AccessFlag;
18 18
19public class BridgeMarker { 19public class BridgeMarker {
20 20
21 private final JarIndex m_jarIndex; 21 private JarIndex m_jarIndex;
22 22
23 public BridgeMarker(JarIndex jarIndex) { 23 public BridgeMarker(JarIndex jarIndex) {
24 this.m_jarIndex = jarIndex; 24 this.m_jarIndex = jarIndex;
diff --git a/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java b/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java
index f5227bb9..2a231cb5 100644
--- a/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java
+++ b/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java
@@ -17,6 +17,7 @@ import java.util.List;
17import javax.swing.tree.DefaultMutableTreeNode; 17import javax.swing.tree.DefaultMutableTreeNode;
18 18
19import cuchaz.enigma.mapping.ClassEntry; 19import cuchaz.enigma.mapping.ClassEntry;
20import cuchaz.enigma.mapping.MethodEntry;
20import cuchaz.enigma.mapping.Translator; 21import cuchaz.enigma.mapping.Translator;
21 22
22public class ClassImplementationsTreeNode extends DefaultMutableTreeNode { 23public class ClassImplementationsTreeNode extends DefaultMutableTreeNode {
@@ -56,4 +57,20 @@ public class ClassImplementationsTreeNode extends DefaultMutableTreeNode {
56 // add them to this node 57 // add them to this node
57 nodes.forEach(this::add); 58 nodes.forEach(this::add);
58 } 59 }
60
61 public static ClassImplementationsTreeNode findNode(ClassImplementationsTreeNode node, MethodEntry entry) {
62 // is this the node?
63 if (node.entry.equals(entry)) {
64 return node;
65 }
66
67 // recurse
68 for (int i = 0; i < node.getChildCount(); i++) {
69 ClassImplementationsTreeNode foundNode = findNode((ClassImplementationsTreeNode) node.getChildAt(i), entry);
70 if (foundNode != null) {
71 return foundNode;
72 }
73 }
74 return null;
75 }
59} 76}
diff --git a/src/main/java/cuchaz/enigma/analysis/EntryRenamer.java b/src/main/java/cuchaz/enigma/analysis/EntryRenamer.java
index f0e73062..7233fcf9 100644
--- a/src/main/java/cuchaz/enigma/analysis/EntryRenamer.java
+++ b/src/main/java/cuchaz/enigma/analysis/EntryRenamer.java
@@ -56,6 +56,59 @@ public class EntryRenamer {
56 } 56 }
57 } 57 }
58 58
59 public static <Key, Val> void renameMethodsInMultimap(Map<MethodEntry, MethodEntry> renames, Multimap<Key, Val> map) {
60 // for each key/value pair...
61 Set<Map.Entry<Key, Val>> entriesToAdd = Sets.newHashSet();
62 for (Map.Entry<Key, Val> entry : map.entries()) {
63 entriesToAdd.add(new AbstractMap.SimpleEntry<>(renameMethodsInThing(renames, entry.getKey()), renameMethodsInThing(renames, entry.getValue())));
64 }
65 map.clear();
66 for (Map.Entry<Key, Val> entry : entriesToAdd) {
67 map.put(entry.getKey(), entry.getValue());
68 }
69 }
70
71 public static <Key, Val> void renameMethodsInMap(Map<MethodEntry, MethodEntry> renames, Map<Key, Val> map) {
72 // for each key/value pair...
73 Set<Map.Entry<Key, Val>> entriesToAdd = Sets.newHashSet();
74 for (Map.Entry<Key, Val> entry : map.entrySet()) {
75 entriesToAdd.add(new AbstractMap.SimpleEntry<>(renameMethodsInThing(renames, entry.getKey()), renameMethodsInThing(renames, entry.getValue())));
76 }
77 map.clear();
78 for (Map.Entry<Key, Val> entry : entriesToAdd) {
79 map.put(entry.getKey(), entry.getValue());
80 }
81 }
82
83 @SuppressWarnings("unchecked")
84 public static <T> T renameMethodsInThing(Map<MethodEntry,MethodEntry> renames, T thing) {
85 if (thing instanceof MethodEntry) {
86 MethodEntry methodEntry = (MethodEntry)thing;
87 MethodEntry newMethodEntry = renames.get(methodEntry);
88 if (newMethodEntry != null) {
89 return (T)new MethodEntry(
90 methodEntry.getClassEntry(),
91 newMethodEntry.getName(),
92 methodEntry.getSignature()
93 );
94 }
95 return thing;
96 } else if (thing instanceof ArgumentEntry) {
97 ArgumentEntry argumentEntry = (ArgumentEntry)thing;
98 return (T)new ArgumentEntry(
99 renameMethodsInThing(renames, argumentEntry.getBehaviorEntry()),
100 argumentEntry.getIndex(),
101 argumentEntry.getName()
102 );
103 } else if (thing instanceof EntryReference) {
104 EntryReference<Entry,Entry> reference = (EntryReference<Entry,Entry>)thing;
105 reference.entry = renameMethodsInThing(renames, reference.entry);
106 reference.context = renameMethodsInThing(renames, reference.context);
107 return thing;
108 }
109 return thing;
110 }
111
59 @SuppressWarnings("unchecked") 112 @SuppressWarnings("unchecked")
60 public static <T> T renameClassesInThing(final Map<String, String> renames, T thing) { 113 public static <T> T renameClassesInThing(final Map<String, String> renames, T thing) {
61 if (thing instanceof String) { 114 if (thing instanceof String) {
diff --git a/src/main/java/cuchaz/enigma/analysis/JarIndex.java b/src/main/java/cuchaz/enigma/analysis/JarIndex.java
index 51a2543a..bb36c9ea 100644
--- a/src/main/java/cuchaz/enigma/analysis/JarIndex.java
+++ b/src/main/java/cuchaz/enigma/analysis/JarIndex.java
@@ -503,6 +503,22 @@ public class JarIndex {
503 return this.obfClassEntries; 503 return this.obfClassEntries;
504 } 504 }
505 505
506 public Collection<FieldEntry> getObfFieldEntries() {
507 return this.fields.values();
508 }
509
510 public Collection<FieldEntry> getObfFieldEntries(ClassEntry classEntry) {
511 return this.fields.get(classEntry);
512 }
513
514 public Collection<BehaviorEntry> getObfBehaviorEntries() {
515 return this.behaviors.values();
516 }
517
518 public Collection<BehaviorEntry> getObfBehaviorEntries(ClassEntry classEntry) {
519 return this.behaviors.get(classEntry);
520 }
521
506 public TranslationIndex getTranslationIndex() { 522 public TranslationIndex getTranslationIndex() {
507 return this.translationIndex; 523 return this.translationIndex;
508 } 524 }
diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndex.java b/src/main/java/cuchaz/enigma/analysis/SourceIndex.java
index 73e04319..719930e9 100644
--- a/src/main/java/cuchaz/enigma/analysis/SourceIndex.java
+++ b/src/main/java/cuchaz/enigma/analysis/SourceIndex.java
@@ -145,6 +145,14 @@ public class SourceIndex {
145 return this.tokenToReference.keySet(); 145 return this.tokenToReference.keySet();
146 } 146 }
147 147
148 public Iterable<Token> declarationTokens() {
149 return this.declarationToToken.values();
150 }
151
152 public Iterable<Entry> declarations() {
153 return this.declarationToToken.keySet();
154 }
155
148 public Token getDeclarationToken(Entry deobfEntry) { 156 public Token getDeclarationToken(Entry deobfEntry) {
149 return this.declarationToToken.get(deobfEntry); 157 return this.declarationToToken.get(deobfEntry);
150 } 158 }
diff --git a/src/main/java/cuchaz/enigma/analysis/TranslationIndex.java b/src/main/java/cuchaz/enigma/analysis/TranslationIndex.java
index 921fff44..17bf51ba 100644
--- a/src/main/java/cuchaz/enigma/analysis/TranslationIndex.java
+++ b/src/main/java/cuchaz/enigma/analysis/TranslationIndex.java
@@ -148,6 +148,13 @@ public class TranslationIndex {
148 return subclasses; 148 return subclasses;
149 } 149 }
150 150
151 public void getSubclassesRecursively(Set<ClassEntry> out, ClassEntry classEntry) {
152 for (ClassEntry subclassEntry : getSubclass(classEntry)) {
153 out.add(subclassEntry);
154 getSubclassesRecursively(out, subclassEntry);
155 }
156 }
157
151 public void getSubclassNamesRecursively(Set<String> out, ClassEntry classEntry) { 158 public void getSubclassNamesRecursively(Set<String> out, ClassEntry classEntry) {
152 for (ClassEntry subclassEntry : getSubclass(classEntry)) { 159 for (ClassEntry subclassEntry : getSubclass(classEntry)) {
153 out.add(subclassEntry.getName()); 160 out.add(subclassEntry.getName());
diff --git a/src/main/java/cuchaz/enigma/analysis/TreeDumpVisitor.java b/src/main/java/cuchaz/enigma/analysis/TreeDumpVisitor.java
new file mode 100644
index 00000000..ef8a190c
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/TreeDumpVisitor.java
@@ -0,0 +1,441 @@
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 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.strobel.componentmodel.Key;
14import com.strobel.decompiler.languages.java.ast.*;
15import com.strobel.decompiler.patterns.Pattern;
16
17import java.io.File;
18import java.io.FileWriter;
19import java.io.IOException;
20import java.io.Writer;
21
22public class TreeDumpVisitor implements IAstVisitor<Void, Void> {
23
24 private File m_file;
25 private Writer m_out;
26
27 public TreeDumpVisitor(File file) {
28 m_file = file;
29 m_out = null;
30 }
31
32 @Override
33 public Void visitCompilationUnit(CompilationUnit node, Void ignored) {
34 try {
35 m_out = new FileWriter(m_file);
36 recurse(node, ignored);
37 m_out.close();
38 return null;
39 } catch (IOException ex) {
40 throw new Error(ex);
41 }
42 }
43
44 private Void recurse(AstNode node, Void ignored) {
45 // show the tree
46 try {
47 m_out.write(getIndent(node) + node.getClass().getSimpleName() + " " + getText(node) + " " + dumpUserData(node) + " " + node.getRegion() + "\n");
48 } catch (IOException ex) {
49 throw new Error(ex);
50 }
51
52 // recurse
53 for (final AstNode child : node.getChildren()) {
54 child.acceptVisitor(this, ignored);
55 }
56 return null;
57 }
58
59 private String getText(AstNode node) {
60 if (node instanceof Identifier) {
61 return "\"" + ((Identifier) node).getName() + "\"";
62 }
63 return "";
64 }
65
66 private String dumpUserData(AstNode node) {
67 StringBuilder buf = new StringBuilder();
68 for (Key<?> key : Keys.ALL_KEYS) {
69 Object val = node.getUserData(key);
70 if (val != null) {
71 buf.append(String.format(" [%s=%s]", key, val));
72 }
73 }
74 return buf.toString();
75 }
76
77 private String getIndent(AstNode node) {
78 StringBuilder buf = new StringBuilder();
79 int depth = getDepth(node);
80 for (int i = 0; i < depth; i++) {
81 buf.append("\t");
82 }
83 return buf.toString();
84 }
85
86 private int getDepth(AstNode node) {
87 int depth = -1;
88 while (node != null) {
89 depth++;
90 node = node.getParent();
91 }
92 return depth;
93 }
94
95 // OVERRIDES WE DON'T CARE ABOUT
96
97 @Override
98 public Void visitInvocationExpression(InvocationExpression node, Void ignored) {
99 return recurse(node, ignored);
100 }
101
102 @Override
103 public Void visitMemberReferenceExpression(MemberReferenceExpression node, Void ignored) {
104 return recurse(node, ignored);
105 }
106
107 @Override
108 public Void visitSimpleType(SimpleType node, Void ignored) {
109 return recurse(node, ignored);
110 }
111
112 @Override
113 public Void visitMethodDeclaration(MethodDeclaration node, Void ignored) {
114 return recurse(node, ignored);
115 }
116
117 @Override
118 public Void visitConstructorDeclaration(ConstructorDeclaration node, Void ignored) {
119 return recurse(node, ignored);
120 }
121
122 @Override
123 public Void visitParameterDeclaration(ParameterDeclaration node, Void ignored) {
124 return recurse(node, ignored);
125 }
126
127 @Override
128 public Void visitFieldDeclaration(FieldDeclaration node, Void ignored) {
129 return recurse(node, ignored);
130 }
131
132 @Override
133 public Void visitTypeDeclaration(TypeDeclaration node, Void ignored) {
134 return recurse(node, ignored);
135 }
136
137 @Override
138 public Void visitComment(Comment node, Void ignored) {
139 return recurse(node, ignored);
140 }
141
142 @Override
143 public Void visitPatternPlaceholder(AstNode node, Pattern pattern, Void ignored) {
144 return recurse(node, ignored);
145 }
146
147 @Override
148 public Void visitTypeReference(TypeReferenceExpression node, Void ignored) {
149 return recurse(node, ignored);
150 }
151
152 @Override
153 public Void visitJavaTokenNode(JavaTokenNode node, Void ignored) {
154 return recurse(node, ignored);
155 }
156
157 @Override
158 public Void visitIdentifier(Identifier node, Void ignored) {
159 return recurse(node, ignored);
160 }
161
162 @Override
163 public Void visitNullReferenceExpression(NullReferenceExpression node, Void ignored) {
164 return recurse(node, ignored);
165 }
166
167 @Override
168 public Void visitThisReferenceExpression(ThisReferenceExpression node, Void ignored) {
169 return recurse(node, ignored);
170 }
171
172 @Override
173 public Void visitSuperReferenceExpression(SuperReferenceExpression node, Void ignored) {
174 return recurse(node, ignored);
175 }
176
177 @Override
178 public Void visitClassOfExpression(ClassOfExpression node, Void ignored) {
179 return recurse(node, ignored);
180 }
181
182 @Override
183 public Void visitBlockStatement(BlockStatement node, Void ignored) {
184 return recurse(node, ignored);
185 }
186
187 @Override
188 public Void visitExpressionStatement(ExpressionStatement node, Void ignored) {
189 return recurse(node, ignored);
190 }
191
192 @Override
193 public Void visitBreakStatement(BreakStatement node, Void ignored) {
194 return recurse(node, ignored);
195 }
196
197 @Override
198 public Void visitContinueStatement(ContinueStatement node, Void ignored) {
199 return recurse(node, ignored);
200 }
201
202 @Override
203 public Void visitDoWhileStatement(DoWhileStatement node, Void ignored) {
204 return recurse(node, ignored);
205 }
206
207 @Override
208 public Void visitEmptyStatement(EmptyStatement node, Void ignored) {
209 return recurse(node, ignored);
210 }
211
212 @Override
213 public Void visitIfElseStatement(IfElseStatement node, Void ignored) {
214 return recurse(node, ignored);
215 }
216
217 @Override
218 public Void visitLabelStatement(LabelStatement node, Void ignored) {
219 return recurse(node, ignored);
220 }
221
222 @Override
223 public Void visitLabeledStatement(LabeledStatement node, Void ignored) {
224 return recurse(node, ignored);
225 }
226
227 @Override
228 public Void visitReturnStatement(ReturnStatement node, Void ignored) {
229 return recurse(node, ignored);
230 }
231
232 @Override
233 public Void visitSwitchStatement(SwitchStatement node, Void ignored) {
234 return recurse(node, ignored);
235 }
236
237 @Override
238 public Void visitSwitchSection(SwitchSection node, Void ignored) {
239 return recurse(node, ignored);
240 }
241
242 @Override
243 public Void visitCaseLabel(CaseLabel node, Void ignored) {
244 return recurse(node, ignored);
245 }
246
247 @Override
248 public Void visitThrowStatement(ThrowStatement node, Void ignored) {
249 return recurse(node, ignored);
250 }
251
252 @Override
253 public Void visitCatchClause(CatchClause node, Void ignored) {
254 return recurse(node, ignored);
255 }
256
257 @Override
258 public Void visitAnnotation(Annotation node, Void ignored) {
259 return recurse(node, ignored);
260 }
261
262 @Override
263 public Void visitNewLine(NewLineNode node, Void ignored) {
264 return recurse(node, ignored);
265 }
266
267 @Override
268 public Void visitVariableDeclaration(VariableDeclarationStatement node, Void ignored) {
269 return recurse(node, ignored);
270 }
271
272 @Override
273 public Void visitVariableInitializer(VariableInitializer node, Void ignored) {
274 return recurse(node, ignored);
275 }
276
277 @Override
278 public Void visitText(TextNode node, Void ignored) {
279 return recurse(node, ignored);
280 }
281
282 @Override
283 public Void visitImportDeclaration(ImportDeclaration node, Void ignored) {
284 return recurse(node, ignored);
285 }
286
287 @Override
288 public Void visitInitializerBlock(InstanceInitializer node, Void ignored) {
289 return recurse(node, ignored);
290 }
291
292 @Override
293 public Void visitTypeParameterDeclaration(TypeParameterDeclaration node, Void ignored) {
294 return recurse(node, ignored);
295 }
296
297 @Override
298 public Void visitPackageDeclaration(PackageDeclaration node, Void ignored) {
299 return recurse(node, ignored);
300 }
301
302 @Override
303 public Void visitArraySpecifier(ArraySpecifier node, Void ignored) {
304 return recurse(node, ignored);
305 }
306
307 @Override
308 public Void visitComposedType(ComposedType node, Void ignored) {
309 return recurse(node, ignored);
310 }
311
312 @Override
313 public Void visitWhileStatement(WhileStatement node, Void ignored) {
314 return recurse(node, ignored);
315 }
316
317 @Override
318 public Void visitPrimitiveExpression(PrimitiveExpression node, Void ignored) {
319 return recurse(node, ignored);
320 }
321
322 @Override
323 public Void visitCastExpression(CastExpression node, Void ignored) {
324 return recurse(node, ignored);
325 }
326
327 @Override
328 public Void visitBinaryOperatorExpression(BinaryOperatorExpression node, Void ignored) {
329 return recurse(node, ignored);
330 }
331
332 @Override
333 public Void visitInstanceOfExpression(InstanceOfExpression node, Void ignored) {
334 return recurse(node, ignored);
335 }
336
337 @Override
338 public Void visitIndexerExpression(IndexerExpression node, Void ignored) {
339 return recurse(node, ignored);
340 }
341
342 @Override
343 public Void visitIdentifierExpression(IdentifierExpression node, Void ignored) {
344 return recurse(node, ignored);
345 }
346
347 @Override
348 public Void visitUnaryOperatorExpression(UnaryOperatorExpression node, Void ignored) {
349 return recurse(node, ignored);
350 }
351
352 @Override
353 public Void visitConditionalExpression(ConditionalExpression node, Void ignored) {
354 return recurse(node, ignored);
355 }
356
357 @Override
358 public Void visitArrayInitializerExpression(ArrayInitializerExpression node, Void ignored) {
359 return recurse(node, ignored);
360 }
361
362 @Override
363 public Void visitObjectCreationExpression(ObjectCreationExpression node, Void ignored) {
364 return recurse(node, ignored);
365 }
366
367 @Override
368 public Void visitArrayCreationExpression(ArrayCreationExpression node, Void ignored) {
369 return recurse(node, ignored);
370 }
371
372 @Override
373 public Void visitAssignmentExpression(AssignmentExpression node, Void ignored) {
374 return recurse(node, ignored);
375 }
376
377 @Override
378 public Void visitForStatement(ForStatement node, Void ignored) {
379 return recurse(node, ignored);
380 }
381
382 @Override
383 public Void visitForEachStatement(ForEachStatement node, Void ignored) {
384 return recurse(node, ignored);
385 }
386
387 @Override
388 public Void visitTryCatchStatement(TryCatchStatement node, Void ignored) {
389 return recurse(node, ignored);
390 }
391
392 @Override
393 public Void visitGotoStatement(GotoStatement node, Void ignored) {
394 return recurse(node, ignored);
395 }
396
397 @Override
398 public Void visitParenthesizedExpression(ParenthesizedExpression node, Void ignored) {
399 return recurse(node, ignored);
400 }
401
402 @Override
403 public Void visitSynchronizedStatement(SynchronizedStatement node, Void ignored) {
404 return recurse(node, ignored);
405 }
406
407 @Override
408 public Void visitAnonymousObjectCreationExpression(AnonymousObjectCreationExpression node, Void ignored) {
409 return recurse(node, ignored);
410 }
411
412 @Override
413 public Void visitWildcardType(WildcardType node, Void ignored) {
414 return recurse(node, ignored);
415 }
416
417 @Override
418 public Void visitMethodGroupExpression(MethodGroupExpression node, Void ignored) {
419 return recurse(node, ignored);
420 }
421
422 @Override
423 public Void visitEnumValueDeclaration(EnumValueDeclaration node, Void ignored) {
424 return recurse(node, ignored);
425 }
426
427 @Override
428 public Void visitAssertStatement(AssertStatement node, Void ignored) {
429 return recurse(node, ignored);
430 }
431
432 @Override
433 public Void visitLambdaExpression(LambdaExpression node, Void ignored) {
434 return recurse(node, ignored);
435 }
436
437 @Override
438 public Void visitLocalTypeDeclarationStatement(LocalTypeDeclarationStatement node, Void ignored) {
439 return recurse(node, ignored);
440 }
441}
diff --git a/src/main/java/cuchaz/enigma/bytecode/CheckCastIterator.java b/src/main/java/cuchaz/enigma/bytecode/CheckCastIterator.java
new file mode 100644
index 00000000..19c39d3c
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/CheckCastIterator.java
@@ -0,0 +1,117 @@
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 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.util.Iterator;
14
15import cuchaz.enigma.bytecode.CheckCastIterator.CheckCast;
16import cuchaz.enigma.mapping.ClassEntry;
17import cuchaz.enigma.mapping.MethodEntry;
18import cuchaz.enigma.mapping.Signature;
19import javassist.bytecode.*;
20
21public class CheckCastIterator implements Iterator<CheckCast> {
22
23 public static class CheckCast {
24
25 public String className;
26 public MethodEntry prevMethodEntry;
27
28 public CheckCast(String className, MethodEntry prevMethodEntry) {
29 this.className = className;
30 this.prevMethodEntry = prevMethodEntry;
31 }
32 }
33
34 private ConstPool constants;
35 private CodeAttribute attribute;
36 private CodeIterator iter;
37 private CheckCast next;
38
39 public CheckCastIterator(CodeAttribute codeAttribute) throws BadBytecode {
40 this.constants = codeAttribute.getConstPool();
41 this.attribute = codeAttribute;
42 this.iter = this.attribute.iterator();
43
44 this.next = getNext();
45 }
46
47 @Override
48 public boolean hasNext() {
49 return this.next != null;
50 }
51
52 @Override
53 public CheckCast next() {
54 CheckCast out = this.next;
55 try {
56 this.next = getNext();
57 } catch (BadBytecode ex) {
58 throw new Error(ex);
59 }
60 return out;
61 }
62
63 @Override
64 public void remove() {
65 throw new UnsupportedOperationException();
66 }
67
68 private CheckCast getNext() throws BadBytecode {
69 int prevPos = 0;
70 while (this.iter.hasNext()) {
71 int pos = this.iter.next();
72 int opcode = this.iter.byteAt(pos);
73 switch (opcode) {
74 case Opcode.CHECKCAST:
75
76 // get the type of this op code (next two bytes are a classinfo index)
77 MethodEntry prevMethodEntry = getMethodEntry(prevPos);
78 if (prevMethodEntry != null) {
79 return new CheckCast(this.constants.getClassInfo(this.iter.s16bitAt(pos + 1)), prevMethodEntry);
80 }
81 break;
82 }
83 prevPos = pos;
84 }
85 return null;
86 }
87
88 private MethodEntry getMethodEntry(int pos) {
89 switch (this.iter.byteAt(pos)) {
90 case Opcode.INVOKEVIRTUAL:
91 case Opcode.INVOKESTATIC:
92 case Opcode.INVOKEDYNAMIC:
93 case Opcode.INVOKESPECIAL: {
94 int index = this.iter.s16bitAt(pos + 1);
95 return new MethodEntry(
96 new ClassEntry(Descriptor.toJvmName(this.constants.getMethodrefClassName(index))),
97 this.constants.getMethodrefName(index),
98 new Signature(this.constants.getMethodrefType(index))
99 );
100 }
101
102 case Opcode.INVOKEINTERFACE: {
103 int index = this.iter.s16bitAt(pos + 1);
104 return new MethodEntry(
105 new ClassEntry(Descriptor.toJvmName(this.constants.getInterfaceMethodrefClassName(index))),
106 this.constants.getInterfaceMethodrefName(index),
107 new Signature(this.constants.getInterfaceMethodrefType(index))
108 );
109 }
110 }
111 return null;
112 }
113
114 public Iterable<CheckCast> casts() {
115 return () -> CheckCastIterator.this;
116 }
117}
diff --git a/src/main/java/cuchaz/enigma/bytecode/ClassRenamer.java b/src/main/java/cuchaz/enigma/bytecode/ClassRenamer.java
index 4a77eec1..c13aae4a 100644
--- a/src/main/java/cuchaz/enigma/bytecode/ClassRenamer.java
+++ b/src/main/java/cuchaz/enigma/bytecode/ClassRenamer.java
@@ -90,6 +90,16 @@ public class ClassRenamer {
90 }); 90 });
91 } 91 }
92 92
93 public static void moveAllClassesIntoDefaultPackage(CtClass c, final String oldPackageName) {
94 renameClasses(c, className -> {
95 ClassEntry entry = new ClassEntry(className);
96 if (entry.getPackageName().equals(oldPackageName)) {
97 return entry.getSimpleName();
98 }
99 return null;
100 });
101 }
102
93 @SuppressWarnings("unchecked") 103 @SuppressWarnings("unchecked")
94 public static void renameClasses(CtClass c, ClassNameReplacer replacer) { 104 public static void renameClasses(CtClass c, ClassNameReplacer replacer) {
95 105
diff --git a/src/main/java/cuchaz/enigma/bytecode/ConstPoolEditor.java b/src/main/java/cuchaz/enigma/bytecode/ConstPoolEditor.java
index af8c79a1..256df61e 100644
--- a/src/main/java/cuchaz/enigma/bytecode/ConstPoolEditor.java
+++ b/src/main/java/cuchaz/enigma/bytecode/ConstPoolEditor.java
@@ -17,6 +17,7 @@ import java.lang.reflect.Field;
17import java.lang.reflect.Method; 17import java.lang.reflect.Method;
18import java.util.HashMap; 18import java.util.HashMap;
19 19
20import cuchaz.enigma.bytecode.accessors.ClassInfoAccessor;
20import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor; 21import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor;
21import cuchaz.enigma.bytecode.accessors.MemberRefInfoAccessor; 22import cuchaz.enigma.bytecode.accessors.MemberRefInfoAccessor;
22import javassist.bytecode.ConstPool; 23import javassist.bytecode.ConstPool;
@@ -77,6 +78,22 @@ public class ConstPoolEditor {
77 this.pool = pool; 78 this.pool = pool;
78 } 79 }
79 80
81 public void writePool(DataOutputStream out) {
82 try {
83 methodWritePool.invoke(this.pool, out);
84 } catch (Exception ex) {
85 throw new Error(ex);
86 }
87 }
88
89 public static ConstPool readPool(DataInputStream in) {
90 try {
91 return constructorPool.newInstance(in);
92 } catch (Exception ex) {
93 throw new Error(ex);
94 }
95 }
96
80 public String getMemberrefClassname(int memberrefIndex) { 97 public String getMemberrefClassname(int memberrefIndex) {
81 return Descriptor.toJvmName(this.pool.getClassInfo(this.pool.getMemberClass(memberrefIndex))); 98 return Descriptor.toJvmName(this.pool.getClassInfo(this.pool.getMemberClass(memberrefIndex)));
82 } 99 }
@@ -101,6 +118,48 @@ public class ConstPoolEditor {
101 } 118 }
102 } 119 }
103 120
121 public int addItem(Object item) {
122 try {
123 return (Integer) addItem.invoke(this.pool, item);
124 } catch (Exception ex) {
125 throw new Error(ex);
126 }
127 }
128
129 public int addItemForceNew(Object item) {
130 try {
131 return (Integer) addItem0.invoke(this.pool, item);
132 } catch (Exception ex) {
133 throw new Error(ex);
134 }
135 }
136
137 @SuppressWarnings("rawtypes")
138 public void removeLastItem() {
139 try {
140 // remove the item from the cache
141 HashMap cache = getCache();
142 if (cache != null) {
143 Object item = getItem(this.pool.getSize() - 1);
144 cache.remove(item);
145 }
146
147 // remove the actual item
148 // based off of LongVector.addElement()
149 Object item = items.get(this.pool);
150 Object[][] object = (Object[][]) objects.get(items);
151 int numElements = (Integer) elements.get(items) - 1;
152 int nth = numElements >> 7;
153 int offset = numElements & (128 - 1);
154 object[nth][offset] = null;
155
156 // decrement the number of items
157 elements.set(item, numElements);
158 numItems.set(this.pool, (Integer) numItems.get(this.pool) - 1);
159 } catch (Exception ex) {
160 throw new Error(ex);
161 }
162 }
104 163
105 @SuppressWarnings("rawtypes") 164 @SuppressWarnings("rawtypes")
106 public HashMap getCache() { 165 public HashMap getCache() {
@@ -138,4 +197,67 @@ public class ConstPoolEditor {
138 assert (newName.equals(getMemberrefName(memberrefIndex))); 197 assert (newName.equals(getMemberrefName(memberrefIndex)));
139 assert (newType.equals(getMemberrefType(memberrefIndex))); 198 assert (newType.equals(getMemberrefType(memberrefIndex)));
140 } 199 }
200
201 @SuppressWarnings({"rawtypes", "unchecked"})
202 public void changeClassName(int classNameIndex, String newName) {
203 // NOTE: when changing values, we always need to copy-on-write
204 try {
205 // get the class item
206 Object item = getItem(classNameIndex).getItem();
207
208 // update the cache
209 HashMap cache = getCache();
210 if (cache != null) {
211 cache.remove(item);
212 }
213
214 // add the new name and repoint the name-and-type to it
215 new ClassInfoAccessor(item).setNameIndex(this.pool.addUtf8Info(newName));
216
217 // update the cache
218 if (cache != null) {
219 cache.put(item, item);
220 }
221 } catch (Exception ex) {
222 throw new Error(ex);
223 }
224 }
225
226 public static ConstPool newConstPool() {
227 // const pool expects the name of a class to initialize itself
228 // but we want an empty pool
229 // so give it a bogus name, and then clear the entries afterwards
230 ConstPool pool = new ConstPool("a");
231
232 ConstPoolEditor editor = new ConstPoolEditor(pool);
233 int size = pool.getSize();
234 for (int i = 0; i < size - 1; i++) {
235 editor.removeLastItem();
236 }
237
238 // make sure the pool is actually empty
239 // although, in this case "empty" means one thing in it
240 // the JVM spec says index 0 should be reserved
241 assert (pool.getSize() == 1);
242 assert (editor.getItem(0) == null);
243 assert (editor.getItem(1) == null);
244 assert (editor.getItem(2) == null);
245 assert (editor.getItem(3) == null);
246
247 // also, clear the cache
248 editor.getCache().clear();
249
250 return pool;
251 }
252
253 public String dump() {
254 StringBuilder buf = new StringBuilder();
255 for (int i = 1; i < this.pool.getSize(); i++) {
256 buf.append(String.format("%4d", i));
257 buf.append(" ");
258 buf.append(getItem(i).toString());
259 buf.append("\n");
260 }
261 return buf.toString();
262 }
141} 263}
diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java
index 316bb5e4..66f22839 100644
--- a/src/main/java/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java
+++ b/src/main/java/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java
@@ -39,6 +39,10 @@ public class ClassInfoAccessor {
39 } 39 }
40 } 40 }
41 41
42 public static boolean isType(ConstInfoAccessor accessor) {
43 return clazz.isAssignableFrom(accessor.getItem().getClass());
44 }
45
42 static { 46 static {
43 try { 47 try {
44 clazz = Class.forName("javassist.bytecode.ClassInfo"); 48 clazz = Class.forName("javassist.bytecode.ClassInfo");
diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java
index 474a3ef0..bc7af870 100644
--- a/src/main/java/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java
+++ b/src/main/java/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java
@@ -10,8 +10,12 @@
10 ******************************************************************************/ 10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors; 11package cuchaz.enigma.bytecode.accessors;
12 12
13import java.io.ByteArrayInputStream;
13import java.io.ByteArrayOutputStream; 14import java.io.ByteArrayOutputStream;
15import java.io.DataInputStream;
16import java.io.DataOutputStream;
14import java.io.FileOutputStream; 17import java.io.FileOutputStream;
18import java.io.IOException;
15import java.io.OutputStreamWriter; 19import java.io.OutputStreamWriter;
16import java.io.PrintWriter; 20import java.io.PrintWriter;
17import java.lang.reflect.Field; 21import java.lang.reflect.Field;
@@ -55,6 +59,44 @@ public class ConstInfoAccessor {
55 } 59 }
56 } 60 }
57 61
62 public ConstInfoAccessor copy() {
63 return new ConstInfoAccessor(copyItem());
64 }
65
66 public Object copyItem() {
67 // I don't know of a simpler way to copy one of these silly things...
68 try {
69 // serialize the item
70 ByteArrayOutputStream buf = new ByteArrayOutputStream();
71 DataOutputStream out = new DataOutputStream(buf);
72 write(out);
73
74 // deserialize the item
75 DataInputStream in = new DataInputStream(new ByteArrayInputStream(buf.toByteArray()));
76 Object item = new ConstInfoAccessor(in).getItem();
77 in.close();
78
79 return item;
80 } catch (Exception ex) {
81 throw new Error(ex);
82 }
83 }
84
85 public void write(DataOutputStream out) throws IOException {
86 try {
87 out.writeUTF(this.item.getClass().getName());
88 out.writeInt(getIndex());
89
90 Method method = this.item.getClass().getMethod("write", DataOutputStream.class);
91 method.setAccessible(true);
92 method.invoke(this.item, out);
93 } catch (IOException ex) {
94 throw ex;
95 } catch (Exception ex) {
96 throw new Error(ex);
97 }
98 }
99
58 @Override 100 @Override
59 public String toString() { 101 public String toString() {
60 try { 102 try {
diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java
index a1583945..69aee160 100644
--- a/src/main/java/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java
+++ b/src/main/java/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java
@@ -57,6 +57,10 @@ public class InvokeDynamicInfoAccessor {
57 } 57 }
58 } 58 }
59 59
60 public static boolean isType(ConstInfoAccessor accessor) {
61 return clazz.isAssignableFrom(accessor.getItem().getClass());
62 }
63
60 static { 64 static {
61 try { 65 try {
62 clazz = Class.forName("javassist.bytecode.InvokeDynamicInfo"); 66 clazz = Class.forName("javassist.bytecode.InvokeDynamicInfo");
diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java
index 2835508a..0e0297be 100644
--- a/src/main/java/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java
+++ b/src/main/java/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java
@@ -56,6 +56,10 @@ public class MemberRefInfoAccessor {
56 } 56 }
57 } 57 }
58 58
59 public static boolean isType(ConstInfoAccessor accessor) {
60 return clazz.isAssignableFrom(accessor.getItem().getClass());
61 }
62
59 static { 63 static {
60 try { 64 try {
61 clazz = Class.forName("javassist.bytecode.MemberrefInfo"); 65 clazz = Class.forName("javassist.bytecode.MemberrefInfo");
diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java
index a203b43a..9a7dd698 100644
--- a/src/main/java/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java
+++ b/src/main/java/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java
@@ -56,6 +56,10 @@ public class MethodHandleInfoAccessor {
56 } 56 }
57 } 57 }
58 58
59 public static boolean isType(ConstInfoAccessor accessor) {
60 return clazz.isAssignableFrom(accessor.getItem().getClass());
61 }
62
59 static { 63 static {
60 try { 64 try {
61 clazz = Class.forName("javassist.bytecode.MethodHandleInfo"); 65 clazz = Class.forName("javassist.bytecode.MethodHandleInfo");
diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java
index 993c79bb..5ec9c3b4 100644
--- a/src/main/java/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java
+++ b/src/main/java/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java
@@ -39,6 +39,10 @@ public class MethodTypeInfoAccessor {
39 } 39 }
40 } 40 }
41 41
42 public static boolean isType(ConstInfoAccessor accessor) {
43 return clazz.isAssignableFrom(accessor.getItem().getClass());
44 }
45
42 static { 46 static {
43 try { 47 try {
44 clazz = Class.forName("javassist.bytecode.MethodTypeInfo"); 48 clazz = Class.forName("javassist.bytecode.MethodTypeInfo");
diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java
index d6c2531f..95df37c1 100644
--- a/src/main/java/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java
+++ b/src/main/java/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java
@@ -56,6 +56,10 @@ public class NameAndTypeInfoAccessor {
56 } 56 }
57 } 57 }
58 58
59 public static boolean isType(ConstInfoAccessor accessor) {
60 return clazz.isAssignableFrom(accessor.getItem().getClass());
61 }
62
59 static { 63 static {
60 try { 64 try {
61 clazz = Class.forName("javassist.bytecode.NameAndTypeInfo"); 65 clazz = Class.forName("javassist.bytecode.NameAndTypeInfo");
diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java
index e211381b..1c55a443 100644
--- a/src/main/java/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java
+++ b/src/main/java/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java
@@ -39,6 +39,10 @@ public class StringInfoAccessor {
39 } 39 }
40 } 40 }
41 41
42 public static boolean isType(ConstInfoAccessor accessor) {
43 return clazz.isAssignableFrom(accessor.getItem().getClass());
44 }
45
42 static { 46 static {
43 try { 47 try {
44 clazz = Class.forName("javassist.bytecode.StringInfo"); 48 clazz = Class.forName("javassist.bytecode.StringInfo");
diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java
new file mode 100644
index 00000000..7a2cb667
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java
@@ -0,0 +1,28 @@
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 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors;
12
13public class Utf8InfoAccessor {
14
15 private static Class<?> clazz;
16
17 static {
18 try {
19 clazz = Class.forName("javassist.bytecode.Utf8Info");
20 } catch (Exception ex) {
21 throw new Error(ex);
22 }
23 }
24
25 public static boolean isType(ConstInfoAccessor accessor) {
26 return clazz.isAssignableFrom(accessor.getItem().getClass());
27 }
28}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassForest.java b/src/main/java/cuchaz/enigma/convert/ClassForest.java
new file mode 100644
index 00000000..b08d48fb
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/convert/ClassForest.java
@@ -0,0 +1,60 @@
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 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import com.google.common.collect.HashMultimap;
14import com.google.common.collect.Multimap;
15
16import java.util.Collection;
17
18import cuchaz.enigma.mapping.ClassEntry;
19
20
21public class ClassForest {
22
23 private ClassIdentifier identifier;
24 private Multimap<ClassIdentity, ClassEntry> forest;
25
26 public ClassForest(ClassIdentifier identifier) {
27 this.identifier = identifier;
28 this.forest = HashMultimap.create();
29 }
30
31 public void addAll(Iterable<ClassEntry> entries) {
32 for (ClassEntry entry : entries) {
33 add(entry);
34 }
35 }
36
37 public void add(ClassEntry entry) {
38 try {
39 this.forest.put(this.identifier.identify(entry), entry);
40 } catch (ClassNotFoundException ex) {
41 throw new Error("Unable to find class " + entry.getName());
42 }
43 }
44
45 public Collection<ClassIdentity> identities() {
46 return this.forest.keySet();
47 }
48
49 public Collection<ClassEntry> classes() {
50 return this.forest.values();
51 }
52
53 public Collection<ClassEntry> getClasses(ClassIdentity identity) {
54 return this.forest.get(identity);
55 }
56
57 public boolean containsIdentity(ClassIdentity identity) {
58 return this.forest.containsKey(identity);
59 }
60}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassIdentifier.java b/src/main/java/cuchaz/enigma/convert/ClassIdentifier.java
new file mode 100644
index 00000000..f5454377
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/convert/ClassIdentifier.java
@@ -0,0 +1,56 @@
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 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import com.google.common.collect.Maps;
14
15import java.util.Map;
16import java.util.jar.JarFile;
17
18import cuchaz.enigma.TranslatingTypeLoader;
19import cuchaz.enigma.analysis.JarIndex;
20import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
21import cuchaz.enigma.mapping.ClassEntry;
22import cuchaz.enigma.mapping.TranslationDirection;
23import cuchaz.enigma.mapping.Translator;
24import javassist.CtClass;
25
26
27public class ClassIdentifier {
28
29 private JarIndex index;
30 private SidedClassNamer namer;
31 private boolean useReferences;
32 private TranslatingTypeLoader loader;
33 private Map<ClassEntry, ClassIdentity> cache;
34
35 public ClassIdentifier(JarFile jar, JarIndex index, SidedClassNamer namer, boolean useReferences) {
36 this.index = index;
37 this.namer = namer;
38 this.useReferences = useReferences;
39 this.loader = new TranslatingTypeLoader(jar, index, new Translator(), new Translator());
40 this.cache = Maps.newHashMap();
41 }
42
43 public ClassIdentity identify(ClassEntry classEntry)
44 throws ClassNotFoundException {
45 ClassIdentity identity = this.cache.get(classEntry);
46 if (identity == null) {
47 CtClass c = this.loader.loadClass(classEntry.getName());
48 if (c == null) {
49 throw new ClassNotFoundException(classEntry.getName());
50 }
51 identity = new ClassIdentity(c, this.namer, this.index, this.useReferences);
52 this.cache.put(classEntry, identity);
53 }
54 return identity;
55 }
56}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassIdentity.java b/src/main/java/cuchaz/enigma/convert/ClassIdentity.java
new file mode 100644
index 00000000..606c1df1
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/convert/ClassIdentity.java
@@ -0,0 +1,441 @@
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 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import com.google.common.collect.*;
14
15import java.io.UnsupportedEncodingException;
16import java.security.MessageDigest;
17import java.security.NoSuchAlgorithmException;
18import java.util.Enumeration;
19import java.util.List;
20import java.util.Map;
21import java.util.Set;
22
23import cuchaz.enigma.Constants;
24import cuchaz.enigma.Util;
25import cuchaz.enigma.analysis.ClassImplementationsTreeNode;
26import cuchaz.enigma.analysis.EntryReference;
27import cuchaz.enigma.analysis.JarIndex;
28import cuchaz.enigma.bytecode.ConstPoolEditor;
29import cuchaz.enigma.bytecode.InfoType;
30import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor;
31import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
32import cuchaz.enigma.mapping.*;
33import javassist.*;
34import javassist.bytecode.*;
35import javassist.expr.*;
36
37public class ClassIdentity {
38
39 private ClassEntry classEntry;
40 private SidedClassNamer namer;
41 private Multiset<String> fields;
42 private Multiset<String> methods;
43 private Multiset<String> constructors;
44 private String staticInitializer;
45 private String extendz;
46 private Multiset<String> implementz;
47 private Set<String> stringLiterals;
48 private Multiset<String> implementations;
49 private Multiset<String> references;
50 private String outer;
51
52 private final ClassNameReplacer m_classNameReplacer = new ClassNameReplacer() {
53
54 private Map<String, String> m_classNames = Maps.newHashMap();
55
56 @Override
57 public String replace(String className) {
58
59 // classes not in the none package can be passed through
60 ClassEntry classEntry = new ClassEntry(className);
61 if (!classEntry.getPackageName().equals(Constants.NONE_PACKAGE)) {
62 return className;
63 }
64
65 // is this class ourself?
66 if (className.equals(classEntry.getName())) {
67 return "CSelf";
68 }
69
70 // try the namer
71 if (namer != null) {
72 String newName = namer.getName(className);
73 if (newName != null) {
74 return newName;
75 }
76 }
77
78 // otherwise, use local naming
79 if (!m_classNames.containsKey(className)) {
80 m_classNames.put(className, getNewClassName());
81 }
82 return m_classNames.get(className);
83 }
84
85 private String getNewClassName() {
86 return String.format("C%03d", m_classNames.size());
87 }
88 };
89
90 public ClassIdentity(CtClass c, SidedClassNamer namer, JarIndex index, boolean useReferences) {
91 this.namer = namer;
92
93 // stuff from the bytecode
94
95 this.classEntry = EntryFactory.getClassEntry(c);
96 this.fields = HashMultiset.create();
97 for (CtField field : c.getDeclaredFields()) {
98 this.fields.add(scrubType(field.getSignature()));
99 }
100 this.methods = HashMultiset.create();
101 for (CtMethod method : c.getDeclaredMethods()) {
102 this.methods.add(scrubSignature(method.getSignature()) + "0x" + getBehaviorSignature(method));
103 }
104 this.constructors = HashMultiset.create();
105 for (CtConstructor constructor : c.getDeclaredConstructors()) {
106 this.constructors.add(scrubSignature(constructor.getSignature()) + "0x" + getBehaviorSignature(constructor));
107 }
108 this.staticInitializer = "";
109 if (c.getClassInitializer() != null) {
110 this.staticInitializer = getBehaviorSignature(c.getClassInitializer());
111 }
112 this.extendz = "";
113 if (c.getClassFile().getSuperclass() != null) {
114 this.extendz = scrubClassName(Descriptor.toJvmName(c.getClassFile().getSuperclass()));
115 }
116 this.implementz = HashMultiset.create();
117 for (String interfaceName : c.getClassFile().getInterfaces()) {
118 this.implementz.add(scrubClassName(Descriptor.toJvmName(interfaceName)));
119 }
120
121 this.stringLiterals = Sets.newHashSet();
122 ConstPool constants = c.getClassFile().getConstPool();
123 for (int i = 1; i < constants.getSize(); i++) {
124 if (constants.getTag(i) == ConstPool.CONST_String) {
125 this.stringLiterals.add(constants.getStringInfo(i));
126 }
127 }
128
129 // stuff from the jar index
130
131 this.implementations = HashMultiset.create();
132 ClassImplementationsTreeNode implementationsNode = index.getClassImplementations(null, this.classEntry);
133 if (implementationsNode != null) {
134 @SuppressWarnings("unchecked")
135 Enumeration<ClassImplementationsTreeNode> implementations = implementationsNode.children();
136 while (implementations.hasMoreElements()) {
137 ClassImplementationsTreeNode node = implementations.nextElement();
138 this.implementations.add(scrubClassName(node.getClassEntry().getName()));
139 }
140 }
141
142 this.references = HashMultiset.create();
143 if (useReferences) {
144 for (CtField field : c.getDeclaredFields()) {
145 FieldEntry fieldEntry = EntryFactory.getFieldEntry(field);
146 index.getFieldReferences(fieldEntry).forEach(this::addReference);
147 }
148 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
149 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
150 index.getBehaviorReferences(behaviorEntry).forEach(this::addReference);
151 }
152 }
153
154 this.outer = null;
155 if (this.classEntry.isInnerClass()) {
156 this.outer = this.classEntry.getOuterClassName();
157 }
158 }
159
160 private void addReference(EntryReference<? extends Entry, BehaviorEntry> reference) {
161 if (reference.context.getSignature() != null) {
162 this.references.add(String.format("%s_%s",
163 scrubClassName(reference.context.getClassName()),
164 scrubSignature(reference.context.getSignature())
165 ));
166 } else {
167 this.references.add(String.format("%s_<clinit>",
168 scrubClassName(reference.context.getClassName())
169 ));
170 }
171 }
172
173 public ClassEntry getClassEntry() {
174 return this.classEntry;
175 }
176
177 @Override
178 public String toString() {
179 StringBuilder buf = new StringBuilder();
180 buf.append("class: ");
181 buf.append(this.classEntry.getName());
182 buf.append(" ");
183 buf.append(hashCode());
184 buf.append("\n");
185 for (String field : this.fields) {
186 buf.append("\tfield ");
187 buf.append(field);
188 buf.append("\n");
189 }
190 for (String method : this.methods) {
191 buf.append("\tmethod ");
192 buf.append(method);
193 buf.append("\n");
194 }
195 for (String constructor : this.constructors) {
196 buf.append("\tconstructor ");
197 buf.append(constructor);
198 buf.append("\n");
199 }
200 if (this.staticInitializer.length() > 0) {
201 buf.append("\tinitializer ");
202 buf.append(this.staticInitializer);
203 buf.append("\n");
204 }
205 if (this.extendz.length() > 0) {
206 buf.append("\textends ");
207 buf.append(this.extendz);
208 buf.append("\n");
209 }
210 for (String interfaceName : this.implementz) {
211 buf.append("\timplements ");
212 buf.append(interfaceName);
213 buf.append("\n");
214 }
215 for (String implementation : this.implementations) {
216 buf.append("\timplemented by ");
217 buf.append(implementation);
218 buf.append("\n");
219 }
220 for (String reference : this.references) {
221 buf.append("\treference ");
222 buf.append(reference);
223 buf.append("\n");
224 }
225 buf.append("\touter ");
226 buf.append(this.outer);
227 buf.append("\n");
228 return buf.toString();
229 }
230
231 private String scrubClassName(String className) {
232 return m_classNameReplacer.replace(className);
233 }
234
235 private String scrubType(String typeName) {
236 return scrubType(new Type(typeName)).toString();
237 }
238
239 private Type scrubType(Type type) {
240 if (type.hasClass()) {
241 return new Type(type, m_classNameReplacer);
242 } else {
243 return type;
244 }
245 }
246
247 private String scrubSignature(String signature) {
248 return scrubSignature(new Signature(signature)).toString();
249 }
250
251 private Signature scrubSignature(Signature signature) {
252 return new Signature(signature, m_classNameReplacer);
253 }
254
255 private boolean isClassMatchedUniquely(String className) {
256 return this.namer != null && this.namer.getName(Descriptor.toJvmName(className)) != null;
257 }
258
259 private String getBehaviorSignature(CtBehavior behavior) {
260 try {
261 // does this method have an implementation?
262 if (behavior.getMethodInfo().getCodeAttribute() == null) {
263 return "(none)";
264 }
265
266 // compute the hash from the opcodes
267 ConstPool constants = behavior.getMethodInfo().getConstPool();
268 final MessageDigest digest = MessageDigest.getInstance("MD5");
269 CodeIterator iter = behavior.getMethodInfo().getCodeAttribute().iterator();
270 while (iter.hasNext()) {
271 int pos = iter.next();
272
273 // update the hash with the opcode
274 int opcode = iter.byteAt(pos);
275 digest.update((byte) opcode);
276
277 switch (opcode) {
278 case Opcode.LDC: {
279 int constIndex = iter.byteAt(pos + 1);
280 updateHashWithConstant(digest, constants, constIndex);
281 }
282 break;
283
284 case Opcode.LDC_W:
285 case Opcode.LDC2_W: {
286 int constIndex = (iter.byteAt(pos + 1) << 8) | iter.byteAt(pos + 2);
287 updateHashWithConstant(digest, constants, constIndex);
288 }
289 break;
290 }
291 }
292
293 // update hash with method and field accesses
294 behavior.instrument(new ExprEditor() {
295 @Override
296 public void edit(MethodCall call) {
297 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName())));
298 updateHashWithString(digest, scrubSignature(call.getSignature()));
299 if (isClassMatchedUniquely(call.getClassName())) {
300 updateHashWithString(digest, call.getMethodName());
301 }
302 }
303
304 @Override
305 public void edit(FieldAccess access) {
306 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(access.getClassName())));
307 updateHashWithString(digest, scrubType(access.getSignature()));
308 if (isClassMatchedUniquely(access.getClassName())) {
309 updateHashWithString(digest, access.getFieldName());
310 }
311 }
312
313 @Override
314 public void edit(ConstructorCall call) {
315 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName())));
316 updateHashWithString(digest, scrubSignature(call.getSignature()));
317 }
318
319 @Override
320 public void edit(NewExpr expr) {
321 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(expr.getClassName())));
322 }
323 });
324
325 // convert the hash to a hex string
326 return toHex(digest.digest());
327 } catch (BadBytecode | NoSuchAlgorithmException | CannotCompileException ex) {
328 throw new Error(ex);
329 }
330 }
331
332 private void updateHashWithConstant(MessageDigest digest, ConstPool constants, int index) {
333 ConstPoolEditor editor = new ConstPoolEditor(constants);
334 ConstInfoAccessor item = editor.getItem(index);
335 if (item.getType() == InfoType.StringInfo) {
336 updateHashWithString(digest, constants.getStringInfo(index));
337 }
338 // TODO: other constants
339 }
340
341 private void updateHashWithString(MessageDigest digest, String val) {
342 try {
343 digest.update(val.getBytes("UTF8"));
344 } catch (UnsupportedEncodingException ex) {
345 throw new Error(ex);
346 }
347 }
348
349 private String toHex(byte[] bytes) {
350 // function taken from:
351 // http://stackoverflow.com/questions/9655181/convert-from-byte-array-to-hex-string-in-java
352 final char[] hexArray = "0123456789ABCDEF".toCharArray();
353 char[] hexChars = new char[bytes.length * 2];
354 for (int j = 0; j < bytes.length; j++) {
355 int v = bytes[j] & 0xFF;
356 hexChars[j * 2] = hexArray[v >>> 4];
357 hexChars[j * 2 + 1] = hexArray[v & 0x0F];
358 }
359 return new String(hexChars);
360 }
361
362 @Override
363 public boolean equals(Object other) {
364 return other instanceof ClassIdentity && equals((ClassIdentity) other);
365 }
366
367 public boolean equals(ClassIdentity other) {
368 return this.fields.equals(other.fields)
369 && this.methods.equals(other.methods)
370 && this.constructors.equals(other.constructors)
371 && this.staticInitializer.equals(other.staticInitializer)
372 && this.extendz.equals(other.extendz)
373 && this.implementz.equals(other.implementz)
374 && this.implementations.equals(other.implementations)
375 && this.references.equals(other.references);
376 }
377
378 @Override
379 public int hashCode() {
380 List<Object> objs = Lists.newArrayList();
381 objs.addAll(this.fields);
382 objs.addAll(this.methods);
383 objs.addAll(this.constructors);
384 objs.add(this.staticInitializer);
385 objs.add(this.extendz);
386 objs.addAll(this.implementz);
387 objs.addAll(this.implementations);
388 objs.addAll(this.references);
389 return Util.combineHashesOrdered(objs);
390 }
391
392 public int getMatchScore(ClassIdentity other) {
393 return 2 * getNumMatches(this.extendz, other.extendz)
394 + 2 * getNumMatches(this.outer, other.outer)
395 + 2 * getNumMatches(this.implementz, other.implementz)
396 + getNumMatches(this.stringLiterals, other.stringLiterals)
397 + getNumMatches(this.fields, other.fields)
398 + getNumMatches(this.methods, other.methods)
399 + getNumMatches(this.constructors, other.constructors);
400 }
401
402 public int getMaxMatchScore() {
403 return 2 + 2 + 2 * this.implementz.size() + this.stringLiterals.size() + this.fields.size() + this.methods.size() + this.constructors.size();
404 }
405
406 public boolean matches(CtClass c) {
407 // just compare declaration counts
408 return this.fields.size() == c.getDeclaredFields().length
409 && this.methods.size() == c.getDeclaredMethods().length
410 && this.constructors.size() == c.getDeclaredConstructors().length;
411 }
412
413 private int getNumMatches(Set<String> a, Set<String> b) {
414 int numMatches = 0;
415 for (String val : a) {
416 if (b.contains(val)) {
417 numMatches++;
418 }
419 }
420 return numMatches;
421 }
422
423 private int getNumMatches(Multiset<String> a, Multiset<String> b) {
424 int numMatches = 0;
425 for (String val : a) {
426 if (b.contains(val)) {
427 numMatches++;
428 }
429 }
430 return numMatches;
431 }
432
433 private int getNumMatches(String a, String b) {
434 if (a == null && b == null) {
435 return 1;
436 } else if (a != null && b != null && a.equals(b)) {
437 return 1;
438 }
439 return 0;
440 }
441}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassMatch.java b/src/main/java/cuchaz/enigma/convert/ClassMatch.java
new file mode 100644
index 00000000..422529ec
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/convert/ClassMatch.java
@@ -0,0 +1,84 @@
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 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import com.google.common.collect.Sets;
14
15import java.util.Collection;
16import java.util.Set;
17
18import cuchaz.enigma.Util;
19import cuchaz.enigma.mapping.ClassEntry;
20
21
22public class ClassMatch {
23
24 public Set<ClassEntry> sourceClasses;
25 public Set<ClassEntry> destClasses;
26
27 public ClassMatch(Collection<ClassEntry> sourceClasses, Collection<ClassEntry> destClasses) {
28 this.sourceClasses = Sets.newHashSet(sourceClasses);
29 this.destClasses = Sets.newHashSet(destClasses);
30 }
31
32 public ClassMatch(ClassEntry sourceClass, ClassEntry destClass) {
33 sourceClasses = Sets.newHashSet();
34 if (sourceClass != null) {
35 sourceClasses.add(sourceClass);
36 }
37 destClasses = Sets.newHashSet();
38 if (destClass != null) {
39 destClasses.add(destClass);
40 }
41 }
42
43 public boolean isMatched() {
44 return sourceClasses.size() > 0 && destClasses.size() > 0;
45 }
46
47 public boolean isAmbiguous() {
48 return sourceClasses.size() > 1 || destClasses.size() > 1;
49 }
50
51 public ClassEntry getUniqueSource() {
52 if (sourceClasses.size() != 1) {
53 throw new IllegalStateException("Match has ambiguous source!");
54 }
55 return sourceClasses.iterator().next();
56 }
57
58 public ClassEntry getUniqueDest() {
59 if (destClasses.size() != 1) {
60 throw new IllegalStateException("Match has ambiguous source!");
61 }
62 return destClasses.iterator().next();
63 }
64
65 public Set<ClassEntry> intersectSourceClasses(Set<ClassEntry> classes) {
66 Set<ClassEntry> intersection = Sets.newHashSet(sourceClasses);
67 intersection.retainAll(classes);
68 return intersection;
69 }
70
71 @Override
72 public int hashCode() {
73 return Util.combineHashesOrdered(sourceClasses, destClasses);
74 }
75
76 @Override
77 public boolean equals(Object other) {
78 return other instanceof ClassMatch && equals((ClassMatch) other);
79 }
80
81 public boolean equals(ClassMatch other) {
82 return this.sourceClasses.equals(other.sourceClasses) && this.destClasses.equals(other.destClasses);
83 }
84}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassMatches.java b/src/main/java/cuchaz/enigma/convert/ClassMatches.java
new file mode 100644
index 00000000..3a254357
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/convert/ClassMatches.java
@@ -0,0 +1,159 @@
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 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import com.google.common.collect.BiMap;
14import com.google.common.collect.HashBiMap;
15import com.google.common.collect.Maps;
16import com.google.common.collect.Sets;
17
18import java.util.*;
19
20import cuchaz.enigma.mapping.ClassEntry;
21
22
23public class ClassMatches implements Iterable<ClassMatch> {
24
25 Collection<ClassMatch> m_matches;
26 Map<ClassEntry, ClassMatch> m_matchesBySource;
27 Map<ClassEntry, ClassMatch> m_matchesByDest;
28 BiMap<ClassEntry, ClassEntry> m_uniqueMatches;
29 Map<ClassEntry, ClassMatch> m_ambiguousMatchesBySource;
30 Map<ClassEntry, ClassMatch> m_ambiguousMatchesByDest;
31 Set<ClassEntry> m_unmatchedSourceClasses;
32 Set<ClassEntry> m_unmatchedDestClasses;
33
34 public ClassMatches() {
35 this(new ArrayList<>());
36 }
37
38 public ClassMatches(Collection<ClassMatch> matches) {
39 m_matches = matches;
40 m_matchesBySource = Maps.newHashMap();
41 m_matchesByDest = Maps.newHashMap();
42 m_uniqueMatches = HashBiMap.create();
43 m_ambiguousMatchesBySource = Maps.newHashMap();
44 m_ambiguousMatchesByDest = Maps.newHashMap();
45 m_unmatchedSourceClasses = Sets.newHashSet();
46 m_unmatchedDestClasses = Sets.newHashSet();
47
48 for (ClassMatch match : matches) {
49 indexMatch(match);
50 }
51 }
52
53 public void add(ClassMatch match) {
54 m_matches.add(match);
55 indexMatch(match);
56 }
57
58 public void remove(ClassMatch match) {
59 for (ClassEntry sourceClass : match.sourceClasses) {
60 m_matchesBySource.remove(sourceClass);
61 m_uniqueMatches.remove(sourceClass);
62 m_ambiguousMatchesBySource.remove(sourceClass);
63 m_unmatchedSourceClasses.remove(sourceClass);
64 }
65 for (ClassEntry destClass : match.destClasses) {
66 m_matchesByDest.remove(destClass);
67 m_uniqueMatches.inverse().remove(destClass);
68 m_ambiguousMatchesByDest.remove(destClass);
69 m_unmatchedDestClasses.remove(destClass);
70 }
71 m_matches.remove(match);
72 }
73
74 public int size() {
75 return m_matches.size();
76 }
77
78 @Override
79 public Iterator<ClassMatch> iterator() {
80 return m_matches.iterator();
81 }
82
83 private void indexMatch(ClassMatch match) {
84 if (!match.isMatched()) {
85 // unmatched
86 m_unmatchedSourceClasses.addAll(match.sourceClasses);
87 m_unmatchedDestClasses.addAll(match.destClasses);
88 } else {
89 if (match.isAmbiguous()) {
90 // ambiguously matched
91 for (ClassEntry entry : match.sourceClasses) {
92 m_ambiguousMatchesBySource.put(entry, match);
93 }
94 for (ClassEntry entry : match.destClasses) {
95 m_ambiguousMatchesByDest.put(entry, match);
96 }
97 } else {
98 // uniquely matched
99 m_uniqueMatches.put(match.getUniqueSource(), match.getUniqueDest());
100 }
101 }
102 for (ClassEntry entry : match.sourceClasses) {
103 m_matchesBySource.put(entry, match);
104 }
105 for (ClassEntry entry : match.destClasses) {
106 m_matchesByDest.put(entry, match);
107 }
108 }
109
110 public BiMap<ClassEntry, ClassEntry> getUniqueMatches() {
111 return m_uniqueMatches;
112 }
113
114 public Set<ClassEntry> getUnmatchedSourceClasses() {
115 return m_unmatchedSourceClasses;
116 }
117
118 public Set<ClassEntry> getUnmatchedDestClasses() {
119 return m_unmatchedDestClasses;
120 }
121
122 public Set<ClassEntry> getAmbiguouslyMatchedSourceClasses() {
123 return m_ambiguousMatchesBySource.keySet();
124 }
125
126 public ClassMatch getAmbiguousMatchBySource(ClassEntry sourceClass) {
127 return m_ambiguousMatchesBySource.get(sourceClass);
128 }
129
130 public ClassMatch getMatchBySource(ClassEntry sourceClass) {
131 return m_matchesBySource.get(sourceClass);
132 }
133
134 public ClassMatch getMatchByDest(ClassEntry destClass) {
135 return m_matchesByDest.get(destClass);
136 }
137
138 public void removeSource(ClassEntry sourceClass) {
139 ClassMatch match = m_matchesBySource.get(sourceClass);
140 if (match != null) {
141 remove(match);
142 match.sourceClasses.remove(sourceClass);
143 if (!match.sourceClasses.isEmpty() || !match.destClasses.isEmpty()) {
144 add(match);
145 }
146 }
147 }
148
149 public void removeDest(ClassEntry destClass) {
150 ClassMatch match = m_matchesByDest.get(destClass);
151 if (match != null) {
152 remove(match);
153 match.destClasses.remove(destClass);
154 if (!match.sourceClasses.isEmpty() || !match.destClasses.isEmpty()) {
155 add(match);
156 }
157 }
158 }
159}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassMatching.java b/src/main/java/cuchaz/enigma/convert/ClassMatching.java
new file mode 100644
index 00000000..9350ea7f
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/convert/ClassMatching.java
@@ -0,0 +1,155 @@
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 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import com.google.common.collect.BiMap;
14import com.google.common.collect.HashBiMap;
15import com.google.common.collect.Lists;
16import com.google.common.collect.Sets;
17
18import java.util.ArrayList;
19import java.util.Collection;
20import java.util.List;
21import java.util.Map.Entry;
22import java.util.Set;
23
24import cuchaz.enigma.mapping.ClassEntry;
25
26public class ClassMatching {
27
28 private ClassForest m_sourceClasses;
29 private ClassForest m_destClasses;
30 private BiMap<ClassEntry, ClassEntry> m_knownMatches;
31
32 public ClassMatching(ClassIdentifier sourceIdentifier, ClassIdentifier destIdentifier) {
33 m_sourceClasses = new ClassForest(sourceIdentifier);
34 m_destClasses = new ClassForest(destIdentifier);
35 m_knownMatches = HashBiMap.create();
36 }
37
38 public void addKnownMatches(BiMap<ClassEntry, ClassEntry> knownMatches) {
39 m_knownMatches.putAll(knownMatches);
40 }
41
42 public void match(Iterable<ClassEntry> sourceClasses, Iterable<ClassEntry> destClasses) {
43 for (ClassEntry sourceClass : sourceClasses) {
44 if (!m_knownMatches.containsKey(sourceClass)) {
45 m_sourceClasses.add(sourceClass);
46 }
47 }
48 for (ClassEntry destClass : destClasses) {
49 if (!m_knownMatches.containsValue(destClass)) {
50 m_destClasses.add(destClass);
51 }
52 }
53 }
54
55 public Collection<ClassMatch> matches() {
56 List<ClassMatch> matches = Lists.newArrayList();
57 for (Entry<ClassEntry, ClassEntry> entry : m_knownMatches.entrySet()) {
58 matches.add(new ClassMatch(
59 entry.getKey(),
60 entry.getValue()
61 ));
62 }
63 for (ClassIdentity identity : m_sourceClasses.identities()) {
64 matches.add(new ClassMatch(
65 m_sourceClasses.getClasses(identity),
66 m_destClasses.getClasses(identity)
67 ));
68 }
69 for (ClassIdentity identity : m_destClasses.identities()) {
70 if (!m_sourceClasses.containsIdentity(identity)) {
71 matches.add(new ClassMatch(
72 new ArrayList<>(),
73 m_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 StringBuilder buf = new StringBuilder();
148 buf.append(String.format("%20s%8s%8s\n", "", "Source", "Dest"));
149 buf.append(String.format("%20s%8d%8d\n", "Classes", sourceClasses().size(), destClasses().size()));
150 buf.append(String.format("%20s%8d%8d\n", "Uniquely matched", uniqueMatches().size(), uniqueMatches().size()));
151 buf.append(String.format("%20s%8d%8d\n", "Ambiguously matched", numAmbiguousSource, numAmbiguousDest));
152 buf.append(String.format("%20s%8d%8d\n", "Unmatched", unmatchedSourceClasses().size(), unmatchedDestClasses().size()));
153 return buf.toString();
154 }
155}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassNamer.java b/src/main/java/cuchaz/enigma/convert/ClassNamer.java
new file mode 100644
index 00000000..e471c7dd
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/convert/ClassNamer.java
@@ -0,0 +1,56 @@
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 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import com.google.common.collect.BiMap;
14import com.google.common.collect.Maps;
15
16import java.util.Map;
17
18import cuchaz.enigma.mapping.ClassEntry;
19
20public class ClassNamer {
21
22 public interface SidedClassNamer {
23 String getName(String name);
24 }
25
26 private Map<String, String> sourceNames;
27 private Map<String, String> destNames;
28
29 public ClassNamer(BiMap<ClassEntry, ClassEntry> mappings) {
30 // convert the identity mappings to name maps
31 this.sourceNames = Maps.newHashMap();
32 this.destNames = Maps.newHashMap();
33 int i = 0;
34 for (Map.Entry<ClassEntry, ClassEntry> entry : mappings.entrySet()) {
35 String name = String.format("M%04d", i++);
36 this.sourceNames.put(entry.getKey().getName(), name);
37 this.destNames.put(entry.getValue().getName(), name);
38 }
39 }
40
41 public String getSourceName(String name) {
42 return this.sourceNames.get(name);
43 }
44
45 public String getDestName(String name) {
46 return this.destNames.get(name);
47 }
48
49 public SidedClassNamer getSourceNamer() {
50 return this::getSourceName;
51 }
52
53 public SidedClassNamer getDestNamer() {
54 return this::getDestName;
55 }
56}
diff --git a/src/main/java/cuchaz/enigma/convert/FieldMatches.java b/src/main/java/cuchaz/enigma/convert/FieldMatches.java
new file mode 100644
index 00000000..0899cd2e
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/convert/FieldMatches.java
@@ -0,0 +1,151 @@
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 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import com.google.common.collect.*;
14
15import java.util.Collection;
16import java.util.Set;
17
18import cuchaz.enigma.mapping.ClassEntry;
19import cuchaz.enigma.mapping.FieldEntry;
20
21
22public class FieldMatches {
23
24 private BiMap<FieldEntry, FieldEntry> m_matches;
25 private Multimap<ClassEntry, FieldEntry> m_matchedSourceFields;
26 private Multimap<ClassEntry, FieldEntry> m_unmatchedSourceFields;
27 private Multimap<ClassEntry, FieldEntry> m_unmatchedDestFields;
28 private Multimap<ClassEntry, FieldEntry> m_unmatchableSourceFields;
29
30 public FieldMatches() {
31 m_matches = HashBiMap.create();
32 m_matchedSourceFields = HashMultimap.create();
33 m_unmatchedSourceFields = HashMultimap.create();
34 m_unmatchedDestFields = HashMultimap.create();
35 m_unmatchableSourceFields = HashMultimap.create();
36 }
37
38 public void addMatch(FieldEntry srcField, FieldEntry destField) {
39 boolean wasAdded = m_matches.put(srcField, destField) == null;
40 assert (wasAdded);
41 wasAdded = m_matchedSourceFields.put(srcField.getClassEntry(), srcField);
42 assert (wasAdded);
43 }
44
45 public void addUnmatchedSourceField(FieldEntry fieldEntry) {
46 boolean wasAdded = m_unmatchedSourceFields.put(fieldEntry.getClassEntry(), fieldEntry);
47 assert (wasAdded);
48 }
49
50 public void addUnmatchedSourceFields(Iterable<FieldEntry> fieldEntries) {
51 for (FieldEntry fieldEntry : fieldEntries) {
52 addUnmatchedSourceField(fieldEntry);
53 }
54 }
55
56 public void addUnmatchedDestField(FieldEntry fieldEntry) {
57 boolean wasAdded = m_unmatchedDestFields.put(fieldEntry.getClassEntry(), fieldEntry);
58 assert (wasAdded);
59 }
60
61 public void addUnmatchedDestFields(Iterable<FieldEntry> fieldEntries) {
62 for (FieldEntry fieldEntry : fieldEntries) {
63 addUnmatchedDestField(fieldEntry);
64 }
65 }
66
67 public void addUnmatchableSourceField(FieldEntry sourceField) {
68 boolean wasAdded = m_unmatchableSourceFields.put(sourceField.getClassEntry(), sourceField);
69 assert (wasAdded);
70 }
71
72 public Set<ClassEntry> getSourceClassesWithUnmatchedFields() {
73 return m_unmatchedSourceFields.keySet();
74 }
75
76 public Collection<ClassEntry> getSourceClassesWithoutUnmatchedFields() {
77 Set<ClassEntry> out = Sets.newHashSet();
78 out.addAll(m_matchedSourceFields.keySet());
79 out.removeAll(m_unmatchedSourceFields.keySet());
80 return out;
81 }
82
83 public Collection<FieldEntry> getUnmatchedSourceFields() {
84 return m_unmatchedSourceFields.values();
85 }
86
87 public Collection<FieldEntry> getUnmatchedSourceFields(ClassEntry sourceClass) {
88 return m_unmatchedSourceFields.get(sourceClass);
89 }
90
91 public Collection<FieldEntry> getUnmatchedDestFields() {
92 return m_unmatchedDestFields.values();
93 }
94
95 public Collection<FieldEntry> getUnmatchedDestFields(ClassEntry destClass) {
96 return m_unmatchedDestFields.get(destClass);
97 }
98
99 public Collection<FieldEntry> getUnmatchableSourceFields() {
100 return m_unmatchableSourceFields.values();
101 }
102
103 public boolean hasSource(FieldEntry fieldEntry) {
104 return m_matches.containsKey(fieldEntry) || m_unmatchedSourceFields.containsValue(fieldEntry);
105 }
106
107 public boolean hasDest(FieldEntry fieldEntry) {
108 return m_matches.containsValue(fieldEntry) || m_unmatchedDestFields.containsValue(fieldEntry);
109 }
110
111 public BiMap<FieldEntry, FieldEntry> matches() {
112 return m_matches;
113 }
114
115 public boolean isMatchedSourceField(FieldEntry sourceField) {
116 return m_matches.containsKey(sourceField);
117 }
118
119 public boolean isMatchedDestField(FieldEntry destField) {
120 return m_matches.containsValue(destField);
121 }
122
123 public void makeMatch(FieldEntry sourceField, FieldEntry destField) {
124 boolean wasRemoved = m_unmatchedSourceFields.remove(sourceField.getClassEntry(), sourceField);
125 assert (wasRemoved);
126 wasRemoved = m_unmatchedDestFields.remove(destField.getClassEntry(), destField);
127 assert (wasRemoved);
128 addMatch(sourceField, destField);
129 }
130
131 public boolean isMatched(FieldEntry sourceField, FieldEntry destField) {
132 FieldEntry match = m_matches.get(sourceField);
133 return match != null && match.equals(destField);
134 }
135
136 public void unmakeMatch(FieldEntry sourceField, FieldEntry destField) {
137 boolean wasRemoved = m_matches.remove(sourceField) != null;
138 assert (wasRemoved);
139 wasRemoved = m_matchedSourceFields.remove(sourceField.getClassEntry(), sourceField);
140 assert (wasRemoved);
141 addUnmatchedSourceField(sourceField);
142 addUnmatchedDestField(destField);
143 }
144
145 public void makeSourceUnmatchable(FieldEntry sourceField) {
146 assert (!isMatchedSourceField(sourceField));
147 boolean wasRemoved = m_unmatchedSourceFields.remove(sourceField.getClassEntry(), sourceField);
148 assert (wasRemoved);
149 addUnmatchableSourceField(sourceField);
150 }
151}
diff --git a/src/main/java/cuchaz/enigma/convert/MappingsConverter.java b/src/main/java/cuchaz/enigma/convert/MappingsConverter.java
new file mode 100644
index 00000000..61b0e7ef
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/convert/MappingsConverter.java
@@ -0,0 +1,583 @@
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 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import com.google.common.collect.*;
14
15import java.util.*;
16import java.util.jar.JarFile;
17
18import cuchaz.enigma.Constants;
19import cuchaz.enigma.Deobfuscator;
20import cuchaz.enigma.analysis.JarIndex;
21import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
22import cuchaz.enigma.mapping.*;
23import cuchaz.enigma.throwables.MappingConflict;
24
25public class MappingsConverter {
26
27 public static ClassMatches computeClassMatches(JarFile sourceJar, JarFile destJar, Mappings mappings) {
28
29 // index jars
30 System.out.println("Indexing source jar...");
31 JarIndex sourceIndex = new JarIndex();
32 sourceIndex.indexJar(sourceJar, false);
33 System.out.println("Indexing dest jar...");
34 JarIndex destIndex = new JarIndex();
35 destIndex.indexJar(destJar, false);
36
37 // compute the matching
38 ClassMatching matching = computeMatching(sourceJar, sourceIndex, destJar, destIndex, null);
39 return new ClassMatches(matching.matches());
40 }
41
42 public static ClassMatching computeMatching(JarFile sourceJar, JarIndex sourceIndex, JarFile destJar, JarIndex destIndex, BiMap<ClassEntry, ClassEntry> knownMatches) {
43
44 System.out.println("Iteratively matching classes");
45
46 ClassMatching lastMatching = null;
47 int round = 0;
48 SidedClassNamer sourceNamer = null;
49 SidedClassNamer destNamer = null;
50 for (boolean useReferences : Arrays.asList(false, true)) {
51
52 int numUniqueMatchesLastTime = 0;
53 if (lastMatching != null) {
54 numUniqueMatchesLastTime = lastMatching.uniqueMatches().size();
55 }
56
57 while (true) {
58
59 System.out.println("Round " + (++round) + "...");
60
61 // init the matching with identity settings
62 ClassMatching matching = new ClassMatching(
63 new ClassIdentifier(sourceJar, sourceIndex, sourceNamer, useReferences),
64 new ClassIdentifier(destJar, destIndex, destNamer, useReferences)
65 );
66
67 if (knownMatches != null) {
68 matching.addKnownMatches(knownMatches);
69 }
70
71 if (lastMatching == null) {
72 // search all classes
73 matching.match(sourceIndex.getObfClassEntries(), destIndex.getObfClassEntries());
74 } else {
75 // we already know about these matches from last time
76 matching.addKnownMatches(lastMatching.uniqueMatches());
77
78 // search unmatched and ambiguously-matched classes
79 matching.match(lastMatching.unmatchedSourceClasses(), lastMatching.unmatchedDestClasses());
80 for (ClassMatch match : lastMatching.ambiguousMatches()) {
81 matching.match(match.sourceClasses, match.destClasses);
82 }
83 }
84 System.out.println(matching);
85 BiMap<ClassEntry, ClassEntry> uniqueMatches = matching.uniqueMatches();
86
87 // did we match anything new this time?
88 if (uniqueMatches.size() > numUniqueMatchesLastTime) {
89 numUniqueMatchesLastTime = uniqueMatches.size();
90 lastMatching = matching;
91 } else {
92 break;
93 }
94
95 // update the namers
96 ClassNamer namer = new ClassNamer(uniqueMatches);
97 sourceNamer = namer.getSourceNamer();
98 destNamer = namer.getDestNamer();
99 }
100 }
101
102 return lastMatching;
103 }
104
105 public static Mappings newMappings(ClassMatches matches, Mappings oldMappings, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator)
106 throws MappingConflict {
107 // sort the unique matches by size of inner class chain
108 Multimap<Integer, java.util.Map.Entry<ClassEntry, ClassEntry>> matchesByDestChainSize = HashMultimap.create();
109 for (java.util.Map.Entry<ClassEntry, ClassEntry> match : matches.getUniqueMatches().entrySet()) {
110 int chainSize = destDeobfuscator.getJarIndex().getObfClassChain(match.getValue()).size();
111 matchesByDestChainSize.put(chainSize, match);
112 }
113
114 // build the mappings (in order of small-to-large inner chains)
115 Mappings newMappings = new Mappings();
116 List<Integer> chainSizes = Lists.newArrayList(matchesByDestChainSize.keySet());
117 Collections.sort(chainSizes);
118 for (int chainSize : chainSizes) {
119 for (java.util.Map.Entry<ClassEntry, ClassEntry> match : matchesByDestChainSize.get(chainSize)) {
120
121 // get class info
122 ClassEntry obfSourceClassEntry = match.getKey();
123 ClassEntry obfDestClassEntry = match.getValue();
124 List<ClassEntry> destClassChain = destDeobfuscator.getJarIndex().getObfClassChain(obfDestClassEntry);
125
126 ClassMapping sourceMapping = sourceDeobfuscator.getMappings().getClassByObf(obfSourceClassEntry);
127 if (sourceMapping == null) {
128 // if this class was never deobfuscated, don't try to match it
129 continue;
130 }
131
132 // find out where to make the dest class mapping
133 if (destClassChain.size() == 1) {
134 // not an inner class, add directly to mappings
135 newMappings.addClassMapping(migrateClassMapping(obfDestClassEntry, sourceMapping, matches, false));
136 } else {
137 // inner class, find the outer class mapping
138 ClassMapping destMapping = null;
139 for (int i = 0; i < destClassChain.size() - 1; i++) {
140 ClassEntry destChainClassEntry = destClassChain.get(i);
141 if (destMapping == null) {
142 destMapping = newMappings.getClassByObf(destChainClassEntry);
143 if (destMapping == null) {
144 destMapping = new ClassMapping(destChainClassEntry.getName());
145 newMappings.addClassMapping(destMapping);
146 }
147 } else {
148 destMapping = destMapping.getInnerClassByObfSimple(destChainClassEntry.getInnermostClassName());
149 if (destMapping == null) {
150 destMapping = new ClassMapping(destChainClassEntry.getName());
151 destMapping.addInnerClassMapping(destMapping);
152 }
153 }
154 }
155 destMapping.addInnerClassMapping(migrateClassMapping(obfDestClassEntry, sourceMapping, matches, true));
156 }
157 }
158 }
159 return newMappings;
160 }
161
162 private static ClassMapping migrateClassMapping(ClassEntry newObfClass, ClassMapping oldClassMapping, final ClassMatches matches, boolean useSimpleName) {
163
164 ClassNameReplacer replacer = new ClassNameReplacer() {
165 @Override
166 public String replace(String className) {
167 ClassEntry newClassEntry = matches.getUniqueMatches().get(new ClassEntry(className));
168 if (newClassEntry != null) {
169 return newClassEntry.getName();
170 }
171 return null;
172 }
173 };
174
175 ClassMapping newClassMapping;
176 String deobfName = oldClassMapping.getDeobfName();
177 if (deobfName != null) {
178 if (useSimpleName) {
179 deobfName = new ClassEntry(deobfName).getSimpleName();
180 }
181 newClassMapping = new ClassMapping(newObfClass.getName(), deobfName);
182 } else {
183 newClassMapping = new ClassMapping(newObfClass.getName());
184 }
185
186 // migrate fields
187 for (FieldMapping oldFieldMapping : oldClassMapping.fields()) {
188 if (canMigrate(oldFieldMapping.getObfType(), matches)) {
189 newClassMapping.addFieldMapping(new FieldMapping(oldFieldMapping, replacer));
190 } else {
191 System.out.println(String.format("Can't map field, dropping: %s.%s %s",
192 oldClassMapping.getDeobfName(),
193 oldFieldMapping.getDeobfName(),
194 oldFieldMapping.getObfType()
195 ));
196 }
197 }
198
199 // migrate methods
200 for (MethodMapping oldMethodMapping : oldClassMapping.methods()) {
201 if (canMigrate(oldMethodMapping.getObfSignature(), matches)) {
202 newClassMapping.addMethodMapping(new MethodMapping(oldMethodMapping, replacer));
203 } else {
204 System.out.println(String.format("Can't map method, dropping: %s.%s %s",
205 oldClassMapping.getDeobfName(),
206 oldMethodMapping.getDeobfName(),
207 oldMethodMapping.getObfSignature()
208 ));
209 }
210 }
211
212 return newClassMapping;
213 }
214
215 private static boolean canMigrate(Signature oldObfSignature, ClassMatches classMatches) {
216 for (Type oldObfType : oldObfSignature.types()) {
217 if (!canMigrate(oldObfType, classMatches)) {
218 return false;
219 }
220 }
221 return true;
222 }
223
224 private static boolean canMigrate(Type oldObfType, ClassMatches classMatches) {
225
226 // non classes can be migrated
227 if (!oldObfType.hasClass()) {
228 return true;
229 }
230
231 // non obfuscated classes can be migrated
232 ClassEntry classEntry = oldObfType.getClassEntry();
233 if (!classEntry.getPackageName().equals(Constants.NONE_PACKAGE)) {
234 return true;
235 }
236
237 // obfuscated classes with mappings can be migrated
238 return classMatches.getUniqueMatches().containsKey(classEntry);
239 }
240
241 public static void convertMappings(Mappings mappings, BiMap<ClassEntry, ClassEntry> changes) {
242
243 // sort the changes so classes are renamed in the correct order
244 // ie. if we have the mappings a->b, b->c, we have to apply b->c before a->b
245 LinkedHashMap<ClassEntry, ClassEntry> sortedChanges = Maps.newLinkedHashMap();
246 int numChangesLeft = changes.size();
247 while (!changes.isEmpty()) {
248 Iterator<Map.Entry<ClassEntry, ClassEntry>> iter = changes.entrySet().iterator();
249 while (iter.hasNext()) {
250 Map.Entry<ClassEntry, ClassEntry> change = iter.next();
251 if (changes.containsKey(change.getValue())) {
252 sortedChanges.put(change.getKey(), change.getValue());
253 iter.remove();
254 }
255 }
256
257 // did we remove any changes?
258 if (numChangesLeft - changes.size() > 0) {
259 // keep going
260 numChangesLeft = changes.size();
261 } else {
262 // can't sort anymore. There must be a loop
263 break;
264 }
265 }
266 if (!changes.isEmpty()) {
267 throw new Error("Unable to sort class changes! There must be a cycle.");
268 }
269
270 // convert the mappings in the correct class order
271 for (Map.Entry<ClassEntry, ClassEntry> entry : sortedChanges.entrySet()) {
272 mappings.renameObfClass(entry.getKey().getName(), entry.getValue().getName());
273 }
274 }
275
276 public interface Doer<T extends Entry> {
277 Collection<T> getDroppedEntries(MappingsChecker checker);
278
279 Collection<T> getObfEntries(JarIndex jarIndex);
280
281 Collection<? extends MemberMapping<T>> getMappings(ClassMapping destClassMapping);
282
283 Set<T> filterEntries(Collection<T> obfEntries, T obfSourceEntry, ClassMatches classMatches);
284
285 void setUpdateObfMember(ClassMapping classMapping, MemberMapping<T> memberMapping, T newEntry);
286
287 boolean hasObfMember(ClassMapping classMapping, T obfEntry);
288
289 void removeMemberByObf(ClassMapping classMapping, T obfEntry);
290 }
291
292 public static Doer<FieldEntry> getFieldDoer() {
293 return new Doer<FieldEntry>() {
294
295 @Override
296 public Collection<FieldEntry> getDroppedEntries(MappingsChecker checker) {
297 return checker.getDroppedFieldMappings().keySet();
298 }
299
300 @Override
301 public Collection<FieldEntry> getObfEntries(JarIndex jarIndex) {
302 return jarIndex.getObfFieldEntries();
303 }
304
305 @Override
306 public Collection<? extends MemberMapping<FieldEntry>> getMappings(ClassMapping destClassMapping) {
307 return (Collection<? extends MemberMapping<FieldEntry>>) destClassMapping.fields();
308 }
309
310 @Override
311 public Set<FieldEntry> filterEntries(Collection<FieldEntry> obfDestFields, FieldEntry obfSourceField, ClassMatches classMatches) {
312 Set<FieldEntry> out = Sets.newHashSet();
313 for (FieldEntry obfDestField : obfDestFields) {
314 Type translatedDestType = translate(obfDestField.getType(), classMatches.getUniqueMatches().inverse());
315 if (translatedDestType.equals(obfSourceField.getType())) {
316 out.add(obfDestField);
317 }
318 }
319 return out;
320 }
321
322 @Override
323 public void setUpdateObfMember(ClassMapping classMapping, MemberMapping<FieldEntry> memberMapping, FieldEntry newField) {
324 FieldMapping fieldMapping = (FieldMapping) memberMapping;
325 classMapping.setFieldObfNameAndType(fieldMapping.getObfName(), fieldMapping.getObfType(), newField.getName(), newField.getType());
326 }
327
328 @Override
329 public boolean hasObfMember(ClassMapping classMapping, FieldEntry obfField) {
330 return classMapping.getFieldByObf(obfField.getName(), obfField.getType()) != null;
331 }
332
333 @Override
334 public void removeMemberByObf(ClassMapping classMapping, FieldEntry obfField) {
335 classMapping.removeFieldMapping(classMapping.getFieldByObf(obfField.getName(), obfField.getType()));
336 }
337 };
338 }
339
340 public static Doer<BehaviorEntry> getMethodDoer() {
341 return new Doer<BehaviorEntry>() {
342
343 @Override
344 public Collection<BehaviorEntry> getDroppedEntries(MappingsChecker checker) {
345 return checker.getDroppedMethodMappings().keySet();
346 }
347
348 @Override
349 public Collection<BehaviorEntry> getObfEntries(JarIndex jarIndex) {
350 return jarIndex.getObfBehaviorEntries();
351 }
352
353 @Override
354 public Collection<? extends MemberMapping<BehaviorEntry>> getMappings(ClassMapping destClassMapping) {
355 return (Collection<? extends MemberMapping<BehaviorEntry>>) destClassMapping.methods();
356 }
357
358 @Override
359 public Set<BehaviorEntry> filterEntries(Collection<BehaviorEntry> obfDestFields, BehaviorEntry obfSourceField, ClassMatches classMatches) {
360 Set<BehaviorEntry> out = Sets.newHashSet();
361 for (BehaviorEntry obfDestField : obfDestFields) {
362 Signature translatedDestSignature = translate(obfDestField.getSignature(), classMatches.getUniqueMatches().inverse());
363 if (translatedDestSignature == null && obfSourceField.getSignature() == null) {
364 out.add(obfDestField);
365 } else if (translatedDestSignature == null || obfSourceField.getSignature() == null) {
366 // skip it
367 } else if (translatedDestSignature.equals(obfSourceField.getSignature())) {
368 out.add(obfDestField);
369 }
370 }
371 return out;
372 }
373
374 @Override
375 public void setUpdateObfMember(ClassMapping classMapping, MemberMapping<BehaviorEntry> memberMapping, BehaviorEntry newBehavior) {
376 MethodMapping methodMapping = (MethodMapping) memberMapping;
377 classMapping.setMethodObfNameAndSignature(methodMapping.getObfName(), methodMapping.getObfSignature(), newBehavior.getName(), newBehavior.getSignature());
378 }
379
380 @Override
381 public boolean hasObfMember(ClassMapping classMapping, BehaviorEntry obfBehavior) {
382 return classMapping.getMethodByObf(obfBehavior.getName(), obfBehavior.getSignature()) != null;
383 }
384
385 @Override
386 public void removeMemberByObf(ClassMapping classMapping, BehaviorEntry obfBehavior) {
387 classMapping.removeMethodMapping(classMapping.getMethodByObf(obfBehavior.getName(), obfBehavior.getSignature()));
388 }
389 };
390 }
391
392 public static <T extends Entry> MemberMatches<T> computeMemberMatches(Deobfuscator destDeobfuscator, Mappings destMappings, ClassMatches classMatches, Doer<T> doer) {
393
394 MemberMatches<T> memberMatches = new MemberMatches<T>();
395
396 // unmatched source fields are easy
397 MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex());
398 checker.dropBrokenMappings(destMappings);
399 for (T destObfEntry : doer.getDroppedEntries(checker)) {
400 T srcObfEntry = translate(destObfEntry, classMatches.getUniqueMatches().inverse());
401 memberMatches.addUnmatchedSourceEntry(srcObfEntry);
402 }
403
404 // get matched fields (anything that's left after the checks/drops is matched(
405 for (ClassMapping classMapping : destMappings.classes()) {
406 collectMatchedFields(memberMatches, classMapping, classMatches, doer);
407 }
408
409 // get unmatched dest fields
410 for (T destEntry : doer.getObfEntries(destDeobfuscator.getJarIndex())) {
411 if (!memberMatches.isMatchedDestEntry(destEntry)) {
412 memberMatches.addUnmatchedDestEntry(destEntry);
413 }
414 }
415
416 System.out.println("Automatching " + memberMatches.getUnmatchedSourceEntries().size() + " unmatched source entries...");
417
418 // go through the unmatched source fields and try to pick out the easy matches
419 for (ClassEntry obfSourceClass : Lists.newArrayList(memberMatches.getSourceClassesWithUnmatchedEntries())) {
420 for (T obfSourceEntry : Lists.newArrayList(memberMatches.getUnmatchedSourceEntries(obfSourceClass))) {
421
422 // get the possible dest matches
423 ClassEntry obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass);
424
425 // filter by type/signature
426 Set<T> obfDestEntries = doer.filterEntries(memberMatches.getUnmatchedDestEntries(obfDestClass), obfSourceEntry, classMatches);
427
428 if (obfDestEntries.size() == 1) {
429 // make the easy match
430 memberMatches.makeMatch(obfSourceEntry, obfDestEntries.iterator().next());
431 } else if (obfDestEntries.isEmpty()) {
432 // no match is possible =(
433 memberMatches.makeSourceUnmatchable(obfSourceEntry);
434 }
435 }
436 }
437
438 System.out.println(String.format("Ended up with %d ambiguous and %d unmatchable source entries",
439 memberMatches.getUnmatchedSourceEntries().size(),
440 memberMatches.getUnmatchableSourceEntries().size()
441 ));
442
443 return memberMatches;
444 }
445
446 private static <T extends Entry> void collectMatchedFields(MemberMatches<T> memberMatches, ClassMapping destClassMapping, ClassMatches classMatches, Doer<T> doer) {
447
448 // get the fields for this class
449 for (MemberMapping<T> destEntryMapping : doer.getMappings(destClassMapping)) {
450 T destObfField = destEntryMapping.getObfEntry(destClassMapping.getObfEntry());
451 T srcObfField = translate(destObfField, classMatches.getUniqueMatches().inverse());
452 memberMatches.addMatch(srcObfField, destObfField);
453 }
454
455 // recurse
456 for (ClassMapping destInnerClassMapping : destClassMapping.innerClasses()) {
457 collectMatchedFields(memberMatches, destInnerClassMapping, classMatches, doer);
458 }
459 }
460
461 @SuppressWarnings("unchecked")
462 private static <T extends Entry> T translate(T in, BiMap<ClassEntry, ClassEntry> map) {
463 if (in instanceof FieldEntry) {
464 return (T) new FieldEntry(
465 map.get(in.getClassEntry()),
466 in.getName(),
467 translate(((FieldEntry) in).getType(), map)
468 );
469 } else if (in instanceof MethodEntry) {
470 return (T) new MethodEntry(
471 map.get(in.getClassEntry()),
472 in.getName(),
473 translate(((MethodEntry) in).getSignature(), map)
474 );
475 } else if (in instanceof ConstructorEntry) {
476 return (T) new ConstructorEntry(
477 map.get(in.getClassEntry()),
478 translate(((ConstructorEntry) in).getSignature(), map)
479 );
480 }
481 throw new Error("Unhandled entry type: " + in.getClass());
482 }
483
484 private static Type translate(Type type, final BiMap<ClassEntry, ClassEntry> map) {
485 return new Type(type, new ClassNameReplacer() {
486 @Override
487 public String replace(String inClassName) {
488 ClassEntry outClassEntry = map.get(new ClassEntry(inClassName));
489 if (outClassEntry == null) {
490 return null;
491 }
492 return outClassEntry.getName();
493 }
494 });
495 }
496
497 private static Signature translate(Signature signature, final BiMap<ClassEntry, ClassEntry> map) {
498 if (signature == null) {
499 return null;
500 }
501 return new Signature(signature, new ClassNameReplacer() {
502 @Override
503 public String replace(String inClassName) {
504 ClassEntry outClassEntry = map.get(new ClassEntry(inClassName));
505 if (outClassEntry == null) {
506 return null;
507 }
508 return outClassEntry.getName();
509 }
510 });
511 }
512
513 public static <T extends Entry> void applyMemberMatches(Mappings mappings, ClassMatches classMatches, MemberMatches<T> memberMatches, Doer<T> doer) {
514 for (ClassMapping classMapping : mappings.classes()) {
515 applyMemberMatches(classMapping, classMatches, memberMatches, doer);
516 }
517 }
518
519 private static <T extends Entry> void applyMemberMatches(ClassMapping classMapping, ClassMatches classMatches, MemberMatches<T> memberMatches, Doer<T> doer) {
520
521 // get the classes
522 ClassEntry obfDestClass = new ClassEntry(classMapping.getObfFullName());
523
524 // make a map of all the renames we need to make
525 Map<T, T> renames = Maps.newHashMap();
526 for (MemberMapping<T> memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) {
527 T obfOldDestEntry = memberMapping.getObfEntry(obfDestClass);
528 T obfSourceEntry = getSourceEntryFromDestMapping(memberMapping, obfDestClass, classMatches);
529
530 // but drop the unmatchable things
531 if (memberMatches.isUnmatchableSourceEntry(obfSourceEntry)) {
532 doer.removeMemberByObf(classMapping, obfOldDestEntry);
533 continue;
534 }
535
536 T obfNewDestEntry = memberMatches.matches().get(obfSourceEntry);
537 if (obfNewDestEntry != null && !obfOldDestEntry.getName().equals(obfNewDestEntry.getName())) {
538 renames.put(obfOldDestEntry, obfNewDestEntry);
539 }
540 }
541
542 if (!renames.isEmpty()) {
543
544 // apply to this class (should never need more than n passes)
545 int numRenamesAppliedThisRound;
546 do {
547 numRenamesAppliedThisRound = 0;
548
549 for (MemberMapping<T> memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) {
550 T obfOldDestEntry = memberMapping.getObfEntry(obfDestClass);
551 T obfNewDestEntry = renames.get(obfOldDestEntry);
552 if (obfNewDestEntry != null) {
553 // make sure this rename won't cause a collision
554 // otherwise, save it for the next round and try again next time
555 if (!doer.hasObfMember(classMapping, obfNewDestEntry)) {
556 doer.setUpdateObfMember(classMapping, memberMapping, obfNewDestEntry);
557 renames.remove(obfOldDestEntry);
558 numRenamesAppliedThisRound++;
559 }
560 }
561 }
562 } while (numRenamesAppliedThisRound > 0);
563
564 if (!renames.isEmpty()) {
565 System.err.println(String.format("WARNING: Couldn't apply all the renames for class %s. %d renames left.",
566 classMapping.getObfFullName(), renames.size()
567 ));
568 for (Map.Entry<T, T> entry : renames.entrySet()) {
569 System.err.println(String.format("\t%s -> %s", entry.getKey().getName(), entry.getValue().getName()));
570 }
571 }
572 }
573
574 // recurse
575 for (ClassMapping innerClassMapping : classMapping.innerClasses()) {
576 applyMemberMatches(innerClassMapping, classMatches, memberMatches, doer);
577 }
578 }
579
580 private static <T extends Entry> T getSourceEntryFromDestMapping(MemberMapping<T> destMemberMapping, ClassEntry obfDestClass, ClassMatches classMatches) {
581 return translate(destMemberMapping.getObfEntry(obfDestClass), classMatches.getUniqueMatches().inverse());
582 }
583}
diff --git a/src/main/java/cuchaz/enigma/convert/MatchesReader.java b/src/main/java/cuchaz/enigma/convert/MatchesReader.java
new file mode 100644
index 00000000..773566df
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/convert/MatchesReader.java
@@ -0,0 +1,109 @@
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 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import com.google.common.collect.Lists;
14
15import java.io.BufferedReader;
16import java.io.File;
17import java.io.FileReader;
18import java.io.IOException;
19import java.util.Collection;
20import java.util.List;
21
22import cuchaz.enigma.mapping.*;
23
24
25public class MatchesReader {
26
27 public static ClassMatches readClasses(File file)
28 throws IOException {
29 try (BufferedReader in = new BufferedReader(new FileReader(file))) {
30 ClassMatches matches = new ClassMatches();
31 String line = null;
32 while ((line = in.readLine()) != null) {
33 matches.add(readClassMatch(line));
34 }
35 return matches;
36 }
37 }
38
39 private static ClassMatch readClassMatch(String line)
40 throws IOException {
41 String[] sides = line.split(":", 2);
42 return new ClassMatch(readClasses(sides[0]), readClasses(sides[1]));
43 }
44
45 private static Collection<ClassEntry> readClasses(String in) {
46 List<ClassEntry> entries = Lists.newArrayList();
47 for (String className : in.split(",")) {
48 className = className.trim();
49 if (className.length() > 0) {
50 entries.add(new ClassEntry(className));
51 }
52 }
53 return entries;
54 }
55
56 public static <T extends Entry> MemberMatches<T> readMembers(File file)
57 throws IOException {
58 try (BufferedReader in = new BufferedReader(new FileReader(file))) {
59 MemberMatches<T> matches = new MemberMatches<T>();
60 String line = null;
61 while ((line = in.readLine()) != null) {
62 readMemberMatch(matches, line);
63 }
64 return matches;
65 }
66 }
67
68 private static <T extends Entry> void readMemberMatch(MemberMatches<T> matches, String line) {
69 if (line.startsWith("!")) {
70 T source = readEntry(line.substring(1));
71 matches.addUnmatchableSourceEntry(source);
72 } else {
73 String[] parts = line.split(":", 2);
74 T source = readEntry(parts[0]);
75 T dest = readEntry(parts[1]);
76 if (source != null && dest != null) {
77 matches.addMatch(source, dest);
78 } else if (source != null) {
79 matches.addUnmatchedSourceEntry(source);
80 } else if (dest != null) {
81 matches.addUnmatchedDestEntry(dest);
82 }
83 }
84 }
85
86 @SuppressWarnings("unchecked")
87 private static <T extends Entry> T readEntry(String in) {
88 if (in.length() <= 0) {
89 return null;
90 }
91 String[] parts = in.split(" ");
92 if (parts.length == 3 && parts[2].indexOf('(') < 0) {
93 return (T) new FieldEntry(
94 new ClassEntry(parts[0]),
95 parts[1],
96 new Type(parts[2])
97 );
98 } else {
99 assert (parts.length == 2 || parts.length == 3);
100 if (parts.length == 2) {
101 return (T) EntryFactory.getBehaviorEntry(parts[0], parts[1]);
102 } else if (parts.length == 3) {
103 return (T) EntryFactory.getBehaviorEntry(parts[0], parts[1], parts[2]);
104 } else {
105 throw new Error("Malformed behavior entry: " + in);
106 }
107 }
108 }
109}
diff --git a/src/main/java/cuchaz/enigma/convert/MatchesWriter.java b/src/main/java/cuchaz/enigma/convert/MatchesWriter.java
new file mode 100644
index 00000000..baf79293
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/convert/MatchesWriter.java
@@ -0,0 +1,121 @@
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 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.io.File;
14import java.io.FileWriter;
15import java.io.IOException;
16import java.util.Map;
17
18import cuchaz.enigma.mapping.BehaviorEntry;
19import cuchaz.enigma.mapping.ClassEntry;
20import cuchaz.enigma.mapping.Entry;
21import cuchaz.enigma.mapping.FieldEntry;
22
23
24public class MatchesWriter {
25
26 public static void writeClasses(ClassMatches matches, File file)
27 throws IOException {
28 try (FileWriter out = new FileWriter(file)) {
29 for (ClassMatch match : matches) {
30 writeClassMatch(out, match);
31 }
32 }
33 }
34
35 private static void writeClassMatch(FileWriter out, ClassMatch match)
36 throws IOException {
37 writeClasses(out, match.sourceClasses);
38 out.write(":");
39 writeClasses(out, match.destClasses);
40 out.write("\n");
41 }
42
43 private static void writeClasses(FileWriter out, Iterable<ClassEntry> classes)
44 throws IOException {
45 boolean isFirst = true;
46 for (ClassEntry entry : classes) {
47 if (isFirst) {
48 isFirst = false;
49 } else {
50 out.write(",");
51 }
52 out.write(entry.toString());
53 }
54 }
55
56 public static <T extends Entry> void writeMembers(MemberMatches<T> matches, File file)
57 throws IOException {
58 try (FileWriter out = new FileWriter(file)) {
59 for (Map.Entry<T, T> match : matches.matches().entrySet()) {
60 writeMemberMatch(out, match.getKey(), match.getValue());
61 }
62 for (T entry : matches.getUnmatchedSourceEntries()) {
63 writeMemberMatch(out, entry, null);
64 }
65 for (T entry : matches.getUnmatchedDestEntries()) {
66 writeMemberMatch(out, null, entry);
67 }
68 for (T entry : matches.getUnmatchableSourceEntries()) {
69 writeUnmatchableEntry(out, entry);
70 }
71 }
72 }
73
74 private static <T extends Entry> void writeMemberMatch(FileWriter out, T source, T dest)
75 throws IOException {
76 if (source != null) {
77 writeEntry(out, source);
78 }
79 out.write(":");
80 if (dest != null) {
81 writeEntry(out, dest);
82 }
83 out.write("\n");
84 }
85
86 private static <T extends Entry> void writeUnmatchableEntry(FileWriter out, T entry)
87 throws IOException {
88 out.write("!");
89 writeEntry(out, entry);
90 out.write("\n");
91 }
92
93 private static <T extends Entry> void writeEntry(FileWriter out, T entry)
94 throws IOException {
95 if (entry instanceof FieldEntry) {
96 writeField(out, (FieldEntry) entry);
97 } else if (entry instanceof BehaviorEntry) {
98 writeBehavior(out, (BehaviorEntry) entry);
99 }
100 }
101
102 private static void writeField(FileWriter out, FieldEntry fieldEntry)
103 throws IOException {
104 out.write(fieldEntry.getClassName());
105 out.write(" ");
106 out.write(fieldEntry.getName());
107 out.write(" ");
108 out.write(fieldEntry.getType().toString());
109 }
110
111 private static void writeBehavior(FileWriter out, BehaviorEntry behaviorEntry)
112 throws IOException {
113 out.write(behaviorEntry.getClassName());
114 out.write(" ");
115 out.write(behaviorEntry.getName());
116 out.write(" ");
117 if (behaviorEntry.getSignature() != null) {
118 out.write(behaviorEntry.getSignature().toString());
119 }
120 }
121}
diff --git a/src/main/java/cuchaz/enigma/convert/MemberMatches.java b/src/main/java/cuchaz/enigma/convert/MemberMatches.java
new file mode 100644
index 00000000..32850cca
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/convert/MemberMatches.java
@@ -0,0 +1,155 @@
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 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import com.google.common.collect.*;
14
15import java.util.Collection;
16import java.util.Set;
17
18import cuchaz.enigma.mapping.ClassEntry;
19import cuchaz.enigma.mapping.Entry;
20
21
22public class MemberMatches<T extends Entry> {
23
24 private BiMap<T, T> m_matches;
25 private Multimap<ClassEntry, T> m_matchedSourceEntries;
26 private Multimap<ClassEntry, T> m_unmatchedSourceEntries;
27 private Multimap<ClassEntry, T> m_unmatchedDestEntries;
28 private Multimap<ClassEntry, T> m_unmatchableSourceEntries;
29
30 public MemberMatches() {
31 m_matches = HashBiMap.create();
32 m_matchedSourceEntries = HashMultimap.create();
33 m_unmatchedSourceEntries = HashMultimap.create();
34 m_unmatchedDestEntries = HashMultimap.create();
35 m_unmatchableSourceEntries = HashMultimap.create();
36 }
37
38 public void addMatch(T srcEntry, T destEntry) {
39 boolean wasAdded = m_matches.put(srcEntry, destEntry) == null;
40 assert (wasAdded);
41 wasAdded = m_matchedSourceEntries.put(srcEntry.getClassEntry(), srcEntry);
42 assert (wasAdded);
43 }
44
45 public void addUnmatchedSourceEntry(T sourceEntry) {
46 boolean wasAdded = m_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 boolean wasAdded = m_unmatchedDestEntries.put(destEntry.getClassEntry(), destEntry);
58 assert (wasAdded);
59 }
60
61 public void addUnmatchedDestEntries(Iterable<T> destEntriesntries) {
62 for (T entry : destEntriesntries) {
63 addUnmatchedDestEntry(entry);
64 }
65 }
66
67 public void addUnmatchableSourceEntry(T sourceEntry) {
68 boolean wasAdded = m_unmatchableSourceEntries.put(sourceEntry.getClassEntry(), sourceEntry);
69 assert (wasAdded);
70 }
71
72 public Set<ClassEntry> getSourceClassesWithUnmatchedEntries() {
73 return m_unmatchedSourceEntries.keySet();
74 }
75
76 public Collection<ClassEntry> getSourceClassesWithoutUnmatchedEntries() {
77 Set<ClassEntry> out = Sets.newHashSet();
78 out.addAll(m_matchedSourceEntries.keySet());
79 out.removeAll(m_unmatchedSourceEntries.keySet());
80 return out;
81 }
82
83 public Collection<T> getUnmatchedSourceEntries() {
84 return m_unmatchedSourceEntries.values();
85 }
86
87 public Collection<T> getUnmatchedSourceEntries(ClassEntry sourceClass) {
88 return m_unmatchedSourceEntries.get(sourceClass);
89 }
90
91 public Collection<T> getUnmatchedDestEntries() {
92 return m_unmatchedDestEntries.values();
93 }
94
95 public Collection<T> getUnmatchedDestEntries(ClassEntry destClass) {
96 return m_unmatchedDestEntries.get(destClass);
97 }
98
99 public Collection<T> getUnmatchableSourceEntries() {
100 return m_unmatchableSourceEntries.values();
101 }
102
103 public boolean hasSource(T sourceEntry) {
104 return m_matches.containsKey(sourceEntry) || m_unmatchedSourceEntries.containsValue(sourceEntry);
105 }
106
107 public boolean hasDest(T destEntry) {
108 return m_matches.containsValue(destEntry) || m_unmatchedDestEntries.containsValue(destEntry);
109 }
110
111 public BiMap<T, T> matches() {
112 return m_matches;
113 }
114
115 public boolean isMatchedSourceEntry(T sourceEntry) {
116 return m_matches.containsKey(sourceEntry);
117 }
118
119 public boolean isMatchedDestEntry(T destEntry) {
120 return m_matches.containsValue(destEntry);
121 }
122
123 public boolean isUnmatchableSourceEntry(T sourceEntry) {
124 return m_unmatchableSourceEntries.containsEntry(sourceEntry.getClassEntry(), sourceEntry);
125 }
126
127 public void makeMatch(T sourceEntry, T destEntry) {
128 boolean wasRemoved = m_unmatchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry);
129 assert (wasRemoved);
130 wasRemoved = m_unmatchedDestEntries.remove(destEntry.getClassEntry(), destEntry);
131 assert (wasRemoved);
132 addMatch(sourceEntry, destEntry);
133 }
134
135 public boolean isMatched(T sourceEntry, T destEntry) {
136 T match = m_matches.get(sourceEntry);
137 return match != null && match.equals(destEntry);
138 }
139
140 public void unmakeMatch(T sourceEntry, T destEntry) {
141 boolean wasRemoved = m_matches.remove(sourceEntry) != null;
142 assert (wasRemoved);
143 wasRemoved = m_matchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry);
144 assert (wasRemoved);
145 addUnmatchedSourceEntry(sourceEntry);
146 addUnmatchedDestEntry(destEntry);
147 }
148
149 public void makeSourceUnmatchable(T sourceEntry) {
150 assert (!isMatchedSourceEntry(sourceEntry));
151 boolean wasRemoved = m_unmatchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry);
152 assert (wasRemoved);
153 addUnmatchableSourceEntry(sourceEntry);
154 }
155}
diff --git a/src/main/java/cuchaz/enigma/gui/BrowserCaret.java b/src/main/java/cuchaz/enigma/gui/BrowserCaret.java
index f58d0129..f9701f28 100644
--- a/src/main/java/cuchaz/enigma/gui/BrowserCaret.java
+++ b/src/main/java/cuchaz/enigma/gui/BrowserCaret.java
@@ -30,6 +30,6 @@ public class BrowserCaret extends DefaultCaret {
30 30
31 @Override 31 @Override
32 public Highlighter.HighlightPainter getSelectionPainter() { 32 public Highlighter.HighlightPainter getSelectionPainter() {
33 return selectionPainter; 33 return this.selectionPainter;
34 } 34 }
35} 35}
diff --git a/src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java b/src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java
new file mode 100644
index 00000000..ec639001
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java
@@ -0,0 +1,546 @@
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 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import com.google.common.collect.BiMap;
14import com.google.common.collect.Lists;
15import com.google.common.collect.Maps;
16
17import java.awt.BorderLayout;
18import java.awt.Container;
19import java.awt.Dimension;
20import java.awt.FlowLayout;
21import java.awt.event.ActionListener;
22import java.util.Collection;
23import java.util.Collections;
24import java.util.List;
25import java.util.Map;
26
27import javax.swing.*;
28
29import cuchaz.enigma.Constants;
30import cuchaz.enigma.Deobfuscator;
31import cuchaz.enigma.convert.*;
32import cuchaz.enigma.gui.node.ClassSelectorClassNode;
33import cuchaz.enigma.gui.node.ClassSelectorPackageNode;
34import cuchaz.enigma.mapping.ClassEntry;
35import cuchaz.enigma.mapping.Mappings;
36import cuchaz.enigma.mapping.MappingsChecker;
37import cuchaz.enigma.throwables.MappingConflict;
38import de.sciss.syntaxpane.DefaultSyntaxKit;
39
40
41public class ClassMatchingGui {
42
43 private enum SourceType {
44 Matched {
45 @Override
46 public Collection<ClassEntry> getSourceClasses(ClassMatches matches) {
47 return matches.getUniqueMatches().keySet();
48 }
49 },
50 Unmatched {
51 @Override
52 public Collection<ClassEntry> getSourceClasses(ClassMatches matches) {
53 return matches.getUnmatchedSourceClasses();
54 }
55 },
56 Ambiguous {
57 @Override
58 public Collection<ClassEntry> getSourceClasses(ClassMatches matches) {
59 return matches.getAmbiguouslyMatchedSourceClasses();
60 }
61 };
62
63 public JRadioButton newRadio(ActionListener listener, ButtonGroup group) {
64 JRadioButton button = new JRadioButton(name(), this == getDefault());
65 button.setActionCommand(name());
66 button.addActionListener(listener);
67 group.add(button);
68 return button;
69 }
70
71 public abstract Collection<ClassEntry> getSourceClasses(ClassMatches matches);
72
73 public static SourceType getDefault() {
74 return values()[0];
75 }
76 }
77
78 public interface SaveListener {
79 void save(ClassMatches matches);
80 }
81
82 // controls
83 private JFrame m_frame;
84 private ClassSelector m_sourceClasses;
85 private ClassSelector m_destClasses;
86 private CodeReader m_sourceReader;
87 private CodeReader m_destReader;
88 private JLabel m_sourceClassLabel;
89 private JLabel m_destClassLabel;
90 private JButton m_matchButton;
91 private Map<SourceType, JRadioButton> m_sourceTypeButtons;
92 private JCheckBox m_advanceCheck;
93 private JCheckBox m_top10Matches;
94
95 private ClassMatches m_classMatches;
96 private Deobfuscator m_sourceDeobfuscator;
97 private Deobfuscator m_destDeobfuscator;
98 private ClassEntry m_sourceClass;
99 private ClassEntry m_destClass;
100 private SourceType m_sourceType;
101 private SaveListener m_saveListener;
102
103 public ClassMatchingGui(ClassMatches matches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
104
105 m_classMatches = matches;
106 m_sourceDeobfuscator = sourceDeobfuscator;
107 m_destDeobfuscator = destDeobfuscator;
108
109 // init frame
110 m_frame = new JFrame(Constants.NAME + " - Class Matcher");
111 final Container pane = m_frame.getContentPane();
112 pane.setLayout(new BorderLayout());
113
114 // init source side
115 JPanel sourcePanel = new JPanel();
116 sourcePanel.setLayout(new BoxLayout(sourcePanel, BoxLayout.PAGE_AXIS));
117 sourcePanel.setPreferredSize(new Dimension(200, 0));
118 pane.add(sourcePanel, BorderLayout.WEST);
119 sourcePanel.add(new JLabel("Source Classes"));
120
121 // init source type radios
122 JPanel sourceTypePanel = new JPanel();
123 sourcePanel.add(sourceTypePanel);
124 sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS));
125 ActionListener sourceTypeListener = event -> setSourceType(SourceType.valueOf(event.getActionCommand()));
126 ButtonGroup sourceTypeButtons = new ButtonGroup();
127 m_sourceTypeButtons = Maps.newHashMap();
128 for (SourceType sourceType : SourceType.values()) {
129 JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons);
130 m_sourceTypeButtons.put(sourceType, button);
131 sourceTypePanel.add(button);
132 }
133
134 m_sourceClasses = new ClassSelector(ClassSelector.DEOBF_CLASS_COMPARATOR);
135 m_sourceClasses.setListener(classEntry -> setSourceClass(classEntry));
136 JScrollPane sourceScroller = new JScrollPane(m_sourceClasses);
137 sourcePanel.add(sourceScroller);
138
139 // init dest side
140 JPanel destPanel = new JPanel();
141 destPanel.setLayout(new BoxLayout(destPanel, BoxLayout.PAGE_AXIS));
142 destPanel.setPreferredSize(new Dimension(200, 0));
143 pane.add(destPanel, BorderLayout.WEST);
144 destPanel.add(new JLabel("Destination Classes"));
145
146 m_top10Matches = new JCheckBox("Show only top 10 matches");
147 destPanel.add(m_top10Matches);
148 m_top10Matches.addActionListener(event -> toggleTop10Matches());
149
150 m_destClasses = new ClassSelector(ClassSelector.DEOBF_CLASS_COMPARATOR);
151 m_destClasses.setListener(this::setDestClass);
152 JScrollPane destScroller = new JScrollPane(m_destClasses);
153 destPanel.add(destScroller);
154
155 JButton autoMatchButton = new JButton("AutoMatch");
156 autoMatchButton.addActionListener(event -> autoMatch());
157 destPanel.add(autoMatchButton);
158
159 // init source panels
160 DefaultSyntaxKit.initKit();
161 m_sourceReader = new CodeReader();
162 m_destReader = new CodeReader();
163
164 // init all the splits
165 JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, sourcePanel, new JScrollPane(m_sourceReader));
166 splitLeft.setResizeWeight(0); // let the right side take all the slack
167 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(m_destReader), destPanel);
168 splitRight.setResizeWeight(1); // let the left side take all the slack
169 JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, splitLeft, splitRight);
170 splitCenter.setResizeWeight(0.5); // resize 50:50
171 pane.add(splitCenter, BorderLayout.CENTER);
172 splitCenter.resetToPreferredSizes();
173
174 // init bottom panel
175 JPanel bottomPanel = new JPanel();
176 bottomPanel.setLayout(new FlowLayout());
177
178 m_sourceClassLabel = new JLabel();
179 m_sourceClassLabel.setHorizontalAlignment(SwingConstants.RIGHT);
180 m_destClassLabel = new JLabel();
181 m_destClassLabel.setHorizontalAlignment(SwingConstants.LEFT);
182
183 m_matchButton = new JButton();
184
185 m_advanceCheck = new JCheckBox("Advance to next likely match");
186 m_advanceCheck.addActionListener(event -> {
187 if (m_advanceCheck.isSelected()) {
188 advance();
189 }
190 });
191
192 bottomPanel.add(m_sourceClassLabel);
193 bottomPanel.add(m_matchButton);
194 bottomPanel.add(m_destClassLabel);
195 bottomPanel.add(m_advanceCheck);
196 pane.add(bottomPanel, BorderLayout.SOUTH);
197
198 // show the frame
199 pane.doLayout();
200 m_frame.setSize(1024, 576);
201 m_frame.setMinimumSize(new Dimension(640, 480));
202 m_frame.setVisible(true);
203 m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
204
205 // init state
206 updateDestMappings();
207 setSourceType(SourceType.getDefault());
208 updateMatchButton();
209 m_saveListener = null;
210 }
211
212 public void setSaveListener(SaveListener val) {
213 m_saveListener = val;
214 }
215
216 private void updateDestMappings() {
217 try {
218 Mappings newMappings = MappingsConverter.newMappings(
219 m_classMatches,
220 m_sourceDeobfuscator.getMappings(),
221 m_sourceDeobfuscator,
222 m_destDeobfuscator
223 );
224
225 // look for dropped mappings
226 MappingsChecker checker = new MappingsChecker(m_destDeobfuscator.getJarIndex());
227 checker.dropBrokenMappings(newMappings);
228
229 // count them
230 int numDroppedFields = checker.getDroppedFieldMappings().size();
231 int numDroppedMethods = checker.getDroppedMethodMappings().size();
232 System.out.println(String.format(
233 "%d mappings from matched classes don't match the dest jar:\n\t%5d fields\n\t%5d methods",
234 numDroppedFields + numDroppedMethods,
235 numDroppedFields,
236 numDroppedMethods
237 ));
238
239 m_destDeobfuscator.setMappings(newMappings);
240 } catch (MappingConflict ex) {
241 System.out.println(ex.getMessage());
242 ex.printStackTrace();
243 return;
244 }
245 }
246
247 protected void setSourceType(SourceType val) {
248
249 // show the source classes
250 m_sourceType = val;
251 m_sourceClasses.setClasses(deobfuscateClasses(m_sourceType.getSourceClasses(m_classMatches), m_sourceDeobfuscator));
252
253 // update counts
254 for (SourceType sourceType : SourceType.values()) {
255 m_sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)",
256 sourceType.name(),
257 sourceType.getSourceClasses(m_classMatches).size()
258 ));
259 }
260 }
261
262 private Collection<ClassEntry> deobfuscateClasses(Collection<ClassEntry> in, Deobfuscator deobfuscator) {
263 List<ClassEntry> out = Lists.newArrayList();
264 for (ClassEntry entry : in) {
265
266 ClassEntry deobf = deobfuscator.deobfuscateEntry(entry);
267
268 // make sure we preserve any scores
269 if (entry instanceof ScoredClassEntry) {
270 deobf = new ScoredClassEntry(deobf, ((ScoredClassEntry) entry).getScore());
271 }
272
273 out.add(deobf);
274 }
275 return out;
276 }
277
278 protected void setSourceClass(ClassEntry classEntry) {
279
280 Runnable onGetDestClasses = null;
281 if (m_advanceCheck.isSelected()) {
282 onGetDestClasses = this::pickBestDestClass;
283 }
284
285 setSourceClass(classEntry, onGetDestClasses);
286 }
287
288 protected void setSourceClass(ClassEntry classEntry, final Runnable onGetDestClasses) {
289
290 // update the current source class
291 m_sourceClass = classEntry;
292 m_sourceClassLabel.setText(m_sourceClass != null ? m_sourceClass.getName() : "");
293
294 if (m_sourceClass != null) {
295
296 // show the dest class(es)
297 ClassMatch match = m_classMatches.getMatchBySource(m_sourceDeobfuscator.obfuscateEntry(m_sourceClass));
298 assert (match != null);
299 if (match.destClasses.isEmpty()) {
300
301 m_destClasses.setClasses(null);
302
303 // run in a separate thread to keep ui responsive
304 new Thread() {
305 @Override
306 public void run() {
307 m_destClasses.setClasses(deobfuscateClasses(getLikelyMatches(m_sourceClass), m_destDeobfuscator));
308 m_destClasses.expandAll();
309
310 if (onGetDestClasses != null) {
311 onGetDestClasses.run();
312 }
313 }
314 }.start();
315
316 } else {
317
318 m_destClasses.setClasses(deobfuscateClasses(match.destClasses, m_destDeobfuscator));
319 m_destClasses.expandAll();
320
321 if (onGetDestClasses != null) {
322 onGetDestClasses.run();
323 }
324 }
325 }
326
327 setDestClass(null);
328 m_sourceReader.decompileClass(m_sourceClass, m_sourceDeobfuscator, () -> m_sourceReader.navigateToClassDeclaration(m_sourceClass));
329
330 updateMatchButton();
331 }
332
333 private Collection<ClassEntry> getLikelyMatches(ClassEntry sourceClass) {
334
335 ClassEntry obfSourceClass = m_sourceDeobfuscator.obfuscateEntry(sourceClass);
336
337 // set up identifiers
338 ClassNamer namer = new ClassNamer(m_classMatches.getUniqueMatches());
339 ClassIdentifier sourceIdentifier = new ClassIdentifier(
340 m_sourceDeobfuscator.getJar(), m_sourceDeobfuscator.getJarIndex(),
341 namer.getSourceNamer(), true
342 );
343 ClassIdentifier destIdentifier = new ClassIdentifier(
344 m_destDeobfuscator.getJar(), m_destDeobfuscator.getJarIndex(),
345 namer.getDestNamer(), true
346 );
347
348 try {
349
350 // rank all the unmatched dest classes against the source class
351 ClassIdentity sourceIdentity = sourceIdentifier.identify(obfSourceClass);
352 List<ClassEntry> scoredDestClasses = Lists.newArrayList();
353 for (ClassEntry unmatchedDestClass : m_classMatches.getUnmatchedDestClasses()) {
354 ClassIdentity destIdentity = destIdentifier.identify(unmatchedDestClass);
355 float score = 100.0f * (sourceIdentity.getMatchScore(destIdentity) + destIdentity.getMatchScore(sourceIdentity))
356 / (sourceIdentity.getMaxMatchScore() + destIdentity.getMaxMatchScore());
357 scoredDestClasses.add(new ScoredClassEntry(unmatchedDestClass, score));
358 }
359
360 if (m_top10Matches.isSelected() && scoredDestClasses.size() > 10) {
361 Collections.sort(scoredDestClasses, (a, b) -> {
362 ScoredClassEntry sa = (ScoredClassEntry) a;
363 ScoredClassEntry sb = (ScoredClassEntry) b;
364 return -Float.compare(sa.getScore(), sb.getScore());
365 });
366 scoredDestClasses = scoredDestClasses.subList(0, 10);
367 }
368
369 return scoredDestClasses;
370
371 } catch (ClassNotFoundException ex) {
372 throw new Error("Unable to find class " + ex.getMessage());
373 }
374 }
375
376 protected void setDestClass(ClassEntry classEntry) {
377
378 // update the current source class
379 m_destClass = classEntry;
380 m_destClassLabel.setText(m_destClass != null ? m_destClass.getName() : "");
381
382 m_destReader.decompileClass(m_destClass, m_destDeobfuscator, () -> m_destReader.navigateToClassDeclaration(m_destClass));
383
384 updateMatchButton();
385 }
386
387 private void updateMatchButton() {
388
389 ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass);
390 ClassEntry obfDest = m_destDeobfuscator.obfuscateEntry(m_destClass);
391
392 BiMap<ClassEntry, ClassEntry> uniqueMatches = m_classMatches.getUniqueMatches();
393 boolean twoSelected = m_sourceClass != null && m_destClass != null;
394 boolean isMatched = uniqueMatches.containsKey(obfSource) && uniqueMatches.containsValue(obfDest);
395 boolean canMatch = !uniqueMatches.containsKey(obfSource) && !uniqueMatches.containsValue(obfDest);
396
397 GuiTricks.deactivateButton(m_matchButton);
398 if (twoSelected) {
399 if (isMatched) {
400 GuiTricks.activateButton(m_matchButton, "Unmatch", event -> onUnmatchClick());
401 } else if (canMatch) {
402 GuiTricks.activateButton(m_matchButton, "Match", event -> onMatchClick());
403 }
404 }
405 }
406
407 private void onMatchClick() {
408 // precondition: source and dest classes are set correctly
409
410 ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass);
411 ClassEntry obfDest = m_destDeobfuscator.obfuscateEntry(m_destClass);
412
413 // remove the classes from their match
414 m_classMatches.removeSource(obfSource);
415 m_classMatches.removeDest(obfDest);
416
417 // add them as matched classes
418 m_classMatches.add(new ClassMatch(obfSource, obfDest));
419
420 ClassEntry nextClass = null;
421 if (m_advanceCheck.isSelected()) {
422 nextClass = m_sourceClasses.getNextClass(m_sourceClass);
423 }
424
425 save();
426 updateMatches();
427
428 if (nextClass != null) {
429 advance(nextClass);
430 }
431 }
432
433 private void onUnmatchClick() {
434 // precondition: source and dest classes are set to a unique match
435
436 ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass);
437
438 // remove the source to break the match, then add the source back as unmatched
439 m_classMatches.removeSource(obfSource);
440 m_classMatches.add(new ClassMatch(obfSource, null));
441
442 save();
443 updateMatches();
444 }
445
446 private void updateMatches() {
447 updateDestMappings();
448 setDestClass(null);
449 m_destClasses.setClasses(null);
450 updateMatchButton();
451
452 // remember where we were in the source tree
453 String packageName = m_sourceClasses.getSelectedPackage();
454
455 setSourceType(m_sourceType);
456
457 m_sourceClasses.expandPackage(packageName);
458 }
459
460 private void save() {
461 if (m_saveListener != null) {
462 m_saveListener.save(m_classMatches);
463 }
464 }
465
466 private void autoMatch() {
467
468 System.out.println("Automatching...");
469
470 // compute a new matching
471 ClassMatching matching = MappingsConverter.computeMatching(
472 m_sourceDeobfuscator.getJar(), m_sourceDeobfuscator.getJarIndex(),
473 m_destDeobfuscator.getJar(), m_destDeobfuscator.getJarIndex(),
474 m_classMatches.getUniqueMatches()
475 );
476 ClassMatches newMatches = new ClassMatches(matching.matches());
477 System.out.println(String.format("Automatch found %d new matches",
478 newMatches.getUniqueMatches().size() - m_classMatches.getUniqueMatches().size()
479 ));
480
481 // update the current matches
482 m_classMatches = newMatches;
483 save();
484 updateMatches();
485 }
486
487 private void advance() {
488 advance(null);
489 }
490
491 private void advance(ClassEntry sourceClass) {
492
493 // make sure we have a source class
494 if (sourceClass == null) {
495 sourceClass = m_sourceClasses.getSelectedClass();
496 if (sourceClass != null) {
497 sourceClass = m_sourceClasses.getNextClass(sourceClass);
498 } else {
499 sourceClass = m_sourceClasses.getFirstClass();
500 }
501 }
502
503 // set the source class
504 setSourceClass(sourceClass, this::pickBestDestClass);
505 m_sourceClasses.setSelectionClass(sourceClass);
506 }
507
508 private void pickBestDestClass() {
509
510 // then, pick the best dest class
511 ClassEntry firstClass = null;
512 ScoredClassEntry bestDestClass = null;
513 for (ClassSelectorPackageNode packageNode : m_destClasses.packageNodes()) {
514 for (ClassSelectorClassNode classNode : m_destClasses.classNodes(packageNode)) {
515 if (firstClass == null) {
516 firstClass = classNode.getClassEntry();
517 }
518 if (classNode.getClassEntry() instanceof ScoredClassEntry) {
519 ScoredClassEntry scoredClass = (ScoredClassEntry) classNode.getClassEntry();
520 if (bestDestClass == null || bestDestClass.getScore() < scoredClass.getScore()) {
521 bestDestClass = scoredClass;
522 }
523 }
524 }
525 }
526
527 // pick the entry to show
528 ClassEntry destClass = null;
529 if (bestDestClass != null) {
530 destClass = bestDestClass;
531 } else if (firstClass != null) {
532 destClass = firstClass;
533 }
534
535 setDestClass(destClass);
536 m_destClasses.setSelectionClass(destClass);
537 }
538
539 private void toggleTop10Matches() {
540 if (m_sourceClass != null) {
541 m_destClasses.clearSelection();
542 m_destClasses.setClasses(deobfuscateClasses(getLikelyMatches(m_sourceClass), m_destDeobfuscator));
543 m_destClasses.expandAll();
544 }
545 }
546}
diff --git a/src/main/java/cuchaz/enigma/gui/ClassSelector.java b/src/main/java/cuchaz/enigma/gui/ClassSelector.java
index 27b4d360..3df90420 100644
--- a/src/main/java/cuchaz/enigma/gui/ClassSelector.java
+++ b/src/main/java/cuchaz/enigma/gui/ClassSelector.java
@@ -138,6 +138,31 @@ public class ClassSelector extends JTree {
138 restoreExpanstionState(this, 0, state); 138 restoreExpanstionState(this, 0, state);
139 } 139 }
140 140
141 public ClassEntry getSelectedClass() {
142 if (!isSelectionEmpty()) {
143 Object selectedNode = getSelectionPath().getLastPathComponent();
144 if (selectedNode instanceof ClassSelectorClassNode) {
145 ClassSelectorClassNode classNode = (ClassSelectorClassNode)selectedNode;
146 return classNode.getClassEntry();
147 }
148 }
149 return null;
150 }
151
152 public String getSelectedPackage() {
153 if (!isSelectionEmpty()) {
154 Object selectedNode = getSelectionPath().getLastPathComponent();
155 if (selectedNode instanceof ClassSelectorPackageNode) {
156 ClassSelectorPackageNode packageNode = (ClassSelectorPackageNode)selectedNode;
157 return packageNode.getPackageName();
158 } else if (selectedNode instanceof ClassSelectorClassNode) {
159 ClassSelectorClassNode classNode = (ClassSelectorClassNode)selectedNode;
160 return classNode.getClassEntry().getPackageName();
161 }
162 }
163 return null;
164 }
165
141 public boolean isDescendant(TreePath path1, TreePath path2) { 166 public boolean isDescendant(TreePath path1, TreePath path2) {
142 int count1 = path1.getPathCount(); 167 int count1 = path1.getPathCount();
143 int count2 = path2.getPathCount(); 168 int count2 = path2.getPathCount();
@@ -175,4 +200,99 @@ public class ClassSelector extends JTree {
175 tree.expandRow(token); 200 tree.expandRow(token);
176 } 201 }
177 } 202 }
203
204 public Iterable<ClassSelectorPackageNode> packageNodes() {
205 List<ClassSelectorPackageNode> nodes = Lists.newArrayList();
206 DefaultMutableTreeNode root = (DefaultMutableTreeNode)getModel().getRoot();
207 Enumeration<?> children = root.children();
208 while (children.hasMoreElements()) {
209 ClassSelectorPackageNode packageNode = (ClassSelectorPackageNode)children.nextElement();
210 nodes.add(packageNode);
211 }
212 return nodes;
213 }
214
215 public Iterable<ClassSelectorClassNode> classNodes(ClassSelectorPackageNode packageNode) {
216 List<ClassSelectorClassNode> nodes = Lists.newArrayList();
217 Enumeration<?> children = packageNode.children();
218 while (children.hasMoreElements()) {
219 ClassSelectorClassNode classNode = (ClassSelectorClassNode)children.nextElement();
220 nodes.add(classNode);
221 }
222 return nodes;
223 }
224
225 public void expandPackage(String packageName) {
226 if (packageName == null) {
227 return;
228 }
229 for (ClassSelectorPackageNode packageNode : packageNodes()) {
230 if (packageNode.getPackageName().equals(packageName)) {
231 expandPath(new TreePath(new Object[] {getModel().getRoot(), packageNode}));
232 return;
233 }
234 }
235 }
236
237 public void expandAll() {
238 for (ClassSelectorPackageNode packageNode : packageNodes()) {
239 expandPath(new TreePath(new Object[] {getModel().getRoot(), packageNode}));
240 }
241 }
242
243 public ClassEntry getFirstClass() {
244 for (ClassSelectorPackageNode packageNode : packageNodes()) {
245 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
246 return classNode.getClassEntry();
247 }
248 }
249 return null;
250 }
251
252 public ClassSelectorPackageNode getPackageNode(ClassEntry entry) {
253 for (ClassSelectorPackageNode packageNode : packageNodes()) {
254 if (packageNode.getPackageName().equals(entry.getPackageName())) {
255 return packageNode;
256 }
257 }
258 return null;
259 }
260
261 public ClassEntry getNextClass(ClassEntry entry) {
262 boolean foundIt = false;
263 for (ClassSelectorPackageNode packageNode : packageNodes()) {
264 if (!foundIt) {
265 // skip to the package with our target in it
266 if (packageNode.getPackageName().equals(entry.getPackageName())) {
267 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
268 if (!foundIt) {
269 if (classNode.getClassEntry().equals(entry)) {
270 foundIt = true;
271 }
272 } else {
273 // return the next class
274 return classNode.getClassEntry();
275 }
276 }
277 }
278 } else {
279 // return the next class
280 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
281 return classNode.getClassEntry();
282 }
283 }
284 }
285 return null;
286 }
287
288 public void setSelectionClass(ClassEntry classEntry) {
289 expandPackage(classEntry.getPackageName());
290 for (ClassSelectorPackageNode packageNode : packageNodes()) {
291 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
292 if (classNode.getClassEntry().equals(classEntry)) {
293 setSelectionPath(new TreePath(new Object[] {getModel().getRoot(), packageNode, classNode}));
294 }
295 }
296 }
297 }
178} 298}
diff --git a/src/main/java/cuchaz/enigma/gui/CodeReader.java b/src/main/java/cuchaz/enigma/gui/CodeReader.java
new file mode 100644
index 00000000..601e5b95
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/CodeReader.java
@@ -0,0 +1,223 @@
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 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import com.strobel.decompiler.languages.java.ast.CompilationUnit;
14
15import java.awt.Rectangle;
16import java.awt.event.ActionEvent;
17import java.awt.event.ActionListener;
18
19import javax.swing.JEditorPane;
20import javax.swing.SwingUtilities;
21import javax.swing.Timer;
22import javax.swing.event.CaretEvent;
23import javax.swing.event.CaretListener;
24import javax.swing.text.BadLocationException;
25import javax.swing.text.Highlighter.HighlightPainter;
26
27import cuchaz.enigma.Deobfuscator;
28import cuchaz.enigma.analysis.EntryReference;
29import cuchaz.enigma.analysis.SourceIndex;
30import cuchaz.enigma.analysis.Token;
31import cuchaz.enigma.gui.highlight.SelectionHighlightPainter;
32import cuchaz.enigma.mapping.ClassEntry;
33import cuchaz.enigma.mapping.Entry;
34import de.sciss.syntaxpane.DefaultSyntaxKit;
35
36
37public class CodeReader extends JEditorPane {
38
39 private static final long serialVersionUID = 3673180950485748810L;
40
41 private static final Object m_lock = new Object();
42
43 public interface SelectionListener {
44 void onSelect(EntryReference<Entry, Entry> reference);
45 }
46
47 private SelectionHighlightPainter m_selectionHighlightPainter;
48 private SourceIndex m_sourceIndex;
49 private SelectionListener m_selectionListener;
50
51 public CodeReader() {
52
53 setEditable(false);
54 setContentType("text/java");
55
56 // turn off token highlighting (it's wrong most of the time anyway...)
57 DefaultSyntaxKit kit = (DefaultSyntaxKit) getEditorKit();
58 kit.toggleComponent(this, "de.sciss.syntaxpane.components.TokenMarker");
59
60 // hook events
61 addCaretListener(new CaretListener() {
62 @Override
63 public void caretUpdate(CaretEvent event) {
64 if (m_selectionListener != null && m_sourceIndex != null) {
65 Token token = m_sourceIndex.getReferenceToken(event.getDot());
66 if (token != null) {
67 m_selectionListener.onSelect(m_sourceIndex.getDeobfReference(token));
68 } else {
69 m_selectionListener.onSelect(null);
70 }
71 }
72 }
73 });
74
75 m_selectionHighlightPainter = new SelectionHighlightPainter();
76 m_sourceIndex = null;
77 m_selectionListener = null;
78 }
79
80 public void setSelectionListener(SelectionListener val) {
81 m_selectionListener = val;
82 }
83
84 public void setCode(String code) {
85 // sadly, the java lexer is not thread safe, so we have to serialize all these calls
86 synchronized (m_lock) {
87 setText(code);
88 }
89 }
90
91 public SourceIndex getSourceIndex() {
92 return m_sourceIndex;
93 }
94
95 public void decompileClass(ClassEntry classEntry, Deobfuscator deobfuscator) {
96 decompileClass(classEntry, deobfuscator, null);
97 }
98
99 public void decompileClass(ClassEntry classEntry, Deobfuscator deobfuscator, Runnable callback) {
100 decompileClass(classEntry, deobfuscator, null, callback);
101 }
102
103 public void decompileClass(final ClassEntry classEntry, final Deobfuscator deobfuscator, final Boolean ignoreBadTokens, final Runnable callback) {
104
105 if (classEntry == null) {
106 setCode(null);
107 return;
108 }
109
110 setCode("(decompiling...)");
111
112 // run decompilation in a separate thread to keep ui responsive
113 new Thread() {
114 @Override
115 public void run() {
116
117 // decompile it
118 CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getOutermostClassName());
119 String source = deobfuscator.getSource(sourceTree);
120 setCode(source);
121 m_sourceIndex = deobfuscator.getSourceIndex(sourceTree, source, ignoreBadTokens);
122
123 if (callback != null) {
124 callback.run();
125 }
126 }
127 }.start();
128 }
129
130 public void navigateToClassDeclaration(ClassEntry classEntry) {
131
132 // navigate to the class declaration
133 Token token = m_sourceIndex.getDeclarationToken(classEntry);
134 if (token == null) {
135 // couldn't find the class declaration token, might be an anonymous class
136 // look for any declaration in that class instead
137 for (Entry entry : m_sourceIndex.declarations()) {
138 if (entry.getClassEntry().equals(classEntry)) {
139 token = m_sourceIndex.getDeclarationToken(entry);
140 break;
141 }
142 }
143 }
144
145 if (token != null) {
146 navigateToToken(token);
147 } else {
148 // couldn't find anything =(
149 System.out.println("Unable to find declaration in source for " + classEntry);
150 }
151 }
152
153 public void navigateToToken(final Token token) {
154 navigateToToken(this, token, m_selectionHighlightPainter);
155 }
156
157 // HACKHACK: someday we can update the main GUI to use this code reader
158 public static void navigateToToken(final JEditorPane editor, final Token token, final HighlightPainter highlightPainter) {
159
160 // set the caret position to the token
161 editor.setCaretPosition(token.start);
162 editor.grabFocus();
163
164 try {
165 // make sure the token is visible in the scroll window
166 Rectangle start = editor.modelToView(token.start);
167 Rectangle end = editor.modelToView(token.end);
168 final Rectangle show = start.union(end);
169 show.grow(start.width * 10, start.height * 6);
170 SwingUtilities.invokeLater(new Runnable() {
171 @Override
172 public void run() {
173 editor.scrollRectToVisible(show);
174 }
175 });
176 } catch (BadLocationException ex) {
177 throw new Error(ex);
178 }
179
180 // highlight the token momentarily
181 final Timer timer = new Timer(200, new ActionListener() {
182 private int m_counter = 0;
183 private Object m_highlight = null;
184
185 @Override
186 public void actionPerformed(ActionEvent event) {
187 if (m_counter % 2 == 0) {
188 try {
189 m_highlight = editor.getHighlighter().addHighlight(token.start, token.end, highlightPainter);
190 } catch (BadLocationException ex) {
191 // don't care
192 }
193 } else if (m_highlight != null) {
194 editor.getHighlighter().removeHighlight(m_highlight);
195 }
196
197 if (m_counter++ > 6) {
198 Timer timer = (Timer) event.getSource();
199 timer.stop();
200 }
201 }
202 });
203 timer.start();
204 }
205
206 public void setHighlightedTokens(Iterable<Token> tokens, HighlightPainter painter) {
207 for (Token token : tokens) {
208 setHighlightedToken(token, painter);
209 }
210 }
211
212 public void setHighlightedToken(Token token, HighlightPainter painter) {
213 try {
214 getHighlighter().addHighlight(token.start, token.end, painter);
215 } catch (BadLocationException ex) {
216 throw new IllegalArgumentException(ex);
217 }
218 }
219
220 public void clearHighlights() {
221 getHighlighter().removeAllHighlights();
222 }
223}
diff --git a/src/main/java/cuchaz/enigma/gui/Gui.java b/src/main/java/cuchaz/enigma/gui/Gui.java
index 80cb3edf..fd59a816 100644
--- a/src/main/java/cuchaz/enigma/gui/Gui.java
+++ b/src/main/java/cuchaz/enigma/gui/Gui.java
@@ -370,7 +370,7 @@ public class Gui {
370 if (token == null) { 370 if (token == null) {
371 throw new IllegalArgumentException("Token cannot be null!"); 371 throw new IllegalArgumentException("Token cannot be null!");
372 } 372 }
373 Utils.navigateToToken(this.editor, token, m_selectionHighlightPainter); 373 CodeReader.navigateToToken(this.editor, token, m_selectionHighlightPainter);
374 redraw(); 374 redraw();
375 } 375 }
376 376
diff --git a/src/main/java/cuchaz/enigma/gui/GuiTricks.java b/src/main/java/cuchaz/enigma/gui/GuiTricks.java
new file mode 100644
index 00000000..da2ec74f
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/GuiTricks.java
@@ -0,0 +1,56 @@
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 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Font;
14import java.awt.event.ActionListener;
15import java.awt.event.MouseEvent;
16import java.util.Arrays;
17
18import javax.swing.JButton;
19import javax.swing.JComponent;
20import javax.swing.JLabel;
21import javax.swing.ToolTipManager;
22
23public class GuiTricks {
24
25 public static JLabel unboldLabel(JLabel label) {
26 Font font = label.getFont();
27 label.setFont(font.deriveFont(font.getStyle() & ~Font.BOLD));
28 return label;
29 }
30
31 public static void showToolTipNow(JComponent component) {
32 // HACKHACK: trick the tooltip manager into showing the tooltip right now
33 ToolTipManager manager = ToolTipManager.sharedInstance();
34 int oldDelay = manager.getInitialDelay();
35 manager.setInitialDelay(0);
36 manager.mouseMoved(new MouseEvent(component, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, 0, 0, 0, false));
37 manager.setInitialDelay(oldDelay);
38 }
39
40 public static void deactivateButton(JButton button) {
41 button.setEnabled(false);
42 button.setText("");
43 for (ActionListener listener : Arrays.asList(button.getActionListeners())) {
44 button.removeActionListener(listener);
45 }
46 }
47
48 public static void activateButton(JButton button, String text, ActionListener newListener) {
49 button.setText(text);
50 button.setEnabled(true);
51 for (ActionListener listener : Arrays.asList(button.getActionListeners())) {
52 button.removeActionListener(listener);
53 }
54 button.addActionListener(newListener);
55 }
56}
diff --git a/src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java b/src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java
new file mode 100644
index 00000000..60c6d8e8
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java
@@ -0,0 +1,490 @@
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 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import com.google.common.collect.Lists;
14import com.google.common.collect.Maps;
15
16import java.awt.BorderLayout;
17import java.awt.Container;
18import java.awt.Dimension;
19import java.awt.FlowLayout;
20import java.awt.event.ActionEvent;
21import java.awt.event.ActionListener;
22import java.awt.event.KeyAdapter;
23import java.awt.event.KeyEvent;
24import java.util.Collection;
25import java.util.List;
26import java.util.Map;
27
28import javax.swing.*;
29import javax.swing.text.Highlighter.HighlightPainter;
30
31import cuchaz.enigma.Constants;
32import cuchaz.enigma.Deobfuscator;
33import cuchaz.enigma.analysis.EntryReference;
34import cuchaz.enigma.analysis.SourceIndex;
35import cuchaz.enigma.analysis.Token;
36import cuchaz.enigma.convert.ClassMatches;
37import cuchaz.enigma.convert.MemberMatches;
38import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener;
39import cuchaz.enigma.gui.highlight.DeobfuscatedHighlightPainter;
40import cuchaz.enigma.gui.highlight.ObfuscatedHighlightPainter;
41import cuchaz.enigma.mapping.ClassEntry;
42import cuchaz.enigma.mapping.Entry;
43import de.sciss.syntaxpane.DefaultSyntaxKit;
44
45
46public class MemberMatchingGui<T extends Entry> {
47
48 private enum SourceType {
49 Matched {
50 @Override
51 public <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches) {
52 return matches.getSourceClassesWithoutUnmatchedEntries();
53 }
54 },
55 Unmatched {
56 @Override
57 public <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches) {
58 return matches.getSourceClassesWithUnmatchedEntries();
59 }
60 };
61
62 public JRadioButton newRadio(ActionListener listener, ButtonGroup group) {
63 JRadioButton button = new JRadioButton(name(), this == getDefault());
64 button.setActionCommand(name());
65 button.addActionListener(listener);
66 group.add(button);
67 return button;
68 }
69
70 public abstract <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches);
71
72 public static SourceType getDefault() {
73 return values()[0];
74 }
75 }
76
77 public interface SaveListener<T extends Entry> {
78 void save(MemberMatches<T> matches);
79 }
80
81 // controls
82 private JFrame m_frame;
83 private Map<SourceType, JRadioButton> m_sourceTypeButtons;
84 private ClassSelector m_sourceClasses;
85 private CodeReader m_sourceReader;
86 private CodeReader m_destReader;
87 private JButton m_matchButton;
88 private JButton m_unmatchableButton;
89 private JLabel m_sourceLabel;
90 private JLabel m_destLabel;
91 private HighlightPainter m_unmatchedHighlightPainter;
92 private HighlightPainter m_matchedHighlightPainter;
93
94 private ClassMatches m_classMatches;
95 private MemberMatches<T> m_memberMatches;
96 private Deobfuscator m_sourceDeobfuscator;
97 private Deobfuscator m_destDeobfuscator;
98 private SaveListener<T> m_saveListener;
99 private SourceType m_sourceType;
100 private ClassEntry m_obfSourceClass;
101 private ClassEntry m_obfDestClass;
102 private T m_obfSourceEntry;
103 private T m_obfDestEntry;
104
105 public MemberMatchingGui(ClassMatches classMatches, MemberMatches<T> fieldMatches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
106
107 m_classMatches = classMatches;
108 m_memberMatches = fieldMatches;
109 m_sourceDeobfuscator = sourceDeobfuscator;
110 m_destDeobfuscator = destDeobfuscator;
111
112 // init frame
113 m_frame = new JFrame(Constants.NAME + " - Member Matcher");
114 final Container pane = m_frame.getContentPane();
115 pane.setLayout(new BorderLayout());
116
117 // init classes side
118 JPanel classesPanel = new JPanel();
119 classesPanel.setLayout(new BoxLayout(classesPanel, BoxLayout.PAGE_AXIS));
120 classesPanel.setPreferredSize(new Dimension(200, 0));
121 pane.add(classesPanel, BorderLayout.WEST);
122 classesPanel.add(new JLabel("Classes"));
123
124 // init source type radios
125 JPanel sourceTypePanel = new JPanel();
126 classesPanel.add(sourceTypePanel);
127 sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS));
128 ActionListener sourceTypeListener = new ActionListener() {
129 @Override
130 public void actionPerformed(ActionEvent event) {
131 setSourceType(SourceType.valueOf(event.getActionCommand()));
132 }
133 };
134 ButtonGroup sourceTypeButtons = new ButtonGroup();
135 m_sourceTypeButtons = Maps.newHashMap();
136 for (SourceType sourceType : SourceType.values()) {
137 JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons);
138 m_sourceTypeButtons.put(sourceType, button);
139 sourceTypePanel.add(button);
140 }
141
142 m_sourceClasses = new ClassSelector(ClassSelector.DEOBF_CLASS_COMPARATOR);
143 m_sourceClasses.setListener(new ClassSelectionListener() {
144 @Override
145 public void onSelectClass(ClassEntry classEntry) {
146 setSourceClass(classEntry);
147 }
148 });
149 JScrollPane sourceScroller = new JScrollPane(m_sourceClasses);
150 classesPanel.add(sourceScroller);
151
152 // init readers
153 DefaultSyntaxKit.initKit();
154 m_sourceReader = new CodeReader();
155 m_sourceReader.setSelectionListener(new CodeReader.SelectionListener() {
156 @Override
157 public void onSelect(EntryReference<Entry, Entry> reference) {
158 if (reference != null) {
159 onSelectSource(reference.entry);
160 } else {
161 onSelectSource(null);
162 }
163 }
164 });
165 m_destReader = new CodeReader();
166 m_destReader.setSelectionListener(new CodeReader.SelectionListener() {
167 @Override
168 public void onSelect(EntryReference<Entry, Entry> reference) {
169 if (reference != null) {
170 onSelectDest(reference.entry);
171 } else {
172 onSelectDest(null);
173 }
174 }
175 });
176
177 // add key bindings
178 KeyAdapter keyListener = new KeyAdapter() {
179 @Override
180 public void keyPressed(KeyEvent event) {
181 switch (event.getKeyCode()) {
182 case KeyEvent.VK_M:
183 m_matchButton.doClick();
184 break;
185 }
186 }
187 };
188 m_sourceReader.addKeyListener(keyListener);
189 m_destReader.addKeyListener(keyListener);
190
191 // init all the splits
192 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(m_sourceReader), new JScrollPane(m_destReader));
193 splitRight.setResizeWeight(0.5); // resize 50:50
194 JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, classesPanel, splitRight);
195 splitLeft.setResizeWeight(0); // let the right side take all the slack
196 pane.add(splitLeft, BorderLayout.CENTER);
197 splitLeft.resetToPreferredSizes();
198
199 // init bottom panel
200 JPanel bottomPanel = new JPanel();
201 bottomPanel.setLayout(new FlowLayout());
202 pane.add(bottomPanel, BorderLayout.SOUTH);
203
204 m_matchButton = new JButton();
205 m_unmatchableButton = new JButton();
206
207 m_sourceLabel = new JLabel();
208 bottomPanel.add(m_sourceLabel);
209 bottomPanel.add(m_matchButton);
210 bottomPanel.add(m_unmatchableButton);
211 m_destLabel = new JLabel();
212 bottomPanel.add(m_destLabel);
213
214 // show the frame
215 pane.doLayout();
216 m_frame.setSize(1024, 576);
217 m_frame.setMinimumSize(new Dimension(640, 480));
218 m_frame.setVisible(true);
219 m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
220
221 m_unmatchedHighlightPainter = new ObfuscatedHighlightPainter();
222 m_matchedHighlightPainter = new DeobfuscatedHighlightPainter();
223
224 // init state
225 m_saveListener = null;
226 m_obfSourceClass = null;
227 m_obfDestClass = null;
228 m_obfSourceEntry = null;
229 m_obfDestEntry = null;
230 setSourceType(SourceType.getDefault());
231 updateButtons();
232 }
233
234 protected void setSourceType(SourceType val) {
235 m_sourceType = val;
236 updateSourceClasses();
237 }
238
239 public void setSaveListener(SaveListener<T> val) {
240 m_saveListener = val;
241 }
242
243 private void updateSourceClasses() {
244
245 String selectedPackage = m_sourceClasses.getSelectedPackage();
246
247 List<ClassEntry> deobfClassEntries = Lists.newArrayList();
248 for (ClassEntry entry : m_sourceType.getObfSourceClasses(m_memberMatches)) {
249 deobfClassEntries.add(m_sourceDeobfuscator.deobfuscateEntry(entry));
250 }
251 m_sourceClasses.setClasses(deobfClassEntries);
252
253 if (selectedPackage != null) {
254 m_sourceClasses.expandPackage(selectedPackage);
255 }
256
257 for (SourceType sourceType : SourceType.values()) {
258 m_sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)",
259 sourceType.name(), sourceType.getObfSourceClasses(m_memberMatches).size()
260 ));
261 }
262 }
263
264 protected void setSourceClass(ClassEntry sourceClass) {
265
266 m_obfSourceClass = m_sourceDeobfuscator.obfuscateEntry(sourceClass);
267 m_obfDestClass = m_classMatches.getUniqueMatches().get(m_obfSourceClass);
268 if (m_obfDestClass == null) {
269 throw new Error("No matching dest class for source class: " + m_obfSourceClass);
270 }
271
272 m_sourceReader.decompileClass(m_obfSourceClass, m_sourceDeobfuscator, false, new Runnable() {
273 @Override
274 public void run() {
275 updateSourceHighlights();
276 }
277 });
278 m_destReader.decompileClass(m_obfDestClass, m_destDeobfuscator, false, new Runnable() {
279 @Override
280 public void run() {
281 updateDestHighlights();
282 }
283 });
284 }
285
286 protected void updateSourceHighlights() {
287 highlightEntries(m_sourceReader, m_sourceDeobfuscator, m_memberMatches.matches().keySet(), m_memberMatches.getUnmatchedSourceEntries());
288 }
289
290 protected void updateDestHighlights() {
291 highlightEntries(m_destReader, m_destDeobfuscator, m_memberMatches.matches().values(), m_memberMatches.getUnmatchedDestEntries());
292 }
293
294 private void highlightEntries(CodeReader reader, Deobfuscator deobfuscator, Collection<T> obfMatchedEntries, Collection<T> obfUnmatchedEntries) {
295 reader.clearHighlights();
296 SourceIndex index = reader.getSourceIndex();
297
298 // matched fields
299 for (T obfT : obfMatchedEntries) {
300 T deobfT = deobfuscator.deobfuscateEntry(obfT);
301 Token token = index.getDeclarationToken(deobfT);
302 if (token != null) {
303 reader.setHighlightedToken(token, m_matchedHighlightPainter);
304 }
305 }
306
307 // unmatched fields
308 for (T obfT : obfUnmatchedEntries) {
309 T deobfT = deobfuscator.deobfuscateEntry(obfT);
310 Token token = index.getDeclarationToken(deobfT);
311 if (token != null) {
312 reader.setHighlightedToken(token, m_unmatchedHighlightPainter);
313 }
314 }
315 }
316
317 private boolean isSelectionMatched() {
318 return m_obfSourceEntry != null && m_obfDestEntry != null
319 && m_memberMatches.isMatched(m_obfSourceEntry, m_obfDestEntry);
320 }
321
322 protected void onSelectSource(Entry source) {
323
324 // start with no selection
325 if (isSelectionMatched()) {
326 setDest(null);
327 }
328 setSource(null);
329
330 // then look for a valid source selection
331 if (source != null) {
332
333 // this looks really scary, but it's actually ok
334 // Deobfuscator.obfuscateEntry can handle all implementations of Entry
335 // and MemberMatches.hasSource() will only pass entries that actually match T
336 @SuppressWarnings("unchecked")
337 T sourceEntry = (T) source;
338
339 T obfSourceEntry = m_sourceDeobfuscator.obfuscateEntry(sourceEntry);
340 if (m_memberMatches.hasSource(obfSourceEntry)) {
341 setSource(obfSourceEntry);
342
343 // look for a matched dest too
344 T obfDestEntry = m_memberMatches.matches().get(obfSourceEntry);
345 if (obfDestEntry != null) {
346 setDest(obfDestEntry);
347 }
348 }
349 }
350
351 updateButtons();
352 }
353
354 protected void onSelectDest(Entry dest) {
355
356 // start with no selection
357 if (isSelectionMatched()) {
358 setSource(null);
359 }
360 setDest(null);
361
362 // then look for a valid dest selection
363 if (dest != null) {
364
365 // this looks really scary, but it's actually ok
366 // Deobfuscator.obfuscateEntry can handle all implementations of Entry
367 // and MemberMatches.hasSource() will only pass entries that actually match T
368 @SuppressWarnings("unchecked")
369 T destEntry = (T) dest;
370
371 T obfDestEntry = m_destDeobfuscator.obfuscateEntry(destEntry);
372 if (m_memberMatches.hasDest(obfDestEntry)) {
373 setDest(obfDestEntry);
374
375 // look for a matched source too
376 T obfSourceEntry = m_memberMatches.matches().inverse().get(obfDestEntry);
377 if (obfSourceEntry != null) {
378 setSource(obfSourceEntry);
379 }
380 }
381 }
382
383 updateButtons();
384 }
385
386 private void setSource(T obfEntry) {
387 if (obfEntry == null) {
388 m_obfSourceEntry = obfEntry;
389 m_sourceLabel.setText("");
390 } else {
391 m_obfSourceEntry = obfEntry;
392 m_sourceLabel.setText(getEntryLabel(obfEntry, m_sourceDeobfuscator));
393 }
394 }
395
396 private void setDest(T obfEntry) {
397 if (obfEntry == null) {
398 m_obfDestEntry = obfEntry;
399 m_destLabel.setText("");
400 } else {
401 m_obfDestEntry = obfEntry;
402 m_destLabel.setText(getEntryLabel(obfEntry, m_destDeobfuscator));
403 }
404 }
405
406 private String getEntryLabel(T obfEntry, Deobfuscator deobfuscator) {
407 // show obfuscated and deobfuscated names, but no types/signatures
408 T deobfEntry = deobfuscator.deobfuscateEntry(obfEntry);
409 return String.format("%s (%s)", deobfEntry.getName(), obfEntry.getName());
410 }
411
412 private void updateButtons() {
413
414 GuiTricks.deactivateButton(m_matchButton);
415 GuiTricks.deactivateButton(m_unmatchableButton);
416
417 if (m_obfSourceEntry != null && m_obfDestEntry != null) {
418 if (m_memberMatches.isMatched(m_obfSourceEntry, m_obfDestEntry)) {
419 GuiTricks.activateButton(m_matchButton, "Unmatch", new ActionListener() {
420 @Override
421 public void actionPerformed(ActionEvent event) {
422 unmatch();
423 }
424 });
425 } else if (!m_memberMatches.isMatchedSourceEntry(m_obfSourceEntry) && !m_memberMatches.isMatchedDestEntry(m_obfDestEntry)) {
426 GuiTricks.activateButton(m_matchButton, "Match", new ActionListener() {
427 @Override
428 public void actionPerformed(ActionEvent event) {
429 match();
430 }
431 });
432 }
433 } else if (m_obfSourceEntry != null) {
434 GuiTricks.activateButton(m_unmatchableButton, "Set Unmatchable", new ActionListener() {
435 @Override
436 public void actionPerformed(ActionEvent event) {
437 unmatchable();
438 }
439 });
440 }
441 }
442
443 protected void match() {
444
445 // update the field matches
446 m_memberMatches.makeMatch(m_obfSourceEntry, m_obfDestEntry);
447 save();
448
449 // update the ui
450 onSelectSource(null);
451 onSelectDest(null);
452 updateSourceHighlights();
453 updateDestHighlights();
454 updateSourceClasses();
455 }
456
457 protected void unmatch() {
458
459 // update the field matches
460 m_memberMatches.unmakeMatch(m_obfSourceEntry, m_obfDestEntry);
461 save();
462
463 // update the ui
464 onSelectSource(null);
465 onSelectDest(null);
466 updateSourceHighlights();
467 updateDestHighlights();
468 updateSourceClasses();
469 }
470
471 protected void unmatchable() {
472
473 // update the field matches
474 m_memberMatches.makeSourceUnmatchable(m_obfSourceEntry);
475 save();
476
477 // update the ui
478 onSelectSource(null);
479 onSelectDest(null);
480 updateSourceHighlights();
481 updateDestHighlights();
482 updateSourceClasses();
483 }
484
485 private void save() {
486 if (m_saveListener != null) {
487 m_saveListener.save(m_memberMatches);
488 }
489 }
490}
diff --git a/src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java b/src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java
new file mode 100644
index 00000000..d1e2de0e
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java
@@ -0,0 +1,30 @@
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 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import cuchaz.enigma.mapping.ClassEntry;
14
15
16public class ScoredClassEntry extends ClassEntry {
17
18 private static final long serialVersionUID = -8798725308554217105L;
19
20 private float m_score;
21
22 public ScoredClassEntry(ClassEntry other, float score) {
23 super(other);
24 m_score = score;
25 }
26
27 public float getScore() {
28 return m_score;
29 }
30}
diff --git a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java
index 805b3a8e..dfdc765b 100644
--- a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java
+++ b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java
@@ -20,6 +20,10 @@ public class ClassSelectorPackageNode extends DefaultMutableTreeNode {
20 this.packageName = packageName; 20 this.packageName = packageName;
21 } 21 }
22 22
23 public String getPackageName() {
24 return packageName;
25 }
26
23 @Override 27 @Override
24 public String toString() { 28 public String toString() {
25 return this.packageName; 29 return this.packageName;
diff --git a/src/main/java/cuchaz/enigma/mapping/ArgumentEntry.java b/src/main/java/cuchaz/enigma/mapping/ArgumentEntry.java
index 1409fc43..741849a9 100644
--- a/src/main/java/cuchaz/enigma/mapping/ArgumentEntry.java
+++ b/src/main/java/cuchaz/enigma/mapping/ArgumentEntry.java
@@ -34,6 +34,12 @@ public class ArgumentEntry implements Entry {
34 this.name = name; 34 this.name = name;
35 } 35 }
36 36
37 public ArgumentEntry(ArgumentEntry other) {
38 this.behaviorEntry = other.getBehaviorEntry();
39 this.index = other.index;
40 this.name = other.name;
41 }
42
37 public ArgumentEntry(ArgumentEntry other, String newClassName) { 43 public ArgumentEntry(ArgumentEntry other, String newClassName) {
38 this.behaviorEntry = (BehaviorEntry) other.behaviorEntry.cloneToNewClass(new ClassEntry(newClassName)); 44 this.behaviorEntry = (BehaviorEntry) other.behaviorEntry.cloneToNewClass(new ClassEntry(newClassName));
39 this.index = other.index; 45 this.index = other.index;
diff --git a/src/main/java/cuchaz/enigma/mapping/ArgumentMapping.java b/src/main/java/cuchaz/enigma/mapping/ArgumentMapping.java
index 918395f9..d117de02 100644
--- a/src/main/java/cuchaz/enigma/mapping/ArgumentMapping.java
+++ b/src/main/java/cuchaz/enigma/mapping/ArgumentMapping.java
@@ -21,6 +21,11 @@ public class ArgumentMapping implements Comparable<ArgumentMapping> {
21 this.name = NameValidator.validateArgumentName(name); 21 this.name = NameValidator.validateArgumentName(name);
22 } 22 }
23 23
24 public ArgumentMapping(ArgumentMapping other) {
25 this.index = other.index;
26 this.name = other.name;
27 }
28
24 public int getIndex() { 29 public int getIndex() {
25 return this.index; 30 return this.index;
26 } 31 }
diff --git a/src/main/java/cuchaz/enigma/mapping/ClassMapping.java b/src/main/java/cuchaz/enigma/mapping/ClassMapping.java
index b2c076a3..36b35f73 100644
--- a/src/main/java/cuchaz/enigma/mapping/ClassMapping.java
+++ b/src/main/java/cuchaz/enigma/mapping/ClassMapping.java
@@ -12,6 +12,7 @@ package cuchaz.enigma.mapping;
12 12
13import com.google.common.collect.Maps; 13import com.google.common.collect.Maps;
14 14
15import java.util.ArrayList;
15import java.util.Map; 16import java.util.Map;
16 17
17import cuchaz.enigma.throwables.MappingConflict; 18import cuchaz.enigma.throwables.MappingConflict;
@@ -119,6 +120,15 @@ public class ClassMapping implements Comparable<ClassMapping> {
119 return classMapping; 120 return classMapping;
120 } 121 }
121 122
123 public String getDeobfInnerClassName(String obfSimpleName) {
124 assert (isSimpleClassName(obfSimpleName));
125 ClassMapping classMapping = m_innerClassesByObfSimple.get(obfSimpleName);
126 if (classMapping != null) {
127 return classMapping.getDeobfName();
128 }
129 return null;
130 }
131
122 public void setInnerClassName(ClassEntry obfInnerClass, String deobfName) { 132 public void setInnerClassName(ClassEntry obfInnerClass, String deobfName) {
123 ClassMapping classMapping = getOrCreateInnerClass(obfInnerClass); 133 ClassMapping classMapping = getOrCreateInnerClass(obfInnerClass);
124 if (classMapping.getDeobfName() != null) { 134 if (classMapping.getDeobfName() != null) {
@@ -149,6 +159,10 @@ public class ClassMapping implements Comparable<ClassMapping> {
149 return m_fieldsByObf.values(); 159 return m_fieldsByObf.values();
150 } 160 }
151 161
162 public boolean containsObfField(String obfName, Type obfType) {
163 return m_fieldsByObf.containsKey(getFieldKey(obfName, obfType));
164 }
165
152 public boolean containsDeobfField(String deobfName, Type deobfType) { 166 public boolean containsDeobfField(String deobfName, Type deobfType) {
153 return m_fieldsByDeobf.containsKey(getFieldKey(deobfName, deobfType)); 167 return m_fieldsByDeobf.containsKey(getFieldKey(deobfName, deobfType));
154 } 168 }
@@ -182,6 +196,10 @@ public class ClassMapping implements Comparable<ClassMapping> {
182 return m_fieldsByObf.get(getFieldKey(obfName, obfType)); 196 return m_fieldsByObf.get(getFieldKey(obfName, obfType));
183 } 197 }
184 198
199 public FieldMapping getFieldByDeobf(String deobfName, Type obfType) {
200 return m_fieldsByDeobf.get(getFieldKey(deobfName, obfType));
201 }
202
185 public String getObfFieldName(String deobfName, Type obfType) { 203 public String getObfFieldName(String deobfName, Type obfType) {
186 FieldMapping fieldMapping = m_fieldsByDeobf.get(getFieldKey(deobfName, obfType)); 204 FieldMapping fieldMapping = m_fieldsByDeobf.get(getFieldKey(deobfName, obfType));
187 if (fieldMapping != null) { 205 if (fieldMapping != null) {
@@ -227,6 +245,16 @@ public class ClassMapping implements Comparable<ClassMapping> {
227 } 245 }
228 } 246 }
229 247
248 public void setFieldObfNameAndType(String oldObfName, Type obfType, String newObfName, Type newObfType) {
249 assert(newObfName != null);
250 FieldMapping fieldMapping = m_fieldsByObf.remove(getFieldKey(oldObfName, obfType));
251 assert(fieldMapping != null);
252 fieldMapping.setObfName(newObfName);
253 fieldMapping.setObfType(newObfType);
254 boolean obfWasAdded = m_fieldsByObf.put(getFieldKey(newObfName, newObfType), fieldMapping) == null;
255 assert(obfWasAdded);
256 }
257
230 //// METHODS //////// 258 //// METHODS ////////
231 259
232 public Iterable<MethodMapping> methods() { 260 public Iterable<MethodMapping> methods() {
@@ -234,6 +262,10 @@ public class ClassMapping implements Comparable<ClassMapping> {
234 return m_methodsByObf.values(); 262 return m_methodsByObf.values();
235 } 263 }
236 264
265 public boolean containsObfMethod(String obfName, Signature obfSignature) {
266 return m_methodsByObf.containsKey(getMethodKey(obfName, obfSignature));
267 }
268
237 public boolean containsDeobfMethod(String deobfName, Signature obfSignature) { 269 public boolean containsDeobfMethod(String deobfName, Signature obfSignature) {
238 return m_methodsByDeobf.containsKey(getMethodKey(deobfName, obfSignature)); 270 return m_methodsByDeobf.containsKey(getMethodKey(deobfName, obfSignature));
239 } 271 }
@@ -298,6 +330,16 @@ public class ClassMapping implements Comparable<ClassMapping> {
298 } 330 }
299 } 331 }
300 332
333 public void setMethodObfNameAndSignature(String oldObfName, Signature obfSignature, String newObfName, Signature newObfSignature) {
334 assert(newObfName != null);
335 MethodMapping methodMapping = m_methodsByObf.remove(getMethodKey(oldObfName, obfSignature));
336 assert(methodMapping != null);
337 methodMapping.setObfName(newObfName);
338 methodMapping.setObfSignature(newObfSignature);
339 boolean obfWasAdded = m_methodsByObf.put(getMethodKey(newObfName, newObfSignature), methodMapping) == null;
340 assert(obfWasAdded);
341 }
342
301 //// ARGUMENTS //////// 343 //// ARGUMENTS ////////
302 344
303 public void setArgumentName(String obfMethodName, Signature obfMethodSignature, int argumentIndex, String argumentName) { 345 public void setArgumentName(String obfMethodName, Signature obfMethodSignature, int argumentIndex, String argumentName) {
@@ -360,6 +402,48 @@ public class ClassMapping implements Comparable<ClassMapping> {
360 return m_obfFullName.compareTo(other.m_obfFullName); 402 return m_obfFullName.compareTo(other.m_obfFullName);
361 } 403 }
362 404
405 public boolean renameObfClass(String oldObfClassName, String newObfClassName) {
406
407 // rename inner classes
408 for (ClassMapping innerClassMapping : new ArrayList<>(m_innerClassesByObfSimple.values())) {
409 if (innerClassMapping.renameObfClass(oldObfClassName, newObfClassName)) {
410 boolean wasRemoved = m_innerClassesByObfSimple.remove(oldObfClassName) != null;
411 assert (wasRemoved);
412 boolean wasAdded = m_innerClassesByObfSimple.put(newObfClassName, innerClassMapping) == null;
413 assert (wasAdded);
414 }
415 }
416
417 // rename field types
418 for (FieldMapping fieldMapping : new ArrayList<>(m_fieldsByObf.values())) {
419 String oldFieldKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType());
420 if (fieldMapping.renameObfClass(oldObfClassName, newObfClassName)) {
421 boolean wasRemoved = m_fieldsByObf.remove(oldFieldKey) != null;
422 assert (wasRemoved);
423 boolean wasAdded = m_fieldsByObf.put(getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType()), fieldMapping) == null;
424 assert (wasAdded);
425 }
426 }
427
428 // rename method signatures
429 for (MethodMapping methodMapping : new ArrayList<>(m_methodsByObf.values())) {
430 String oldMethodKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature());
431 if (methodMapping.renameObfClass(oldObfClassName, newObfClassName)) {
432 boolean wasRemoved = m_methodsByObf.remove(oldMethodKey) != null;
433 assert (wasRemoved);
434 boolean wasAdded = m_methodsByObf.put(getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()), methodMapping) == null;
435 assert (wasAdded);
436 }
437 }
438
439 if (m_obfFullName.equals(oldObfClassName)) {
440 // rename this class
441 m_obfFullName = newObfClassName;
442 return true;
443 }
444 return false;
445 }
446
363 public boolean containsArgument(BehaviorEntry obfBehaviorEntry, String name) { 447 public boolean containsArgument(BehaviorEntry obfBehaviorEntry, String name) {
364 MethodMapping methodMapping = m_methodsByObf.get(getMethodKey(obfBehaviorEntry.getName(), obfBehaviorEntry.getSignature())); 448 MethodMapping methodMapping = m_methodsByObf.get(getMethodKey(obfBehaviorEntry.getName(), obfBehaviorEntry.getSignature()));
365 return methodMapping != null && methodMapping.containsArgument(name); 449 return methodMapping != null && methodMapping.containsArgument(name);
@@ -369,4 +453,7 @@ public class ClassMapping implements Comparable<ClassMapping> {
369 return name.indexOf('/') < 0 && name.indexOf('$') < 0; 453 return name.indexOf('/') < 0 && name.indexOf('$') < 0;
370 } 454 }
371 455
456 public ClassEntry getObfEntry() {
457 return new ClassEntry(m_obfFullName);
458 }
372} 459}
diff --git a/src/main/java/cuchaz/enigma/mapping/EntryFactory.java b/src/main/java/cuchaz/enigma/mapping/EntryFactory.java
index 2351dcfb..ce4b948a 100644
--- a/src/main/java/cuchaz/enigma/mapping/EntryFactory.java
+++ b/src/main/java/cuchaz/enigma/mapping/EntryFactory.java
@@ -33,6 +33,10 @@ public class EntryFactory {
33 return new ClassEntry(classMapping.getObfFullName()); 33 return new ClassEntry(classMapping.getObfFullName());
34 } 34 }
35 35
36 public static ClassEntry getDeobfClassEntry(ClassMapping classMapping) {
37 return new ClassEntry(classMapping.getDeobfName());
38 }
39
36 public static ClassEntry getSuperclassEntry(CtClass c) { 40 public static ClassEntry getSuperclassEntry(CtClass c) {
37 return new ClassEntry(Descriptor.toJvmName(c.getClassFile().getSuperclass())); 41 return new ClassEntry(Descriptor.toJvmName(c.getClassFile().getSuperclass()));
38 } 42 }
@@ -90,6 +94,10 @@ public class EntryFactory {
90 return getBehaviorEntry(new ClassEntry(className), behaviorName, new Signature(behaviorSignature)); 94 return getBehaviorEntry(new ClassEntry(className), behaviorName, new Signature(behaviorSignature));
91 } 95 }
92 96
97 public static BehaviorEntry getBehaviorEntry(String className, String behaviorName) {
98 return getBehaviorEntry(new ClassEntry(className), behaviorName);
99 }
100
93 public static BehaviorEntry getBehaviorEntry(String className) { 101 public static BehaviorEntry getBehaviorEntry(String className) {
94 return new ConstructorEntry(new ClassEntry(className)); 102 return new ConstructorEntry(new ClassEntry(className));
95 } 103 }
@@ -105,7 +113,19 @@ public class EntryFactory {
105 } 113 }
106 } 114 }
107 115
116 public static BehaviorEntry getBehaviorEntry(ClassEntry classEntry, String behaviorName) {
117 if(behaviorName.equals("<clinit>")) {
118 return new ConstructorEntry(classEntry);
119 } else {
120 throw new IllegalArgumentException("Only class initializers don't have signatures");
121 }
122 }
123
108 public static BehaviorEntry getObfBehaviorEntry(ClassEntry classEntry, MethodMapping methodMapping) { 124 public static BehaviorEntry getObfBehaviorEntry(ClassEntry classEntry, MethodMapping methodMapping) {
109 return getBehaviorEntry(classEntry, methodMapping.getObfName(), methodMapping.getObfSignature()); 125 return getBehaviorEntry(classEntry, methodMapping.getObfName(), methodMapping.getObfSignature());
110 } 126 }
127
128 public static BehaviorEntry getObfBehaviorEntry(ClassMapping classMapping, MethodMapping methodMapping) {
129 return getObfBehaviorEntry(getObfClassEntry(classMapping), methodMapping);
130 }
111} 131}
diff --git a/src/main/java/cuchaz/enigma/mapping/EntryPair.java b/src/main/java/cuchaz/enigma/mapping/EntryPair.java
new file mode 100644
index 00000000..1c93d532
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/EntryPair.java
@@ -0,0 +1,22 @@
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 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13public class EntryPair<T extends Entry> {
14
15 public T obf;
16 public T deobf;
17
18 public EntryPair(T obf, T deobf) {
19 this.obf = obf;
20 this.deobf = deobf;
21 }
22}
diff --git a/src/main/java/cuchaz/enigma/mapping/FieldMapping.java b/src/main/java/cuchaz/enigma/mapping/FieldMapping.java
index 3ec1af0d..1b596606 100644
--- a/src/main/java/cuchaz/enigma/mapping/FieldMapping.java
+++ b/src/main/java/cuchaz/enigma/mapping/FieldMapping.java
@@ -22,6 +22,17 @@ public class FieldMapping implements Comparable<FieldMapping>, MemberMapping<Fie
22 this.obfType = obfType; 22 this.obfType = obfType;
23 } 23 }
24 24
25 public FieldMapping(FieldMapping other, ClassNameReplacer obfClassNameReplacer) {
26 this.obfName = other.obfName;
27 this.deobfName = other.deobfName;
28 this.obfType = new Type(other.obfType, obfClassNameReplacer);
29 }
30
31 @Override
32 public FieldEntry getObfEntry(ClassEntry classEntry) {
33 return new FieldEntry(classEntry, this.obfName, this.obfType);
34 }
35
25 @Override 36 @Override
26 public String getObfName() { 37 public String getObfName() {
27 return this.obfName; 38 return this.obfName;
@@ -35,12 +46,40 @@ public class FieldMapping implements Comparable<FieldMapping>, MemberMapping<Fie
35 this.deobfName = NameValidator.validateFieldName(val); 46 this.deobfName = NameValidator.validateFieldName(val);
36 } 47 }
37 48
49 public void setObfName(String val) {
50 this.obfName = NameValidator.validateFieldName(val);
51 }
52
38 public Type getObfType() { 53 public Type getObfType() {
39 return this.obfType; 54 return this.obfType;
40 } 55 }
41 56
57 public void setObfType(Type val) {
58 this.obfType = val;
59 }
60
42 @Override 61 @Override
43 public int compareTo(FieldMapping other) { 62 public int compareTo(FieldMapping other) {
44 return (this.obfName + this.obfType).compareTo(other.obfName + other.obfType); 63 return (this.obfName + this.obfType).compareTo(other.obfName + other.obfType);
45 } 64 }
65
66 public boolean renameObfClass(final String oldObfClassName, final String newObfClassName) {
67 // rename obf classes in the type
68 Type newType = new Type(this.obfType, new ClassNameReplacer() {
69 @Override
70 public String replace(String className) {
71 if (className.equals(oldObfClassName)) {
72 return newObfClassName;
73 }
74 return null;
75 }
76 });
77
78 if (!newType.equals(this.obfType)) {
79 this.obfType = newType;
80 return true;
81 }
82 return false;
83 }
84
46} 85}
diff --git a/src/main/java/cuchaz/enigma/mapping/Mappings.java b/src/main/java/cuchaz/enigma/mapping/Mappings.java
index 538c67e8..171ddf16 100644
--- a/src/main/java/cuchaz/enigma/mapping/Mappings.java
+++ b/src/main/java/cuchaz/enigma/mapping/Mappings.java
@@ -13,10 +13,13 @@ package cuchaz.enigma.mapping;
13import com.google.common.collect.Lists; 13import com.google.common.collect.Lists;
14import com.google.common.collect.Maps; 14import com.google.common.collect.Maps;
15 15
16import java.util.ArrayList;
16import java.util.Collection; 17import java.util.Collection;
17import java.util.List; 18import java.util.List;
18import java.util.Map; 19import java.util.Map;
20import java.util.Set;
19 21
22import com.google.common.collect.Sets;
20import cuchaz.enigma.analysis.TranslationIndex; 23import cuchaz.enigma.analysis.TranslationIndex;
21import cuchaz.enigma.throwables.MappingConflict; 24import cuchaz.enigma.throwables.MappingConflict;
22 25
@@ -65,10 +68,23 @@ public class Mappings {
65 } 68 }
66 } 69 }
67 70
71
72 public ClassMapping getClassByObf(ClassEntry entry) {
73 return getClassByObf(entry.getName());
74 }
75
68 public ClassMapping getClassByObf(String obfName) { 76 public ClassMapping getClassByObf(String obfName) {
69 return this.classesByObf.get(obfName); 77 return this.classesByObf.get(obfName);
70 } 78 }
71 79
80 public ClassMapping getClassByDeobf(ClassEntry entry) {
81 return getClassByDeobf(entry.getName());
82 }
83
84 public ClassMapping getClassByDeobf(String deobfName) {
85 return this.classesByDeobf.get(deobfName);
86 }
87
72 public void setClassDeobfName(ClassMapping classMapping, String deobfName) { 88 public void setClassDeobfName(ClassMapping classMapping, String deobfName) {
73 if (classMapping.getDeobfName() != null) { 89 if (classMapping.getDeobfName() != null) {
74 boolean wasRemoved = this.classesByDeobf.remove(classMapping.getDeobfName()) != null; 90 boolean wasRemoved = this.classesByDeobf.remove(classMapping.getDeobfName()) != null;
@@ -120,6 +136,34 @@ public class Mappings {
120 return buf.toString(); 136 return buf.toString();
121 } 137 }
122 138
139 public void renameObfClass(String oldObfName, String newObfName) {
140 new ArrayList<>(classes()).stream().filter(classMapping -> classMapping.renameObfClass(oldObfName, newObfName)).forEach(classMapping -> {
141 boolean wasRemoved = this.classesByObf.remove(oldObfName) != null;
142 assert (wasRemoved);
143 boolean wasAdded = this.classesByObf.put(newObfName, classMapping) == null;
144 assert (wasAdded);
145 });
146 }
147
148 public Set<String> getAllObfClassNames() {
149 final Set<String> classNames = Sets.newHashSet();
150 for (ClassMapping classMapping : classes()) {
151
152 // add the class name
153 classNames.add(classMapping.getObfFullName());
154
155 // add classes from method signatures
156 for (MethodMapping methodMapping : classMapping.methods()) {
157 for (Type type : methodMapping.getObfSignature().types()) {
158 if (type.hasClass()) {
159 classNames.add(type.getClassEntry().getClassName());
160 }
161 }
162 }
163 }
164 return classNames;
165 }
166
123 public boolean containsDeobfClass(String deobfName) { 167 public boolean containsDeobfClass(String deobfName) {
124 return this.classesByDeobf.containsKey(deobfName); 168 return this.classesByDeobf.containsKey(deobfName);
125 } 169 }
diff --git a/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaReader.java b/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaReader.java
index 70f3f184..a27f72e6 100644
--- a/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaReader.java
+++ b/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaReader.java
@@ -85,7 +85,7 @@ public class MappingsEnigmaReader
85 ClassMapping classMapping; 85 ClassMapping classMapping;
86 if (indent <= 0) { 86 if (indent <= 0) {
87 // outer class 87 // outer class
88 classMapping = readClass(parts); 88 classMapping = readClass(parts, false);
89 mappings.addClassMapping(classMapping); 89 mappings.addClassMapping(classMapping);
90 } else { 90 } else {
91 91
@@ -94,7 +94,7 @@ public class MappingsEnigmaReader
94 throw new MappingParseException(lineNumber, "Unexpected CLASS entry here!"); 94 throw new MappingParseException(lineNumber, "Unexpected CLASS entry here!");
95 } 95 }
96 96
97 classMapping = readClass(parts); 97 classMapping = readClass(parts, true);
98 ((ClassMapping) mappingStack.peek()).addInnerClassMapping(classMapping); 98 ((ClassMapping) mappingStack.peek()).addInnerClassMapping(classMapping);
99 } 99 }
100 mappingStack.push(classMapping); 100 mappingStack.push(classMapping);
@@ -130,7 +130,7 @@ public class MappingsEnigmaReader
130 return new ArgumentMapping(Integer.parseInt(parts[1]), parts[2]); 130 return new ArgumentMapping(Integer.parseInt(parts[1]), parts[2]);
131 } 131 }
132 132
133 private ClassMapping readClass(String[] parts) { 133 private ClassMapping readClass(String[] parts, boolean makeSimple) {
134 if (parts.length == 2) { 134 if (parts.length == 2) {
135 return new ClassMapping(parts[1]); 135 return new ClassMapping(parts[1]);
136 } else { 136 } else {
diff --git a/src/main/java/cuchaz/enigma/mapping/MappingsRenamer.java b/src/main/java/cuchaz/enigma/mapping/MappingsRenamer.java
index afb8c978..80028137 100644
--- a/src/main/java/cuchaz/enigma/mapping/MappingsRenamer.java
+++ b/src/main/java/cuchaz/enigma/mapping/MappingsRenamer.java
@@ -10,8 +10,12 @@
10 ******************************************************************************/ 10 ******************************************************************************/
11package cuchaz.enigma.mapping; 11package cuchaz.enigma.mapping;
12 12
13import java.io.IOException;
14import java.io.ObjectOutputStream;
15import java.io.OutputStream;
13import java.util.List; 16import java.util.List;
14import java.util.Set; 17import java.util.Set;
18import java.util.zip.GZIPOutputStream;
15 19
16import cuchaz.enigma.analysis.JarIndex; 20import cuchaz.enigma.analysis.JarIndex;
17import cuchaz.enigma.throwables.IllegalNameException; 21import cuchaz.enigma.throwables.IllegalNameException;
@@ -165,6 +169,42 @@ public class MappingsRenamer {
165 classMapping.setArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex(), obf.getName()); 169 classMapping.setArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex(), obf.getName());
166 } 170 }
167 171
172 public boolean moveFieldToObfClass(ClassMapping classMapping, FieldMapping fieldMapping, ClassEntry obfClass) {
173 classMapping.removeFieldMapping(fieldMapping);
174 ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass);
175 if (!targetClassMapping.containsObfField(fieldMapping.getObfName(), fieldMapping.getObfType())) {
176 if (!targetClassMapping.containsDeobfField(fieldMapping.getDeobfName(), fieldMapping.getObfType())) {
177 targetClassMapping.addFieldMapping(fieldMapping);
178 return true;
179 } else {
180 System.err.println("WARNING: deobf field was already there: " + obfClass + "." + fieldMapping.getDeobfName());
181 }
182 }
183 return false;
184 }
185
186 public boolean moveMethodToObfClass(ClassMapping classMapping, MethodMapping methodMapping, ClassEntry obfClass) {
187 classMapping.removeMethodMapping(methodMapping);
188 ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass);
189 if (!targetClassMapping.containsObfMethod(methodMapping.getObfName(), methodMapping.getObfSignature())) {
190 if (!targetClassMapping.containsDeobfMethod(methodMapping.getDeobfName(), methodMapping.getObfSignature())) {
191 targetClassMapping.addMethodMapping(methodMapping);
192 return true;
193 } else {
194 System.err.println("WARNING: deobf method was already there: " + obfClass + "." + methodMapping.getDeobfName() + methodMapping.getObfSignature());
195 }
196 }
197 return false;
198 }
199
200 public void write(OutputStream out) throws IOException {
201 // TEMP: just use the object output for now. We can find a more efficient storage format later
202 GZIPOutputStream gzipout = new GZIPOutputStream(out);
203 ObjectOutputStream oout = new ObjectOutputStream(gzipout);
204 oout.writeObject(this);
205 gzipout.finish();
206 }
207
168 private ClassMapping getOrCreateClassMapping(ClassEntry obfClassEntry) { 208 private ClassMapping getOrCreateClassMapping(ClassEntry obfClassEntry) {
169 List<ClassMapping> mappingChain = getOrCreateClassMappingChain(obfClassEntry); 209 List<ClassMapping> mappingChain = getOrCreateClassMappingChain(obfClassEntry);
170 return mappingChain.get(mappingChain.size() - 1); 210 return mappingChain.get(mappingChain.size() - 1);
diff --git a/src/main/java/cuchaz/enigma/mapping/MemberMapping.java b/src/main/java/cuchaz/enigma/mapping/MemberMapping.java
index 590c830a..90c096fa 100644
--- a/src/main/java/cuchaz/enigma/mapping/MemberMapping.java
+++ b/src/main/java/cuchaz/enigma/mapping/MemberMapping.java
@@ -12,5 +12,7 @@ package cuchaz.enigma.mapping;
12 12
13 13
14public interface MemberMapping<T extends Entry> { 14public interface MemberMapping<T extends Entry> {
15 T getObfEntry(ClassEntry classEntry);
16
15 String getObfName(); 17 String getObfName();
16} 18}
diff --git a/src/main/java/cuchaz/enigma/mapping/MethodMapping.java b/src/main/java/cuchaz/enigma/mapping/MethodMapping.java
index 6e7c1ef9..99b9c887 100644
--- a/src/main/java/cuchaz/enigma/mapping/MethodMapping.java
+++ b/src/main/java/cuchaz/enigma/mapping/MethodMapping.java
@@ -39,6 +39,16 @@ public class MethodMapping implements Comparable<MethodMapping>, MemberMapping<B
39 this.obfSignature = obfSignature; 39 this.obfSignature = obfSignature;
40 this.arguments = Maps.newTreeMap(); 40 this.arguments = Maps.newTreeMap();
41 } 41 }
42
43 public MethodMapping(MethodMapping other, ClassNameReplacer obfClassNameReplacer) {
44 this.obfName = other.obfName;
45 this.deobfName = other.deobfName;
46 this.obfSignature = new Signature(other.obfSignature, obfClassNameReplacer);
47 this.arguments = Maps.newTreeMap();
48 for (Map.Entry<Integer,ArgumentMapping> entry : other.arguments.entrySet()) {
49 this.arguments.put(entry.getKey(), new ArgumentMapping(entry.getValue()));
50 }
51 }
42 52
43 @Override 53 @Override
44 public String getObfName() { 54 public String getObfName() {
@@ -57,6 +67,14 @@ public class MethodMapping implements Comparable<MethodMapping>, MemberMapping<B
57 return this.obfSignature; 67 return this.obfSignature;
58 } 68 }
59 69
70 public void setObfName(String name) {
71 this.obfName = NameValidator.validateMethodName(name);
72 }
73
74 public void setObfSignature(Signature val) {
75 this.obfSignature = val;
76 }
77
60 public Iterable<ArgumentMapping> arguments() { 78 public Iterable<ArgumentMapping> arguments() {
61 return this.arguments.values(); 79 return this.arguments.values();
62 } 80 }
@@ -137,4 +155,36 @@ public class MethodMapping implements Comparable<MethodMapping>, MemberMapping<B
137 } 155 }
138 return false; 156 return false;
139 } 157 }
158
159 public boolean renameObfClass(final String oldObfClassName, final String newObfClassName) {
160 // rename obf classes in the signature
161 Signature newSignature = new Signature(this.obfSignature, new ClassNameReplacer() {
162 @Override
163 public String replace(String className) {
164 if (className.equals(oldObfClassName)) {
165 return newObfClassName;
166 }
167 return null;
168 }
169 });
170
171 if (!newSignature.equals(this.obfSignature)) {
172 this.obfSignature = newSignature;
173 return true;
174 }
175 return false;
176 }
177
178 public boolean isConstructor() {
179 return this.obfName.startsWith("<");
180 }
181
182 @Override
183 public BehaviorEntry getObfEntry(ClassEntry classEntry) {
184 if (isConstructor()) {
185 return new ConstructorEntry(classEntry, this.obfSignature);
186 } else {
187 return new MethodEntry(classEntry, this.obfName, this.obfSignature);
188 }
189 }
140} 190}
diff --git a/src/main/java/cuchaz/enigma/mapping/NameValidator.java b/src/main/java/cuchaz/enigma/mapping/NameValidator.java
index 15b0314c..7be83c7f 100644
--- a/src/main/java/cuchaz/enigma/mapping/NameValidator.java
+++ b/src/main/java/cuchaz/enigma/mapping/NameValidator.java
@@ -32,6 +32,17 @@ public class NameValidator {
32 static { 32 static {
33 33
34 // java allows all kinds of weird characters... 34 // java allows all kinds of weird characters...
35 StringBuilder startChars = new StringBuilder();
36 StringBuilder partChars = new StringBuilder();
37 for (int i = Character.MIN_CODE_POINT; i <= Character.MAX_CODE_POINT; i++) {
38 if (Character.isJavaIdentifierStart(i)) {
39 startChars.appendCodePoint(i);
40 }
41 if (Character.isJavaIdentifierPart(i)) {
42 partChars.appendCodePoint(i);
43 }
44 }
45
35 String identifierRegex = "[A-Za-z_<][A-Za-z0-9_>]*"; 46 String identifierRegex = "[A-Za-z_<][A-Za-z0-9_>]*";
36 IdentifierPattern = Pattern.compile(identifierRegex); 47 IdentifierPattern = Pattern.compile(identifierRegex);
37 ClassPattern = Pattern.compile(String.format("^(%s(\\.|/))*(%s)$", identifierRegex, identifierRegex)); 48 ClassPattern = Pattern.compile(String.format("^(%s(\\.|/))*(%s)$", identifierRegex, identifierRegex));
diff --git a/src/main/java/cuchaz/enigma/mapping/SignatureUpdater.java b/src/main/java/cuchaz/enigma/mapping/SignatureUpdater.java
new file mode 100644
index 00000000..98643330
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/SignatureUpdater.java
@@ -0,0 +1,91 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import com.google.common.collect.Lists;
14
15import java.io.IOException;
16import java.io.StringReader;
17import java.util.List;
18
19public class SignatureUpdater {
20
21 public interface ClassNameUpdater {
22 String update(String className);
23 }
24
25 public static String update(String signature, ClassNameUpdater updater) {
26 try {
27 StringBuilder buf = new StringBuilder();
28
29 // read the signature character-by-character
30 StringReader reader = new StringReader(signature);
31 int i;
32 while ((i = reader.read()) != -1) {
33 char c = (char) i;
34
35 // does this character start a class name?
36 if (c == 'L') {
37 // update the class name and add it to the buffer
38 buf.append('L');
39 String className = readClass(reader);
40 if (className == null) {
41 throw new IllegalArgumentException("Malformed signature: " + signature);
42 }
43 buf.append(updater.update(className));
44 buf.append(';');
45 } else {
46 // copy the character into the buffer
47 buf.append(c);
48 }
49 }
50
51 return buf.toString();
52 } catch (IOException ex) {
53 // I'm pretty sure a StringReader will never throw one of these
54 throw new Error(ex);
55 }
56 }
57
58 private static String readClass(StringReader reader) throws IOException {
59 // read all the characters in the buffer until we hit a ';'
60 // remember to treat generics correctly
61 StringBuilder buf = new StringBuilder();
62 int depth = 0;
63 int i;
64 while ((i = reader.read()) != -1) {
65 char c = (char) i;
66
67 if (c == '<') {
68 depth++;
69 } else if (c == '>') {
70 depth--;
71 } else if (depth == 0) {
72 if (c == ';') {
73 return buf.toString();
74 } else {
75 buf.append(c);
76 }
77 }
78 }
79
80 return null;
81 }
82
83 public static List<String> getClasses(String signature) {
84 final List<String> classNames = Lists.newArrayList();
85 update(signature, className -> {
86 classNames.add(className);
87 return className;
88 });
89 return classNames;
90 }
91}
diff --git a/src/main/java/cuchaz/enigma/mapping/Translator.java b/src/main/java/cuchaz/enigma/mapping/Translator.java
index 125e03f4..bebac4e1 100644
--- a/src/main/java/cuchaz/enigma/mapping/Translator.java
+++ b/src/main/java/cuchaz/enigma/mapping/Translator.java
@@ -38,6 +38,14 @@ public class Translator {
38 this.index = index; 38 this.index = index;
39 } 39 }
40 40
41 public TranslationDirection getDirection() {
42 return direction;
43 }
44
45 public TranslationIndex getTranslationIndex() {
46 return index;
47 }
48
41 @SuppressWarnings("unchecked") 49 @SuppressWarnings("unchecked")
42 public <T extends Entry> T translateEntry(T entry) { 50 public <T extends Entry> T translateEntry(T entry) {
43 if (entry instanceof ClassEntry) { 51 if (entry instanceof ClassEntry) {
diff --git a/src/main/java/cuchaz/enigma/utils/Utils.java b/src/main/java/cuchaz/enigma/utils/Utils.java
index e391b5ab..c16c1fbf 100644
--- a/src/main/java/cuchaz/enigma/utils/Utils.java
+++ b/src/main/java/cuchaz/enigma/utils/Utils.java
@@ -88,47 +88,4 @@ public class Utils {
88 manager.mouseMoved(new MouseEvent(component, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, 0, 0, 0, false)); 88 manager.mouseMoved(new MouseEvent(component, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, 0, 0, 0, false));
89 manager.setInitialDelay(oldDelay); 89 manager.setInitialDelay(oldDelay);
90 } 90 }
91
92 public static void navigateToToken(final JEditorPane editor, final Token token, final Highlighter.HighlightPainter highlightPainter) {
93
94 // set the caret position to the token
95 editor.setCaretPosition(token.start);
96 editor.grabFocus();
97
98 try {
99 // make sure the token is visible in the scroll window
100 Rectangle start = editor.modelToView(token.start);
101 Rectangle end = editor.modelToView(token.end);
102 final Rectangle show = start.union(end);
103 show.grow(start.width * 10, start.height * 6);
104 SwingUtilities.invokeLater(() -> editor.scrollRectToVisible(show));
105 } catch (BadLocationException ex) {
106 throw new Error(ex);
107 }
108
109 // highlight the token momentarily
110 final Timer timer = new Timer(200, new ActionListener() {
111 private int m_counter = 0;
112 private Object m_highlight = null;
113
114 @Override
115 public void actionPerformed(ActionEvent event) {
116 if (m_counter % 2 == 0) {
117 try {
118 m_highlight = editor.getHighlighter().addHighlight(token.start, token.end, highlightPainter);
119 } catch (BadLocationException ex) {
120 // don't care
121 }
122 } else if (m_highlight != null) {
123 editor.getHighlighter().removeHighlight(m_highlight);
124 }
125
126 if (m_counter++ > 6) {
127 Timer timer = (Timer) event.getSource();
128 timer.stop();
129 }
130 }
131 });
132 timer.start();
133 }
134} 91}