summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/cuchaz/enigma/CommandMain.java186
-rw-r--r--src/cuchaz/enigma/Constants.java20
-rw-r--r--src/cuchaz/enigma/ConvertMain.java322
-rw-r--r--src/cuchaz/enigma/Deobfuscator.java548
-rw-r--r--src/cuchaz/enigma/ExceptionIgnorer.java34
-rw-r--r--src/cuchaz/enigma/Main.java51
-rw-r--r--src/cuchaz/enigma/MainFormatConverter.java130
-rw-r--r--src/cuchaz/enigma/TranslatingTypeLoader.java249
-rw-r--r--src/cuchaz/enigma/Util.java104
-rw-r--r--src/cuchaz/enigma/analysis/Access.java43
-rw-r--r--src/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java93
-rw-r--r--src/cuchaz/enigma/analysis/BridgeMarker.java43
-rw-r--r--src/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java80
-rw-r--r--src/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java85
-rw-r--r--src/cuchaz/enigma/analysis/EntryReference.java126
-rw-r--r--src/cuchaz/enigma/analysis/EntryRenamer.java192
-rw-r--r--src/cuchaz/enigma/analysis/FieldReferenceTreeNode.java81
-rw-r--r--src/cuchaz/enigma/analysis/JarClassIterator.java137
-rw-r--r--src/cuchaz/enigma/analysis/JarIndex.java837
-rw-r--r--src/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java101
-rw-r--r--src/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java114
-rw-r--r--src/cuchaz/enigma/analysis/ReferenceTreeNode.java18
-rw-r--r--src/cuchaz/enigma/analysis/RelatedMethodChecker.java106
-rw-r--r--src/cuchaz/enigma/analysis/SourceIndex.java184
-rw-r--r--src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java150
-rw-r--r--src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java112
-rw-r--r--src/cuchaz/enigma/analysis/SourceIndexVisitor.java452
-rw-r--r--src/cuchaz/enigma/analysis/Token.java56
-rw-r--r--src/cuchaz/enigma/analysis/TranslationIndex.java298
-rw-r--r--src/cuchaz/enigma/analysis/TreeDumpVisitor.java512
-rw-r--r--src/cuchaz/enigma/bytecode/CheckCastIterator.java127
-rw-r--r--src/cuchaz/enigma/bytecode/ClassProtectifier.java51
-rw-r--r--src/cuchaz/enigma/bytecode/ClassPublifier.java51
-rw-r--r--src/cuchaz/enigma/bytecode/ClassRenamer.java544
-rw-r--r--src/cuchaz/enigma/bytecode/ClassTranslator.java157
-rw-r--r--src/cuchaz/enigma/bytecode/ConstPoolEditor.java263
-rw-r--r--src/cuchaz/enigma/bytecode/InfoType.java317
-rw-r--r--src/cuchaz/enigma/bytecode/InnerClassWriter.java132
-rw-r--r--src/cuchaz/enigma/bytecode/LocalVariableRenamer.java123
-rw-r--r--src/cuchaz/enigma/bytecode/MethodParameterWriter.java70
-rw-r--r--src/cuchaz/enigma/bytecode/MethodParametersAttribute.java86
-rw-r--r--src/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java55
-rw-r--r--src/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java156
-rw-r--r--src/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java74
-rw-r--r--src/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java74
-rw-r--r--src/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java74
-rw-r--r--src/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java55
-rw-r--r--src/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java74
-rw-r--r--src/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java55
-rw-r--r--src/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java28
-rw-r--r--src/cuchaz/enigma/convert/ClassForest.java60
-rw-r--r--src/cuchaz/enigma/convert/ClassIdentifier.java54
-rw-r--r--src/cuchaz/enigma/convert/ClassIdentity.java468
-rw-r--r--src/cuchaz/enigma/convert/ClassMatch.java88
-rw-r--r--src/cuchaz/enigma/convert/ClassMatches.java163
-rw-r--r--src/cuchaz/enigma/convert/ClassMatching.java155
-rw-r--r--src/cuchaz/enigma/convert/ClassNamer.java66
-rw-r--r--src/cuchaz/enigma/convert/FieldMatches.java155
-rw-r--r--src/cuchaz/enigma/convert/MappingsConverter.java559
-rw-r--r--src/cuchaz/enigma/convert/MatchesReader.java113
-rw-r--r--src/cuchaz/enigma/convert/MatchesWriter.java121
-rw-r--r--src/cuchaz/enigma/convert/MemberMatches.java159
-rw-r--r--src/cuchaz/enigma/gui/AboutDialog.java86
-rw-r--r--src/cuchaz/enigma/gui/BoxHighlightPainter.java64
-rw-r--r--src/cuchaz/enigma/gui/BrowserCaret.java45
-rw-r--r--src/cuchaz/enigma/gui/ClassListCellRenderer.java36
-rw-r--r--src/cuchaz/enigma/gui/ClassMatchingGui.java589
-rw-r--r--src/cuchaz/enigma/gui/ClassSelector.java293
-rw-r--r--src/cuchaz/enigma/gui/ClassSelectorClassNode.java50
-rw-r--r--src/cuchaz/enigma/gui/ClassSelectorPackageNode.java45
-rw-r--r--src/cuchaz/enigma/gui/CodeReader.java222
-rw-r--r--src/cuchaz/enigma/gui/CrashDialog.java101
-rw-r--r--src/cuchaz/enigma/gui/DeobfuscatedHighlightPainter.java21
-rw-r--r--src/cuchaz/enigma/gui/Gui.java1122
-rw-r--r--src/cuchaz/enigma/gui/GuiController.java358
-rw-r--r--src/cuchaz/enigma/gui/GuiTricks.java56
-rw-r--r--src/cuchaz/enigma/gui/MemberMatchingGui.java499
-rw-r--r--src/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java21
-rw-r--r--src/cuchaz/enigma/gui/OtherHighlightPainter.java21
-rw-r--r--src/cuchaz/enigma/gui/ProgressDialog.java105
-rw-r--r--src/cuchaz/enigma/gui/ReadableToken.java36
-rw-r--r--src/cuchaz/enigma/gui/RenameListener.java17
-rw-r--r--src/cuchaz/enigma/gui/ScoredClassEntry.java30
-rw-r--r--src/cuchaz/enigma/gui/SelectionHighlightPainter.java34
-rw-r--r--src/cuchaz/enigma/gui/TokenListCellRenderer.java38
-rw-r--r--src/cuchaz/enigma/mapping/ArgumentEntry.java116
-rw-r--r--src/cuchaz/enigma/mapping/ArgumentMapping.java49
-rw-r--r--src/cuchaz/enigma/mapping/BehaviorEntry.java15
-rw-r--r--src/cuchaz/enigma/mapping/ClassEntry.java172
-rw-r--r--src/cuchaz/enigma/mapping/ClassMapping.java460
-rw-r--r--src/cuchaz/enigma/mapping/ClassNameReplacer.java15
-rw-r--r--src/cuchaz/enigma/mapping/ConstructorEntry.java116
-rw-r--r--src/cuchaz/enigma/mapping/Entry.java18
-rw-r--r--src/cuchaz/enigma/mapping/EntryFactory.java166
-rw-r--r--src/cuchaz/enigma/mapping/EntryPair.java22
-rw-r--r--src/cuchaz/enigma/mapping/FieldEntry.java99
-rw-r--r--src/cuchaz/enigma/mapping/FieldMapping.java89
-rw-r--r--src/cuchaz/enigma/mapping/IllegalNameException.java44
-rw-r--r--src/cuchaz/enigma/mapping/MappingParseException.java29
-rw-r--r--src/cuchaz/enigma/mapping/Mappings.java216
-rw-r--r--src/cuchaz/enigma/mapping/MappingsChecker.java107
-rw-r--r--src/cuchaz/enigma/mapping/MappingsReader.java134
-rw-r--r--src/cuchaz/enigma/mapping/MappingsRenamer.java237
-rw-r--r--src/cuchaz/enigma/mapping/MappingsWriter.java88
-rw-r--r--src/cuchaz/enigma/mapping/MemberMapping.java17
-rw-r--r--src/cuchaz/enigma/mapping/MethodEntry.java104
-rw-r--r--src/cuchaz/enigma/mapping/MethodMapping.java191
-rw-r--r--src/cuchaz/enigma/mapping/NameValidator.java80
-rw-r--r--src/cuchaz/enigma/mapping/ProcyonEntryFactory.java55
-rw-r--r--src/cuchaz/enigma/mapping/Signature.java117
-rw-r--r--src/cuchaz/enigma/mapping/SignatureUpdater.java94
-rw-r--r--src/cuchaz/enigma/mapping/TranslationDirection.java29
-rw-r--r--src/cuchaz/enigma/mapping/Translator.java289
-rw-r--r--src/cuchaz/enigma/mapping/Type.java247
114 files changed, 17495 insertions, 0 deletions
diff --git a/src/cuchaz/enigma/CommandMain.java b/src/cuchaz/enigma/CommandMain.java
new file mode 100644
index 00000000..540cfb95
--- /dev/null
+++ b/src/cuchaz/enigma/CommandMain.java
@@ -0,0 +1,186 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import java.io.File;
14import java.io.FileReader;
15import java.util.jar.JarFile;
16
17import cuchaz.enigma.Deobfuscator.ProgressListener;
18import cuchaz.enigma.mapping.Mappings;
19import cuchaz.enigma.mapping.MappingsReader;
20
21public class CommandMain {
22
23 public static class ConsoleProgressListener implements ProgressListener {
24
25 private static final int ReportTime = 5000; // 5s
26
27 private int m_totalWork;
28 private long m_startTime;
29 private long m_lastReportTime;
30
31 @Override
32 public void init(int totalWork, String title) {
33 m_totalWork = totalWork;
34 m_startTime = System.currentTimeMillis();
35 m_lastReportTime = m_startTime;
36 System.out.println(title);
37 }
38
39 @Override
40 public void onProgress(int numDone, String message) {
41
42 long now = System.currentTimeMillis();
43 boolean isLastUpdate = numDone == m_totalWork;
44 boolean shouldReport = isLastUpdate || now - m_lastReportTime > ReportTime;
45
46 if (shouldReport) {
47 int percent = numDone*100/m_totalWork;
48 System.out.println(String.format("\tProgress: %3d%%", percent));
49 m_lastReportTime = now;
50 }
51 if (isLastUpdate) {
52 double elapsedSeconds = (now - m_startTime)/1000;
53 System.out.println(String.format("Finished in %.1f seconds", elapsedSeconds));
54 }
55 }
56 }
57
58 public static void main(String[] args)
59 throws Exception {
60
61 try {
62
63 // process the command
64 String command = getArg(args, 0, "command", true);
65 if (command.equalsIgnoreCase("deobfuscate")) {
66 deobfuscate(args);
67 } else if (command.equalsIgnoreCase("decompile")) {
68 decompile(args);
69 } else if (command.equalsIgnoreCase("protectify")) {
70 protectify(args);
71 } else if (command.equalsIgnoreCase("publify")) {
72 publify(args);
73 } else {
74 throw new IllegalArgumentException("Command not recognized: " + command);
75 }
76 } catch (IllegalArgumentException ex) {
77 System.out.println(ex.getMessage());
78 printHelp();
79 }
80 }
81
82 private static void printHelp() {
83 System.out.println(String.format("%s - %s", Constants.Name, Constants.Version));
84 System.out.println("Usage:");
85 System.out.println("\tjava -cp enigma.jar cuchaz.enigma.CommandMain <command>");
86 System.out.println("\twhere <command> is one of:");
87 System.out.println("\t\tdeobfuscate <in jar> <out jar> [<mappings file>]");
88 System.out.println("\t\tdecompile <in jar> <out folder> [<mappings file>]");
89 System.out.println("\t\tprotectify <in jar> <out jar>");
90 }
91
92 private static void decompile(String[] args)
93 throws Exception {
94 File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true));
95 File fileJarOut = getWritableFolder(getArg(args, 2, "out folder", true));
96 File fileMappings = getReadableFile(getArg(args, 3, "mappings file", false));
97 Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn));
98 deobfuscator.writeSources(fileJarOut, new ConsoleProgressListener());
99 }
100
101 private static void deobfuscate(String[] args)
102 throws Exception {
103 File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true));
104 File fileJarOut = getWritableFile(getArg(args, 2, "out jar", true));
105 File fileMappings = getReadableFile(getArg(args, 3, "mappings file", false));
106 Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn));
107 deobfuscator.writeJar(fileJarOut, new ConsoleProgressListener());
108 }
109
110 private static void protectify(String[] args)
111 throws Exception {
112 File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true));
113 File fileJarOut = getWritableFile(getArg(args, 2, "out jar", true));
114 Deobfuscator deobfuscator = getDeobfuscator(null, new JarFile(fileJarIn));
115 deobfuscator.protectifyJar(fileJarOut, new ConsoleProgressListener());
116 }
117
118 private static void publify(String[] args)
119 throws Exception {
120 File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true));
121 File fileJarOut = getWritableFile(getArg(args, 2, "out jar", true));
122 Deobfuscator deobfuscator = getDeobfuscator(null, new JarFile(fileJarIn));
123 deobfuscator.publifyJar(fileJarOut, new ConsoleProgressListener());
124 }
125
126 private static Deobfuscator getDeobfuscator(File fileMappings, JarFile jar)
127 throws Exception {
128 System.out.println("Reading jar...");
129 Deobfuscator deobfuscator = new Deobfuscator(jar);
130 if (fileMappings != null) {
131 System.out.println("Reading mappings...");
132 Mappings mappings = new MappingsReader().read(new FileReader(fileMappings));
133 deobfuscator.setMappings(mappings);
134 }
135 return deobfuscator;
136 }
137
138 private static String getArg(String[] args, int i, String name, boolean required) {
139 if (i >= args.length) {
140 if (required) {
141 throw new IllegalArgumentException(name + " is required");
142 } else {
143 return null;
144 }
145 }
146 return args[i];
147 }
148
149 private static File getWritableFile(String path) {
150 if (path == null) {
151 return null;
152 }
153 File file = new File(path).getAbsoluteFile();
154 File dir = file.getParentFile();
155 if (dir == null) {
156 throw new IllegalArgumentException("Cannot write to folder: " + dir);
157 }
158 // quick fix to avoid stupid stuff in Gradle code
159 if (!dir.isDirectory()) {
160 dir.mkdirs();
161 }
162 return file;
163 }
164
165 private static File getWritableFolder(String path) {
166 if (path == null) {
167 return null;
168 }
169 File dir = new File(path).getAbsoluteFile();
170 if (!dir.exists()) {
171 throw new IllegalArgumentException("Cannot write to folder: " + dir);
172 }
173 return dir;
174 }
175
176 private static File getReadableFile(String path) {
177 if (path == null) {
178 return null;
179 }
180 File file = new File(path).getAbsoluteFile();
181 if (!file.exists()) {
182 throw new IllegalArgumentException("Cannot find file: " + file.getAbsolutePath());
183 }
184 return file;
185 }
186}
diff --git a/src/cuchaz/enigma/Constants.java b/src/cuchaz/enigma/Constants.java
new file mode 100644
index 00000000..951fa8f3
--- /dev/null
+++ b/src/cuchaz/enigma/Constants.java
@@ -0,0 +1,20 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13public class Constants {
14 public static final String Name = "Enigma";
15 public static final String Version = "0.10.4 beta";
16 public static final String Url = "http://www.cuchazinteractive.com/enigma";
17 public static final int MiB = 1024 * 1024; // 1 mebibyte
18 public static final int KiB = 1024; // 1 kebibyte
19 public static final String NonePackage = "none";
20}
diff --git a/src/cuchaz/enigma/ConvertMain.java b/src/cuchaz/enigma/ConvertMain.java
new file mode 100644
index 00000000..17bd2f80
--- /dev/null
+++ b/src/cuchaz/enigma/ConvertMain.java
@@ -0,0 +1,322 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import java.io.File;
14import java.io.FileReader;
15import java.io.FileWriter;
16import java.io.IOException;
17import java.util.jar.JarFile;
18
19import cuchaz.enigma.convert.ClassMatches;
20import cuchaz.enigma.convert.MappingsConverter;
21import cuchaz.enigma.convert.MatchesReader;
22import cuchaz.enigma.convert.MatchesWriter;
23import cuchaz.enigma.convert.MemberMatches;
24import cuchaz.enigma.gui.ClassMatchingGui;
25import cuchaz.enigma.gui.MemberMatchingGui;
26import cuchaz.enigma.mapping.BehaviorEntry;
27import cuchaz.enigma.mapping.ClassEntry;
28import cuchaz.enigma.mapping.ClassMapping;
29import cuchaz.enigma.mapping.FieldEntry;
30import cuchaz.enigma.mapping.FieldMapping;
31import cuchaz.enigma.mapping.MappingParseException;
32import cuchaz.enigma.mapping.Mappings;
33import cuchaz.enigma.mapping.MappingsChecker;
34import cuchaz.enigma.mapping.MappingsReader;
35import cuchaz.enigma.mapping.MappingsWriter;
36import cuchaz.enigma.mapping.MethodMapping;
37
38
39public class ConvertMain {
40
41 public static void main(String[] args)
42 throws IOException, MappingParseException {
43
44 // init files
45 File home = new File(System.getProperty("user.home"));
46 JarFile sourceJar = new JarFile(new File(home, ".minecraft/versions/1.8/1.8.jar"));
47 JarFile destJar = new JarFile(new File(home, ".minecraft/versions/1.8.3/1.8.3.jar"));
48 File inMappingsFile = new File("../Enigma Mappings/1.8.mappings");
49 File outMappingsFile = new File("../Enigma Mappings/1.8.3.mappings");
50 Mappings mappings = new MappingsReader().read(new FileReader(inMappingsFile));
51 File classMatchesFile = new File(inMappingsFile.getName() + ".class.matches");
52 File fieldMatchesFile = new File(inMappingsFile.getName() + ".field.matches");
53 File methodMatchesFile = new File(inMappingsFile.getName() + ".method.matches");
54
55 // match classes
56 //computeClassMatches(classMatchesFile, sourceJar, destJar, mappings);
57 //editClasssMatches(classMatchesFile, sourceJar, destJar, mappings);
58 //convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile);
59
60 // match fields
61 //computeFieldMatches(fieldMatchesFile, destJar, outMappingsFile, classMatchesFile);
62 //editFieldMatches(sourceJar, destJar, outMappingsFile, mappings, classMatchesFile, fieldMatchesFile);
63 //convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile);
64
65 // match methods/constructors
66 //computeMethodMatches(methodMatchesFile, destJar, outMappingsFile, classMatchesFile);
67 //editMethodMatches(sourceJar, destJar, outMappingsFile, mappings, classMatchesFile, methodMatchesFile);
68 convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile, methodMatchesFile);
69 }
70
71 private static void computeClassMatches(File classMatchesFile, JarFile sourceJar, JarFile destJar, Mappings mappings)
72 throws IOException {
73 ClassMatches classMatches = MappingsConverter.computeClassMatches(sourceJar, destJar, mappings);
74 MatchesWriter.writeClasses(classMatches, classMatchesFile);
75 System.out.println("Wrote:\n\t" + classMatchesFile.getAbsolutePath());
76 }
77
78 private static void editClasssMatches(final File classMatchesFile, JarFile sourceJar, JarFile destJar, Mappings mappings)
79 throws IOException {
80 System.out.println("Reading class matches...");
81 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
82 Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
83 deobfuscators.source.setMappings(mappings);
84 System.out.println("Starting GUI...");
85 new ClassMatchingGui(classMatches, deobfuscators.source, deobfuscators.dest).setSaveListener(new ClassMatchingGui.SaveListener() {
86 @Override
87 public void save(ClassMatches matches) {
88 try {
89 MatchesWriter.writeClasses(matches, classMatchesFile);
90 } catch (IOException ex) {
91 throw new Error(ex);
92 }
93 }
94 });
95 }
96
97 private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchesFile)
98 throws IOException {
99 System.out.println("Reading class matches...");
100 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
101 Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
102 deobfuscators.source.setMappings(mappings);
103
104 Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.dest);
105
106 try (FileWriter out = new FileWriter(outMappingsFile)) {
107 new MappingsWriter().write(out, newMappings);
108 }
109 System.out.println("Write converted mappings to: " + outMappingsFile.getAbsolutePath());
110 }
111
112 private static void computeFieldMatches(File memberMatchesFile, JarFile destJar, File destMappingsFile, File classMatchesFile)
113 throws IOException, MappingParseException {
114
115 System.out.println("Reading class matches...");
116 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
117 System.out.println("Reading mappings...");
118 Mappings destMappings = new MappingsReader().read(new FileReader(destMappingsFile));
119 System.out.println("Indexing dest jar...");
120 Deobfuscator destDeobfuscator = new Deobfuscator(destJar);
121
122 System.out.println("Writing matches...");
123
124 // get the matched and unmatched mappings
125 MemberMatches<FieldEntry> fieldMatches = MappingsConverter.computeMemberMatches(
126 destDeobfuscator,
127 destMappings,
128 classMatches,
129 MappingsConverter.getFieldDoer()
130 );
131
132 MatchesWriter.writeMembers(fieldMatches, memberMatchesFile);
133 System.out.println("Wrote:\n\t" + memberMatchesFile.getAbsolutePath());
134 }
135
136 private static void editFieldMatches(JarFile sourceJar, JarFile destJar, File destMappingsFile, Mappings sourceMappings, File classMatchesFile, final File fieldMatchesFile)
137 throws IOException, MappingParseException {
138
139 System.out.println("Reading matches...");
140 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
141 MemberMatches<FieldEntry> fieldMatches = MatchesReader.readMembers(fieldMatchesFile);
142
143 // prep deobfuscators
144 Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
145 deobfuscators.source.setMappings(sourceMappings);
146 Mappings destMappings = new MappingsReader().read(new FileReader(destMappingsFile));
147 MappingsChecker checker = new MappingsChecker(deobfuscators.dest.getJarIndex());
148 checker.dropBrokenMappings(destMappings);
149 deobfuscators.dest.setMappings(destMappings);
150
151 new MemberMatchingGui<FieldEntry>(classMatches, fieldMatches, deobfuscators.source, deobfuscators.dest).setSaveListener(new MemberMatchingGui.SaveListener<FieldEntry>() {
152 @Override
153 public void save(MemberMatches<FieldEntry> matches) {
154 try {
155 MatchesWriter.writeMembers(matches, fieldMatchesFile);
156 } catch (IOException ex) {
157 throw new Error(ex);
158 }
159 }
160 });
161 }
162
163 private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchesFile, File fieldMatchesFile)
164 throws IOException {
165
166 System.out.println("Reading matches...");
167 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
168 MemberMatches<FieldEntry> fieldMatches = MatchesReader.readMembers(fieldMatchesFile);
169
170 Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
171 deobfuscators.source.setMappings(mappings);
172
173 // apply matches
174 Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.dest);
175 MappingsConverter.applyMemberMatches(newMappings, classMatches, fieldMatches, MappingsConverter.getFieldDoer());
176
177 // write out the converted mappings
178 try (FileWriter out = new FileWriter(outMappingsFile)) {
179 new MappingsWriter().write(out, newMappings);
180 }
181 System.out.println("Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath());
182 }
183
184
185 private static void computeMethodMatches(File methodMatchesFile, JarFile destJar, File destMappingsFile, File classMatchesFile)
186 throws IOException, MappingParseException {
187
188 System.out.println("Reading class matches...");
189 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
190 System.out.println("Reading mappings...");
191 Mappings destMappings = new MappingsReader().read(new FileReader(destMappingsFile));
192 System.out.println("Indexing dest jar...");
193 Deobfuscator destDeobfuscator = new Deobfuscator(destJar);
194
195 System.out.println("Writing method matches...");
196
197 // get the matched and unmatched mappings
198 MemberMatches<BehaviorEntry> methodMatches = MappingsConverter.computeMemberMatches(
199 destDeobfuscator,
200 destMappings,
201 classMatches,
202 MappingsConverter.getMethodDoer()
203 );
204
205 MatchesWriter.writeMembers(methodMatches, methodMatchesFile);
206 System.out.println("Wrote:\n\t" + methodMatchesFile.getAbsolutePath());
207 }
208
209 private static void editMethodMatches(JarFile sourceJar, JarFile destJar, File destMappingsFile, Mappings sourceMappings, File classMatchesFile, final File methodMatchesFile)
210 throws IOException, MappingParseException {
211
212 System.out.println("Reading matches...");
213 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
214 MemberMatches<BehaviorEntry> methodMatches = MatchesReader.readMembers(methodMatchesFile);
215
216 // prep deobfuscators
217 Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
218 deobfuscators.source.setMappings(sourceMappings);
219 Mappings destMappings = new MappingsReader().read(new FileReader(destMappingsFile));
220 MappingsChecker checker = new MappingsChecker(deobfuscators.dest.getJarIndex());
221 checker.dropBrokenMappings(destMappings);
222 deobfuscators.dest.setMappings(destMappings);
223
224 new MemberMatchingGui<BehaviorEntry>(classMatches, methodMatches, deobfuscators.source, deobfuscators.dest).setSaveListener(new MemberMatchingGui.SaveListener<BehaviorEntry>() {
225 @Override
226 public void save(MemberMatches<BehaviorEntry> matches) {
227 try {
228 MatchesWriter.writeMembers(matches, methodMatchesFile);
229 } catch (IOException ex) {
230 throw new Error(ex);
231 }
232 }
233 });
234 }
235
236 private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchesFile, File fieldMatchesFile, File methodMatchesFile)
237 throws IOException {
238
239 System.out.println("Reading matches...");
240 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
241 MemberMatches<FieldEntry> fieldMatches = MatchesReader.readMembers(fieldMatchesFile);
242 MemberMatches<BehaviorEntry> methodMatches = MatchesReader.readMembers(methodMatchesFile);
243
244 Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
245 deobfuscators.source.setMappings(mappings);
246
247 // apply matches
248 Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.dest);
249 MappingsConverter.applyMemberMatches(newMappings, classMatches, fieldMatches, MappingsConverter.getFieldDoer());
250 MappingsConverter.applyMemberMatches(newMappings, classMatches, methodMatches, MappingsConverter.getMethodDoer());
251
252 // check the final mappings
253 MappingsChecker checker = new MappingsChecker(deobfuscators.dest.getJarIndex());
254 checker.dropBrokenMappings(newMappings);
255
256 for (java.util.Map.Entry<ClassEntry,ClassMapping> mapping : checker.getDroppedClassMappings().entrySet()) {
257 System.out.println("WARNING: Broken class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")");
258 }
259 for (java.util.Map.Entry<ClassEntry,ClassMapping> mapping : checker.getDroppedInnerClassMappings().entrySet()) {
260 System.out.println("WARNING: Broken inner class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")");
261 }
262 for (java.util.Map.Entry<FieldEntry,FieldMapping> mapping : checker.getDroppedFieldMappings().entrySet()) {
263 System.out.println("WARNING: Broken field entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")");
264 }
265 for (java.util.Map.Entry<BehaviorEntry,MethodMapping> mapping : checker.getDroppedMethodMappings().entrySet()) {
266 System.out.println("WARNING: Broken behavior entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")");
267 }
268
269 // write out the converted mappings
270 try (FileWriter out = new FileWriter(outMappingsFile)) {
271 new MappingsWriter().write(out, newMappings);
272 }
273 System.out.println("Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath());
274 }
275
276 private static class Deobfuscators {
277
278 public Deobfuscator source;
279 public Deobfuscator dest;
280
281 public Deobfuscators(JarFile sourceJar, JarFile destJar) {
282 System.out.println("Indexing source jar...");
283 IndexerThread sourceIndexer = new IndexerThread(sourceJar);
284 sourceIndexer.start();
285 System.out.println("Indexing dest jar...");
286 IndexerThread destIndexer = new IndexerThread(destJar);
287 destIndexer.start();
288 sourceIndexer.joinOrBail();
289 destIndexer.joinOrBail();
290 source = sourceIndexer.deobfuscator;
291 dest = destIndexer.deobfuscator;
292 }
293 }
294
295 private static class IndexerThread extends Thread {
296
297 private JarFile m_jarFile;
298 public Deobfuscator deobfuscator;
299
300 public IndexerThread(JarFile jarFile) {
301 m_jarFile = jarFile;
302 deobfuscator = null;
303 }
304
305 public void joinOrBail() {
306 try {
307 join();
308 } catch (InterruptedException ex) {
309 throw new Error(ex);
310 }
311 }
312
313 @Override
314 public void run() {
315 try {
316 deobfuscator = new Deobfuscator(m_jarFile);
317 } catch (IOException ex) {
318 throw new Error(ex);
319 }
320 }
321 }
322}
diff --git a/src/cuchaz/enigma/Deobfuscator.java b/src/cuchaz/enigma/Deobfuscator.java
new file mode 100644
index 00000000..08a974aa
--- /dev/null
+++ b/src/cuchaz/enigma/Deobfuscator.java
@@ -0,0 +1,548 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import java.io.File;
14import java.io.FileOutputStream;
15import java.io.FileWriter;
16import java.io.IOException;
17import java.io.StringWriter;
18import java.util.List;
19import java.util.Map;
20import java.util.Set;
21import java.util.jar.JarEntry;
22import java.util.jar.JarFile;
23import java.util.jar.JarOutputStream;
24
25import javassist.CtClass;
26import javassist.bytecode.Descriptor;
27
28import com.google.common.collect.Maps;
29import com.google.common.collect.Sets;
30import com.strobel.assembler.metadata.MetadataSystem;
31import com.strobel.assembler.metadata.TypeDefinition;
32import com.strobel.assembler.metadata.TypeReference;
33import com.strobel.decompiler.DecompilerContext;
34import com.strobel.decompiler.DecompilerSettings;
35import com.strobel.decompiler.PlainTextOutput;
36import com.strobel.decompiler.languages.java.JavaOutputVisitor;
37import com.strobel.decompiler.languages.java.ast.AstBuilder;
38import com.strobel.decompiler.languages.java.ast.CompilationUnit;
39import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor;
40
41import cuchaz.enigma.analysis.EntryReference;
42import cuchaz.enigma.analysis.JarClassIterator;
43import cuchaz.enigma.analysis.JarIndex;
44import cuchaz.enigma.analysis.SourceIndex;
45import cuchaz.enigma.analysis.SourceIndexVisitor;
46import cuchaz.enigma.analysis.Token;
47import cuchaz.enigma.bytecode.ClassProtectifier;
48import cuchaz.enigma.bytecode.ClassPublifier;
49import cuchaz.enigma.mapping.ArgumentEntry;
50import cuchaz.enigma.mapping.BehaviorEntry;
51import cuchaz.enigma.mapping.ClassEntry;
52import cuchaz.enigma.mapping.ClassMapping;
53import cuchaz.enigma.mapping.ConstructorEntry;
54import cuchaz.enigma.mapping.Entry;
55import cuchaz.enigma.mapping.FieldEntry;
56import cuchaz.enigma.mapping.FieldMapping;
57import cuchaz.enigma.mapping.Mappings;
58import cuchaz.enigma.mapping.MappingsChecker;
59import cuchaz.enigma.mapping.MappingsRenamer;
60import cuchaz.enigma.mapping.MethodEntry;
61import cuchaz.enigma.mapping.MethodMapping;
62import cuchaz.enigma.mapping.TranslationDirection;
63import cuchaz.enigma.mapping.Translator;
64
65public class Deobfuscator {
66
67 public interface ProgressListener {
68 void init(int totalWork, String title);
69 void onProgress(int numDone, String message);
70 }
71
72 private JarFile m_jar;
73 private DecompilerSettings m_settings;
74 private JarIndex m_jarIndex;
75 private Mappings m_mappings;
76 private MappingsRenamer m_renamer;
77 private Map<TranslationDirection,Translator> m_translatorCache;
78
79 public Deobfuscator(JarFile jar) throws IOException {
80 m_jar = jar;
81
82 // build the jar index
83 m_jarIndex = new JarIndex();
84 m_jarIndex.indexJar(m_jar, true);
85
86 // config the decompiler
87 m_settings = DecompilerSettings.javaDefaults();
88 m_settings.setMergeVariables(true);
89 m_settings.setForceExplicitImports(true);
90 m_settings.setForceExplicitTypeArguments(true);
91 m_settings.setShowDebugLineNumbers(true);
92 // DEBUG
93 //m_settings.setShowSyntheticMembers(true);
94
95 // init defaults
96 m_translatorCache = Maps.newTreeMap();
97
98 // init mappings
99 setMappings(new Mappings());
100 }
101
102 public JarFile getJar() {
103 return m_jar;
104 }
105
106 public String getJarName() {
107 return m_jar.getName();
108 }
109
110 public JarIndex getJarIndex() {
111 return m_jarIndex;
112 }
113
114 public Mappings getMappings() {
115 return m_mappings;
116 }
117
118 public void setMappings(Mappings val) {
119 setMappings(val, true);
120 }
121
122 public void setMappings(Mappings val, boolean warnAboutDrops) {
123 if (val == null) {
124 val = new Mappings();
125 }
126
127 // drop mappings that don't match the jar
128 MappingsChecker checker = new MappingsChecker(m_jarIndex);
129 checker.dropBrokenMappings(val);
130 if (warnAboutDrops) {
131 for (java.util.Map.Entry<ClassEntry,ClassMapping> mapping : checker.getDroppedClassMappings().entrySet()) {
132 System.out.println("WARNING: Couldn't find class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped.");
133 }
134 for (java.util.Map.Entry<ClassEntry,ClassMapping> mapping : checker.getDroppedInnerClassMappings().entrySet()) {
135 System.out.println("WARNING: Couldn't find inner class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped.");
136 }
137 for (java.util.Map.Entry<FieldEntry,FieldMapping> mapping : checker.getDroppedFieldMappings().entrySet()) {
138 System.out.println("WARNING: Couldn't find field entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped.");
139 }
140 for (java.util.Map.Entry<BehaviorEntry,MethodMapping> mapping : checker.getDroppedMethodMappings().entrySet()) {
141 System.out.println("WARNING: Couldn't find behavior entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped.");
142 }
143 }
144
145 // check for related method inconsistencies
146 if (checker.getRelatedMethodChecker().hasProblems()) {
147 throw new Error("Related methods are inconsistent! Need to fix the mappings manually.\n" + checker.getRelatedMethodChecker().getReport());
148 }
149
150 m_mappings = val;
151 m_renamer = new MappingsRenamer(m_jarIndex, val);
152 m_translatorCache.clear();
153 }
154
155 public Translator getTranslator(TranslationDirection direction) {
156 Translator translator = m_translatorCache.get(direction);
157 if (translator == null) {
158 translator = m_mappings.getTranslator(direction, m_jarIndex.getTranslationIndex());
159 m_translatorCache.put(direction, translator);
160 }
161 return translator;
162 }
163
164 public void getSeparatedClasses(List<ClassEntry> obfClasses, List<ClassEntry> deobfClasses) {
165 for (ClassEntry obfClassEntry : m_jarIndex.getObfClassEntries()) {
166 // skip inner classes
167 if (obfClassEntry.isInnerClass()) {
168 continue;
169 }
170
171 // separate the classes
172 ClassEntry deobfClassEntry = deobfuscateEntry(obfClassEntry);
173 if (!deobfClassEntry.equals(obfClassEntry)) {
174 // if the class has a mapping, clearly it's deobfuscated
175 deobfClasses.add(deobfClassEntry);
176 } else if (!obfClassEntry.getPackageName().equals(Constants.NonePackage)) {
177 // also call it deobufscated if it's not in the none package
178 deobfClasses.add(obfClassEntry);
179 } else {
180 // otherwise, assume it's still obfuscated
181 obfClasses.add(obfClassEntry);
182 }
183 }
184 }
185
186 public CompilationUnit getSourceTree(String className) {
187
188 // we don't know if this class name is obfuscated or deobfuscated
189 // we need to tell the decompiler the deobfuscated name so it doesn't get freaked out
190 // the decompiler only sees classes after deobfuscation, so we need to load it by the deobfuscated name if there is one
191
192 // first, assume class name is deobf
193 String deobfClassName = className;
194
195 // if it wasn't actually deobf, then we can find a mapping for it and get the deobf name
196 ClassMapping classMapping = m_mappings.getClassByObf(className);
197 if (classMapping != null && classMapping.getDeobfName() != null) {
198 deobfClassName = classMapping.getDeobfName();
199 }
200
201 // set the type loader
202 TranslatingTypeLoader loader = new TranslatingTypeLoader(
203 m_jar,
204 m_jarIndex,
205 getTranslator(TranslationDirection.Obfuscating),
206 getTranslator(TranslationDirection.Deobfuscating)
207 );
208 m_settings.setTypeLoader(loader);
209
210 // see if procyon can find the type
211 TypeReference type = new MetadataSystem(loader).lookupType(deobfClassName);
212 if (type == null) {
213 throw new Error(String.format("Unable to find type: %s (deobf: %s)\nTried class names: %s",
214 className, deobfClassName, loader.getClassNamesToTry(deobfClassName)
215 ));
216 }
217 TypeDefinition resolvedType = type.resolve();
218
219 // decompile it!
220 DecompilerContext context = new DecompilerContext();
221 context.setCurrentType(resolvedType);
222 context.setSettings(m_settings);
223 AstBuilder builder = new AstBuilder(context);
224 builder.addType(resolvedType);
225 builder.runTransformations(null);
226 return builder.getCompilationUnit();
227 }
228
229 public SourceIndex getSourceIndex(CompilationUnit sourceTree, String source) {
230 return getSourceIndex(sourceTree, source, null);
231 }
232
233 public SourceIndex getSourceIndex(CompilationUnit sourceTree, String source, Boolean ignoreBadTokens) {
234
235 // build the source index
236 SourceIndex index;
237 if (ignoreBadTokens != null) {
238 index = new SourceIndex(source, ignoreBadTokens);
239 } else {
240 index = new SourceIndex(source);
241 }
242 sourceTree.acceptVisitor(new SourceIndexVisitor(), index);
243
244 // DEBUG
245 // sourceTree.acceptVisitor( new TreeDumpVisitor( new File( "tree.txt" ) ), null );
246
247 // resolve all the classes in the source references
248 for (Token token : index.referenceTokens()) {
249 EntryReference<Entry,Entry> deobfReference = index.getDeobfReference(token);
250
251 // get the obfuscated entry
252 Entry obfEntry = obfuscateEntry(deobfReference.entry);
253
254 // try to resolve the class
255 ClassEntry resolvedObfClassEntry = m_jarIndex.getTranslationIndex().resolveEntryClass(obfEntry);
256 if (resolvedObfClassEntry != null && !resolvedObfClassEntry.equals(obfEntry.getClassEntry())) {
257 // change the class of the entry
258 obfEntry = obfEntry.cloneToNewClass(resolvedObfClassEntry);
259
260 // save the new deobfuscated reference
261 deobfReference.entry = deobfuscateEntry(obfEntry);
262 index.replaceDeobfReference(token, deobfReference);
263 }
264
265 // DEBUG
266 // System.out.println( token + " -> " + reference + " -> " + index.getReferenceToken( reference ) );
267 }
268
269 return index;
270 }
271
272 public String getSource(CompilationUnit sourceTree) {
273 // render the AST into source
274 StringWriter buf = new StringWriter();
275 sourceTree.acceptVisitor(new InsertParenthesesVisitor(), null);
276 sourceTree.acceptVisitor(new JavaOutputVisitor(new PlainTextOutput(buf), m_settings), null);
277 return buf.toString();
278 }
279
280 public void writeSources(File dirOut, ProgressListener progress) throws IOException {
281 // get the classes to decompile
282 Set<ClassEntry> classEntries = Sets.newHashSet();
283 for (ClassEntry obfClassEntry : m_jarIndex.getObfClassEntries()) {
284 // skip inner classes
285 if (obfClassEntry.isInnerClass()) {
286 continue;
287 }
288
289 classEntries.add(obfClassEntry);
290 }
291
292 if (progress != null) {
293 progress.init(classEntries.size(), "Decompiling classes...");
294 }
295
296 // DEOBFUSCATE ALL THE THINGS!! @_@
297 int i = 0;
298 for (ClassEntry obfClassEntry : classEntries) {
299 ClassEntry deobfClassEntry = deobfuscateEntry(new ClassEntry(obfClassEntry));
300 if (progress != null) {
301 progress.onProgress(i++, deobfClassEntry.toString());
302 }
303
304 try {
305 // get the source
306 String source = getSource(getSourceTree(obfClassEntry.getName()));
307
308 // write the file
309 File file = new File(dirOut, deobfClassEntry.getName().replace('.', '/') + ".java");
310 file.getParentFile().mkdirs();
311 try (FileWriter out = new FileWriter(file)) {
312 out.write(source);
313 }
314 } catch (Throwable t) {
315 throw new Error("Unable to deobfuscate class " + deobfClassEntry.toString() + " (" + obfClassEntry.toString() + ")", t);
316 }
317 }
318 if (progress != null) {
319 progress.onProgress(i, "Done!");
320 }
321 }
322
323 public void writeJar(File out, ProgressListener progress) {
324 final TranslatingTypeLoader loader = new TranslatingTypeLoader(
325 m_jar,
326 m_jarIndex,
327 getTranslator(TranslationDirection.Obfuscating),
328 getTranslator(TranslationDirection.Deobfuscating)
329 );
330 transformJar(out, progress, new ClassTransformer() {
331
332 @Override
333 public CtClass transform(CtClass c) throws Exception {
334 return loader.transformClass(c);
335 }
336 });
337 }
338
339 public void protectifyJar(File out, ProgressListener progress) {
340 transformJar(out, progress, new ClassTransformer() {
341
342 @Override
343 public CtClass transform(CtClass c) throws Exception {
344 return ClassProtectifier.protectify(c);
345 }
346 });
347 }
348
349 public void publifyJar(File out, ProgressListener progress) {
350 transformJar(out, progress, new ClassTransformer() {
351
352 @Override
353 public CtClass transform(CtClass c) throws Exception {
354 return ClassPublifier.publify(c);
355 }
356 });
357 }
358
359 private interface ClassTransformer {
360 public CtClass transform(CtClass c) throws Exception;
361 }
362 private void transformJar(File out, ProgressListener progress, ClassTransformer transformer) {
363 try (JarOutputStream outJar = new JarOutputStream(new FileOutputStream(out))) {
364 if (progress != null) {
365 progress.init(JarClassIterator.getClassEntries(m_jar).size(), "Transforming classes...");
366 }
367
368 int i = 0;
369 for (CtClass c : JarClassIterator.classes(m_jar)) {
370 if (progress != null) {
371 progress.onProgress(i++, c.getName());
372 }
373
374 try {
375 c = transformer.transform(c);
376 outJar.putNextEntry(new JarEntry(c.getName().replace('.', '/') + ".class"));
377 outJar.write(c.toBytecode());
378 outJar.closeEntry();
379 } catch (Throwable t) {
380 throw new Error("Unable to transform class " + c.getName(), t);
381 }
382 }
383 if (progress != null) {
384 progress.onProgress(i, "Done!");
385 }
386
387 outJar.close();
388 } catch (IOException ex) {
389 throw new Error("Unable to write to Jar file!");
390 }
391 }
392
393 public <T extends Entry> T obfuscateEntry(T deobfEntry) {
394 if (deobfEntry == null) {
395 return null;
396 }
397 return getTranslator(TranslationDirection.Obfuscating).translateEntry(deobfEntry);
398 }
399
400 public <T extends Entry> T deobfuscateEntry(T obfEntry) {
401 if (obfEntry == null) {
402 return null;
403 }
404 return getTranslator(TranslationDirection.Deobfuscating).translateEntry(obfEntry);
405 }
406
407 public <E extends Entry,C extends Entry> EntryReference<E,C> obfuscateReference(EntryReference<E,C> deobfReference) {
408 if (deobfReference == null) {
409 return null;
410 }
411 return new EntryReference<E,C>(
412 obfuscateEntry(deobfReference.entry),
413 obfuscateEntry(deobfReference.context),
414 deobfReference
415 );
416 }
417
418 public <E extends Entry,C extends Entry> EntryReference<E,C> deobfuscateReference(EntryReference<E,C> obfReference) {
419 if (obfReference == null) {
420 return null;
421 }
422 return new EntryReference<E,C>(
423 deobfuscateEntry(obfReference.entry),
424 deobfuscateEntry(obfReference.context),
425 obfReference
426 );
427 }
428
429 public boolean isObfuscatedIdentifier(Entry obfEntry) {
430
431 if (obfEntry instanceof MethodEntry) {
432
433 // HACKHACK: Object methods are not obfuscated identifiers
434 MethodEntry obfMethodEntry = (MethodEntry)obfEntry;
435 String name = obfMethodEntry.getName();
436 String sig = obfMethodEntry.getSignature().toString();
437 if (name.equals("clone") && sig.equals("()Ljava/lang/Object;")) {
438 return false;
439 } else if (name.equals("equals") && sig.equals("(Ljava/lang/Object;)Z")) {
440 return false;
441 } else if (name.equals("finalize") && sig.equals("()V")) {
442 return false;
443 } else if (name.equals("getClass") && sig.equals("()Ljava/lang/Class;")) {
444 return false;
445 } else if (name.equals("hashCode") && sig.equals("()I")) {
446 return false;
447 } else if (name.equals("notify") && sig.equals("()V")) {
448 return false;
449 } else if (name.equals("notifyAll") && sig.equals("()V")) {
450 return false;
451 } else if (name.equals("toString") && sig.equals("()Ljava/lang/String;")) {
452 return false;
453 } else if (name.equals("wait") && sig.equals("()V")) {
454 return false;
455 } else if (name.equals("wait") && sig.equals("(J)V")) {
456 return false;
457 } else if (name.equals("wait") && sig.equals("(JI)V")) {
458 return false;
459 }
460 }
461
462 return m_jarIndex.containsObfEntry(obfEntry);
463 }
464
465 public boolean isRenameable(EntryReference<Entry,Entry> obfReference) {
466 return obfReference.isNamed() && isObfuscatedIdentifier(obfReference.getNameableEntry());
467 }
468
469 // NOTE: these methods are a bit messy... oh well
470
471 public boolean hasDeobfuscatedName(Entry obfEntry) {
472 Translator translator = getTranslator(TranslationDirection.Deobfuscating);
473 if (obfEntry instanceof ClassEntry) {
474 ClassEntry obfClass = (ClassEntry)obfEntry;
475 List<ClassMapping> mappingChain = m_mappings.getClassMappingChain(obfClass);
476 ClassMapping classMapping = mappingChain.get(mappingChain.size() - 1);
477 return classMapping != null && classMapping.getDeobfName() != null;
478 } else if (obfEntry instanceof FieldEntry) {
479 return translator.translate((FieldEntry)obfEntry) != null;
480 } else if (obfEntry instanceof MethodEntry) {
481 return translator.translate((MethodEntry)obfEntry) != null;
482 } else if (obfEntry instanceof ConstructorEntry) {
483 // constructors have no names
484 return false;
485 } else if (obfEntry instanceof ArgumentEntry) {
486 return translator.translate((ArgumentEntry)obfEntry) != null;
487 } else {
488 throw new Error("Unknown entry type: " + obfEntry.getClass().getName());
489 }
490 }
491
492 public void rename(Entry obfEntry, String newName) {
493 if (obfEntry instanceof ClassEntry) {
494 m_renamer.setClassName((ClassEntry)obfEntry, Descriptor.toJvmName(newName));
495 } else if (obfEntry instanceof FieldEntry) {
496 m_renamer.setFieldName((FieldEntry)obfEntry, newName);
497 } else if (obfEntry instanceof MethodEntry) {
498 m_renamer.setMethodTreeName((MethodEntry)obfEntry, newName);
499 } else if (obfEntry instanceof ConstructorEntry) {
500 throw new IllegalArgumentException("Cannot rename constructors");
501 } else if (obfEntry instanceof ArgumentEntry) {
502 m_renamer.setArgumentName((ArgumentEntry)obfEntry, newName);
503 } else {
504 throw new Error("Unknown entry type: " + obfEntry.getClass().getName());
505 }
506
507 // clear caches
508 m_translatorCache.clear();
509 }
510
511 public void removeMapping(Entry obfEntry) {
512 if (obfEntry instanceof ClassEntry) {
513 m_renamer.removeClassMapping((ClassEntry)obfEntry);
514 } else if (obfEntry instanceof FieldEntry) {
515 m_renamer.removeFieldMapping((FieldEntry)obfEntry);
516 } else if (obfEntry instanceof MethodEntry) {
517 m_renamer.removeMethodTreeMapping((MethodEntry)obfEntry);
518 } else if (obfEntry instanceof ConstructorEntry) {
519 throw new IllegalArgumentException("Cannot rename constructors");
520 } else if (obfEntry instanceof ArgumentEntry) {
521 m_renamer.removeArgumentMapping((ArgumentEntry)obfEntry);
522 } else {
523 throw new Error("Unknown entry type: " + obfEntry);
524 }
525
526 // clear caches
527 m_translatorCache.clear();
528 }
529
530 public void markAsDeobfuscated(Entry obfEntry) {
531 if (obfEntry instanceof ClassEntry) {
532 m_renamer.markClassAsDeobfuscated((ClassEntry)obfEntry);
533 } else if (obfEntry instanceof FieldEntry) {
534 m_renamer.markFieldAsDeobfuscated((FieldEntry)obfEntry);
535 } else if (obfEntry instanceof MethodEntry) {
536 m_renamer.markMethodTreeAsDeobfuscated((MethodEntry)obfEntry);
537 } else if (obfEntry instanceof ConstructorEntry) {
538 throw new IllegalArgumentException("Cannot rename constructors");
539 } else if (obfEntry instanceof ArgumentEntry) {
540 m_renamer.markArgumentAsDeobfuscated((ArgumentEntry)obfEntry);
541 } else {
542 throw new Error("Unknown entry type: " + obfEntry);
543 }
544
545 // clear caches
546 m_translatorCache.clear();
547 }
548}
diff --git a/src/cuchaz/enigma/ExceptionIgnorer.java b/src/cuchaz/enigma/ExceptionIgnorer.java
new file mode 100644
index 00000000..d8726d13
--- /dev/null
+++ b/src/cuchaz/enigma/ExceptionIgnorer.java
@@ -0,0 +1,34 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13public class ExceptionIgnorer {
14
15 public static boolean shouldIgnore(Throwable t) {
16
17 // is this that pesky concurrent access bug in the highlight painter system?
18 // (ancient ui code is ancient)
19 if (t instanceof ArrayIndexOutOfBoundsException) {
20 StackTraceElement[] stackTrace = t.getStackTrace();
21 if (stackTrace.length > 1) {
22
23 // does this stack frame match javax.swing.text.DefaultHighlighter.paint*() ?
24 StackTraceElement frame = stackTrace[1];
25 if (frame.getClassName().equals("javax.swing.text.DefaultHighlighter") && frame.getMethodName().startsWith("paint")) {
26 return true;
27 }
28 }
29 }
30
31 return false;
32 }
33
34}
diff --git a/src/cuchaz/enigma/Main.java b/src/cuchaz/enigma/Main.java
new file mode 100644
index 00000000..4842a795
--- /dev/null
+++ b/src/cuchaz/enigma/Main.java
@@ -0,0 +1,51 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import java.io.File;
14import java.util.jar.JarFile;
15
16import cuchaz.enigma.gui.Gui;
17
18public class Main {
19
20 public static void main(String[] args) throws Exception {
21 Gui gui = new Gui();
22
23 // parse command-line args
24 if (args.length >= 1) {
25 gui.getController().openJar(new JarFile(getFile(args[0])));
26 }
27 if (args.length >= 2) {
28 gui.getController().openMappings(getFile(args[1]));
29 }
30
31 // DEBUG
32 //gui.getController().openDeclaration(new ClassEntry("none/bxq"));
33 }
34
35 private static File getFile(String path) {
36 // expand ~ to the home dir
37 if (path.startsWith("~")) {
38 // get the home dir
39 File dirHome = new File(System.getProperty("user.home"));
40
41 // is the path just ~/ or is it ~user/ ?
42 if (path.startsWith("~/")) {
43 return new File(dirHome, path.substring(2));
44 } else {
45 return new File(dirHome.getParentFile(), path.substring(1));
46 }
47 }
48
49 return new File(path);
50 }
51}
diff --git a/src/cuchaz/enigma/MainFormatConverter.java b/src/cuchaz/enigma/MainFormatConverter.java
new file mode 100644
index 00000000..73ee41f4
--- /dev/null
+++ b/src/cuchaz/enigma/MainFormatConverter.java
@@ -0,0 +1,130 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import java.io.File;
14import java.io.FileReader;
15import java.io.FileWriter;
16import java.lang.reflect.Field;
17import java.util.Map;
18import java.util.jar.JarFile;
19
20import javassist.CtClass;
21import javassist.CtField;
22
23import com.google.common.collect.Maps;
24
25import cuchaz.enigma.analysis.JarClassIterator;
26import cuchaz.enigma.mapping.ClassEntry;
27import cuchaz.enigma.mapping.ClassMapping;
28import cuchaz.enigma.mapping.ClassNameReplacer;
29import cuchaz.enigma.mapping.FieldEntry;
30import cuchaz.enigma.mapping.FieldMapping;
31import cuchaz.enigma.mapping.EntryFactory;
32import cuchaz.enigma.mapping.Mappings;
33import cuchaz.enigma.mapping.MappingsReader;
34import cuchaz.enigma.mapping.MappingsWriter;
35import cuchaz.enigma.mapping.Type;
36
37public class MainFormatConverter {
38
39 public static void main(String[] args)
40 throws Exception {
41
42 System.out.println("Getting field types from jar...");
43
44 JarFile jar = new JarFile(System.getProperty("user.home") + "/.minecraft/versions/1.8/1.8.jar");
45 Map<String,Type> fieldTypes = Maps.newHashMap();
46 for (CtClass c : JarClassIterator.classes(jar)) {
47 for (CtField field : c.getDeclaredFields()) {
48 FieldEntry fieldEntry = EntryFactory.getFieldEntry(field);
49 fieldTypes.put(getFieldKey(fieldEntry), moveClasssesOutOfDefaultPackage(fieldEntry.getType()));
50 }
51 }
52
53 System.out.println("Reading mappings...");
54
55 File fileMappings = new File("../Enigma Mappings/1.8.mappings");
56 MappingsReader mappingsReader = new MappingsReader() {
57
58 @Override
59 protected FieldMapping readField(String[] parts) {
60 // assume the void type for now
61 return new FieldMapping(parts[1], new Type("V"), parts[2]);
62 }
63 };
64 Mappings mappings = mappingsReader.read(new FileReader(fileMappings));
65
66 System.out.println("Updating field types...");
67
68 for (ClassMapping classMapping : mappings.classes()) {
69 updateFieldsInClass(fieldTypes, classMapping);
70 }
71
72 System.out.println("Saving mappings...");
73
74 try (FileWriter writer = new FileWriter(fileMappings)) {
75 new MappingsWriter().write(writer, mappings);
76 }
77
78 System.out.println("Done!");
79 }
80
81 private static Type moveClasssesOutOfDefaultPackage(Type type) {
82 return new Type(type, new ClassNameReplacer() {
83 @Override
84 public String replace(String className) {
85 ClassEntry entry = new ClassEntry(className);
86 if (entry.isInDefaultPackage()) {
87 return Constants.NonePackage + "/" + className;
88 }
89 return null;
90 }
91 });
92 }
93
94 private static void updateFieldsInClass(Map<String,Type> fieldTypes, ClassMapping classMapping)
95 throws Exception {
96
97 // update the fields
98 for (FieldMapping fieldMapping : classMapping.fields()) {
99 setFieldType(fieldTypes, classMapping, fieldMapping);
100 }
101
102 // recurse
103 for (ClassMapping innerClassMapping : classMapping.innerClasses()) {
104 updateFieldsInClass(fieldTypes, innerClassMapping);
105 }
106 }
107
108 private static void setFieldType(Map<String,Type> fieldTypes, ClassMapping classMapping, FieldMapping fieldMapping)
109 throws Exception {
110
111 // get the new type
112 Type newType = fieldTypes.get(getFieldKey(classMapping, fieldMapping));
113 if (newType == null) {
114 throw new Error("Can't find type for field: " + getFieldKey(classMapping, fieldMapping));
115 }
116
117 // hack in the new field type
118 Field field = fieldMapping.getClass().getDeclaredField("m_obfType");
119 field.setAccessible(true);
120 field.set(fieldMapping, newType);
121 }
122
123 private static Object getFieldKey(ClassMapping classMapping, FieldMapping fieldMapping) {
124 return classMapping.getObfSimpleName() + "." + fieldMapping.getObfName();
125 }
126
127 private static String getFieldKey(FieldEntry obfFieldEntry) {
128 return obfFieldEntry.getClassEntry().getSimpleName() + "." + obfFieldEntry.getName();
129 }
130}
diff --git a/src/cuchaz/enigma/TranslatingTypeLoader.java b/src/cuchaz/enigma/TranslatingTypeLoader.java
new file mode 100644
index 00000000..a2185e5c
--- /dev/null
+++ b/src/cuchaz/enigma/TranslatingTypeLoader.java
@@ -0,0 +1,249 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import java.io.ByteArrayOutputStream;
14import java.io.IOException;
15import java.io.InputStream;
16import java.util.List;
17import java.util.Map;
18import java.util.jar.JarEntry;
19import java.util.jar.JarFile;
20
21import javassist.ByteArrayClassPath;
22import javassist.CannotCompileException;
23import javassist.ClassPool;
24import javassist.CtClass;
25import javassist.NotFoundException;
26import javassist.bytecode.Descriptor;
27
28import com.google.common.collect.Lists;
29import com.google.common.collect.Maps;
30import com.strobel.assembler.metadata.Buffer;
31import com.strobel.assembler.metadata.ClasspathTypeLoader;
32import com.strobel.assembler.metadata.ITypeLoader;
33
34import cuchaz.enigma.analysis.BridgeMarker;
35import cuchaz.enigma.analysis.JarIndex;
36import cuchaz.enigma.bytecode.ClassRenamer;
37import cuchaz.enigma.bytecode.ClassTranslator;
38import cuchaz.enigma.bytecode.InnerClassWriter;
39import cuchaz.enigma.bytecode.LocalVariableRenamer;
40import cuchaz.enigma.bytecode.MethodParameterWriter;
41import cuchaz.enigma.mapping.ClassEntry;
42import cuchaz.enigma.mapping.Translator;
43
44public class TranslatingTypeLoader implements ITypeLoader {
45
46 private JarFile m_jar;
47 private JarIndex m_jarIndex;
48 private Translator m_obfuscatingTranslator;
49 private Translator m_deobfuscatingTranslator;
50 private Map<String,byte[]> m_cache;
51 private ClasspathTypeLoader m_defaultTypeLoader;
52
53 public TranslatingTypeLoader(JarFile jar, JarIndex jarIndex) {
54 this(jar, jarIndex, new Translator(), new Translator());
55 }
56
57 public TranslatingTypeLoader(JarFile jar, JarIndex jarIndex, Translator obfuscatingTranslator, Translator deobfuscatingTranslator) {
58 m_jar = jar;
59 m_jarIndex = jarIndex;
60 m_obfuscatingTranslator = obfuscatingTranslator;
61 m_deobfuscatingTranslator = deobfuscatingTranslator;
62 m_cache = Maps.newHashMap();
63 m_defaultTypeLoader = new ClasspathTypeLoader();
64 }
65
66 public void clearCache() {
67 m_cache.clear();
68 }
69
70 @Override
71 public boolean tryLoadType(String className, Buffer out) {
72
73 // check the cache
74 byte[] data;
75 if (m_cache.containsKey(className)) {
76 data = m_cache.get(className);
77 } else {
78 data = loadType(className);
79 m_cache.put(className, data);
80 }
81
82 if (data == null) {
83 // chain to default type loader
84 return m_defaultTypeLoader.tryLoadType(className, out);
85 }
86
87 // send the class to the decompiler
88 out.reset(data.length);
89 System.arraycopy(data, 0, out.array(), out.position(), data.length);
90 out.position(0);
91 return true;
92 }
93
94 public CtClass loadClass(String deobfClassName) {
95
96 byte[] data = loadType(deobfClassName);
97 if (data == null) {
98 return null;
99 }
100
101 // return a javassist handle for the class
102 String javaClassFileName = Descriptor.toJavaName(deobfClassName);
103 ClassPool classPool = new ClassPool();
104 classPool.insertClassPath(new ByteArrayClassPath(javaClassFileName, data));
105 try {
106 return classPool.get(javaClassFileName);
107 } catch (NotFoundException ex) {
108 throw new Error(ex);
109 }
110 }
111
112 private byte[] loadType(String className) {
113
114 // NOTE: don't know if class name is obf or deobf
115 ClassEntry classEntry = new ClassEntry(className);
116 ClassEntry obfClassEntry = m_obfuscatingTranslator.translateEntry(classEntry);
117
118 // is this an inner class referenced directly? (ie trying to load b instead of a$b)
119 if (!obfClassEntry.isInnerClass()) {
120 List<ClassEntry> classChain = m_jarIndex.getObfClassChain(obfClassEntry);
121 if (classChain.size() > 1) {
122 System.err.println(String.format("WARNING: no class %s after inner class reconstruction. Try %s",
123 className, obfClassEntry.buildClassEntry(classChain)
124 ));
125 return null;
126 }
127 }
128
129 // is this a class we should even know about?
130 if (!m_jarIndex.containsObfClass(obfClassEntry)) {
131 return null;
132 }
133
134 // DEBUG
135 //System.out.println(String.format("Looking for %s (obf: %s)", classEntry.getName(), obfClassEntry.getName()));
136
137 // find the class in the jar
138 String classInJarName = findClassInJar(obfClassEntry);
139 if (classInJarName == null) {
140 // couldn't find it
141 return null;
142 }
143
144 try {
145 // read the class file into a buffer
146 ByteArrayOutputStream data = new ByteArrayOutputStream();
147 byte[] buf = new byte[1024 * 1024]; // 1 KiB
148 InputStream in = m_jar.getInputStream(m_jar.getJarEntry(classInJarName + ".class"));
149 while (true) {
150 int bytesRead = in.read(buf);
151 if (bytesRead <= 0) {
152 break;
153 }
154 data.write(buf, 0, bytesRead);
155 }
156 data.close();
157 in.close();
158 buf = data.toByteArray();
159
160 // load the javassist handle to the raw class
161 ClassPool classPool = new ClassPool();
162 String classInJarJavaName = Descriptor.toJavaName(classInJarName);
163 classPool.insertClassPath(new ByteArrayClassPath(classInJarJavaName, buf));
164 CtClass c = classPool.get(classInJarJavaName);
165
166 c = transformClass(c);
167
168 // sanity checking
169 assertClassName(c, classEntry);
170
171 // DEBUG
172 //Util.writeClass( c );
173
174 // we have a transformed class!
175 return c.toBytecode();
176 } catch (IOException | NotFoundException | CannotCompileException ex) {
177 throw new Error(ex);
178 }
179 }
180
181 private String findClassInJar(ClassEntry obfClassEntry) {
182
183 // try to find the class in the jar
184 for (String className : getClassNamesToTry(obfClassEntry)) {
185 JarEntry jarEntry = m_jar.getJarEntry(className + ".class");
186 if (jarEntry != null) {
187 return className;
188 }
189 }
190
191 // didn't find it ;_;
192 return null;
193 }
194
195 public List<String> getClassNamesToTry(String className) {
196 return getClassNamesToTry(m_obfuscatingTranslator.translateEntry(new ClassEntry(className)));
197 }
198
199 public List<String> getClassNamesToTry(ClassEntry obfClassEntry) {
200 List<String> classNamesToTry = Lists.newArrayList();
201 classNamesToTry.add(obfClassEntry.getName());
202 if (obfClassEntry.getPackageName().equals(Constants.NonePackage)) {
203 // taking off the none package, if any
204 classNamesToTry.add(obfClassEntry.getSimpleName());
205 }
206 if (obfClassEntry.isInnerClass()) {
207 // try just the inner class name
208 classNamesToTry.add(obfClassEntry.getInnermostClassName());
209 }
210 return classNamesToTry;
211 }
212
213 public CtClass transformClass(CtClass c)
214 throws IOException, NotFoundException, CannotCompileException {
215
216 // we moved a lot of classes out of the default package into the none package
217 // make sure all the class references are consistent
218 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
219
220 // reconstruct inner classes
221 new InnerClassWriter(m_jarIndex).write(c);
222
223 // re-get the javassist handle since we changed class names
224 ClassEntry obfClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
225 String javaClassReconstructedName = Descriptor.toJavaName(obfClassEntry.getName());
226 ClassPool classPool = new ClassPool();
227 classPool.insertClassPath(new ByteArrayClassPath(javaClassReconstructedName, c.toBytecode()));
228 c = classPool.get(javaClassReconstructedName);
229
230 // check that the file is correct after inner class reconstruction (ie cause Javassist to fail fast if something is wrong)
231 assertClassName(c, obfClassEntry);
232
233 // do all kinds of deobfuscating transformations on the class
234 new BridgeMarker(m_jarIndex).markBridges(c);
235 new MethodParameterWriter(m_deobfuscatingTranslator).writeMethodArguments(c);
236 new LocalVariableRenamer(m_deobfuscatingTranslator).rename(c);
237 new ClassTranslator(m_deobfuscatingTranslator).translate(c);
238
239 return c;
240 }
241
242 private void assertClassName(CtClass c, ClassEntry obfClassEntry) {
243 String name1 = Descriptor.toJvmName(c.getName());
244 assert (name1.equals(obfClassEntry.getName())) : String.format("Looking for %s, instead found %s", obfClassEntry.getName(), name1);
245
246 String name2 = Descriptor.toJvmName(c.getClassFile().getName());
247 assert (name2.equals(obfClassEntry.getName())) : String.format("Looking for %s, instead found %s", obfClassEntry.getName(), name2);
248 }
249}
diff --git a/src/cuchaz/enigma/Util.java b/src/cuchaz/enigma/Util.java
new file mode 100644
index 00000000..c7e509fa
--- /dev/null
+++ b/src/cuchaz/enigma/Util.java
@@ -0,0 +1,104 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import java.awt.Desktop;
14import java.io.Closeable;
15import java.io.File;
16import java.io.FileOutputStream;
17import java.io.IOException;
18import java.io.InputStream;
19import java.io.InputStreamReader;
20import java.net.URI;
21import java.net.URISyntaxException;
22import java.util.Arrays;
23import java.util.jar.JarFile;
24
25import javassist.CannotCompileException;
26import javassist.CtClass;
27import javassist.bytecode.Descriptor;
28
29import com.google.common.io.CharStreams;
30
31public class Util {
32
33 public static int combineHashesOrdered(Object... objs) {
34 return combineHashesOrdered(Arrays.asList(objs));
35 }
36
37 public static int combineHashesOrdered(Iterable<Object> objs) {
38 final int prime = 67;
39 int result = 1;
40 for (Object obj : objs) {
41 result *= prime;
42 if (obj != null) {
43 result += obj.hashCode();
44 }
45 }
46 return result;
47 }
48
49 public static void closeQuietly(Closeable closeable) {
50 if (closeable != null) {
51 try {
52 closeable.close();
53 } catch (IOException ex) {
54 // just ignore any further exceptions
55 }
56 }
57 }
58
59 public static void closeQuietly(JarFile jarFile) {
60 // silly library should implement Closeable...
61 if (jarFile != null) {
62 try {
63 jarFile.close();
64 } catch (IOException ex) {
65 // just ignore any further exceptions
66 }
67 }
68 }
69
70 public static String readStreamToString(InputStream in) throws IOException {
71 return CharStreams.toString(new InputStreamReader(in, "UTF-8"));
72 }
73
74 public static String readResourceToString(String path) throws IOException {
75 InputStream in = Util.class.getResourceAsStream(path);
76 if (in == null) {
77 throw new IllegalArgumentException("Resource not found! " + path);
78 }
79 return readStreamToString(in);
80 }
81
82 public static void openUrl(String url) {
83 if (Desktop.isDesktopSupported()) {
84 Desktop desktop = Desktop.getDesktop();
85 try {
86 desktop.browse(new URI(url));
87 } catch (IOException ex) {
88 throw new Error(ex);
89 } catch (URISyntaxException ex) {
90 throw new IllegalArgumentException(ex);
91 }
92 }
93 }
94
95 public static void writeClass(CtClass c) {
96 String name = Descriptor.toJavaName(c.getName());
97 File file = new File(name + ".class");
98 try (FileOutputStream out = new FileOutputStream(file)) {
99 out.write(c.toBytecode());
100 } catch (IOException | CannotCompileException ex) {
101 throw new Error(ex);
102 }
103 }
104}
diff --git a/src/cuchaz/enigma/analysis/Access.java b/src/cuchaz/enigma/analysis/Access.java
new file mode 100644
index 00000000..1c8cfc48
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/Access.java
@@ -0,0 +1,43 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.lang.reflect.Modifier;
14
15import javassist.CtBehavior;
16import javassist.CtField;
17
18public enum Access {
19
20 Public,
21 Protected,
22 Private;
23
24 public static Access get(CtBehavior behavior) {
25 return get(behavior.getModifiers());
26 }
27
28 public static Access get(CtField field) {
29 return get(field.getModifiers());
30 }
31
32 public static Access get(int modifiers) {
33 if (Modifier.isPublic(modifiers)) {
34 return Public;
35 } else if (Modifier.isProtected(modifiers)) {
36 return Protected;
37 } else if (Modifier.isPrivate(modifiers)) {
38 return Private;
39 }
40 // assume public by default
41 return Public;
42 }
43}
diff --git a/src/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java b/src/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java
new file mode 100644
index 00000000..353a4bf0
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java
@@ -0,0 +1,93 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.util.Set;
14
15import javax.swing.tree.DefaultMutableTreeNode;
16import javax.swing.tree.TreeNode;
17
18import com.google.common.collect.Sets;
19
20import cuchaz.enigma.mapping.BehaviorEntry;
21import cuchaz.enigma.mapping.Entry;
22import cuchaz.enigma.mapping.Translator;
23
24public class BehaviorReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode<BehaviorEntry,BehaviorEntry> {
25
26 private static final long serialVersionUID = -3658163700783307520L;
27
28 private Translator m_deobfuscatingTranslator;
29 private BehaviorEntry m_entry;
30 private EntryReference<BehaviorEntry,BehaviorEntry> m_reference;
31 private Access m_access;
32
33 public BehaviorReferenceTreeNode(Translator deobfuscatingTranslator, BehaviorEntry entry) {
34 m_deobfuscatingTranslator = deobfuscatingTranslator;
35 m_entry = entry;
36 m_reference = null;
37 }
38
39 public BehaviorReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference<BehaviorEntry,BehaviorEntry> reference, Access access) {
40 m_deobfuscatingTranslator = deobfuscatingTranslator;
41 m_entry = reference.entry;
42 m_reference = reference;
43 m_access = access;
44 }
45
46 @Override
47 public BehaviorEntry getEntry() {
48 return m_entry;
49 }
50
51 @Override
52 public EntryReference<BehaviorEntry,BehaviorEntry> getReference() {
53 return m_reference;
54 }
55
56 @Override
57 public String toString() {
58 if (m_reference != null) {
59 return String.format("%s (%s)", m_deobfuscatingTranslator.translateEntry(m_reference.context), m_access);
60 }
61 return m_deobfuscatingTranslator.translateEntry(m_entry).toString();
62 }
63
64 public void load(JarIndex index, boolean recurse) {
65 // get all the child nodes
66 for (EntryReference<BehaviorEntry,BehaviorEntry> reference : index.getBehaviorReferences(m_entry)) {
67 add(new BehaviorReferenceTreeNode(m_deobfuscatingTranslator, reference, index.getAccess(m_entry)));
68 }
69
70 if (recurse && children != null) {
71 for (Object child : children) {
72 if (child instanceof BehaviorReferenceTreeNode) {
73 BehaviorReferenceTreeNode node = (BehaviorReferenceTreeNode)child;
74
75 // don't recurse into ancestor
76 Set<Entry> ancestors = Sets.newHashSet();
77 TreeNode n = (TreeNode)node;
78 while (n.getParent() != null) {
79 n = n.getParent();
80 if (n instanceof BehaviorReferenceTreeNode) {
81 ancestors.add( ((BehaviorReferenceTreeNode)n).getEntry());
82 }
83 }
84 if (ancestors.contains(node.getEntry())) {
85 continue;
86 }
87
88 node.load(index, true);
89 }
90 }
91 }
92 }
93}
diff --git a/src/cuchaz/enigma/analysis/BridgeMarker.java b/src/cuchaz/enigma/analysis/BridgeMarker.java
new file mode 100644
index 00000000..650b3a78
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/BridgeMarker.java
@@ -0,0 +1,43 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import javassist.CtClass;
14import javassist.CtMethod;
15import javassist.bytecode.AccessFlag;
16import cuchaz.enigma.mapping.EntryFactory;
17import cuchaz.enigma.mapping.MethodEntry;
18
19public class BridgeMarker {
20
21 private JarIndex m_jarIndex;
22
23 public BridgeMarker(JarIndex jarIndex) {
24 m_jarIndex = jarIndex;
25 }
26
27 public void markBridges(CtClass c) {
28
29 for (CtMethod method : c.getDeclaredMethods()) {
30 MethodEntry methodEntry = EntryFactory.getMethodEntry(method);
31
32 // is this a bridge method?
33 MethodEntry bridgedMethodEntry = m_jarIndex.getBridgedMethod(methodEntry);
34 if (bridgedMethodEntry != null) {
35
36 // it's a bridge method! add the bridge flag
37 int flags = method.getMethodInfo().getAccessFlags();
38 flags |= AccessFlag.BRIDGE;
39 method.getMethodInfo().setAccessFlags(flags);
40 }
41 }
42 }
43}
diff --git a/src/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java b/src/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java
new file mode 100644
index 00000000..cc70f51e
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java
@@ -0,0 +1,80 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.util.List;
14
15import javax.swing.tree.DefaultMutableTreeNode;
16
17import com.google.common.collect.Lists;
18
19import cuchaz.enigma.mapping.ClassEntry;
20import cuchaz.enigma.mapping.MethodEntry;
21import cuchaz.enigma.mapping.Translator;
22
23public class ClassImplementationsTreeNode extends DefaultMutableTreeNode {
24
25 private static final long serialVersionUID = 3112703459157851912L;
26
27 private Translator m_deobfuscatingTranslator;
28 private ClassEntry m_entry;
29
30 public ClassImplementationsTreeNode(Translator deobfuscatingTranslator, ClassEntry entry) {
31 m_deobfuscatingTranslator = deobfuscatingTranslator;
32 m_entry = entry;
33 }
34
35 public ClassEntry getClassEntry() {
36 return m_entry;
37 }
38
39 public String getDeobfClassName() {
40 return m_deobfuscatingTranslator.translateClass(m_entry.getClassName());
41 }
42
43 @Override
44 public String toString() {
45 String className = getDeobfClassName();
46 if (className == null) {
47 className = m_entry.getClassName();
48 }
49 return className;
50 }
51
52 public void load(JarIndex index) {
53 // get all method implementations
54 List<ClassImplementationsTreeNode> nodes = Lists.newArrayList();
55 for (String implementingClassName : index.getImplementingClasses(m_entry.getClassName())) {
56 nodes.add(new ClassImplementationsTreeNode(m_deobfuscatingTranslator, new ClassEntry(implementingClassName)));
57 }
58
59 // add them to this node
60 for (ClassImplementationsTreeNode node : nodes) {
61 this.add(node);
62 }
63 }
64
65 public static ClassImplementationsTreeNode findNode(ClassImplementationsTreeNode node, MethodEntry entry) {
66 // is this the node?
67 if (node.m_entry.equals(entry)) {
68 return node;
69 }
70
71 // recurse
72 for (int i = 0; i < node.getChildCount(); i++) {
73 ClassImplementationsTreeNode foundNode = findNode((ClassImplementationsTreeNode)node.getChildAt(i), entry);
74 if (foundNode != null) {
75 return foundNode;
76 }
77 }
78 return null;
79 }
80}
diff --git a/src/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java b/src/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java
new file mode 100644
index 00000000..7542bd9d
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java
@@ -0,0 +1,85 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.util.List;
14
15import javax.swing.tree.DefaultMutableTreeNode;
16
17import com.google.common.collect.Lists;
18
19import cuchaz.enigma.mapping.ClassEntry;
20import cuchaz.enigma.mapping.Translator;
21
22public class ClassInheritanceTreeNode extends DefaultMutableTreeNode {
23
24 private static final long serialVersionUID = 4432367405826178490L;
25
26 private Translator m_deobfuscatingTranslator;
27 private String m_obfClassName;
28
29 public ClassInheritanceTreeNode(Translator deobfuscatingTranslator, String obfClassName) {
30 m_deobfuscatingTranslator = deobfuscatingTranslator;
31 m_obfClassName = obfClassName;
32 }
33
34 public String getObfClassName() {
35 return m_obfClassName;
36 }
37
38 public String getDeobfClassName() {
39 return m_deobfuscatingTranslator.translateClass(m_obfClassName);
40 }
41
42 @Override
43 public String toString() {
44 String deobfClassName = getDeobfClassName();
45 if (deobfClassName != null) {
46 return deobfClassName;
47 }
48 return m_obfClassName;
49 }
50
51 public void load(TranslationIndex ancestries, boolean recurse) {
52 // get all the child nodes
53 List<ClassInheritanceTreeNode> nodes = Lists.newArrayList();
54 for (ClassEntry subclassEntry : ancestries.getSubclass(new ClassEntry(m_obfClassName))) {
55 nodes.add(new ClassInheritanceTreeNode(m_deobfuscatingTranslator, subclassEntry.getName()));
56 }
57
58 // add them to this node
59 for (ClassInheritanceTreeNode node : nodes) {
60 this.add(node);
61 }
62
63 if (recurse) {
64 for (ClassInheritanceTreeNode node : nodes) {
65 node.load(ancestries, true);
66 }
67 }
68 }
69
70 public static ClassInheritanceTreeNode findNode(ClassInheritanceTreeNode node, ClassEntry entry) {
71 // is this the node?
72 if (node.getObfClassName().equals(entry.getName())) {
73 return node;
74 }
75
76 // recurse
77 for (int i = 0; i < node.getChildCount(); i++) {
78 ClassInheritanceTreeNode foundNode = findNode((ClassInheritanceTreeNode)node.getChildAt(i), entry);
79 if (foundNode != null) {
80 return foundNode;
81 }
82 }
83 return null;
84 }
85}
diff --git a/src/cuchaz/enigma/analysis/EntryReference.java b/src/cuchaz/enigma/analysis/EntryReference.java
new file mode 100644
index 00000000..85127239
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/EntryReference.java
@@ -0,0 +1,126 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.util.Arrays;
14import java.util.List;
15
16import cuchaz.enigma.Util;
17import cuchaz.enigma.mapping.ClassEntry;
18import cuchaz.enigma.mapping.ConstructorEntry;
19import cuchaz.enigma.mapping.Entry;
20
21public class EntryReference<E extends Entry,C extends Entry> {
22
23 private static final List<String> ConstructorNonNames = Arrays.asList("this", "super", "static");
24 public E entry;
25 public C context;
26
27 private boolean m_isNamed;
28
29 public EntryReference(E entry, String sourceName) {
30 this(entry, sourceName, null);
31 }
32
33 public EntryReference(E entry, String sourceName, C context) {
34 if (entry == null) {
35 throw new IllegalArgumentException("Entry cannot be null!");
36 }
37
38 this.entry = entry;
39 this.context = context;
40
41 m_isNamed = sourceName != null && sourceName.length() > 0;
42 if (entry instanceof ConstructorEntry && ConstructorNonNames.contains(sourceName)) {
43 m_isNamed = false;
44 }
45 }
46
47 public EntryReference(E entry, C context, EntryReference<E,C> other) {
48 this.entry = entry;
49 this.context = context;
50 m_isNamed = other.m_isNamed;
51 }
52
53 public ClassEntry getLocationClassEntry() {
54 if (context != null) {
55 return context.getClassEntry();
56 }
57 return entry.getClassEntry();
58 }
59
60 public boolean isNamed() {
61 return m_isNamed;
62 }
63
64 public Entry getNameableEntry() {
65 if (entry instanceof ConstructorEntry) {
66 // renaming a constructor really means renaming the class
67 return entry.getClassEntry();
68 }
69 return entry;
70 }
71
72 public String getNamableName() {
73 if (getNameableEntry() instanceof ClassEntry) {
74 ClassEntry classEntry = (ClassEntry)getNameableEntry();
75 if (classEntry.isInnerClass()) {
76 // make sure we only rename the inner class name
77 return classEntry.getInnermostClassName();
78 }
79 }
80
81 return getNameableEntry().getName();
82 }
83
84 @Override
85 public int hashCode() {
86 if (context != null) {
87 return Util.combineHashesOrdered(entry.hashCode(), context.hashCode());
88 }
89 return entry.hashCode();
90 }
91
92 @Override
93 public boolean equals(Object other) {
94 if (other instanceof EntryReference) {
95 return equals((EntryReference<?,?>)other);
96 }
97 return false;
98 }
99
100 public boolean equals(EntryReference<?,?> other) {
101 // check entry first
102 boolean isEntrySame = entry.equals(other.entry);
103 if (!isEntrySame) {
104 return false;
105 }
106
107 // check caller
108 if (context == null && other.context == null) {
109 return true;
110 } else if (context != null && other.context != null) {
111 return context.equals(other.context);
112 }
113 return false;
114 }
115
116 @Override
117 public String toString() {
118 StringBuilder buf = new StringBuilder();
119 buf.append(entry);
120 if (context != null) {
121 buf.append(" called from ");
122 buf.append(context);
123 }
124 return buf.toString();
125 }
126}
diff --git a/src/cuchaz/enigma/analysis/EntryRenamer.java b/src/cuchaz/enigma/analysis/EntryRenamer.java
new file mode 100644
index 00000000..f748274f
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/EntryRenamer.java
@@ -0,0 +1,192 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.util.AbstractMap;
14import java.util.List;
15import java.util.Map;
16import java.util.Set;
17
18import com.google.common.collect.Lists;
19import com.google.common.collect.Multimap;
20import com.google.common.collect.Sets;
21
22import cuchaz.enigma.mapping.ArgumentEntry;
23import cuchaz.enigma.mapping.ClassEntry;
24import cuchaz.enigma.mapping.ClassNameReplacer;
25import cuchaz.enigma.mapping.ConstructorEntry;
26import cuchaz.enigma.mapping.Entry;
27import cuchaz.enigma.mapping.FieldEntry;
28import cuchaz.enigma.mapping.MethodEntry;
29import cuchaz.enigma.mapping.Signature;
30import cuchaz.enigma.mapping.Type;
31
32public class EntryRenamer {
33
34 public static <T> void renameClassesInSet(Map<String,String> renames, Set<T> set) {
35 List<T> entries = Lists.newArrayList();
36 for (T val : set) {
37 entries.add(renameClassesInThing(renames, val));
38 }
39 set.clear();
40 set.addAll(entries);
41 }
42
43 public static <Key,Val> void renameClassesInMap(Map<String,String> renames, Map<Key,Val> map) {
44 // for each key/value pair...
45 Set<Map.Entry<Key,Val>> entriesToAdd = Sets.newHashSet();
46 for (Map.Entry<Key,Val> entry : map.entrySet()) {
47 entriesToAdd.add(new AbstractMap.SimpleEntry<Key,Val>(
48 renameClassesInThing(renames, entry.getKey()),
49 renameClassesInThing(renames, entry.getValue())
50 ));
51 }
52 map.clear();
53 for (Map.Entry<Key,Val> entry : entriesToAdd) {
54 map.put(entry.getKey(), entry.getValue());
55 }
56 }
57
58 public static <Key,Val> void renameClassesInMultimap(Map<String,String> renames, Multimap<Key,Val> map) {
59 // for each key/value pair...
60 Set<Map.Entry<Key,Val>> entriesToAdd = Sets.newHashSet();
61 for (Map.Entry<Key,Val> entry : map.entries()) {
62 entriesToAdd.add(new AbstractMap.SimpleEntry<Key,Val>(
63 renameClassesInThing(renames, entry.getKey()),
64 renameClassesInThing(renames, entry.getValue())
65 ));
66 }
67 map.clear();
68 for (Map.Entry<Key,Val> entry : entriesToAdd) {
69 map.put(entry.getKey(), entry.getValue());
70 }
71 }
72
73 public static <Key,Val> void renameMethodsInMultimap(Map<MethodEntry,MethodEntry> renames, Multimap<Key,Val> map) {
74 // for each key/value pair...
75 Set<Map.Entry<Key,Val>> entriesToAdd = Sets.newHashSet();
76 for (Map.Entry<Key,Val> entry : map.entries()) {
77 entriesToAdd.add(new AbstractMap.SimpleEntry<Key,Val>(
78 renameMethodsInThing(renames, entry.getKey()),
79 renameMethodsInThing(renames, entry.getValue())
80 ));
81 }
82 map.clear();
83 for (Map.Entry<Key,Val> entry : entriesToAdd) {
84 map.put(entry.getKey(), entry.getValue());
85 }
86 }
87
88 public static <Key,Val> void renameMethodsInMap(Map<MethodEntry,MethodEntry> renames, Map<Key,Val> map) {
89 // for each key/value pair...
90 Set<Map.Entry<Key,Val>> entriesToAdd = Sets.newHashSet();
91 for (Map.Entry<Key,Val> entry : map.entrySet()) {
92 entriesToAdd.add(new AbstractMap.SimpleEntry<Key,Val>(
93 renameMethodsInThing(renames, entry.getKey()),
94 renameMethodsInThing(renames, entry.getValue())
95 ));
96 }
97 map.clear();
98 for (Map.Entry<Key,Val> entry : entriesToAdd) {
99 map.put(entry.getKey(), entry.getValue());
100 }
101 }
102
103 @SuppressWarnings("unchecked")
104 public static <T> T renameMethodsInThing(Map<MethodEntry,MethodEntry> renames, T thing) {
105 if (thing instanceof MethodEntry) {
106 MethodEntry methodEntry = (MethodEntry)thing;
107 MethodEntry newMethodEntry = renames.get(methodEntry);
108 if (newMethodEntry != null) {
109 return (T)new MethodEntry(
110 methodEntry.getClassEntry(),
111 newMethodEntry.getName(),
112 methodEntry.getSignature()
113 );
114 }
115 return thing;
116 } else if (thing instanceof ArgumentEntry) {
117 ArgumentEntry argumentEntry = (ArgumentEntry)thing;
118 return (T)new ArgumentEntry(
119 renameMethodsInThing(renames, argumentEntry.getBehaviorEntry()),
120 argumentEntry.getIndex(),
121 argumentEntry.getName()
122 );
123 } else if (thing instanceof EntryReference) {
124 EntryReference<Entry,Entry> reference = (EntryReference<Entry,Entry>)thing;
125 reference.entry = renameMethodsInThing(renames, reference.entry);
126 reference.context = renameMethodsInThing(renames, reference.context);
127 return thing;
128 }
129 return thing;
130 }
131
132 @SuppressWarnings("unchecked")
133 public static <T> T renameClassesInThing(final Map<String,String> renames, T thing) {
134 if (thing instanceof String) {
135 String stringEntry = (String)thing;
136 if (renames.containsKey(stringEntry)) {
137 return (T)renames.get(stringEntry);
138 }
139 } else if (thing instanceof ClassEntry) {
140 ClassEntry classEntry = (ClassEntry)thing;
141 return (T)new ClassEntry(renameClassesInThing(renames, classEntry.getClassName()));
142 } else if (thing instanceof FieldEntry) {
143 FieldEntry fieldEntry = (FieldEntry)thing;
144 return (T)new FieldEntry(
145 renameClassesInThing(renames, fieldEntry.getClassEntry()),
146 fieldEntry.getName(),
147 renameClassesInThing(renames, fieldEntry.getType())
148 );
149 } else if (thing instanceof ConstructorEntry) {
150 ConstructorEntry constructorEntry = (ConstructorEntry)thing;
151 return (T)new ConstructorEntry(
152 renameClassesInThing(renames, constructorEntry.getClassEntry()),
153 renameClassesInThing(renames, constructorEntry.getSignature())
154 );
155 } else if (thing instanceof MethodEntry) {
156 MethodEntry methodEntry = (MethodEntry)thing;
157 return (T)new MethodEntry(
158 renameClassesInThing(renames, methodEntry.getClassEntry()),
159 methodEntry.getName(),
160 renameClassesInThing(renames, methodEntry.getSignature())
161 );
162 } else if (thing instanceof ArgumentEntry) {
163 ArgumentEntry argumentEntry = (ArgumentEntry)thing;
164 return (T)new ArgumentEntry(
165 renameClassesInThing(renames, argumentEntry.getBehaviorEntry()),
166 argumentEntry.getIndex(),
167 argumentEntry.getName()
168 );
169 } else if (thing instanceof EntryReference) {
170 EntryReference<Entry,Entry> reference = (EntryReference<Entry,Entry>)thing;
171 reference.entry = renameClassesInThing(renames, reference.entry);
172 reference.context = renameClassesInThing(renames, reference.context);
173 return thing;
174 } else if (thing instanceof Signature) {
175 return (T)new Signature((Signature)thing, new ClassNameReplacer() {
176 @Override
177 public String replace(String className) {
178 return renameClassesInThing(renames, className);
179 }
180 });
181 } else if (thing instanceof Type) {
182 return (T)new Type((Type)thing, new ClassNameReplacer() {
183 @Override
184 public String replace(String className) {
185 return renameClassesInThing(renames, className);
186 }
187 });
188 }
189
190 return thing;
191 }
192}
diff --git a/src/cuchaz/enigma/analysis/FieldReferenceTreeNode.java b/src/cuchaz/enigma/analysis/FieldReferenceTreeNode.java
new file mode 100644
index 00000000..4ed8fee2
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/FieldReferenceTreeNode.java
@@ -0,0 +1,81 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import javax.swing.tree.DefaultMutableTreeNode;
14
15import cuchaz.enigma.mapping.BehaviorEntry;
16import cuchaz.enigma.mapping.FieldEntry;
17import cuchaz.enigma.mapping.Translator;
18
19public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode<FieldEntry,BehaviorEntry> {
20
21 private static final long serialVersionUID = -7934108091928699835L;
22
23 private Translator m_deobfuscatingTranslator;
24 private FieldEntry m_entry;
25 private EntryReference<FieldEntry,BehaviorEntry> m_reference;
26 private Access m_access;
27
28 public FieldReferenceTreeNode(Translator deobfuscatingTranslator, FieldEntry entry) {
29 m_deobfuscatingTranslator = deobfuscatingTranslator;
30 m_entry = entry;
31 m_reference = null;
32 }
33
34 private FieldReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference<FieldEntry,BehaviorEntry> reference, Access access) {
35 m_deobfuscatingTranslator = deobfuscatingTranslator;
36 m_entry = reference.entry;
37 m_reference = reference;
38 m_access = access;
39 }
40
41 @Override
42 public FieldEntry getEntry() {
43 return m_entry;
44 }
45
46 @Override
47 public EntryReference<FieldEntry,BehaviorEntry> getReference() {
48 return m_reference;
49 }
50
51 @Override
52 public String toString() {
53 if (m_reference != null) {
54 return String.format("%s (%s)", m_deobfuscatingTranslator.translateEntry(m_reference.context), m_access);
55 }
56 return m_deobfuscatingTranslator.translateEntry(m_entry).toString();
57 }
58
59 public void load(JarIndex index, boolean recurse) {
60 // get all the child nodes
61 if (m_reference == null) {
62 for (EntryReference<FieldEntry,BehaviorEntry> reference : index.getFieldReferences(m_entry)) {
63 add(new FieldReferenceTreeNode(m_deobfuscatingTranslator, reference, index.getAccess(m_entry)));
64 }
65 } else {
66 for (EntryReference<BehaviorEntry,BehaviorEntry> reference : index.getBehaviorReferences(m_reference.context)) {
67 add(new BehaviorReferenceTreeNode(m_deobfuscatingTranslator, reference, index.getAccess(m_reference.context)));
68 }
69 }
70
71 if (recurse && children != null) {
72 for (Object node : children) {
73 if (node instanceof BehaviorReferenceTreeNode) {
74 ((BehaviorReferenceTreeNode)node).load(index, true);
75 } else if (node instanceof FieldReferenceTreeNode) {
76 ((FieldReferenceTreeNode)node).load(index, true);
77 }
78 }
79 }
80 }
81}
diff --git a/src/cuchaz/enigma/analysis/JarClassIterator.java b/src/cuchaz/enigma/analysis/JarClassIterator.java
new file mode 100644
index 00000000..aa58e9ec
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/JarClassIterator.java
@@ -0,0 +1,137 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.io.ByteArrayOutputStream;
14import java.io.IOException;
15import java.io.InputStream;
16import java.util.Enumeration;
17import java.util.Iterator;
18import java.util.List;
19import java.util.jar.JarEntry;
20import java.util.jar.JarFile;
21
22import javassist.ByteArrayClassPath;
23import javassist.ClassPool;
24import javassist.CtClass;
25import javassist.NotFoundException;
26import javassist.bytecode.Descriptor;
27
28import com.google.common.collect.Lists;
29
30import cuchaz.enigma.Constants;
31import cuchaz.enigma.mapping.ClassEntry;
32
33public class JarClassIterator implements Iterator<CtClass> {
34
35 private JarFile m_jar;
36 private Iterator<JarEntry> m_iter;
37
38 public JarClassIterator(JarFile jar) {
39 m_jar = jar;
40
41 // get the jar entries that correspond to classes
42 List<JarEntry> classEntries = Lists.newArrayList();
43 Enumeration<JarEntry> entries = m_jar.entries();
44 while (entries.hasMoreElements()) {
45 JarEntry entry = entries.nextElement();
46
47 // is this a class file?
48 if (entry.getName().endsWith(".class")) {
49 classEntries.add(entry);
50 }
51 }
52 m_iter = classEntries.iterator();
53 }
54
55 @Override
56 public boolean hasNext() {
57 return m_iter.hasNext();
58 }
59
60 @Override
61 public CtClass next() {
62 JarEntry entry = m_iter.next();
63 try {
64 return getClass(m_jar, entry);
65 } catch (IOException | NotFoundException ex) {
66 throw new Error("Unable to load class: " + entry.getName());
67 }
68 }
69
70 @Override
71 public void remove() {
72 throw new UnsupportedOperationException();
73 }
74
75 public static List<ClassEntry> getClassEntries(JarFile jar) {
76 List<ClassEntry> classEntries = Lists.newArrayList();
77 Enumeration<JarEntry> entries = jar.entries();
78 while (entries.hasMoreElements()) {
79 JarEntry entry = entries.nextElement();
80
81 // is this a class file?
82 if (!entry.isDirectory() && entry.getName().endsWith(".class")) {
83 classEntries.add(getClassEntry(entry));
84 }
85 }
86 return classEntries;
87 }
88
89 public static Iterable<CtClass> classes(final JarFile jar) {
90 return new Iterable<CtClass>() {
91 @Override
92 public Iterator<CtClass> iterator() {
93 return new JarClassIterator(jar);
94 }
95 };
96 }
97
98 public static CtClass getClass(JarFile jar, ClassEntry classEntry) {
99 try {
100 return getClass(jar, new JarEntry(classEntry.getName() + ".class"));
101 } catch (IOException | NotFoundException ex) {
102 throw new Error("Unable to load class: " + classEntry.getName());
103 }
104 }
105
106 private static CtClass getClass(JarFile jar, JarEntry entry) throws IOException, NotFoundException {
107 // read the class into a buffer
108 ByteArrayOutputStream bos = new ByteArrayOutputStream();
109 byte[] buf = new byte[Constants.KiB];
110 int totalNumBytesRead = 0;
111 InputStream in = jar.getInputStream(entry);
112 while (in.available() > 0) {
113 int numBytesRead = in.read(buf);
114 if (numBytesRead < 0) {
115 break;
116 }
117 bos.write(buf, 0, numBytesRead);
118
119 // sanity checking
120 totalNumBytesRead += numBytesRead;
121 if (totalNumBytesRead > Constants.MiB) {
122 throw new Error("Class file " + entry.getName() + " larger than 1 MiB! Something is wrong!");
123 }
124 }
125
126 // get a javassist handle for the class
127 String className = Descriptor.toJavaName(getClassEntry(entry).getName());
128 ClassPool classPool = new ClassPool();
129 classPool.appendSystemPath();
130 classPool.insertClassPath(new ByteArrayClassPath(className, bos.toByteArray()));
131 return classPool.get(className);
132 }
133
134 private static ClassEntry getClassEntry(JarEntry entry) {
135 return new ClassEntry(entry.getName().substring(0, entry.getName().length() - ".class".length()));
136 }
137}
diff --git a/src/cuchaz/enigma/analysis/JarIndex.java b/src/cuchaz/enigma/analysis/JarIndex.java
new file mode 100644
index 00000000..5c8ec1c4
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/JarIndex.java
@@ -0,0 +1,837 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.lang.reflect.Modifier;
14import java.util.Collection;
15import java.util.Collections;
16import java.util.HashSet;
17import java.util.List;
18import java.util.Map;
19import java.util.Set;
20import java.util.jar.JarFile;
21
22import javassist.CannotCompileException;
23import javassist.CtBehavior;
24import javassist.CtClass;
25import javassist.CtConstructor;
26import javassist.CtField;
27import javassist.CtMethod;
28import javassist.NotFoundException;
29import javassist.bytecode.AccessFlag;
30import javassist.bytecode.Descriptor;
31import javassist.bytecode.EnclosingMethodAttribute;
32import javassist.bytecode.FieldInfo;
33import javassist.bytecode.InnerClassesAttribute;
34import javassist.expr.ConstructorCall;
35import javassist.expr.ExprEditor;
36import javassist.expr.FieldAccess;
37import javassist.expr.MethodCall;
38import javassist.expr.NewExpr;
39
40import com.google.common.collect.HashMultimap;
41import com.google.common.collect.Lists;
42import com.google.common.collect.Maps;
43import com.google.common.collect.Multimap;
44import com.google.common.collect.Sets;
45
46import cuchaz.enigma.Constants;
47import cuchaz.enigma.bytecode.ClassRenamer;
48import cuchaz.enigma.mapping.ArgumentEntry;
49import cuchaz.enigma.mapping.BehaviorEntry;
50import cuchaz.enigma.mapping.ClassEntry;
51import cuchaz.enigma.mapping.ConstructorEntry;
52import cuchaz.enigma.mapping.Entry;
53import cuchaz.enigma.mapping.EntryFactory;
54import cuchaz.enigma.mapping.FieldEntry;
55import cuchaz.enigma.mapping.MethodEntry;
56import cuchaz.enigma.mapping.Translator;
57
58public class JarIndex {
59
60 private Set<ClassEntry> m_obfClassEntries;
61 private TranslationIndex m_translationIndex;
62 private Map<Entry,Access> m_access;
63 private Multimap<ClassEntry,FieldEntry> m_fields;
64 private Multimap<ClassEntry,BehaviorEntry> m_behaviors;
65 private Multimap<String,MethodEntry> m_methodImplementations;
66 private Multimap<BehaviorEntry,EntryReference<BehaviorEntry,BehaviorEntry>> m_behaviorReferences;
67 private Multimap<FieldEntry,EntryReference<FieldEntry,BehaviorEntry>> m_fieldReferences;
68 private Multimap<ClassEntry,ClassEntry> m_innerClassesByOuter;
69 private Map<ClassEntry,ClassEntry> m_outerClassesByInner;
70 private Map<ClassEntry,BehaviorEntry> m_anonymousClasses;
71 private Map<MethodEntry,MethodEntry> m_bridgedMethods;
72
73 public JarIndex() {
74 m_obfClassEntries = Sets.newHashSet();
75 m_translationIndex = new TranslationIndex();
76 m_access = Maps.newHashMap();
77 m_fields = HashMultimap.create();
78 m_behaviors = HashMultimap.create();
79 m_methodImplementations = HashMultimap.create();
80 m_behaviorReferences = HashMultimap.create();
81 m_fieldReferences = HashMultimap.create();
82 m_innerClassesByOuter = HashMultimap.create();
83 m_outerClassesByInner = Maps.newHashMap();
84 m_anonymousClasses = Maps.newHashMap();
85 m_bridgedMethods = Maps.newHashMap();
86 }
87
88 public void indexJar(JarFile jar, boolean buildInnerClasses) {
89
90 // step 1: read the class names
91 for (ClassEntry classEntry : JarClassIterator.getClassEntries(jar)) {
92 if (classEntry.isInDefaultPackage()) {
93 // move out of default package
94 classEntry = new ClassEntry(Constants.NonePackage + "/" + classEntry.getName());
95 }
96 m_obfClassEntries.add(classEntry);
97 }
98
99 // step 2: index field/method/constructor access
100 for (CtClass c : JarClassIterator.classes(jar)) {
101 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
102 for (CtField field : c.getDeclaredFields()) {
103 FieldEntry fieldEntry = EntryFactory.getFieldEntry(field);
104 m_access.put(fieldEntry, Access.get(field));
105 m_fields.put(fieldEntry.getClassEntry(), fieldEntry);
106 }
107 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
108 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
109 m_access.put(behaviorEntry, Access.get(behavior));
110 m_behaviors.put(behaviorEntry.getClassEntry(), behaviorEntry);
111 }
112 }
113
114 // step 3: index extends, implements, fields, and methods
115 for (CtClass c : JarClassIterator.classes(jar)) {
116 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
117 m_translationIndex.indexClass(c);
118 String className = Descriptor.toJvmName(c.getName());
119 for (String interfaceName : c.getClassFile().getInterfaces()) {
120 className = Descriptor.toJvmName(className);
121 interfaceName = Descriptor.toJvmName(interfaceName);
122 if (className.equals(interfaceName)) {
123 throw new IllegalArgumentException("Class cannot be its own interface! " + className);
124 }
125 }
126 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
127 indexBehavior(behavior);
128 }
129 }
130
131 // step 4: index field, method, constructor references
132 for (CtClass c : JarClassIterator.classes(jar)) {
133 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
134 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
135 indexBehaviorReferences(behavior);
136 }
137 }
138
139 if (buildInnerClasses) {
140
141 // step 5: index inner classes and anonymous classes
142 for (CtClass c : JarClassIterator.classes(jar)) {
143 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
144 ClassEntry innerClassEntry = EntryFactory.getClassEntry(c);
145 ClassEntry outerClassEntry = findOuterClass(c);
146 if (outerClassEntry != null) {
147 m_innerClassesByOuter.put(outerClassEntry, innerClassEntry);
148 boolean innerWasAdded = m_outerClassesByInner.put(innerClassEntry, outerClassEntry) == null;
149 assert (innerWasAdded);
150
151 BehaviorEntry enclosingBehavior = isAnonymousClass(c, outerClassEntry);
152 if (enclosingBehavior != null) {
153 m_anonymousClasses.put(innerClassEntry, enclosingBehavior);
154
155 // DEBUG
156 //System.out.println("ANONYMOUS: " + outerClassEntry.getName() + "$" + innerClassEntry.getSimpleName());
157 } else {
158 // DEBUG
159 //System.out.println("INNER: " + outerClassEntry.getName() + "$" + innerClassEntry.getSimpleName());
160 }
161 }
162 }
163
164 // step 6: update other indices with inner class info
165 Map<String,String> renames = Maps.newHashMap();
166 for (ClassEntry innerClassEntry : m_innerClassesByOuter.values()) {
167 String newName = innerClassEntry.buildClassEntry(getObfClassChain(innerClassEntry)).getName();
168 if (!innerClassEntry.getName().equals(newName)) {
169 // DEBUG
170 //System.out.println("REPLACE: " + innerClassEntry.getName() + " WITH " + newName);
171 renames.put(innerClassEntry.getName(), newName);
172 }
173 }
174 EntryRenamer.renameClassesInSet(renames, m_obfClassEntries);
175 m_translationIndex.renameClasses(renames);
176 EntryRenamer.renameClassesInMultimap(renames, m_methodImplementations);
177 EntryRenamer.renameClassesInMultimap(renames, m_behaviorReferences);
178 EntryRenamer.renameClassesInMultimap(renames, m_fieldReferences);
179 EntryRenamer.renameClassesInMap(renames, m_access);
180 }
181 }
182
183 private void indexBehavior(CtBehavior behavior) {
184 // get the behavior entry
185 final BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
186 if (behaviorEntry instanceof MethodEntry) {
187 MethodEntry methodEntry = (MethodEntry)behaviorEntry;
188
189 // index implementation
190 m_methodImplementations.put(behaviorEntry.getClassName(), methodEntry);
191
192 // look for bridge and bridged methods
193 CtMethod bridgedMethod = getBridgedMethod((CtMethod)behavior);
194 if (bridgedMethod != null) {
195 m_bridgedMethods.put(methodEntry, EntryFactory.getMethodEntry(bridgedMethod));
196 }
197 }
198 // looks like we don't care about constructors here
199 }
200
201 private void indexBehaviorReferences(CtBehavior behavior) {
202 // index method calls
203 final BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
204 try {
205 behavior.instrument(new ExprEditor() {
206 @Override
207 public void edit(MethodCall call) {
208 MethodEntry calledMethodEntry = EntryFactory.getMethodEntry(call);
209 ClassEntry resolvedClassEntry = m_translationIndex.resolveEntryClass(calledMethodEntry);
210 if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledMethodEntry.getClassEntry())) {
211 calledMethodEntry = new MethodEntry(
212 resolvedClassEntry,
213 calledMethodEntry.getName(),
214 calledMethodEntry.getSignature()
215 );
216 }
217 EntryReference<BehaviorEntry,BehaviorEntry> reference = new EntryReference<BehaviorEntry,BehaviorEntry>(
218 calledMethodEntry,
219 call.getMethodName(),
220 behaviorEntry
221 );
222 m_behaviorReferences.put(calledMethodEntry, reference);
223 }
224
225 @Override
226 public void edit(FieldAccess call) {
227 FieldEntry calledFieldEntry = EntryFactory.getFieldEntry(call);
228 ClassEntry resolvedClassEntry = m_translationIndex.resolveEntryClass(calledFieldEntry);
229 if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledFieldEntry.getClassEntry())) {
230 calledFieldEntry = new FieldEntry(calledFieldEntry, resolvedClassEntry);
231 }
232 EntryReference<FieldEntry,BehaviorEntry> reference = new EntryReference<FieldEntry,BehaviorEntry>(
233 calledFieldEntry,
234 call.getFieldName(),
235 behaviorEntry
236 );
237 m_fieldReferences.put(calledFieldEntry, reference);
238 }
239
240 @Override
241 public void edit(ConstructorCall call) {
242 ConstructorEntry calledConstructorEntry = EntryFactory.getConstructorEntry(call);
243 EntryReference<BehaviorEntry,BehaviorEntry> reference = new EntryReference<BehaviorEntry,BehaviorEntry>(
244 calledConstructorEntry,
245 call.getMethodName(),
246 behaviorEntry
247 );
248 m_behaviorReferences.put(calledConstructorEntry, reference);
249 }
250
251 @Override
252 public void edit(NewExpr call) {
253 ConstructorEntry calledConstructorEntry = EntryFactory.getConstructorEntry(call);
254 EntryReference<BehaviorEntry,BehaviorEntry> reference = new EntryReference<BehaviorEntry,BehaviorEntry>(
255 calledConstructorEntry,
256 call.getClassName(),
257 behaviorEntry
258 );
259 m_behaviorReferences.put(calledConstructorEntry, reference);
260 }
261 });
262 } catch (CannotCompileException ex) {
263 throw new Error(ex);
264 }
265 }
266
267 private CtMethod getBridgedMethod(CtMethod method) {
268
269 // bridge methods just call another method, cast it to the return type, and return the result
270 // let's see if we can detect this scenario
271
272 // skip non-synthetic methods
273 if ((method.getModifiers() & AccessFlag.SYNTHETIC) == 0) {
274 return null;
275 }
276
277 // get all the called methods
278 final List<MethodCall> methodCalls = Lists.newArrayList();
279 try {
280 method.instrument(new ExprEditor() {
281 @Override
282 public void edit(MethodCall call) {
283 methodCalls.add(call);
284 }
285 });
286 } catch (CannotCompileException ex) {
287 // this is stupid... we're not even compiling anything
288 throw new Error(ex);
289 }
290
291 // is there just one?
292 if (methodCalls.size() != 1) {
293 return null;
294 }
295 MethodCall call = methodCalls.get(0);
296
297 try {
298 // we have a bridge method!
299 return call.getMethod();
300 } catch (NotFoundException ex) {
301 // can't find the type? not a bridge method
302 return null;
303 }
304 }
305
306 private ClassEntry findOuterClass(CtClass c) {
307
308 ClassEntry classEntry = EntryFactory.getClassEntry(c);
309
310 // does this class already have an outer class?
311 if (classEntry.isInnerClass()) {
312 return classEntry.getOuterClassEntry();
313 }
314
315 // inner classes:
316 // have constructors that can (illegally) set synthetic fields
317 // the outer class is the only class that calls constructors
318
319 // use the synthetic fields to find the synthetic constructors
320 for (CtConstructor constructor : c.getDeclaredConstructors()) {
321 Set<String> syntheticFieldTypes = Sets.newHashSet();
322 if (!isIllegalConstructor(syntheticFieldTypes, constructor)) {
323 continue;
324 }
325
326 ConstructorEntry constructorEntry = EntryFactory.getConstructorEntry(constructor);
327
328 // gather the classes from the illegally-set synthetic fields
329 Set<ClassEntry> illegallySetClasses = Sets.newHashSet();
330 for (String type : syntheticFieldTypes) {
331 if (type.startsWith("L")) {
332 ClassEntry outerClassEntry = new ClassEntry(type.substring(1, type.length() - 1));
333 if (isSaneOuterClass(outerClassEntry, classEntry)) {
334 illegallySetClasses.add(outerClassEntry);
335 }
336 }
337 }
338
339 // who calls this constructor?
340 Set<ClassEntry> callerClasses = Sets.newHashSet();
341 for (EntryReference<BehaviorEntry,BehaviorEntry> reference : getBehaviorReferences(constructorEntry)) {
342
343 // make sure it's not a call to super
344 if (reference.entry instanceof ConstructorEntry && reference.context instanceof ConstructorEntry) {
345
346 // is the entry a superclass of the context?
347 ClassEntry calledClassEntry = reference.entry.getClassEntry();
348 ClassEntry superclassEntry = m_translationIndex.getSuperclass(reference.context.getClassEntry());
349 if (superclassEntry != null && superclassEntry.equals(calledClassEntry)) {
350 // it's a super call, skip
351 continue;
352 }
353 }
354
355 if (isSaneOuterClass(reference.context.getClassEntry(), classEntry)) {
356 callerClasses.add(reference.context.getClassEntry());
357 }
358 }
359
360 // do we have an answer yet?
361 if (callerClasses.isEmpty()) {
362 if (illegallySetClasses.size() == 1) {
363 return illegallySetClasses.iterator().next();
364 } else {
365 System.out.println(String.format("WARNING: Unable to find outer class for %s. No caller and no illegally set field classes.", classEntry));
366 }
367 } else {
368 if (callerClasses.size() == 1) {
369 return callerClasses.iterator().next();
370 } else {
371 // multiple callers, do the illegally set classes narrow it down?
372 Set<ClassEntry> intersection = Sets.newHashSet(callerClasses);
373 intersection.retainAll(illegallySetClasses);
374 if (intersection.size() == 1) {
375 return intersection.iterator().next();
376 } else {
377 System.out.println(String.format("WARNING: Unable to choose outer class for %s among options: %s", classEntry, callerClasses));
378 }
379 }
380 }
381 }
382
383 return null;
384 }
385
386 private boolean isSaneOuterClass(ClassEntry outerClassEntry, ClassEntry innerClassEntry) {
387
388 // clearly this would be silly
389 if (outerClassEntry.equals(innerClassEntry)) {
390 return false;
391 }
392
393 // is the outer class in the jar?
394 if (!m_obfClassEntries.contains(outerClassEntry)) {
395 return false;
396 }
397
398 return true;
399 }
400
401 @SuppressWarnings("unchecked")
402 private boolean isIllegalConstructor(Set<String> syntheticFieldTypes, CtConstructor constructor) {
403
404 // illegal constructors only set synthetic member fields, then call super()
405 String className = constructor.getDeclaringClass().getName();
406
407 // collect all the field accesses, constructor calls, and method calls
408 final List<FieldAccess> illegalFieldWrites = Lists.newArrayList();
409 final List<ConstructorCall> constructorCalls = Lists.newArrayList();
410 try {
411 constructor.instrument(new ExprEditor() {
412 @Override
413 public void edit(FieldAccess fieldAccess) {
414 if (fieldAccess.isWriter() && constructorCalls.isEmpty()) {
415 illegalFieldWrites.add(fieldAccess);
416 }
417 }
418
419 @Override
420 public void edit(ConstructorCall constructorCall) {
421 constructorCalls.add(constructorCall);
422 }
423 });
424 } catch (CannotCompileException ex) {
425 // we're not compiling anything... this is stupid
426 throw new Error(ex);
427 }
428
429 // are there any illegal field writes?
430 if (illegalFieldWrites.isEmpty()) {
431 return false;
432 }
433
434 // are all the writes to synthetic fields?
435 for (FieldAccess fieldWrite : illegalFieldWrites) {
436
437 // all illegal writes have to be to the local class
438 if (!fieldWrite.getClassName().equals(className)) {
439 System.err.println(String.format("WARNING: illegal write to non-member field %s.%s", fieldWrite.getClassName(), fieldWrite.getFieldName()));
440 return false;
441 }
442
443 // find the field
444 FieldInfo fieldInfo = null;
445 for (FieldInfo info : (List<FieldInfo>)constructor.getDeclaringClass().getClassFile().getFields()) {
446 if (info.getName().equals(fieldWrite.getFieldName()) && info.getDescriptor().equals(fieldWrite.getSignature())) {
447 fieldInfo = info;
448 break;
449 }
450 }
451 if (fieldInfo == null) {
452 // field is in a superclass or something, can't be a local synthetic member
453 return false;
454 }
455
456 // is this field synthetic?
457 boolean isSynthetic = (fieldInfo.getAccessFlags() & AccessFlag.SYNTHETIC) != 0;
458 if (isSynthetic) {
459 syntheticFieldTypes.add(fieldInfo.getDescriptor());
460 } else {
461 System.err.println(String.format("WARNING: illegal write to non synthetic field %s %s.%s", fieldInfo.getDescriptor(), className, fieldInfo.getName()));
462 return false;
463 }
464 }
465
466 // we passed all the tests!
467 return true;
468 }
469
470 private BehaviorEntry isAnonymousClass(CtClass c, ClassEntry outerClassEntry) {
471
472 // is this class already marked anonymous?
473 EnclosingMethodAttribute enclosingMethodAttribute = (EnclosingMethodAttribute)c.getClassFile().getAttribute(EnclosingMethodAttribute.tag);
474 if (enclosingMethodAttribute != null) {
475 if (enclosingMethodAttribute.methodIndex() > 0) {
476 return EntryFactory.getBehaviorEntry(
477 Descriptor.toJvmName(enclosingMethodAttribute.className()),
478 enclosingMethodAttribute.methodName(),
479 enclosingMethodAttribute.methodDescriptor()
480 );
481 } else {
482 // an attribute but no method? assume not anonymous
483 return null;
484 }
485 }
486
487 // if there's an inner class attribute, but not an enclosing method attribute, then it's not anonymous
488 InnerClassesAttribute innerClassesAttribute = (InnerClassesAttribute)c.getClassFile().getAttribute(InnerClassesAttribute.tag);
489 if (innerClassesAttribute != null) {
490 return null;
491 }
492
493 ClassEntry innerClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
494
495 // anonymous classes:
496 // can't be abstract
497 // have only one constructor
498 // it's called exactly once by the outer class
499 // the type the instance is assigned to can't be this type
500
501 // is abstract?
502 if (Modifier.isAbstract(c.getModifiers())) {
503 return null;
504 }
505
506 // is there exactly one constructor?
507 if (c.getDeclaredConstructors().length != 1) {
508 return null;
509 }
510 CtConstructor constructor = c.getDeclaredConstructors()[0];
511
512 // is this constructor called exactly once?
513 ConstructorEntry constructorEntry = EntryFactory.getConstructorEntry(constructor);
514 Collection<EntryReference<BehaviorEntry,BehaviorEntry>> references = getBehaviorReferences(constructorEntry);
515 if (references.size() != 1) {
516 return null;
517 }
518
519 // does the caller use this type?
520 BehaviorEntry caller = references.iterator().next().context;
521 for (FieldEntry fieldEntry : getReferencedFields(caller)) {
522 if (fieldEntry.getType().hasClass() && fieldEntry.getType().getClassEntry().equals(innerClassEntry)) {
523 // caller references this type, so it can't be anonymous
524 return null;
525 }
526 }
527 for (BehaviorEntry behaviorEntry : getReferencedBehaviors(caller)) {
528 if (behaviorEntry.getSignature().hasClass(innerClassEntry)) {
529 return null;
530 }
531 }
532
533 return caller;
534 }
535
536 public Set<ClassEntry> getObfClassEntries() {
537 return m_obfClassEntries;
538 }
539
540 public Collection<FieldEntry> getObfFieldEntries() {
541 return m_fields.values();
542 }
543
544 public Collection<FieldEntry> getObfFieldEntries(ClassEntry classEntry) {
545 return m_fields.get(classEntry);
546 }
547
548 public Collection<BehaviorEntry> getObfBehaviorEntries() {
549 return m_behaviors.values();
550 }
551
552 public Collection<BehaviorEntry> getObfBehaviorEntries(ClassEntry classEntry) {
553 return m_behaviors.get(classEntry);
554 }
555
556 public TranslationIndex getTranslationIndex() {
557 return m_translationIndex;
558 }
559
560 public Access getAccess(Entry entry) {
561 return m_access.get(entry);
562 }
563
564 public ClassInheritanceTreeNode getClassInheritance(Translator deobfuscatingTranslator, ClassEntry obfClassEntry) {
565
566 // get the root node
567 List<String> ancestry = Lists.newArrayList();
568 ancestry.add(obfClassEntry.getName());
569 for (ClassEntry classEntry : m_translationIndex.getAncestry(obfClassEntry)) {
570 ancestry.add(classEntry.getName());
571 }
572 ClassInheritanceTreeNode rootNode = new ClassInheritanceTreeNode(
573 deobfuscatingTranslator,
574 ancestry.get(ancestry.size() - 1)
575 );
576
577 // expand all children recursively
578 rootNode.load(m_translationIndex, true);
579
580 return rootNode;
581 }
582
583 public ClassImplementationsTreeNode getClassImplementations(Translator deobfuscatingTranslator, ClassEntry obfClassEntry) {
584
585 // is this even an interface?
586 if (isInterface(obfClassEntry.getClassName())) {
587 ClassImplementationsTreeNode node = new ClassImplementationsTreeNode(deobfuscatingTranslator, obfClassEntry);
588 node.load(this);
589 return node;
590 }
591 return null;
592 }
593
594 public MethodInheritanceTreeNode getMethodInheritance(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) {
595
596 // travel to the ancestor implementation
597 ClassEntry baseImplementationClassEntry = obfMethodEntry.getClassEntry();
598 for (ClassEntry ancestorClassEntry : m_translationIndex.getAncestry(obfMethodEntry.getClassEntry())) {
599 MethodEntry ancestorMethodEntry = new MethodEntry(
600 new ClassEntry(ancestorClassEntry),
601 obfMethodEntry.getName(),
602 obfMethodEntry.getSignature()
603 );
604 if (containsObfBehavior(ancestorMethodEntry)) {
605 baseImplementationClassEntry = ancestorClassEntry;
606 }
607 }
608
609 // make a root node at the base
610 MethodEntry methodEntry = new MethodEntry(
611 baseImplementationClassEntry,
612 obfMethodEntry.getName(),
613 obfMethodEntry.getSignature()
614 );
615 MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode(
616 deobfuscatingTranslator,
617 methodEntry,
618 containsObfBehavior(methodEntry)
619 );
620
621 // expand the full tree
622 rootNode.load(this, true);
623
624 return rootNode;
625 }
626
627 public List<MethodImplementationsTreeNode> getMethodImplementations(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) {
628
629 List<MethodEntry> interfaceMethodEntries = Lists.newArrayList();
630
631 // is this method on an interface?
632 if (isInterface(obfMethodEntry.getClassName())) {
633 interfaceMethodEntries.add(obfMethodEntry);
634 } else {
635 // get the interface class
636 for (ClassEntry interfaceEntry : getInterfaces(obfMethodEntry.getClassName())) {
637
638 // is this method defined in this interface?
639 MethodEntry methodInterface = new MethodEntry(
640 interfaceEntry,
641 obfMethodEntry.getName(),
642 obfMethodEntry.getSignature()
643 );
644 if (containsObfBehavior(methodInterface)) {
645 interfaceMethodEntries.add(methodInterface);
646 }
647 }
648 }
649
650 List<MethodImplementationsTreeNode> nodes = Lists.newArrayList();
651 if (!interfaceMethodEntries.isEmpty()) {
652 for (MethodEntry interfaceMethodEntry : interfaceMethodEntries) {
653 MethodImplementationsTreeNode node = new MethodImplementationsTreeNode(deobfuscatingTranslator, interfaceMethodEntry);
654 node.load(this);
655 nodes.add(node);
656 }
657 }
658 return nodes;
659 }
660
661 public Set<MethodEntry> getRelatedMethodImplementations(MethodEntry obfMethodEntry) {
662 Set<MethodEntry> methodEntries = Sets.newHashSet();
663 getRelatedMethodImplementations(methodEntries, getMethodInheritance(null, obfMethodEntry));
664 return methodEntries;
665 }
666
667 private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodInheritanceTreeNode node) {
668 MethodEntry methodEntry = node.getMethodEntry();
669 if (containsObfBehavior(methodEntry)) {
670 // collect the entry
671 methodEntries.add(methodEntry);
672 }
673
674 // look at interface methods too
675 for (MethodImplementationsTreeNode implementationsNode : getMethodImplementations(null, methodEntry)) {
676 getRelatedMethodImplementations(methodEntries, implementationsNode);
677 }
678
679 // recurse
680 for (int i = 0; i < node.getChildCount(); i++) {
681 getRelatedMethodImplementations(methodEntries, (MethodInheritanceTreeNode)node.getChildAt(i));
682 }
683 }
684
685 private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodImplementationsTreeNode node) {
686 MethodEntry methodEntry = node.getMethodEntry();
687 if (containsObfBehavior(methodEntry)) {
688 // collect the entry
689 methodEntries.add(methodEntry);
690 }
691
692 // recurse
693 for (int i = 0; i < node.getChildCount(); i++) {
694 getRelatedMethodImplementations(methodEntries, (MethodImplementationsTreeNode)node.getChildAt(i));
695 }
696 }
697
698 public Collection<EntryReference<FieldEntry,BehaviorEntry>> getFieldReferences(FieldEntry fieldEntry) {
699 return m_fieldReferences.get(fieldEntry);
700 }
701
702 public Collection<FieldEntry> getReferencedFields(BehaviorEntry behaviorEntry) {
703 // linear search is fast enough for now
704 Set<FieldEntry> fieldEntries = Sets.newHashSet();
705 for (EntryReference<FieldEntry,BehaviorEntry> reference : m_fieldReferences.values()) {
706 if (reference.context == behaviorEntry) {
707 fieldEntries.add(reference.entry);
708 }
709 }
710 return fieldEntries;
711 }
712
713 public Collection<EntryReference<BehaviorEntry,BehaviorEntry>> getBehaviorReferences(BehaviorEntry behaviorEntry) {
714 return m_behaviorReferences.get(behaviorEntry);
715 }
716
717 public Collection<BehaviorEntry> getReferencedBehaviors(BehaviorEntry behaviorEntry) {
718 // linear search is fast enough for now
719 Set<BehaviorEntry> behaviorEntries = Sets.newHashSet();
720 for (EntryReference<BehaviorEntry,BehaviorEntry> reference : m_behaviorReferences.values()) {
721 if (reference.context == behaviorEntry) {
722 behaviorEntries.add(reference.entry);
723 }
724 }
725 return behaviorEntries;
726 }
727
728 public Collection<ClassEntry> getInnerClasses(ClassEntry obfOuterClassEntry) {
729 return m_innerClassesByOuter.get(obfOuterClassEntry);
730 }
731
732 public ClassEntry getOuterClass(ClassEntry obfInnerClassEntry) {
733 return m_outerClassesByInner.get(obfInnerClassEntry);
734 }
735
736 public boolean isAnonymousClass(ClassEntry obfInnerClassEntry) {
737 return m_anonymousClasses.containsKey(obfInnerClassEntry);
738 }
739
740 public BehaviorEntry getAnonymousClassCaller(ClassEntry obfInnerClassName) {
741 return m_anonymousClasses.get(obfInnerClassName);
742 }
743
744 public Set<ClassEntry> getInterfaces(String className) {
745 ClassEntry classEntry = new ClassEntry(className);
746 Set<ClassEntry> interfaces = new HashSet<ClassEntry>();
747 interfaces.addAll(m_translationIndex.getInterfaces(classEntry));
748 for (ClassEntry ancestor : m_translationIndex.getAncestry(classEntry)) {
749 interfaces.addAll(m_translationIndex.getInterfaces(ancestor));
750 }
751 return interfaces;
752 }
753
754 public Set<String> getImplementingClasses(String targetInterfaceName) {
755
756 // linear search is fast enough for now
757 Set<String> classNames = Sets.newHashSet();
758 for (Map.Entry<ClassEntry,ClassEntry> entry : m_translationIndex.getClassInterfaces()) {
759 ClassEntry classEntry = entry.getKey();
760 ClassEntry interfaceEntry = entry.getValue();
761 if (interfaceEntry.getName().equals(targetInterfaceName)) {
762 classNames.add(classEntry.getClassName());
763 m_translationIndex.getSubclassNamesRecursively(classNames, classEntry);
764 }
765 }
766 return classNames;
767 }
768
769 public boolean isInterface(String className) {
770 return m_translationIndex.isInterface(new ClassEntry(className));
771 }
772
773 public boolean containsObfClass(ClassEntry obfClassEntry) {
774 return m_obfClassEntries.contains(obfClassEntry);
775 }
776
777 public boolean containsObfField(FieldEntry obfFieldEntry) {
778 return m_access.containsKey(obfFieldEntry);
779 }
780
781 public boolean containsObfBehavior(BehaviorEntry obfBehaviorEntry) {
782 return m_access.containsKey(obfBehaviorEntry);
783 }
784
785 public boolean containsObfArgument(ArgumentEntry obfArgumentEntry) {
786 // check the behavior
787 if (!containsObfBehavior(obfArgumentEntry.getBehaviorEntry())) {
788 return false;
789 }
790
791 // check the argument
792 if (obfArgumentEntry.getIndex() >= obfArgumentEntry.getBehaviorEntry().getSignature().getArgumentTypes().size()) {
793 return false;
794 }
795
796 return true;
797 }
798
799 public boolean containsObfEntry(Entry obfEntry) {
800 if (obfEntry instanceof ClassEntry) {
801 return containsObfClass((ClassEntry)obfEntry);
802 } else if (obfEntry instanceof FieldEntry) {
803 return containsObfField((FieldEntry)obfEntry);
804 } else if (obfEntry instanceof BehaviorEntry) {
805 return containsObfBehavior((BehaviorEntry)obfEntry);
806 } else if (obfEntry instanceof ArgumentEntry) {
807 return containsObfArgument((ArgumentEntry)obfEntry);
808 } else {
809 throw new Error("Entry type not supported: " + obfEntry.getClass().getName());
810 }
811 }
812
813 public MethodEntry getBridgedMethod(MethodEntry bridgeMethodEntry) {
814 return m_bridgedMethods.get(bridgeMethodEntry);
815 }
816
817 public List<ClassEntry> getObfClassChain(ClassEntry obfClassEntry) {
818
819 // build class chain in inner-to-outer order
820 List<ClassEntry> obfClassChain = Lists.newArrayList(obfClassEntry);
821 ClassEntry checkClassEntry = obfClassEntry;
822 while (true) {
823 ClassEntry obfOuterClassEntry = getOuterClass(checkClassEntry);
824 if (obfOuterClassEntry != null) {
825 obfClassChain.add(obfOuterClassEntry);
826 checkClassEntry = obfOuterClassEntry;
827 } else {
828 break;
829 }
830 }
831
832 // switch to outer-to-inner order
833 Collections.reverse(obfClassChain);
834
835 return obfClassChain;
836 }
837}
diff --git a/src/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java b/src/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java
new file mode 100644
index 00000000..aa0aeca6
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java
@@ -0,0 +1,101 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.util.List;
14
15import javax.swing.tree.DefaultMutableTreeNode;
16
17import com.google.common.collect.Lists;
18
19import cuchaz.enigma.mapping.ClassEntry;
20import cuchaz.enigma.mapping.MethodEntry;
21import cuchaz.enigma.mapping.Translator;
22
23public class MethodImplementationsTreeNode extends DefaultMutableTreeNode {
24
25 private static final long serialVersionUID = 3781080657461899915L;
26
27 private Translator m_deobfuscatingTranslator;
28 private MethodEntry m_entry;
29
30 public MethodImplementationsTreeNode(Translator deobfuscatingTranslator, MethodEntry entry) {
31 if (entry == null) {
32 throw new IllegalArgumentException("entry cannot be null!");
33 }
34
35 m_deobfuscatingTranslator = deobfuscatingTranslator;
36 m_entry = entry;
37 }
38
39 public MethodEntry getMethodEntry() {
40 return m_entry;
41 }
42
43 public String getDeobfClassName() {
44 return m_deobfuscatingTranslator.translateClass(m_entry.getClassName());
45 }
46
47 public String getDeobfMethodName() {
48 return m_deobfuscatingTranslator.translate(m_entry);
49 }
50
51 @Override
52 public String toString() {
53 String className = getDeobfClassName();
54 if (className == null) {
55 className = m_entry.getClassName();
56 }
57
58 String methodName = getDeobfMethodName();
59 if (methodName == null) {
60 methodName = m_entry.getName();
61 }
62 return className + "." + methodName + "()";
63 }
64
65 public void load(JarIndex index) {
66
67 // get all method implementations
68 List<MethodImplementationsTreeNode> nodes = Lists.newArrayList();
69 for (String implementingClassName : index.getImplementingClasses(m_entry.getClassName())) {
70 MethodEntry methodEntry = new MethodEntry(
71 new ClassEntry(implementingClassName),
72 m_entry.getName(),
73 m_entry.getSignature()
74 );
75 if (index.containsObfBehavior(methodEntry)) {
76 nodes.add(new MethodImplementationsTreeNode(m_deobfuscatingTranslator, methodEntry));
77 }
78 }
79
80 // add them to this node
81 for (MethodImplementationsTreeNode node : nodes) {
82 this.add(node);
83 }
84 }
85
86 public static MethodImplementationsTreeNode findNode(MethodImplementationsTreeNode node, MethodEntry entry) {
87 // is this the node?
88 if (node.getMethodEntry().equals(entry)) {
89 return node;
90 }
91
92 // recurse
93 for (int i = 0; i < node.getChildCount(); i++) {
94 MethodImplementationsTreeNode foundNode = findNode((MethodImplementationsTreeNode)node.getChildAt(i), entry);
95 if (foundNode != null) {
96 return foundNode;
97 }
98 }
99 return null;
100 }
101}
diff --git a/src/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java b/src/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java
new file mode 100644
index 00000000..0da3c8c9
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java
@@ -0,0 +1,114 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.util.List;
14
15import javax.swing.tree.DefaultMutableTreeNode;
16
17import com.google.common.collect.Lists;
18
19import cuchaz.enigma.mapping.ClassEntry;
20import cuchaz.enigma.mapping.MethodEntry;
21import cuchaz.enigma.mapping.Translator;
22
23public class MethodInheritanceTreeNode extends DefaultMutableTreeNode {
24
25 private static final long serialVersionUID = 1096677030991810007L;
26
27 private Translator m_deobfuscatingTranslator;
28 private MethodEntry m_entry;
29 private boolean m_isImplemented;
30
31 public MethodInheritanceTreeNode(Translator deobfuscatingTranslator, MethodEntry entry, boolean isImplemented) {
32 m_deobfuscatingTranslator = deobfuscatingTranslator;
33 m_entry = entry;
34 m_isImplemented = isImplemented;
35 }
36
37 public MethodEntry getMethodEntry() {
38 return m_entry;
39 }
40
41 public String getDeobfClassName() {
42 return m_deobfuscatingTranslator.translateClass(m_entry.getClassName());
43 }
44
45 public String getDeobfMethodName() {
46 return m_deobfuscatingTranslator.translate(m_entry);
47 }
48
49 public boolean isImplemented() {
50 return m_isImplemented;
51 }
52
53 @Override
54 public String toString() {
55 String className = getDeobfClassName();
56 if (className == null) {
57 className = m_entry.getClassName();
58 }
59
60 if (!m_isImplemented) {
61 return className;
62 } else {
63 String methodName = getDeobfMethodName();
64 if (methodName == null) {
65 methodName = m_entry.getName();
66 }
67 return className + "." + methodName + "()";
68 }
69 }
70
71 public void load(JarIndex index, boolean recurse) {
72 // get all the child nodes
73 List<MethodInheritanceTreeNode> nodes = Lists.newArrayList();
74 for (ClassEntry subclassEntry : index.getTranslationIndex().getSubclass(m_entry.getClassEntry())) {
75 MethodEntry methodEntry = new MethodEntry(
76 subclassEntry,
77 m_entry.getName(),
78 m_entry.getSignature()
79 );
80 nodes.add(new MethodInheritanceTreeNode(
81 m_deobfuscatingTranslator,
82 methodEntry,
83 index.containsObfBehavior(methodEntry)
84 ));
85 }
86
87 // add them to this node
88 for (MethodInheritanceTreeNode node : nodes) {
89 this.add(node);
90 }
91
92 if (recurse) {
93 for (MethodInheritanceTreeNode node : nodes) {
94 node.load(index, true);
95 }
96 }
97 }
98
99 public static MethodInheritanceTreeNode findNode(MethodInheritanceTreeNode node, MethodEntry entry) {
100 // is this the node?
101 if (node.getMethodEntry().equals(entry)) {
102 return node;
103 }
104
105 // recurse
106 for (int i = 0; i < node.getChildCount(); i++) {
107 MethodInheritanceTreeNode foundNode = findNode((MethodInheritanceTreeNode)node.getChildAt(i), entry);
108 if (foundNode != null) {
109 return foundNode;
110 }
111 }
112 return null;
113 }
114}
diff --git a/src/cuchaz/enigma/analysis/ReferenceTreeNode.java b/src/cuchaz/enigma/analysis/ReferenceTreeNode.java
new file mode 100644
index 00000000..4d81bf1c
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/ReferenceTreeNode.java
@@ -0,0 +1,18 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import cuchaz.enigma.mapping.Entry;
14
15public interface ReferenceTreeNode<E extends Entry,C extends Entry> {
16 E getEntry();
17 EntryReference<E,C> getReference();
18}
diff --git a/src/cuchaz/enigma/analysis/RelatedMethodChecker.java b/src/cuchaz/enigma/analysis/RelatedMethodChecker.java
new file mode 100644
index 00000000..e592a1c3
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/RelatedMethodChecker.java
@@ -0,0 +1,106 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.util.Map;
14import java.util.Set;
15
16import com.google.common.collect.Maps;
17import com.google.common.collect.Sets;
18
19import cuchaz.enigma.mapping.BehaviorEntry;
20import cuchaz.enigma.mapping.ClassEntry;
21import cuchaz.enigma.mapping.EntryFactory;
22import cuchaz.enigma.mapping.MethodEntry;
23import cuchaz.enigma.mapping.MethodMapping;
24
25public class RelatedMethodChecker {
26
27 private JarIndex m_jarIndex;
28 private Map<Set<MethodEntry>,String> m_deobfNamesByGroup;
29 private Map<MethodEntry,String> m_deobfNamesByObfMethod;
30 private Map<MethodEntry,Set<MethodEntry>> m_groupsByObfMethod;
31 private Set<Set<MethodEntry>> m_inconsistentGroups;
32
33 public RelatedMethodChecker(JarIndex jarIndex) {
34 m_jarIndex = jarIndex;
35 m_deobfNamesByGroup = Maps.newHashMap();
36 m_deobfNamesByObfMethod = Maps.newHashMap();
37 m_groupsByObfMethod = Maps.newHashMap();
38 m_inconsistentGroups = Sets.newHashSet();
39 }
40
41 public void checkMethod(ClassEntry classEntry, MethodMapping methodMapping) {
42
43 // TEMP: disable the expensive check for now, maybe we can optimize it later, or just use it for debugging
44 if (true) return;
45
46 BehaviorEntry obfBehaviorEntry = EntryFactory.getObfBehaviorEntry(classEntry, methodMapping);
47 if (!(obfBehaviorEntry instanceof MethodEntry)) {
48 // only methods have related implementations
49 return;
50 }
51 MethodEntry obfMethodEntry = (MethodEntry)obfBehaviorEntry;
52 String deobfName = methodMapping.getDeobfName();
53 m_deobfNamesByObfMethod.put(obfMethodEntry, deobfName);
54
55 // have we seen this method's group before?
56 Set<MethodEntry> group = m_groupsByObfMethod.get(obfMethodEntry);
57 if (group == null) {
58
59 // no, compute the group and save the name
60 group = m_jarIndex.getRelatedMethodImplementations(obfMethodEntry);
61 m_deobfNamesByGroup.put(group, deobfName);
62
63 assert(group.contains(obfMethodEntry));
64 for (MethodEntry relatedMethodEntry : group) {
65 m_groupsByObfMethod.put(relatedMethodEntry, group);
66 }
67 }
68
69 // check the name
70 if (!sameName(m_deobfNamesByGroup.get(group), deobfName)) {
71 m_inconsistentGroups.add(group);
72 }
73 }
74
75 private boolean sameName(String a, String b) {
76 if (a == null && b == null) {
77 return true;
78 } else if (a != null && b != null) {
79 return a.equals(b);
80 }
81 return false;
82 }
83
84 public boolean hasProblems() {
85 return m_inconsistentGroups.size() > 0;
86 }
87
88 public String getReport() {
89 StringBuilder buf = new StringBuilder();
90 buf.append(m_inconsistentGroups.size());
91 buf.append(" groups of methods related by inheritance and/or interfaces have different deobf names!\n");
92 for (Set<MethodEntry> group : m_inconsistentGroups) {
93 buf.append("\tGroup with ");
94 buf.append(group.size());
95 buf.append(" methods:\n");
96 for (MethodEntry methodEntry : group) {
97 buf.append("\t\t");
98 buf.append(methodEntry.toString());
99 buf.append(" => ");
100 buf.append(m_deobfNamesByObfMethod.get(methodEntry));
101 buf.append("\n");
102 }
103 }
104 return buf.toString();
105 }
106}
diff --git a/src/cuchaz/enigma/analysis/SourceIndex.java b/src/cuchaz/enigma/analysis/SourceIndex.java
new file mode 100644
index 00000000..3c4ac464
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/SourceIndex.java
@@ -0,0 +1,184 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.util.Collection;
14import java.util.List;
15import java.util.Map;
16import java.util.TreeMap;
17
18import com.google.common.collect.HashMultimap;
19import com.google.common.collect.Lists;
20import com.google.common.collect.Maps;
21import com.google.common.collect.Multimap;
22import com.strobel.decompiler.languages.Region;
23import com.strobel.decompiler.languages.java.ast.AstNode;
24import com.strobel.decompiler.languages.java.ast.Identifier;
25
26import cuchaz.enigma.mapping.Entry;
27
28public class SourceIndex {
29
30 private String m_source;
31 private TreeMap<Token,EntryReference<Entry,Entry>> m_tokenToReference;
32 private Multimap<EntryReference<Entry,Entry>,Token> m_referenceToTokens;
33 private Map<Entry,Token> m_declarationToToken;
34 private List<Integer> m_lineOffsets;
35 private boolean m_ignoreBadTokens;
36
37 public SourceIndex(String source) {
38 this(source, true);
39 }
40
41 public SourceIndex(String source, boolean ignoreBadTokens) {
42 m_source = source;
43 m_ignoreBadTokens = ignoreBadTokens;
44 m_tokenToReference = Maps.newTreeMap();
45 m_referenceToTokens = HashMultimap.create();
46 m_declarationToToken = Maps.newHashMap();
47 m_lineOffsets = Lists.newArrayList();
48
49 // count the lines
50 m_lineOffsets.add(0);
51 for (int i = 0; i < source.length(); i++) {
52 if (source.charAt(i) == '\n') {
53 m_lineOffsets.add(i + 1);
54 }
55 }
56 }
57
58 public String getSource() {
59 return m_source;
60 }
61
62 public Token getToken(AstNode node) {
63
64 // get the text of the node
65 String name = "";
66 if (node instanceof Identifier) {
67 name = ((Identifier)node).getName();
68 }
69
70 // get a token for this node's region
71 Region region = node.getRegion();
72 if (region.getBeginLine() == 0 || region.getEndLine() == 0) {
73 // DEBUG
74 System.err.println(String.format("WARNING: %s \"%s\" has invalid region: %s", node.getNodeType(), name, region));
75 return null;
76 }
77 Token token = new Token(
78 toPos(region.getBeginLine(), region.getBeginColumn()),
79 toPos(region.getEndLine(), region.getEndColumn()),
80 m_source
81 );
82 if (token.start == 0) {
83 // DEBUG
84 System.err.println(String.format("WARNING: %s \"%s\" has invalid start: %s", node.getNodeType(), name, region));
85 return null;
86 }
87
88 // DEBUG
89 // System.out.println( String.format( "%s \"%s\" region: %s", node.getNodeType(), name, region ) );
90
91 // if the token has a $ in it, something's wrong. Ignore this token
92 if (name.lastIndexOf('$') >= 0 && m_ignoreBadTokens) {
93 // DEBUG
94 System.err.println(String.format("WARNING: %s \"%s\" is probably a bad token. It was ignored", node.getNodeType(), name));
95 return null;
96 }
97
98 return token;
99 }
100
101 public void addReference(AstNode node, Entry deobfEntry, Entry deobfContext) {
102 Token token = getToken(node);
103 if (token != null) {
104 EntryReference<Entry,Entry> deobfReference = new EntryReference<Entry,Entry>(deobfEntry, token.text, deobfContext);
105 m_tokenToReference.put(token, deobfReference);
106 m_referenceToTokens.put(deobfReference, token);
107 }
108 }
109
110 public void addDeclaration(AstNode node, Entry deobfEntry) {
111 Token token = getToken(node);
112 if (token != null) {
113 EntryReference<Entry,Entry> reference = new EntryReference<Entry,Entry>(deobfEntry, token.text);
114 m_tokenToReference.put(token, reference);
115 m_referenceToTokens.put(reference, token);
116 m_declarationToToken.put(deobfEntry, token);
117 }
118 }
119
120 public Token getReferenceToken(int pos) {
121 Token token = m_tokenToReference.floorKey(new Token(pos, pos, null));
122 if (token != null && token.contains(pos)) {
123 return token;
124 }
125 return null;
126 }
127
128 public Collection<Token> getReferenceTokens(EntryReference<Entry,Entry> deobfReference) {
129 return m_referenceToTokens.get(deobfReference);
130 }
131
132 public EntryReference<Entry,Entry> getDeobfReference(Token token) {
133 if (token == null) {
134 return null;
135 }
136 return m_tokenToReference.get(token);
137 }
138
139 public void replaceDeobfReference(Token token, EntryReference<Entry,Entry> newDeobfReference) {
140 EntryReference<Entry,Entry> oldDeobfReference = m_tokenToReference.get(token);
141 m_tokenToReference.put(token, newDeobfReference);
142 Collection<Token> tokens = m_referenceToTokens.get(oldDeobfReference);
143 m_referenceToTokens.removeAll(oldDeobfReference);
144 m_referenceToTokens.putAll(newDeobfReference, tokens);
145 }
146
147 public Iterable<Token> referenceTokens() {
148 return m_tokenToReference.keySet();
149 }
150
151 public Iterable<Token> declarationTokens() {
152 return m_declarationToToken.values();
153 }
154
155 public Iterable<Entry> declarations() {
156 return m_declarationToToken.keySet();
157 }
158
159 public Token getDeclarationToken(Entry deobfEntry) {
160 return m_declarationToToken.get(deobfEntry);
161 }
162
163 public int getLineNumber(int pos) {
164 // line number is 1-based
165 int line = 0;
166 for (Integer offset : m_lineOffsets) {
167 if (offset > pos) {
168 break;
169 }
170 line++;
171 }
172 return line;
173 }
174
175 public int getColumnNumber(int pos) {
176 // column number is 1-based
177 return pos - m_lineOffsets.get(getLineNumber(pos) - 1) + 1;
178 }
179
180 private int toPos(int line, int col) {
181 // line and col are 1-based
182 return m_lineOffsets.get(line - 1) + col - 1;
183 }
184}
diff --git a/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java b/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java
new file mode 100644
index 00000000..a660a376
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java
@@ -0,0 +1,150 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.strobel.assembler.metadata.MemberReference;
14import com.strobel.assembler.metadata.MethodDefinition;
15import com.strobel.assembler.metadata.MethodReference;
16import com.strobel.assembler.metadata.ParameterDefinition;
17import com.strobel.assembler.metadata.TypeReference;
18import com.strobel.decompiler.languages.TextLocation;
19import com.strobel.decompiler.languages.java.ast.AstNode;
20import com.strobel.decompiler.languages.java.ast.IdentifierExpression;
21import com.strobel.decompiler.languages.java.ast.InvocationExpression;
22import com.strobel.decompiler.languages.java.ast.Keys;
23import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression;
24import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression;
25import com.strobel.decompiler.languages.java.ast.ParameterDeclaration;
26import com.strobel.decompiler.languages.java.ast.SimpleType;
27import com.strobel.decompiler.languages.java.ast.SuperReferenceExpression;
28import com.strobel.decompiler.languages.java.ast.ThisReferenceExpression;
29
30import cuchaz.enigma.mapping.ArgumentEntry;
31import cuchaz.enigma.mapping.BehaviorEntry;
32import cuchaz.enigma.mapping.ClassEntry;
33import cuchaz.enigma.mapping.ConstructorEntry;
34import cuchaz.enigma.mapping.FieldEntry;
35import cuchaz.enigma.mapping.MethodEntry;
36import cuchaz.enigma.mapping.ProcyonEntryFactory;
37import cuchaz.enigma.mapping.Signature;
38import cuchaz.enigma.mapping.Type;
39
40public class SourceIndexBehaviorVisitor extends SourceIndexVisitor {
41
42 private BehaviorEntry m_behaviorEntry;
43
44 public SourceIndexBehaviorVisitor(BehaviorEntry behaviorEntry) {
45 m_behaviorEntry = behaviorEntry;
46 }
47
48 @Override
49 public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) {
50 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
51
52 // get the behavior entry
53 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
54 BehaviorEntry behaviorEntry = null;
55 if (ref instanceof MethodReference) {
56 MethodReference methodRef = (MethodReference)ref;
57 if (methodRef.isConstructor()) {
58 behaviorEntry = new ConstructorEntry(classEntry, new Signature(ref.getErasedSignature()));
59 } else if (methodRef.isTypeInitializer()) {
60 behaviorEntry = new ConstructorEntry(classEntry);
61 } else {
62 behaviorEntry = new MethodEntry(classEntry, ref.getName(), new Signature(ref.getErasedSignature()));
63 }
64 }
65 if (behaviorEntry != null) {
66 // get the node for the token
67 AstNode tokenNode = null;
68 if (node.getTarget() instanceof MemberReferenceExpression) {
69 tokenNode = ((MemberReferenceExpression)node.getTarget()).getMemberNameToken();
70 } else if (node.getTarget() instanceof SuperReferenceExpression) {
71 tokenNode = node.getTarget();
72 } else if (node.getTarget() instanceof ThisReferenceExpression) {
73 tokenNode = node.getTarget();
74 }
75 if (tokenNode != null) {
76 index.addReference(tokenNode, behaviorEntry, m_behaviorEntry);
77 }
78 }
79
80 return recurse(node, index);
81 }
82
83 @Override
84 public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) {
85 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
86 if (ref != null) {
87 // make sure this is actually a field
88 if (ref.getErasedSignature().indexOf('(') >= 0) {
89 throw new Error("Expected a field here! got " + ref);
90 }
91
92 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
93 FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new Type(ref.getErasedSignature()));
94 index.addReference(node.getMemberNameToken(), fieldEntry, m_behaviorEntry);
95 }
96
97 return recurse(node, index);
98 }
99
100 @Override
101 public Void visitSimpleType(SimpleType node, SourceIndex index) {
102 TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE);
103 if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) {
104 ClassEntry classEntry = new ClassEntry(ref.getInternalName());
105 index.addReference(node.getIdentifierToken(), classEntry, m_behaviorEntry);
106 }
107
108 return recurse(node, index);
109 }
110
111 @Override
112 public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) {
113 ParameterDefinition def = node.getUserData(Keys.PARAMETER_DEFINITION);
114 if (def.getMethod() instanceof MethodDefinition) {
115 MethodDefinition methodDef = (MethodDefinition)def.getMethod();
116 BehaviorEntry behaviorEntry = ProcyonEntryFactory.getBehaviorEntry(methodDef);
117 ArgumentEntry argumentEntry = new ArgumentEntry(behaviorEntry, def.getPosition(), node.getName());
118 index.addDeclaration(node.getNameToken(), argumentEntry);
119 }
120
121 return recurse(node, index);
122 }
123
124 @Override
125 public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) {
126 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
127 if (ref != null) {
128 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
129 FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new Type(ref.getErasedSignature()));
130 index.addReference(node.getIdentifierToken(), fieldEntry, m_behaviorEntry);
131 }
132
133 return recurse(node, index);
134 }
135
136 @Override
137 public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) {
138 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
139 if (ref != null) {
140 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
141 ConstructorEntry constructorEntry = new ConstructorEntry(classEntry, new Signature(ref.getErasedSignature()));
142 if (node.getType() instanceof SimpleType) {
143 SimpleType simpleTypeNode = (SimpleType)node.getType();
144 index.addReference(simpleTypeNode.getIdentifierToken(), constructorEntry, m_behaviorEntry);
145 }
146 }
147
148 return recurse(node, index);
149 }
150}
diff --git a/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java b/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java
new file mode 100644
index 00000000..db0bc0b7
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java
@@ -0,0 +1,112 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.strobel.assembler.metadata.FieldDefinition;
14import com.strobel.assembler.metadata.MethodDefinition;
15import com.strobel.assembler.metadata.TypeDefinition;
16import com.strobel.assembler.metadata.TypeReference;
17import com.strobel.decompiler.languages.TextLocation;
18import com.strobel.decompiler.languages.java.ast.AstNode;
19import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration;
20import com.strobel.decompiler.languages.java.ast.EnumValueDeclaration;
21import com.strobel.decompiler.languages.java.ast.FieldDeclaration;
22import com.strobel.decompiler.languages.java.ast.Keys;
23import com.strobel.decompiler.languages.java.ast.MethodDeclaration;
24import com.strobel.decompiler.languages.java.ast.SimpleType;
25import com.strobel.decompiler.languages.java.ast.TypeDeclaration;
26import com.strobel.decompiler.languages.java.ast.VariableInitializer;
27
28import cuchaz.enigma.mapping.BehaviorEntry;
29import cuchaz.enigma.mapping.ClassEntry;
30import cuchaz.enigma.mapping.ConstructorEntry;
31import cuchaz.enigma.mapping.FieldEntry;
32import cuchaz.enigma.mapping.ProcyonEntryFactory;
33
34public class SourceIndexClassVisitor extends SourceIndexVisitor {
35
36 private ClassEntry m_classEntry;
37
38 public SourceIndexClassVisitor(ClassEntry classEntry) {
39 m_classEntry = classEntry;
40 }
41
42 @Override
43 public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) {
44 // is this this class, or a subtype?
45 TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION);
46 ClassEntry classEntry = new ClassEntry(def.getInternalName());
47 if (!classEntry.equals(m_classEntry)) {
48 // it's a sub-type, recurse
49 index.addDeclaration(node.getNameToken(), classEntry);
50 return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index);
51 }
52
53 return recurse(node, index);
54 }
55
56 @Override
57 public Void visitSimpleType(SimpleType node, SourceIndex index) {
58 TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE);
59 if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) {
60 ClassEntry classEntry = new ClassEntry(ref.getInternalName());
61 index.addReference(node.getIdentifierToken(), classEntry, m_classEntry);
62 }
63
64 return recurse(node, index);
65 }
66
67 @Override
68 public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) {
69 MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION);
70 BehaviorEntry behaviorEntry = ProcyonEntryFactory.getBehaviorEntry(def);
71 AstNode tokenNode = node.getNameToken();
72
73 if (behaviorEntry instanceof ConstructorEntry) {
74 ConstructorEntry constructorEntry = (ConstructorEntry)behaviorEntry;
75 if (constructorEntry.isStatic()) {
76 // for static initializers, check elsewhere for the token node
77 tokenNode = node.getModifiers().firstOrNullObject();
78 }
79 }
80 index.addDeclaration(tokenNode, behaviorEntry);
81 return node.acceptVisitor(new SourceIndexBehaviorVisitor(behaviorEntry), index);
82 }
83
84 @Override
85 public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) {
86 MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION);
87 ConstructorEntry constructorEntry = ProcyonEntryFactory.getConstructorEntry(def);
88 index.addDeclaration(node.getNameToken(), constructorEntry);
89 return node.acceptVisitor(new SourceIndexBehaviorVisitor(constructorEntry), index);
90 }
91
92 @Override
93 public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) {
94 FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION);
95 FieldEntry fieldEntry = ProcyonEntryFactory.getFieldEntry(def);
96 assert (node.getVariables().size() == 1);
97 VariableInitializer variable = node.getVariables().firstOrNullObject();
98 index.addDeclaration(variable.getNameToken(), fieldEntry);
99
100 return recurse(node, index);
101 }
102
103 @Override
104 public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) {
105 // treat enum declarations as field declarations
106 FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION);
107 FieldEntry fieldEntry = ProcyonEntryFactory.getFieldEntry(def);
108 index.addDeclaration(node.getNameToken(), fieldEntry);
109
110 return recurse(node, index);
111 }
112}
diff --git a/src/cuchaz/enigma/analysis/SourceIndexVisitor.java b/src/cuchaz/enigma/analysis/SourceIndexVisitor.java
new file mode 100644
index 00000000..08698267
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/SourceIndexVisitor.java
@@ -0,0 +1,452 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.strobel.assembler.metadata.TypeDefinition;
14import com.strobel.decompiler.languages.java.ast.Annotation;
15import com.strobel.decompiler.languages.java.ast.AnonymousObjectCreationExpression;
16import com.strobel.decompiler.languages.java.ast.ArrayCreationExpression;
17import com.strobel.decompiler.languages.java.ast.ArrayInitializerExpression;
18import com.strobel.decompiler.languages.java.ast.ArraySpecifier;
19import com.strobel.decompiler.languages.java.ast.AssertStatement;
20import com.strobel.decompiler.languages.java.ast.AssignmentExpression;
21import com.strobel.decompiler.languages.java.ast.AstNode;
22import com.strobel.decompiler.languages.java.ast.BinaryOperatorExpression;
23import com.strobel.decompiler.languages.java.ast.BlockStatement;
24import com.strobel.decompiler.languages.java.ast.BreakStatement;
25import com.strobel.decompiler.languages.java.ast.CaseLabel;
26import com.strobel.decompiler.languages.java.ast.CastExpression;
27import com.strobel.decompiler.languages.java.ast.CatchClause;
28import com.strobel.decompiler.languages.java.ast.ClassOfExpression;
29import com.strobel.decompiler.languages.java.ast.Comment;
30import com.strobel.decompiler.languages.java.ast.CompilationUnit;
31import com.strobel.decompiler.languages.java.ast.ComposedType;
32import com.strobel.decompiler.languages.java.ast.ConditionalExpression;
33import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration;
34import com.strobel.decompiler.languages.java.ast.ContinueStatement;
35import com.strobel.decompiler.languages.java.ast.DoWhileStatement;
36import com.strobel.decompiler.languages.java.ast.EmptyStatement;
37import com.strobel.decompiler.languages.java.ast.EnumValueDeclaration;
38import com.strobel.decompiler.languages.java.ast.ExpressionStatement;
39import com.strobel.decompiler.languages.java.ast.FieldDeclaration;
40import com.strobel.decompiler.languages.java.ast.ForEachStatement;
41import com.strobel.decompiler.languages.java.ast.ForStatement;
42import com.strobel.decompiler.languages.java.ast.GotoStatement;
43import com.strobel.decompiler.languages.java.ast.IAstVisitor;
44import com.strobel.decompiler.languages.java.ast.Identifier;
45import com.strobel.decompiler.languages.java.ast.IdentifierExpression;
46import com.strobel.decompiler.languages.java.ast.IfElseStatement;
47import com.strobel.decompiler.languages.java.ast.ImportDeclaration;
48import com.strobel.decompiler.languages.java.ast.IndexerExpression;
49import com.strobel.decompiler.languages.java.ast.InstanceInitializer;
50import com.strobel.decompiler.languages.java.ast.InstanceOfExpression;
51import com.strobel.decompiler.languages.java.ast.InvocationExpression;
52import com.strobel.decompiler.languages.java.ast.JavaTokenNode;
53import com.strobel.decompiler.languages.java.ast.Keys;
54import com.strobel.decompiler.languages.java.ast.LabelStatement;
55import com.strobel.decompiler.languages.java.ast.LabeledStatement;
56import com.strobel.decompiler.languages.java.ast.LambdaExpression;
57import com.strobel.decompiler.languages.java.ast.LocalTypeDeclarationStatement;
58import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression;
59import com.strobel.decompiler.languages.java.ast.MethodDeclaration;
60import com.strobel.decompiler.languages.java.ast.MethodGroupExpression;
61import com.strobel.decompiler.languages.java.ast.NewLineNode;
62import com.strobel.decompiler.languages.java.ast.NullReferenceExpression;
63import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression;
64import com.strobel.decompiler.languages.java.ast.PackageDeclaration;
65import com.strobel.decompiler.languages.java.ast.ParameterDeclaration;
66import com.strobel.decompiler.languages.java.ast.ParenthesizedExpression;
67import com.strobel.decompiler.languages.java.ast.PrimitiveExpression;
68import com.strobel.decompiler.languages.java.ast.ReturnStatement;
69import com.strobel.decompiler.languages.java.ast.SimpleType;
70import com.strobel.decompiler.languages.java.ast.SuperReferenceExpression;
71import com.strobel.decompiler.languages.java.ast.SwitchSection;
72import com.strobel.decompiler.languages.java.ast.SwitchStatement;
73import com.strobel.decompiler.languages.java.ast.SynchronizedStatement;
74import com.strobel.decompiler.languages.java.ast.TextNode;
75import com.strobel.decompiler.languages.java.ast.ThisReferenceExpression;
76import com.strobel.decompiler.languages.java.ast.ThrowStatement;
77import com.strobel.decompiler.languages.java.ast.TryCatchStatement;
78import com.strobel.decompiler.languages.java.ast.TypeDeclaration;
79import com.strobel.decompiler.languages.java.ast.TypeParameterDeclaration;
80import com.strobel.decompiler.languages.java.ast.TypeReferenceExpression;
81import com.strobel.decompiler.languages.java.ast.UnaryOperatorExpression;
82import com.strobel.decompiler.languages.java.ast.VariableDeclarationStatement;
83import com.strobel.decompiler.languages.java.ast.VariableInitializer;
84import com.strobel.decompiler.languages.java.ast.WhileStatement;
85import com.strobel.decompiler.languages.java.ast.WildcardType;
86import com.strobel.decompiler.patterns.Pattern;
87
88import cuchaz.enigma.mapping.ClassEntry;
89
90public class SourceIndexVisitor implements IAstVisitor<SourceIndex,Void> {
91
92 @Override
93 public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) {
94 TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION);
95 ClassEntry classEntry = new ClassEntry(def.getInternalName());
96 index.addDeclaration(node.getNameToken(), classEntry);
97
98 return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index);
99 }
100
101 protected Void recurse(AstNode node, SourceIndex index) {
102 for (final AstNode child : node.getChildren()) {
103 child.acceptVisitor(this, index);
104 }
105 return null;
106 }
107
108 @Override
109 public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) {
110 return recurse(node, index);
111 }
112
113 @Override
114 public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) {
115 return recurse(node, index);
116 }
117
118 @Override
119 public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) {
120 return recurse(node, index);
121 }
122
123 @Override
124 public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) {
125 return recurse(node, index);
126 }
127
128 @Override
129 public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) {
130 return recurse(node, index);
131 }
132
133 @Override
134 public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) {
135 return recurse(node, index);
136 }
137
138 @Override
139 public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) {
140 return recurse(node, index);
141 }
142
143 @Override
144 public Void visitSimpleType(SimpleType node, SourceIndex index) {
145 return recurse(node, index);
146 }
147
148 @Override
149 public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) {
150 return recurse(node, index);
151 }
152
153 @Override
154 public Void visitComment(Comment node, SourceIndex index) {
155 return recurse(node, index);
156 }
157
158 @Override
159 public Void visitPatternPlaceholder(AstNode node, Pattern pattern, SourceIndex index) {
160 return recurse(node, index);
161 }
162
163 @Override
164 public Void visitTypeReference(TypeReferenceExpression node, SourceIndex index) {
165 return recurse(node, index);
166 }
167
168 @Override
169 public Void visitJavaTokenNode(JavaTokenNode node, SourceIndex index) {
170 return recurse(node, index);
171 }
172
173 @Override
174 public Void visitIdentifier(Identifier node, SourceIndex index) {
175 return recurse(node, index);
176 }
177
178 @Override
179 public Void visitNullReferenceExpression(NullReferenceExpression node, SourceIndex index) {
180 return recurse(node, index);
181 }
182
183 @Override
184 public Void visitThisReferenceExpression(ThisReferenceExpression node, SourceIndex index) {
185 return recurse(node, index);
186 }
187
188 @Override
189 public Void visitSuperReferenceExpression(SuperReferenceExpression node, SourceIndex index) {
190 return recurse(node, index);
191 }
192
193 @Override
194 public Void visitClassOfExpression(ClassOfExpression node, SourceIndex index) {
195 return recurse(node, index);
196 }
197
198 @Override
199 public Void visitBlockStatement(BlockStatement node, SourceIndex index) {
200 return recurse(node, index);
201 }
202
203 @Override
204 public Void visitExpressionStatement(ExpressionStatement node, SourceIndex index) {
205 return recurse(node, index);
206 }
207
208 @Override
209 public Void visitBreakStatement(BreakStatement node, SourceIndex index) {
210 return recurse(node, index);
211 }
212
213 @Override
214 public Void visitContinueStatement(ContinueStatement node, SourceIndex index) {
215 return recurse(node, index);
216 }
217
218 @Override
219 public Void visitDoWhileStatement(DoWhileStatement node, SourceIndex index) {
220 return recurse(node, index);
221 }
222
223 @Override
224 public Void visitEmptyStatement(EmptyStatement node, SourceIndex index) {
225 return recurse(node, index);
226 }
227
228 @Override
229 public Void visitIfElseStatement(IfElseStatement node, SourceIndex index) {
230 return recurse(node, index);
231 }
232
233 @Override
234 public Void visitLabelStatement(LabelStatement node, SourceIndex index) {
235 return recurse(node, index);
236 }
237
238 @Override
239 public Void visitLabeledStatement(LabeledStatement node, SourceIndex index) {
240 return recurse(node, index);
241 }
242
243 @Override
244 public Void visitReturnStatement(ReturnStatement node, SourceIndex index) {
245 return recurse(node, index);
246 }
247
248 @Override
249 public Void visitSwitchStatement(SwitchStatement node, SourceIndex index) {
250 return recurse(node, index);
251 }
252
253 @Override
254 public Void visitSwitchSection(SwitchSection node, SourceIndex index) {
255 return recurse(node, index);
256 }
257
258 @Override
259 public Void visitCaseLabel(CaseLabel node, SourceIndex index) {
260 return recurse(node, index);
261 }
262
263 @Override
264 public Void visitThrowStatement(ThrowStatement node, SourceIndex index) {
265 return recurse(node, index);
266 }
267
268 @Override
269 public Void visitCatchClause(CatchClause node, SourceIndex index) {
270 return recurse(node, index);
271 }
272
273 @Override
274 public Void visitAnnotation(Annotation node, SourceIndex index) {
275 return recurse(node, index);
276 }
277
278 @Override
279 public Void visitNewLine(NewLineNode node, SourceIndex index) {
280 return recurse(node, index);
281 }
282
283 @Override
284 public Void visitVariableDeclaration(VariableDeclarationStatement node, SourceIndex index) {
285 return recurse(node, index);
286 }
287
288 @Override
289 public Void visitVariableInitializer(VariableInitializer node, SourceIndex index) {
290 return recurse(node, index);
291 }
292
293 @Override
294 public Void visitText(TextNode node, SourceIndex index) {
295 return recurse(node, index);
296 }
297
298 @Override
299 public Void visitImportDeclaration(ImportDeclaration node, SourceIndex index) {
300 return recurse(node, index);
301 }
302
303 @Override
304 public Void visitInitializerBlock(InstanceInitializer node, SourceIndex index) {
305 return recurse(node, index);
306 }
307
308 @Override
309 public Void visitTypeParameterDeclaration(TypeParameterDeclaration node, SourceIndex index) {
310 return recurse(node, index);
311 }
312
313 @Override
314 public Void visitCompilationUnit(CompilationUnit node, SourceIndex index) {
315 return recurse(node, index);
316 }
317
318 @Override
319 public Void visitPackageDeclaration(PackageDeclaration node, SourceIndex index) {
320 return recurse(node, index);
321 }
322
323 @Override
324 public Void visitArraySpecifier(ArraySpecifier node, SourceIndex index) {
325 return recurse(node, index);
326 }
327
328 @Override
329 public Void visitComposedType(ComposedType node, SourceIndex index) {
330 return recurse(node, index);
331 }
332
333 @Override
334 public Void visitWhileStatement(WhileStatement node, SourceIndex index) {
335 return recurse(node, index);
336 }
337
338 @Override
339 public Void visitPrimitiveExpression(PrimitiveExpression node, SourceIndex index) {
340 return recurse(node, index);
341 }
342
343 @Override
344 public Void visitCastExpression(CastExpression node, SourceIndex index) {
345 return recurse(node, index);
346 }
347
348 @Override
349 public Void visitBinaryOperatorExpression(BinaryOperatorExpression node, SourceIndex index) {
350 return recurse(node, index);
351 }
352
353 @Override
354 public Void visitInstanceOfExpression(InstanceOfExpression node, SourceIndex index) {
355 return recurse(node, index);
356 }
357
358 @Override
359 public Void visitIndexerExpression(IndexerExpression node, SourceIndex index) {
360 return recurse(node, index);
361 }
362
363 @Override
364 public Void visitUnaryOperatorExpression(UnaryOperatorExpression node, SourceIndex index) {
365 return recurse(node, index);
366 }
367
368 @Override
369 public Void visitConditionalExpression(ConditionalExpression node, SourceIndex index) {
370 return recurse(node, index);
371 }
372
373 @Override
374 public Void visitArrayInitializerExpression(ArrayInitializerExpression node, SourceIndex index) {
375 return recurse(node, index);
376 }
377
378 @Override
379 public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) {
380 return recurse(node, index);
381 }
382
383 @Override
384 public Void visitArrayCreationExpression(ArrayCreationExpression node, SourceIndex index) {
385 return recurse(node, index);
386 }
387
388 @Override
389 public Void visitAssignmentExpression(AssignmentExpression node, SourceIndex index) {
390 return recurse(node, index);
391 }
392
393 @Override
394 public Void visitForStatement(ForStatement node, SourceIndex index) {
395 return recurse(node, index);
396 }
397
398 @Override
399 public Void visitForEachStatement(ForEachStatement node, SourceIndex index) {
400 return recurse(node, index);
401 }
402
403 @Override
404 public Void visitTryCatchStatement(TryCatchStatement node, SourceIndex index) {
405 return recurse(node, index);
406 }
407
408 @Override
409 public Void visitGotoStatement(GotoStatement node, SourceIndex index) {
410 return recurse(node, index);
411 }
412
413 @Override
414 public Void visitParenthesizedExpression(ParenthesizedExpression node, SourceIndex index) {
415 return recurse(node, index);
416 }
417
418 @Override
419 public Void visitSynchronizedStatement(SynchronizedStatement node, SourceIndex index) {
420 return recurse(node, index);
421 }
422
423 @Override
424 public Void visitAnonymousObjectCreationExpression(AnonymousObjectCreationExpression node, SourceIndex index) {
425 return recurse(node, index);
426 }
427
428 @Override
429 public Void visitWildcardType(WildcardType node, SourceIndex index) {
430 return recurse(node, index);
431 }
432
433 @Override
434 public Void visitMethodGroupExpression(MethodGroupExpression node, SourceIndex index) {
435 return recurse(node, index);
436 }
437
438 @Override
439 public Void visitAssertStatement(AssertStatement node, SourceIndex index) {
440 return recurse(node, index);
441 }
442
443 @Override
444 public Void visitLambdaExpression(LambdaExpression node, SourceIndex index) {
445 return recurse(node, index);
446 }
447
448 @Override
449 public Void visitLocalTypeDeclarationStatement(LocalTypeDeclarationStatement node, SourceIndex index) {
450 return recurse(node, index);
451 }
452}
diff --git a/src/cuchaz/enigma/analysis/Token.java b/src/cuchaz/enigma/analysis/Token.java
new file mode 100644
index 00000000..76d63276
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/Token.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 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13public class Token implements Comparable<Token> {
14
15 public int start;
16 public int end;
17 public String text;
18
19 public Token(int start, int end) {
20 this(start, end, null);
21 }
22
23 public Token(int start, int end, String source) {
24 this.start = start;
25 this.end = end;
26 if (source != null) {
27 this.text = source.substring(start, end);
28 }
29 }
30
31 public boolean contains(int pos) {
32 return pos >= start && pos <= end;
33 }
34
35 @Override
36 public int compareTo(Token other) {
37 return start - other.start;
38 }
39
40 @Override
41 public boolean equals(Object other) {
42 if (other instanceof Token) {
43 return equals((Token)other);
44 }
45 return false;
46 }
47
48 public boolean equals(Token other) {
49 return start == other.start && end == other.end;
50 }
51
52 @Override
53 public String toString() {
54 return String.format("[%d,%d]", start, end);
55 }
56}
diff --git a/src/cuchaz/enigma/analysis/TranslationIndex.java b/src/cuchaz/enigma/analysis/TranslationIndex.java
new file mode 100644
index 00000000..a491cfce
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/TranslationIndex.java
@@ -0,0 +1,298 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.io.IOException;
14import java.io.InputStream;
15import java.io.ObjectInputStream;
16import java.io.ObjectOutputStream;
17import java.io.OutputStream;
18import java.io.Serializable;
19import java.util.Collection;
20import java.util.HashMap;
21import java.util.List;
22import java.util.Map;
23import java.util.Set;
24import java.util.zip.GZIPInputStream;
25import java.util.zip.GZIPOutputStream;
26
27import javassist.CtBehavior;
28import javassist.CtClass;
29import javassist.CtField;
30import javassist.bytecode.Descriptor;
31
32import com.google.common.collect.HashMultimap;
33import com.google.common.collect.Lists;
34import com.google.common.collect.Maps;
35import com.google.common.collect.Multimap;
36
37import cuchaz.enigma.mapping.ArgumentEntry;
38import cuchaz.enigma.mapping.BehaviorEntry;
39import cuchaz.enigma.mapping.ClassEntry;
40import cuchaz.enigma.mapping.Entry;
41import cuchaz.enigma.mapping.EntryFactory;
42import cuchaz.enigma.mapping.FieldEntry;
43import cuchaz.enigma.mapping.Translator;
44
45public class TranslationIndex implements Serializable {
46
47 private static final long serialVersionUID = 738687982126844179L;
48
49 private Map<ClassEntry,ClassEntry> m_superclasses;
50 private Multimap<ClassEntry,FieldEntry> m_fieldEntries;
51 private Multimap<ClassEntry,BehaviorEntry> m_behaviorEntries;
52 private Multimap<ClassEntry,ClassEntry> m_interfaces;
53
54 public TranslationIndex() {
55 m_superclasses = Maps.newHashMap();
56 m_fieldEntries = HashMultimap.create();
57 m_behaviorEntries = HashMultimap.create();
58 m_interfaces = HashMultimap.create();
59 }
60
61 public TranslationIndex(TranslationIndex other, Translator translator) {
62
63 // translate the superclasses
64 m_superclasses = Maps.newHashMap();
65 for (Map.Entry<ClassEntry,ClassEntry> mapEntry : other.m_superclasses.entrySet()) {
66 m_superclasses.put(
67 translator.translateEntry(mapEntry.getKey()),
68 translator.translateEntry(mapEntry.getValue())
69 );
70 }
71
72 // translate the interfaces
73 m_interfaces = HashMultimap.create();
74 for (Map.Entry<ClassEntry,ClassEntry> mapEntry : other.m_interfaces.entries()) {
75 m_interfaces.put(
76 translator.translateEntry(mapEntry.getKey()),
77 translator.translateEntry(mapEntry.getValue())
78 );
79 }
80
81 // translate the fields
82 m_fieldEntries = HashMultimap.create();
83 for (Map.Entry<ClassEntry,FieldEntry> mapEntry : other.m_fieldEntries.entries()) {
84 m_fieldEntries.put(
85 translator.translateEntry(mapEntry.getKey()),
86 translator.translateEntry(mapEntry.getValue())
87 );
88 }
89
90 m_behaviorEntries = HashMultimap.create();
91 for (Map.Entry<ClassEntry,BehaviorEntry> mapEntry : other.m_behaviorEntries.entries()) {
92 m_behaviorEntries.put(
93 translator.translateEntry(mapEntry.getKey()),
94 translator.translateEntry(mapEntry.getValue())
95 );
96 }
97 }
98
99 public void indexClass(CtClass c) {
100 indexClass(c, true);
101 }
102
103 public void indexClass(CtClass c, boolean indexMembers) {
104
105 ClassEntry classEntry = EntryFactory.getClassEntry(c);
106 if (isJre(classEntry)) {
107 return;
108 }
109
110 // add the superclass
111 ClassEntry superclassEntry = EntryFactory.getSuperclassEntry(c);
112 if (superclassEntry != null) {
113 m_superclasses.put(classEntry, superclassEntry);
114 }
115
116 // add the interfaces
117 for (String interfaceClassName : c.getClassFile().getInterfaces()) {
118 ClassEntry interfaceClassEntry = new ClassEntry(Descriptor.toJvmName(interfaceClassName));
119 if (!isJre(interfaceClassEntry)) {
120 m_interfaces.put(classEntry, interfaceClassEntry);
121 }
122 }
123
124 if (indexMembers) {
125 // add fields
126 for (CtField field : c.getDeclaredFields()) {
127 FieldEntry fieldEntry = EntryFactory.getFieldEntry(field);
128 m_fieldEntries.put(fieldEntry.getClassEntry(), fieldEntry);
129 }
130
131 // add behaviors
132 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
133 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
134 m_behaviorEntries.put(behaviorEntry.getClassEntry(), behaviorEntry);
135 }
136 }
137 }
138
139 public void renameClasses(Map<String,String> renames) {
140 EntryRenamer.renameClassesInMap(renames, m_superclasses);
141 EntryRenamer.renameClassesInMultimap(renames, m_fieldEntries);
142 EntryRenamer.renameClassesInMultimap(renames, m_behaviorEntries);
143 }
144
145 public ClassEntry getSuperclass(ClassEntry classEntry) {
146 return m_superclasses.get(classEntry);
147 }
148
149 public List<ClassEntry> getAncestry(ClassEntry classEntry) {
150 List<ClassEntry> ancestors = Lists.newArrayList();
151 while (classEntry != null) {
152 classEntry = getSuperclass(classEntry);
153 if (classEntry != null) {
154 ancestors.add(classEntry);
155 }
156 }
157 return ancestors;
158 }
159
160 public List<ClassEntry> getSubclass(ClassEntry classEntry) {
161
162 // linear search is fast enough for now
163 List<ClassEntry> subclasses = Lists.newArrayList();
164 for (Map.Entry<ClassEntry,ClassEntry> entry : m_superclasses.entrySet()) {
165 ClassEntry subclass = entry.getKey();
166 ClassEntry superclass = entry.getValue();
167 if (classEntry.equals(superclass)) {
168 subclasses.add(subclass);
169 }
170 }
171 return subclasses;
172 }
173
174 public void getSubclassesRecursively(Set<ClassEntry> out, ClassEntry classEntry) {
175 for (ClassEntry subclassEntry : getSubclass(classEntry)) {
176 out.add(subclassEntry);
177 getSubclassesRecursively(out, subclassEntry);
178 }
179 }
180
181 public void getSubclassNamesRecursively(Set<String> out, ClassEntry classEntry) {
182 for (ClassEntry subclassEntry : getSubclass(classEntry)) {
183 out.add(subclassEntry.getName());
184 getSubclassNamesRecursively(out, subclassEntry);
185 }
186 }
187
188 public Collection<Map.Entry<ClassEntry,ClassEntry>> getClassInterfaces() {
189 return m_interfaces.entries();
190 }
191
192 public Collection<ClassEntry> getInterfaces(ClassEntry classEntry) {
193 return m_interfaces.get(classEntry);
194 }
195
196 public boolean isInterface(ClassEntry classEntry) {
197 return m_interfaces.containsValue(classEntry);
198 }
199
200 public boolean entryExists(Entry entry) {
201 if (entry instanceof FieldEntry) {
202 return fieldExists((FieldEntry)entry);
203 } else if (entry instanceof BehaviorEntry) {
204 return behaviorExists((BehaviorEntry)entry);
205 } else if (entry instanceof ArgumentEntry) {
206 return behaviorExists(((ArgumentEntry)entry).getBehaviorEntry());
207 }
208 throw new IllegalArgumentException("Cannot check existence for " + entry.getClass());
209 }
210
211 public boolean fieldExists(FieldEntry fieldEntry) {
212 return m_fieldEntries.containsEntry(fieldEntry.getClassEntry(), fieldEntry);
213 }
214
215 public boolean behaviorExists(BehaviorEntry behaviorEntry) {
216 return m_behaviorEntries.containsEntry(behaviorEntry.getClassEntry(), behaviorEntry);
217 }
218
219 public ClassEntry resolveEntryClass(Entry entry) {
220
221 if (entry instanceof ClassEntry) {
222 return (ClassEntry)entry;
223 }
224
225 ClassEntry superclassEntry = resolveSuperclass(entry);
226 if (superclassEntry != null) {
227 return superclassEntry;
228 }
229
230 ClassEntry interfaceEntry = resolveInterface(entry);
231 if (interfaceEntry != null) {
232 return interfaceEntry;
233 }
234
235 return null;
236 }
237
238 public ClassEntry resolveSuperclass(Entry entry) {
239
240 // this entry could refer to a method on a class where the method is not actually implemented
241 // travel up the inheritance tree to find the closest implementation
242 while (!entryExists(entry)) {
243
244 // is there a parent class?
245 ClassEntry superclassEntry = getSuperclass(entry.getClassEntry());
246 if (superclassEntry == null) {
247 // this is probably a method from a class in a library
248 // we can't trace the implementation up any higher unless we index the library
249 return null;
250 }
251
252 // move up to the parent class
253 entry = entry.cloneToNewClass(superclassEntry);
254 }
255 return entry.getClassEntry();
256 }
257
258 public ClassEntry resolveInterface(Entry entry) {
259
260 // the interfaces for any class is a forest
261 // so let's look at all the trees
262 for (ClassEntry interfaceEntry : m_interfaces.get(entry.getClassEntry())) {
263 ClassEntry resolvedClassEntry = resolveSuperclass(entry.cloneToNewClass(interfaceEntry));
264 if (resolvedClassEntry != null) {
265 return resolvedClassEntry;
266 }
267 }
268 return null;
269 }
270
271 private boolean isJre(ClassEntry classEntry) {
272 String packageName = classEntry.getPackageName();
273 return packageName != null && (packageName.startsWith("java") || packageName.startsWith("javax"));
274 }
275
276 public void write(OutputStream out)
277 throws IOException {
278 GZIPOutputStream gzipout = new GZIPOutputStream(out);
279 ObjectOutputStream oout = new ObjectOutputStream(gzipout);
280 oout.writeObject(m_superclasses);
281 oout.writeObject(m_fieldEntries);
282 oout.writeObject(m_behaviorEntries);
283 gzipout.finish();
284 }
285
286 @SuppressWarnings("unchecked")
287 public void read(InputStream in)
288 throws IOException {
289 try {
290 ObjectInputStream oin = new ObjectInputStream(new GZIPInputStream(in));
291 m_superclasses = (HashMap<ClassEntry,ClassEntry>)oin.readObject();
292 m_fieldEntries = (HashMultimap<ClassEntry,FieldEntry>)oin.readObject();
293 m_behaviorEntries = (HashMultimap<ClassEntry,BehaviorEntry>)oin.readObject();
294 } catch (ClassNotFoundException ex) {
295 throw new Error(ex);
296 }
297 }
298}
diff --git a/src/cuchaz/enigma/analysis/TreeDumpVisitor.java b/src/cuchaz/enigma/analysis/TreeDumpVisitor.java
new file mode 100644
index 00000000..0a90bacc
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/TreeDumpVisitor.java
@@ -0,0 +1,512 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.io.File;
14import java.io.FileWriter;
15import java.io.IOException;
16import java.io.Writer;
17
18import com.strobel.componentmodel.Key;
19import com.strobel.decompiler.languages.java.ast.Annotation;
20import com.strobel.decompiler.languages.java.ast.AnonymousObjectCreationExpression;
21import com.strobel.decompiler.languages.java.ast.ArrayCreationExpression;
22import com.strobel.decompiler.languages.java.ast.ArrayInitializerExpression;
23import com.strobel.decompiler.languages.java.ast.ArraySpecifier;
24import com.strobel.decompiler.languages.java.ast.AssertStatement;
25import com.strobel.decompiler.languages.java.ast.AssignmentExpression;
26import com.strobel.decompiler.languages.java.ast.AstNode;
27import com.strobel.decompiler.languages.java.ast.BinaryOperatorExpression;
28import com.strobel.decompiler.languages.java.ast.BlockStatement;
29import com.strobel.decompiler.languages.java.ast.BreakStatement;
30import com.strobel.decompiler.languages.java.ast.CaseLabel;
31import com.strobel.decompiler.languages.java.ast.CastExpression;
32import com.strobel.decompiler.languages.java.ast.CatchClause;
33import com.strobel.decompiler.languages.java.ast.ClassOfExpression;
34import com.strobel.decompiler.languages.java.ast.Comment;
35import com.strobel.decompiler.languages.java.ast.CompilationUnit;
36import com.strobel.decompiler.languages.java.ast.ComposedType;
37import com.strobel.decompiler.languages.java.ast.ConditionalExpression;
38import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration;
39import com.strobel.decompiler.languages.java.ast.ContinueStatement;
40import com.strobel.decompiler.languages.java.ast.DoWhileStatement;
41import com.strobel.decompiler.languages.java.ast.EmptyStatement;
42import com.strobel.decompiler.languages.java.ast.EnumValueDeclaration;
43import com.strobel.decompiler.languages.java.ast.ExpressionStatement;
44import com.strobel.decompiler.languages.java.ast.FieldDeclaration;
45import com.strobel.decompiler.languages.java.ast.ForEachStatement;
46import com.strobel.decompiler.languages.java.ast.ForStatement;
47import com.strobel.decompiler.languages.java.ast.GotoStatement;
48import com.strobel.decompiler.languages.java.ast.IAstVisitor;
49import com.strobel.decompiler.languages.java.ast.Identifier;
50import com.strobel.decompiler.languages.java.ast.IdentifierExpression;
51import com.strobel.decompiler.languages.java.ast.IfElseStatement;
52import com.strobel.decompiler.languages.java.ast.ImportDeclaration;
53import com.strobel.decompiler.languages.java.ast.IndexerExpression;
54import com.strobel.decompiler.languages.java.ast.InstanceInitializer;
55import com.strobel.decompiler.languages.java.ast.InstanceOfExpression;
56import com.strobel.decompiler.languages.java.ast.InvocationExpression;
57import com.strobel.decompiler.languages.java.ast.JavaTokenNode;
58import com.strobel.decompiler.languages.java.ast.Keys;
59import com.strobel.decompiler.languages.java.ast.LabelStatement;
60import com.strobel.decompiler.languages.java.ast.LabeledStatement;
61import com.strobel.decompiler.languages.java.ast.LambdaExpression;
62import com.strobel.decompiler.languages.java.ast.LocalTypeDeclarationStatement;
63import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression;
64import com.strobel.decompiler.languages.java.ast.MethodDeclaration;
65import com.strobel.decompiler.languages.java.ast.MethodGroupExpression;
66import com.strobel.decompiler.languages.java.ast.NewLineNode;
67import com.strobel.decompiler.languages.java.ast.NullReferenceExpression;
68import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression;
69import com.strobel.decompiler.languages.java.ast.PackageDeclaration;
70import com.strobel.decompiler.languages.java.ast.ParameterDeclaration;
71import com.strobel.decompiler.languages.java.ast.ParenthesizedExpression;
72import com.strobel.decompiler.languages.java.ast.PrimitiveExpression;
73import com.strobel.decompiler.languages.java.ast.ReturnStatement;
74import com.strobel.decompiler.languages.java.ast.SimpleType;
75import com.strobel.decompiler.languages.java.ast.SuperReferenceExpression;
76import com.strobel.decompiler.languages.java.ast.SwitchSection;
77import com.strobel.decompiler.languages.java.ast.SwitchStatement;
78import com.strobel.decompiler.languages.java.ast.SynchronizedStatement;
79import com.strobel.decompiler.languages.java.ast.TextNode;
80import com.strobel.decompiler.languages.java.ast.ThisReferenceExpression;
81import com.strobel.decompiler.languages.java.ast.ThrowStatement;
82import com.strobel.decompiler.languages.java.ast.TryCatchStatement;
83import com.strobel.decompiler.languages.java.ast.TypeDeclaration;
84import com.strobel.decompiler.languages.java.ast.TypeParameterDeclaration;
85import com.strobel.decompiler.languages.java.ast.TypeReferenceExpression;
86import com.strobel.decompiler.languages.java.ast.UnaryOperatorExpression;
87import com.strobel.decompiler.languages.java.ast.VariableDeclarationStatement;
88import com.strobel.decompiler.languages.java.ast.VariableInitializer;
89import com.strobel.decompiler.languages.java.ast.WhileStatement;
90import com.strobel.decompiler.languages.java.ast.WildcardType;
91import com.strobel.decompiler.patterns.Pattern;
92
93public class TreeDumpVisitor implements IAstVisitor<Void,Void> {
94
95 private File m_file;
96 private Writer m_out;
97
98 public TreeDumpVisitor(File file) {
99 m_file = file;
100 m_out = null;
101 }
102
103 @Override
104 public Void visitCompilationUnit(CompilationUnit node, Void ignored) {
105 try {
106 m_out = new FileWriter(m_file);
107 recurse(node, ignored);
108 m_out.close();
109 return null;
110 } catch (IOException ex) {
111 throw new Error(ex);
112 }
113 }
114
115 private Void recurse(AstNode node, Void ignored) {
116 // show the tree
117 try {
118 m_out.write(getIndent(node) + node.getClass().getSimpleName() + " " + getText(node) + " " + dumpUserData(node) + " " + node.getRegion() + "\n");
119 } catch (IOException ex) {
120 throw new Error(ex);
121 }
122
123 // recurse
124 for (final AstNode child : node.getChildren()) {
125 child.acceptVisitor(this, ignored);
126 }
127 return null;
128 }
129
130 private String getText(AstNode node) {
131 if (node instanceof Identifier) {
132 return "\"" + ((Identifier)node).getName() + "\"";
133 }
134 return "";
135 }
136
137 private String dumpUserData(AstNode node) {
138 StringBuilder buf = new StringBuilder();
139 for (Key<?> key : Keys.ALL_KEYS) {
140 Object val = node.getUserData(key);
141 if (val != null) {
142 buf.append(String.format(" [%s=%s]", key, val));
143 }
144 }
145 return buf.toString();
146 }
147
148 private String getIndent(AstNode node) {
149 StringBuilder buf = new StringBuilder();
150 int depth = getDepth(node);
151 for (int i = 0; i < depth; i++) {
152 buf.append("\t");
153 }
154 return buf.toString();
155 }
156
157 private int getDepth(AstNode node) {
158 int depth = -1;
159 while (node != null) {
160 depth++;
161 node = node.getParent();
162 }
163 return depth;
164 }
165
166 // OVERRIDES WE DON'T CARE ABOUT
167
168 @Override
169 public Void visitInvocationExpression(InvocationExpression node, Void ignored) {
170 return recurse(node, ignored);
171 }
172
173 @Override
174 public Void visitMemberReferenceExpression(MemberReferenceExpression node, Void ignored) {
175 return recurse(node, ignored);
176 }
177
178 @Override
179 public Void visitSimpleType(SimpleType node, Void ignored) {
180 return recurse(node, ignored);
181 }
182
183 @Override
184 public Void visitMethodDeclaration(MethodDeclaration node, Void ignored) {
185 return recurse(node, ignored);
186 }
187
188 @Override
189 public Void visitConstructorDeclaration(ConstructorDeclaration node, Void ignored) {
190 return recurse(node, ignored);
191 }
192
193 @Override
194 public Void visitParameterDeclaration(ParameterDeclaration node, Void ignored) {
195 return recurse(node, ignored);
196 }
197
198 @Override
199 public Void visitFieldDeclaration(FieldDeclaration node, Void ignored) {
200 return recurse(node, ignored);
201 }
202
203 @Override
204 public Void visitTypeDeclaration(TypeDeclaration node, Void ignored) {
205 return recurse(node, ignored);
206 }
207
208 @Override
209 public Void visitComment(Comment node, Void ignored) {
210 return recurse(node, ignored);
211 }
212
213 @Override
214 public Void visitPatternPlaceholder(AstNode node, Pattern pattern, Void ignored) {
215 return recurse(node, ignored);
216 }
217
218 @Override
219 public Void visitTypeReference(TypeReferenceExpression node, Void ignored) {
220 return recurse(node, ignored);
221 }
222
223 @Override
224 public Void visitJavaTokenNode(JavaTokenNode node, Void ignored) {
225 return recurse(node, ignored);
226 }
227
228 @Override
229 public Void visitIdentifier(Identifier node, Void ignored) {
230 return recurse(node, ignored);
231 }
232
233 @Override
234 public Void visitNullReferenceExpression(NullReferenceExpression node, Void ignored) {
235 return recurse(node, ignored);
236 }
237
238 @Override
239 public Void visitThisReferenceExpression(ThisReferenceExpression node, Void ignored) {
240 return recurse(node, ignored);
241 }
242
243 @Override
244 public Void visitSuperReferenceExpression(SuperReferenceExpression node, Void ignored) {
245 return recurse(node, ignored);
246 }
247
248 @Override
249 public Void visitClassOfExpression(ClassOfExpression node, Void ignored) {
250 return recurse(node, ignored);
251 }
252
253 @Override
254 public Void visitBlockStatement(BlockStatement node, Void ignored) {
255 return recurse(node, ignored);
256 }
257
258 @Override
259 public Void visitExpressionStatement(ExpressionStatement node, Void ignored) {
260 return recurse(node, ignored);
261 }
262
263 @Override
264 public Void visitBreakStatement(BreakStatement node, Void ignored) {
265 return recurse(node, ignored);
266 }
267
268 @Override
269 public Void visitContinueStatement(ContinueStatement node, Void ignored) {
270 return recurse(node, ignored);
271 }
272
273 @Override
274 public Void visitDoWhileStatement(DoWhileStatement node, Void ignored) {
275 return recurse(node, ignored);
276 }
277
278 @Override
279 public Void visitEmptyStatement(EmptyStatement node, Void ignored) {
280 return recurse(node, ignored);
281 }
282
283 @Override
284 public Void visitIfElseStatement(IfElseStatement node, Void ignored) {
285 return recurse(node, ignored);
286 }
287
288 @Override
289 public Void visitLabelStatement(LabelStatement node, Void ignored) {
290 return recurse(node, ignored);
291 }
292
293 @Override
294 public Void visitLabeledStatement(LabeledStatement node, Void ignored) {
295 return recurse(node, ignored);
296 }
297
298 @Override
299 public Void visitReturnStatement(ReturnStatement node, Void ignored) {
300 return recurse(node, ignored);
301 }
302
303 @Override
304 public Void visitSwitchStatement(SwitchStatement node, Void ignored) {
305 return recurse(node, ignored);
306 }
307
308 @Override
309 public Void visitSwitchSection(SwitchSection node, Void ignored) {
310 return recurse(node, ignored);
311 }
312
313 @Override
314 public Void visitCaseLabel(CaseLabel node, Void ignored) {
315 return recurse(node, ignored);
316 }
317
318 @Override
319 public Void visitThrowStatement(ThrowStatement node, Void ignored) {
320 return recurse(node, ignored);
321 }
322
323 @Override
324 public Void visitCatchClause(CatchClause node, Void ignored) {
325 return recurse(node, ignored);
326 }
327
328 @Override
329 public Void visitAnnotation(Annotation node, Void ignored) {
330 return recurse(node, ignored);
331 }
332
333 @Override
334 public Void visitNewLine(NewLineNode node, Void ignored) {
335 return recurse(node, ignored);
336 }
337
338 @Override
339 public Void visitVariableDeclaration(VariableDeclarationStatement node, Void ignored) {
340 return recurse(node, ignored);
341 }
342
343 @Override
344 public Void visitVariableInitializer(VariableInitializer node, Void ignored) {
345 return recurse(node, ignored);
346 }
347
348 @Override
349 public Void visitText(TextNode node, Void ignored) {
350 return recurse(node, ignored);
351 }
352
353 @Override
354 public Void visitImportDeclaration(ImportDeclaration node, Void ignored) {
355 return recurse(node, ignored);
356 }
357
358 @Override
359 public Void visitInitializerBlock(InstanceInitializer node, Void ignored) {
360 return recurse(node, ignored);
361 }
362
363 @Override
364 public Void visitTypeParameterDeclaration(TypeParameterDeclaration node, Void ignored) {
365 return recurse(node, ignored);
366 }
367
368 @Override
369 public Void visitPackageDeclaration(PackageDeclaration node, Void ignored) {
370 return recurse(node, ignored);
371 }
372
373 @Override
374 public Void visitArraySpecifier(ArraySpecifier node, Void ignored) {
375 return recurse(node, ignored);
376 }
377
378 @Override
379 public Void visitComposedType(ComposedType node, Void ignored) {
380 return recurse(node, ignored);
381 }
382
383 @Override
384 public Void visitWhileStatement(WhileStatement node, Void ignored) {
385 return recurse(node, ignored);
386 }
387
388 @Override
389 public Void visitPrimitiveExpression(PrimitiveExpression node, Void ignored) {
390 return recurse(node, ignored);
391 }
392
393 @Override
394 public Void visitCastExpression(CastExpression node, Void ignored) {
395 return recurse(node, ignored);
396 }
397
398 @Override
399 public Void visitBinaryOperatorExpression(BinaryOperatorExpression node, Void ignored) {
400 return recurse(node, ignored);
401 }
402
403 @Override
404 public Void visitInstanceOfExpression(InstanceOfExpression node, Void ignored) {
405 return recurse(node, ignored);
406 }
407
408 @Override
409 public Void visitIndexerExpression(IndexerExpression node, Void ignored) {
410 return recurse(node, ignored);
411 }
412
413 @Override
414 public Void visitIdentifierExpression(IdentifierExpression node, Void ignored) {
415 return recurse(node, ignored);
416 }
417
418 @Override
419 public Void visitUnaryOperatorExpression(UnaryOperatorExpression node, Void ignored) {
420 return recurse(node, ignored);
421 }
422
423 @Override
424 public Void visitConditionalExpression(ConditionalExpression node, Void ignored) {
425 return recurse(node, ignored);
426 }
427
428 @Override
429 public Void visitArrayInitializerExpression(ArrayInitializerExpression node, Void ignored) {
430 return recurse(node, ignored);
431 }
432
433 @Override
434 public Void visitObjectCreationExpression(ObjectCreationExpression node, Void ignored) {
435 return recurse(node, ignored);
436 }
437
438 @Override
439 public Void visitArrayCreationExpression(ArrayCreationExpression node, Void ignored) {
440 return recurse(node, ignored);
441 }
442
443 @Override
444 public Void visitAssignmentExpression(AssignmentExpression node, Void ignored) {
445 return recurse(node, ignored);
446 }
447
448 @Override
449 public Void visitForStatement(ForStatement node, Void ignored) {
450 return recurse(node, ignored);
451 }
452
453 @Override
454 public Void visitForEachStatement(ForEachStatement node, Void ignored) {
455 return recurse(node, ignored);
456 }
457
458 @Override
459 public Void visitTryCatchStatement(TryCatchStatement node, Void ignored) {
460 return recurse(node, ignored);
461 }
462
463 @Override
464 public Void visitGotoStatement(GotoStatement node, Void ignored) {
465 return recurse(node, ignored);
466 }
467
468 @Override
469 public Void visitParenthesizedExpression(ParenthesizedExpression node, Void ignored) {
470 return recurse(node, ignored);
471 }
472
473 @Override
474 public Void visitSynchronizedStatement(SynchronizedStatement node, Void ignored) {
475 return recurse(node, ignored);
476 }
477
478 @Override
479 public Void visitAnonymousObjectCreationExpression(AnonymousObjectCreationExpression node, Void ignored) {
480 return recurse(node, ignored);
481 }
482
483 @Override
484 public Void visitWildcardType(WildcardType node, Void ignored) {
485 return recurse(node, ignored);
486 }
487
488 @Override
489 public Void visitMethodGroupExpression(MethodGroupExpression node, Void ignored) {
490 return recurse(node, ignored);
491 }
492
493 @Override
494 public Void visitEnumValueDeclaration(EnumValueDeclaration node, Void ignored) {
495 return recurse(node, ignored);
496 }
497
498 @Override
499 public Void visitAssertStatement(AssertStatement node, Void ignored) {
500 return recurse(node, ignored);
501 }
502
503 @Override
504 public Void visitLambdaExpression(LambdaExpression node, Void ignored) {
505 return recurse(node, ignored);
506 }
507
508 @Override
509 public Void visitLocalTypeDeclarationStatement(LocalTypeDeclarationStatement node, Void ignored) {
510 return recurse(node, ignored);
511 }
512}
diff --git a/src/cuchaz/enigma/bytecode/CheckCastIterator.java b/src/cuchaz/enigma/bytecode/CheckCastIterator.java
new file mode 100644
index 00000000..517b9d62
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/CheckCastIterator.java
@@ -0,0 +1,127 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.util.Iterator;
14
15import javassist.bytecode.BadBytecode;
16import javassist.bytecode.CodeAttribute;
17import javassist.bytecode.CodeIterator;
18import javassist.bytecode.ConstPool;
19import javassist.bytecode.Descriptor;
20import javassist.bytecode.Opcode;
21import cuchaz.enigma.bytecode.CheckCastIterator.CheckCast;
22import cuchaz.enigma.mapping.ClassEntry;
23import cuchaz.enigma.mapping.MethodEntry;
24import cuchaz.enigma.mapping.Signature;
25
26public class CheckCastIterator implements Iterator<CheckCast> {
27
28 public static class CheckCast {
29
30 public String className;
31 public MethodEntry prevMethodEntry;
32
33 public CheckCast(String className, MethodEntry prevMethodEntry) {
34 this.className = className;
35 this.prevMethodEntry = prevMethodEntry;
36 }
37 }
38
39 private ConstPool m_constants;
40 private CodeAttribute m_attribute;
41 private CodeIterator m_iter;
42 private CheckCast m_next;
43
44 public CheckCastIterator(CodeAttribute codeAttribute) throws BadBytecode {
45 m_constants = codeAttribute.getConstPool();
46 m_attribute = codeAttribute;
47 m_iter = m_attribute.iterator();
48
49 m_next = getNext();
50 }
51
52 @Override
53 public boolean hasNext() {
54 return m_next != null;
55 }
56
57 @Override
58 public CheckCast next() {
59 CheckCast out = m_next;
60 try {
61 m_next = getNext();
62 } catch (BadBytecode ex) {
63 throw new Error(ex);
64 }
65 return out;
66 }
67
68 @Override
69 public void remove() {
70 throw new UnsupportedOperationException();
71 }
72
73 private CheckCast getNext() throws BadBytecode {
74 int prevPos = 0;
75 while (m_iter.hasNext()) {
76 int pos = m_iter.next();
77 int opcode = m_iter.byteAt(pos);
78 switch (opcode) {
79 case Opcode.CHECKCAST:
80
81 // get the type of this op code (next two bytes are a classinfo index)
82 MethodEntry prevMethodEntry = getMethodEntry(prevPos);
83 if (prevMethodEntry != null) {
84 return new CheckCast(m_constants.getClassInfo(m_iter.s16bitAt(pos + 1)), prevMethodEntry);
85 }
86 break;
87 }
88 prevPos = pos;
89 }
90 return null;
91 }
92
93 private MethodEntry getMethodEntry(int pos) {
94 switch (m_iter.byteAt(pos)) {
95 case Opcode.INVOKEVIRTUAL:
96 case Opcode.INVOKESTATIC:
97 case Opcode.INVOKEDYNAMIC:
98 case Opcode.INVOKESPECIAL: {
99 int index = m_iter.s16bitAt(pos + 1);
100 return new MethodEntry(
101 new ClassEntry(Descriptor.toJvmName(m_constants.getMethodrefClassName(index))),
102 m_constants.getMethodrefName(index),
103 new Signature(m_constants.getMethodrefType(index))
104 );
105 }
106
107 case Opcode.INVOKEINTERFACE: {
108 int index = m_iter.s16bitAt(pos + 1);
109 return new MethodEntry(
110 new ClassEntry(Descriptor.toJvmName(m_constants.getInterfaceMethodrefClassName(index))),
111 m_constants.getInterfaceMethodrefName(index),
112 new Signature(m_constants.getInterfaceMethodrefType(index))
113 );
114 }
115 }
116 return null;
117 }
118
119 public Iterable<CheckCast> casts() {
120 return new Iterable<CheckCast>() {
121 @Override
122 public Iterator<CheckCast> iterator() {
123 return CheckCastIterator.this;
124 }
125 };
126 }
127}
diff --git a/src/cuchaz/enigma/bytecode/ClassProtectifier.java b/src/cuchaz/enigma/bytecode/ClassProtectifier.java
new file mode 100644
index 00000000..f1ee4e77
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/ClassProtectifier.java
@@ -0,0 +1,51 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import javassist.CtBehavior;
14import javassist.CtClass;
15import javassist.CtField;
16import javassist.bytecode.AccessFlag;
17import javassist.bytecode.InnerClassesAttribute;
18
19
20public class ClassProtectifier {
21
22 public static CtClass protectify(CtClass c) {
23
24 // protectify all the fields
25 for (CtField field : c.getDeclaredFields()) {
26 field.setModifiers(protectify(field.getModifiers()));
27 }
28
29 // protectify all the methods and constructors
30 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
31 behavior.setModifiers(protectify(behavior.getModifiers()));
32 }
33
34 // protectify all the inner classes
35 InnerClassesAttribute attr = (InnerClassesAttribute)c.getClassFile().getAttribute(InnerClassesAttribute.tag);
36 if (attr != null) {
37 for (int i=0; i<attr.tableLength(); i++) {
38 attr.setAccessFlags(i, protectify(attr.accessFlags(i)));
39 }
40 }
41
42 return c;
43 }
44
45 private static int protectify(int flags) {
46 if (AccessFlag.isPrivate(flags)) {
47 flags = AccessFlag.setProtected(flags);
48 }
49 return flags;
50 }
51}
diff --git a/src/cuchaz/enigma/bytecode/ClassPublifier.java b/src/cuchaz/enigma/bytecode/ClassPublifier.java
new file mode 100644
index 00000000..dbefd426
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/ClassPublifier.java
@@ -0,0 +1,51 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import javassist.CtBehavior;
14import javassist.CtClass;
15import javassist.CtField;
16import javassist.bytecode.AccessFlag;
17import javassist.bytecode.InnerClassesAttribute;
18
19
20public class ClassPublifier {
21
22 public static CtClass publify(CtClass c) {
23
24 // publify all the fields
25 for (CtField field : c.getDeclaredFields()) {
26 field.setModifiers(publify(field.getModifiers()));
27 }
28
29 // publify all the methods and constructors
30 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
31 behavior.setModifiers(publify(behavior.getModifiers()));
32 }
33
34 // publify all the inner classes
35 InnerClassesAttribute attr = (InnerClassesAttribute)c.getClassFile().getAttribute(InnerClassesAttribute.tag);
36 if (attr != null) {
37 for (int i=0; i<attr.tableLength(); i++) {
38 attr.setAccessFlags(i, publify(attr.accessFlags(i)));
39 }
40 }
41
42 return c;
43 }
44
45 private static int publify(int flags) {
46 if (AccessFlag.isPrivate(flags) || AccessFlag.isProtected(flags)) {
47 flags = AccessFlag.setPublic(flags);
48 }
49 return flags;
50 }
51}
diff --git a/src/cuchaz/enigma/bytecode/ClassRenamer.java b/src/cuchaz/enigma/bytecode/ClassRenamer.java
new file mode 100644
index 00000000..4d95f30e
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/ClassRenamer.java
@@ -0,0 +1,544 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.lang.reflect.InvocationTargetException;
14import java.lang.reflect.Method;
15import java.util.Arrays;
16import java.util.HashMap;
17import java.util.List;
18import java.util.Map;
19
20import javassist.CtClass;
21import javassist.bytecode.AttributeInfo;
22import javassist.bytecode.BadBytecode;
23import javassist.bytecode.ByteArray;
24import javassist.bytecode.ClassFile;
25import javassist.bytecode.CodeAttribute;
26import javassist.bytecode.ConstPool;
27import javassist.bytecode.Descriptor;
28import javassist.bytecode.FieldInfo;
29import javassist.bytecode.InnerClassesAttribute;
30import javassist.bytecode.LocalVariableTypeAttribute;
31import javassist.bytecode.MethodInfo;
32import javassist.bytecode.SignatureAttribute;
33import javassist.bytecode.SignatureAttribute.ArrayType;
34import javassist.bytecode.SignatureAttribute.BaseType;
35import javassist.bytecode.SignatureAttribute.ClassSignature;
36import javassist.bytecode.SignatureAttribute.ClassType;
37import javassist.bytecode.SignatureAttribute.MethodSignature;
38import javassist.bytecode.SignatureAttribute.NestedClassType;
39import javassist.bytecode.SignatureAttribute.ObjectType;
40import javassist.bytecode.SignatureAttribute.Type;
41import javassist.bytecode.SignatureAttribute.TypeArgument;
42import javassist.bytecode.SignatureAttribute.TypeParameter;
43import javassist.bytecode.SignatureAttribute.TypeVariable;
44import cuchaz.enigma.mapping.ClassEntry;
45import cuchaz.enigma.mapping.ClassNameReplacer;
46import cuchaz.enigma.mapping.Translator;
47
48public class ClassRenamer {
49
50 private static enum SignatureType {
51 Class {
52
53 @Override
54 public String rename(String signature, ReplacerClassMap map) {
55 return renameClassSignature(signature, map);
56 }
57 },
58 Field {
59
60 @Override
61 public String rename(String signature, ReplacerClassMap map) {
62 return renameFieldSignature(signature, map);
63 }
64 },
65 Method {
66
67 @Override
68 public String rename(String signature, ReplacerClassMap map) {
69 return renameMethodSignature(signature, map);
70 }
71 };
72
73 public abstract String rename(String signature, ReplacerClassMap map);
74 }
75
76 private static class ReplacerClassMap extends HashMap<String,String> {
77
78 private static final long serialVersionUID = 317915213205066168L;
79
80 private ClassNameReplacer m_replacer;
81
82 public ReplacerClassMap(ClassNameReplacer replacer) {
83 m_replacer = replacer;
84 }
85
86 @Override
87 public String get(Object obj) {
88 if (obj instanceof String) {
89 return get((String)obj);
90 }
91 return null;
92 }
93
94 public String get(String className) {
95 return m_replacer.replace(className);
96 }
97 }
98
99 public static void renameClasses(CtClass c, final Translator translator) {
100 renameClasses(c, new ClassNameReplacer() {
101 @Override
102 public String replace(String className) {
103 ClassEntry entry = translator.translateEntry(new ClassEntry(className));
104 if (entry != null) {
105 return entry.getName();
106 }
107 return null;
108 }
109 });
110 }
111
112 public static void moveAllClassesOutOfDefaultPackage(CtClass c, final String newPackageName) {
113 renameClasses(c, new ClassNameReplacer() {
114 @Override
115 public String replace(String className) {
116 ClassEntry entry = new ClassEntry(className);
117 if (entry.isInDefaultPackage()) {
118 return newPackageName + "/" + entry.getName();
119 }
120 return null;
121 }
122 });
123 }
124
125 public static void moveAllClassesIntoDefaultPackage(CtClass c, final String oldPackageName) {
126 renameClasses(c, new ClassNameReplacer() {
127 @Override
128 public String replace(String className) {
129 ClassEntry entry = new ClassEntry(className);
130 if (entry.getPackageName().equals(oldPackageName)) {
131 return entry.getSimpleName();
132 }
133 return null;
134 }
135 });
136 }
137
138 @SuppressWarnings("unchecked")
139 public static void renameClasses(CtClass c, ClassNameReplacer replacer) {
140
141 // sadly, we can't use CtClass.renameClass() because SignatureAttribute.renameClass() is extremely buggy =(
142
143 ReplacerClassMap map = new ReplacerClassMap(replacer);
144 ClassFile classFile = c.getClassFile();
145
146 // rename the constant pool (covers ClassInfo, MethodTypeInfo, and NameAndTypeInfo)
147 ConstPool constPool = c.getClassFile().getConstPool();
148 constPool.renameClass(map);
149
150 // rename class attributes
151 renameAttributes(classFile.getAttributes(), map, SignatureType.Class);
152
153 // rename methods
154 for (MethodInfo methodInfo : (List<MethodInfo>)classFile.getMethods()) {
155 methodInfo.setDescriptor(Descriptor.rename(methodInfo.getDescriptor(), map));
156 renameAttributes(methodInfo.getAttributes(), map, SignatureType.Method);
157 }
158
159 // rename fields
160 for (FieldInfo fieldInfo : (List<FieldInfo>)classFile.getFields()) {
161 fieldInfo.setDescriptor(Descriptor.rename(fieldInfo.getDescriptor(), map));
162 renameAttributes(fieldInfo.getAttributes(), map, SignatureType.Field);
163 }
164
165 // rename the class name itself last
166 // NOTE: don't use the map here, because setName() calls the buggy SignatureAttribute.renameClass()
167 // we only want to replace exactly this class name
168 String newName = renameClassName(c.getName(), map);
169 if (newName != null) {
170 c.setName(newName);
171 }
172
173 // replace simple names in the InnerClasses attribute too
174 InnerClassesAttribute attr = (InnerClassesAttribute)c.getClassFile().getAttribute(InnerClassesAttribute.tag);
175 if (attr != null) {
176 for (int i = 0; i < attr.tableLength(); i++) {
177
178 // get the inner class full name (which has already been translated)
179 ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(attr.innerClass(i)));
180
181 if (attr.innerNameIndex(i) != 0) {
182 // update the inner name
183 attr.setInnerNameIndex(i, constPool.addUtf8Info(classEntry.getInnermostClassName()));
184 }
185
186 /* DEBUG
187 System.out.println(String.format("\tDEOBF: %s-> ATTR: %s,%s,%s", classEntry, attr.outerClass(i), attr.innerClass(i), attr.innerName(i)));
188 */
189 }
190 }
191 }
192
193 @SuppressWarnings("unchecked")
194 private static void renameAttributes(List<AttributeInfo> attributes, ReplacerClassMap map, SignatureType type) {
195 try {
196
197 // make the rename class method accessible
198 Method renameClassMethod = AttributeInfo.class.getDeclaredMethod("renameClass", Map.class);
199 renameClassMethod.setAccessible(true);
200
201 for (AttributeInfo attribute : attributes) {
202 if (attribute instanceof SignatureAttribute) {
203 // this has to be handled specially because SignatureAttribute.renameClass() is buggy as hell
204 SignatureAttribute signatureAttribute = (SignatureAttribute)attribute;
205 String newSignature = type.rename(signatureAttribute.getSignature(), map);
206 if (newSignature != null) {
207 signatureAttribute.setSignature(newSignature);
208 }
209 } else if (attribute instanceof CodeAttribute) {
210 // code attributes have signature attributes too (indirectly)
211 CodeAttribute codeAttribute = (CodeAttribute)attribute;
212 renameAttributes(codeAttribute.getAttributes(), map, type);
213 } else if (attribute instanceof LocalVariableTypeAttribute) {
214 // lvt attributes have signature attributes too
215 LocalVariableTypeAttribute localVariableAttribute = (LocalVariableTypeAttribute)attribute;
216 renameLocalVariableTypeAttribute(localVariableAttribute, map);
217 } else {
218 renameClassMethod.invoke(attribute, map);
219 }
220 }
221
222 } catch(NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
223 throw new Error("Unable to call javassist methods by reflection!", ex);
224 }
225 }
226
227 private static void renameLocalVariableTypeAttribute(LocalVariableTypeAttribute attribute, ReplacerClassMap map) {
228
229 // adapted from LocalVariableAttribute.renameClass()
230 ConstPool cp = attribute.getConstPool();
231 int n = attribute.tableLength();
232 byte[] info = attribute.get();
233 for (int i = 0; i < n; ++i) {
234 int pos = i * 10 + 2;
235 int index = ByteArray.readU16bit(info, pos + 6);
236 if (index != 0) {
237 String signature = cp.getUtf8Info(index);
238 String newSignature = renameLocalVariableSignature(signature, map);
239 if (newSignature != null) {
240 ByteArray.write16bit(cp.addUtf8Info(newSignature), info, pos + 6);
241 }
242 }
243 }
244 }
245
246 private static String renameLocalVariableSignature(String signature, ReplacerClassMap map) {
247
248 // for some reason, signatures with . in them don't count as field signatures
249 // looks like anonymous classes delimit with . in stead of $
250 // convert the . to $, but keep track of how many we replace
251 // we need to put them back after we translate
252 int start = signature.lastIndexOf('$') + 1;
253 int numConverted = 0;
254 StringBuilder buf = new StringBuilder(signature);
255 for (int i=buf.length()-1; i>=start; i--) {
256 char c = buf.charAt(i);
257 if (c == '.') {
258 buf.setCharAt(i, '$');
259 numConverted++;
260 }
261 }
262 signature = buf.toString();
263
264 // translate
265 String newSignature = renameFieldSignature(signature, map);
266 if (newSignature != null) {
267
268 // put the delimiters back
269 buf = new StringBuilder(newSignature);
270 for (int i=buf.length()-1; i>=0 && numConverted > 0; i--) {
271 char c = buf.charAt(i);
272 if (c == '$') {
273 buf.setCharAt(i, '.');
274 numConverted--;
275 }
276 }
277 assert(numConverted == 0);
278 newSignature = buf.toString();
279
280 return newSignature;
281 }
282
283 return null;
284 }
285
286 private static String renameClassSignature(String signature, ReplacerClassMap map) {
287 try {
288 ClassSignature type = renameType(SignatureAttribute.toClassSignature(signature), map);
289 if (type != null) {
290 return type.encode();
291 }
292 return null;
293 } catch (BadBytecode ex) {
294 throw new Error("Can't parse field signature: " + signature);
295 }
296 }
297
298 private static String renameFieldSignature(String signature, ReplacerClassMap map) {
299 try {
300 ObjectType type = renameType(SignatureAttribute.toFieldSignature(signature), map);
301 if (type != null) {
302 return type.encode();
303 }
304 return null;
305 } catch (BadBytecode ex) {
306 throw new Error("Can't parse class signature: " + signature);
307 }
308 }
309
310 private static String renameMethodSignature(String signature, ReplacerClassMap map) {
311 try {
312 MethodSignature type = renameType(SignatureAttribute.toMethodSignature(signature), map);
313 if (type != null) {
314 return type.encode();
315 }
316 return null;
317 } catch (BadBytecode ex) {
318 throw new Error("Can't parse method signature: " + signature);
319 }
320 }
321
322 private static ClassSignature renameType(ClassSignature type, ReplacerClassMap map) {
323
324 TypeParameter[] typeParamTypes = type.getParameters();
325 if (typeParamTypes != null) {
326 typeParamTypes = Arrays.copyOf(typeParamTypes, typeParamTypes.length);
327 for (int i=0; i<typeParamTypes.length; i++) {
328 TypeParameter newParamType = renameType(typeParamTypes[i], map);
329 if (newParamType != null) {
330 typeParamTypes[i] = newParamType;
331 }
332 }
333 }
334
335 ClassType superclassType = type.getSuperClass();
336 if (superclassType != ClassType.OBJECT) {
337 ClassType newSuperclassType = renameType(superclassType, map);
338 if (newSuperclassType != null) {
339 superclassType = newSuperclassType;
340 }
341 }
342
343 ClassType[] interfaceTypes = type.getInterfaces();
344 if (interfaceTypes != null) {
345 interfaceTypes = Arrays.copyOf(interfaceTypes, interfaceTypes.length);
346 for (int i=0; i<interfaceTypes.length; i++) {
347 ClassType newInterfaceType = renameType(interfaceTypes[i], map);
348 if (newInterfaceType != null) {
349 interfaceTypes[i] = newInterfaceType;
350 }
351 }
352 }
353
354 return new ClassSignature(typeParamTypes, superclassType, interfaceTypes);
355 }
356
357 private static MethodSignature renameType(MethodSignature type, ReplacerClassMap map) {
358
359 TypeParameter[] typeParamTypes = type.getTypeParameters();
360 if (typeParamTypes != null) {
361 typeParamTypes = Arrays.copyOf(typeParamTypes, typeParamTypes.length);
362 for (int i=0; i<typeParamTypes.length; i++) {
363 TypeParameter newParamType = renameType(typeParamTypes[i], map);
364 if (newParamType != null) {
365 typeParamTypes[i] = newParamType;
366 }
367 }
368 }
369
370 Type[] paramTypes = type.getParameterTypes();
371 if (paramTypes != null) {
372 paramTypes = Arrays.copyOf(paramTypes, paramTypes.length);
373 for (int i=0; i<paramTypes.length; i++) {
374 Type newParamType = renameType(paramTypes[i], map);
375 if (newParamType != null) {
376 paramTypes[i] = newParamType;
377 }
378 }
379 }
380
381 Type returnType = type.getReturnType();
382 if (returnType != null) {
383 Type newReturnType = renameType(returnType, map);
384 if (newReturnType != null) {
385 returnType = newReturnType;
386 }
387 }
388
389 ObjectType[] exceptionTypes = type.getExceptionTypes();
390 if (exceptionTypes != null) {
391 exceptionTypes = Arrays.copyOf(exceptionTypes, exceptionTypes.length);
392 for (int i=0; i<exceptionTypes.length; i++) {
393 ObjectType newExceptionType = renameType(exceptionTypes[i], map);
394 if (newExceptionType != null) {
395 exceptionTypes[i] = newExceptionType;
396 }
397 }
398 }
399
400 return new MethodSignature(typeParamTypes, paramTypes, returnType, exceptionTypes);
401 }
402
403 private static Type renameType(Type type, ReplacerClassMap map) {
404 if (type instanceof ObjectType) {
405 return renameType((ObjectType)type, map);
406 } else if (type instanceof BaseType) {
407 return renameType((BaseType)type, map);
408 } else {
409 throw new Error("Don't know how to rename type " + type.getClass());
410 }
411 }
412
413 private static ObjectType renameType(ObjectType type, ReplacerClassMap map) {
414 if (type instanceof ArrayType) {
415 return renameType((ArrayType)type, map);
416 } else if (type instanceof ClassType) {
417 return renameType((ClassType)type, map);
418 } else if (type instanceof TypeVariable) {
419 return renameType((TypeVariable)type, map);
420 } else {
421 throw new Error("Don't know how to rename type " + type.getClass());
422 }
423 }
424
425 private static BaseType renameType(BaseType type, ReplacerClassMap map) {
426 // don't have to rename primitives
427 return null;
428 }
429
430 private static TypeVariable renameType(TypeVariable type, ReplacerClassMap map) {
431 // don't have to rename template args
432 return null;
433 }
434
435 private static ClassType renameType(ClassType type, ReplacerClassMap map) {
436
437 // translate type args
438 TypeArgument[] args = type.getTypeArguments();
439 if (args != null) {
440 args = Arrays.copyOf(args, args.length);
441 for (int i=0; i<args.length; i++) {
442 TypeArgument newType = renameType(args[i], map);
443 if (newType != null) {
444 args[i] = newType;
445 }
446 }
447 }
448
449 if (type instanceof NestedClassType) {
450 NestedClassType nestedType = (NestedClassType)type;
451
452 // translate the name
453 String name = nestedType.getName();
454 String newName = map.get(getClassName(type));
455 if (newName != null) {
456 name = new ClassEntry(newName).getInnermostClassName();
457 }
458
459 // translate the parent class too
460 ClassType parent = renameType(nestedType.getDeclaringClass(), map);
461 if (parent == null) {
462 parent = nestedType.getDeclaringClass();
463 }
464
465 return new NestedClassType(parent, name, args);
466 } else {
467
468 // translate the name
469 String name = type.getName();
470 String newName = renameClassName(name, map);
471 if (newName != null) {
472 name = newName;
473 }
474
475 return new ClassType(name, args);
476 }
477 }
478
479 private static String getClassName(ClassType type) {
480 if (type instanceof NestedClassType) {
481 NestedClassType nestedType = (NestedClassType)type;
482 return getClassName(nestedType.getDeclaringClass()) + "$" + Descriptor.toJvmName(type.getName());
483 } else {
484 return Descriptor.toJvmName(type.getName());
485 }
486 }
487
488 private static String renameClassName(String name, ReplacerClassMap map) {
489 String newName = map.get(Descriptor.toJvmName(name));
490 if (newName != null) {
491 return Descriptor.toJavaName(newName);
492 }
493 return null;
494 }
495
496 private static TypeArgument renameType(TypeArgument type, ReplacerClassMap map) {
497 ObjectType subType = type.getType();
498 if (subType != null) {
499 ObjectType newSubType = renameType(subType, map);
500 if (newSubType != null) {
501 switch (type.getKind()) {
502 case ' ': return new TypeArgument(newSubType);
503 case '+': return TypeArgument.subclassOf(newSubType);
504 case '-': return TypeArgument.superOf(newSubType);
505 default:
506 throw new Error("Unknown type kind: " + type.getKind());
507 }
508 }
509 }
510 return null;
511 }
512
513 private static ArrayType renameType(ArrayType type, ReplacerClassMap map) {
514 Type newSubType = renameType(type.getComponentType(), map);
515 if (newSubType != null) {
516 return new ArrayType(type.getDimension(), newSubType);
517 }
518 return null;
519 }
520
521 private static TypeParameter renameType(TypeParameter type, ReplacerClassMap map) {
522
523 ObjectType superclassType = type.getClassBound();
524 if (superclassType != null) {
525 ObjectType newSuperclassType = renameType(superclassType, map);
526 if (newSuperclassType != null) {
527 superclassType = newSuperclassType;
528 }
529 }
530
531 ObjectType[] interfaceTypes = type.getInterfaceBound();
532 if (interfaceTypes != null) {
533 interfaceTypes = Arrays.copyOf(interfaceTypes, interfaceTypes.length);
534 for (int i=0; i<interfaceTypes.length; i++) {
535 ObjectType newInterfaceType = renameType(interfaceTypes[i], map);
536 if (newInterfaceType != null) {
537 interfaceTypes[i] = newInterfaceType;
538 }
539 }
540 }
541
542 return new TypeParameter(type.getName(), superclassType, interfaceTypes);
543 }
544}
diff --git a/src/cuchaz/enigma/bytecode/ClassTranslator.java b/src/cuchaz/enigma/bytecode/ClassTranslator.java
new file mode 100644
index 00000000..74024598
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/ClassTranslator.java
@@ -0,0 +1,157 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import javassist.CtBehavior;
14import javassist.CtClass;
15import javassist.CtField;
16import javassist.CtMethod;
17import javassist.bytecode.ConstPool;
18import javassist.bytecode.Descriptor;
19import javassist.bytecode.EnclosingMethodAttribute;
20import javassist.bytecode.SourceFileAttribute;
21import cuchaz.enigma.mapping.BehaviorEntry;
22import cuchaz.enigma.mapping.ClassEntry;
23import cuchaz.enigma.mapping.EntryFactory;
24import cuchaz.enigma.mapping.FieldEntry;
25import cuchaz.enigma.mapping.Signature;
26import cuchaz.enigma.mapping.Translator;
27import cuchaz.enigma.mapping.Type;
28
29public class ClassTranslator {
30
31 private Translator m_translator;
32
33 public ClassTranslator(Translator translator) {
34 m_translator = translator;
35 }
36
37 public void translate(CtClass c) {
38
39 // NOTE: the order of these translations is very important
40
41 // translate all the field and method references in the code by editing the constant pool
42 ConstPool constants = c.getClassFile().getConstPool();
43 ConstPoolEditor editor = new ConstPoolEditor(constants);
44 for (int i = 1; i < constants.getSize(); i++) {
45 switch (constants.getTag(i)) {
46
47 case ConstPool.CONST_Fieldref: {
48
49 // translate the name and type
50 FieldEntry entry = EntryFactory.getFieldEntry(
51 Descriptor.toJvmName(constants.getFieldrefClassName(i)),
52 constants.getFieldrefName(i),
53 constants.getFieldrefType(i)
54 );
55 FieldEntry translatedEntry = m_translator.translateEntry(entry);
56 if (!entry.equals(translatedEntry)) {
57 editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedEntry.getType().toString());
58 }
59 }
60 break;
61
62 case ConstPool.CONST_Methodref:
63 case ConstPool.CONST_InterfaceMethodref: {
64
65 // translate the name and type (ie signature)
66 BehaviorEntry entry = EntryFactory.getBehaviorEntry(
67 Descriptor.toJvmName(editor.getMemberrefClassname(i)),
68 editor.getMemberrefName(i),
69 editor.getMemberrefType(i)
70 );
71 BehaviorEntry translatedEntry = m_translator.translateEntry(entry);
72 if (!entry.equals(translatedEntry)) {
73 editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedEntry.getSignature().toString());
74 }
75 }
76 break;
77 }
78 }
79
80 ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
81
82 // translate all the fields
83 for (CtField field : c.getDeclaredFields()) {
84
85 // translate the name
86 FieldEntry entry = EntryFactory.getFieldEntry(field);
87 String translatedName = m_translator.translate(entry);
88 if (translatedName != null) {
89 field.setName(translatedName);
90 }
91
92 // translate the type
93 Type translatedType = m_translator.translateType(entry.getType());
94 field.getFieldInfo().setDescriptor(translatedType.toString());
95 }
96
97 // translate all the methods and constructors
98 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
99
100 BehaviorEntry entry = EntryFactory.getBehaviorEntry(behavior);
101
102 if (behavior instanceof CtMethod) {
103 CtMethod method = (CtMethod)behavior;
104
105 // translate the name
106 String translatedName = m_translator.translate(entry);
107 if (translatedName != null) {
108 method.setName(translatedName);
109 }
110 }
111
112 if (entry.getSignature() != null) {
113 // translate the signature
114 Signature translatedSignature = m_translator.translateSignature(entry.getSignature());
115 behavior.getMethodInfo().setDescriptor(translatedSignature.toString());
116 }
117 }
118
119 // translate the EnclosingMethod attribute
120 EnclosingMethodAttribute enclosingMethodAttr = (EnclosingMethodAttribute)c.getClassFile().getAttribute(EnclosingMethodAttribute.tag);
121 if (enclosingMethodAttr != null) {
122
123 if (enclosingMethodAttr.methodIndex() == 0) {
124 BehaviorEntry obfBehaviorEntry = EntryFactory.getBehaviorEntry(Descriptor.toJvmName(enclosingMethodAttr.className()));
125 BehaviorEntry deobfBehaviorEntry = m_translator.translateEntry(obfBehaviorEntry);
126 c.getClassFile().addAttribute(new EnclosingMethodAttribute(
127 constants,
128 deobfBehaviorEntry.getClassName()
129 ));
130 } else {
131 BehaviorEntry obfBehaviorEntry = EntryFactory.getBehaviorEntry(
132 Descriptor.toJvmName(enclosingMethodAttr.className()),
133 enclosingMethodAttr.methodName(),
134 enclosingMethodAttr.methodDescriptor()
135 );
136 BehaviorEntry deobfBehaviorEntry = m_translator.translateEntry(obfBehaviorEntry);
137 c.getClassFile().addAttribute(new EnclosingMethodAttribute(
138 constants,
139 deobfBehaviorEntry.getClassName(),
140 deobfBehaviorEntry.getName(),
141 deobfBehaviorEntry.getSignature().toString()
142 ));
143 }
144 }
145
146 // translate all the class names referenced in the code
147 // the above code only changed method/field/reference names and types, but not the rest of the class references
148 ClassRenamer.renameClasses(c, m_translator);
149
150 // translate the source file attribute too
151 ClassEntry deobfClassEntry = m_translator.translateEntry(classEntry);
152 if (deobfClassEntry != null) {
153 String sourceFile = Descriptor.toJvmName(deobfClassEntry.getOutermostClassEntry().getSimpleName()) + ".java";
154 c.getClassFile().addAttribute(new SourceFileAttribute(constants, sourceFile));
155 }
156 }
157}
diff --git a/src/cuchaz/enigma/bytecode/ConstPoolEditor.java b/src/cuchaz/enigma/bytecode/ConstPoolEditor.java
new file mode 100644
index 00000000..a00b86b5
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/ConstPoolEditor.java
@@ -0,0 +1,263 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.io.DataInputStream;
14import java.io.DataOutputStream;
15import java.lang.reflect.Constructor;
16import java.lang.reflect.Field;
17import java.lang.reflect.Method;
18import java.util.HashMap;
19
20import javassist.bytecode.ConstPool;
21import javassist.bytecode.Descriptor;
22import cuchaz.enigma.bytecode.accessors.ClassInfoAccessor;
23import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor;
24import cuchaz.enigma.bytecode.accessors.MemberRefInfoAccessor;
25
26public class ConstPoolEditor {
27
28 private static Method m_getItem;
29 private static Method m_addItem;
30 private static Method m_addItem0;
31 private static Field m_items;
32 private static Field m_cache;
33 private static Field m_numItems;
34 private static Field m_objects;
35 private static Field m_elements;
36 private static Method m_methodWritePool;
37 private static Constructor<ConstPool> m_constructorPool;
38
39 static {
40 try {
41 m_getItem = ConstPool.class.getDeclaredMethod("getItem", int.class);
42 m_getItem.setAccessible(true);
43
44 m_addItem = ConstPool.class.getDeclaredMethod("addItem", Class.forName("javassist.bytecode.ConstInfo"));
45 m_addItem.setAccessible(true);
46
47 m_addItem0 = ConstPool.class.getDeclaredMethod("addItem0", Class.forName("javassist.bytecode.ConstInfo"));
48 m_addItem0.setAccessible(true);
49
50 m_items = ConstPool.class.getDeclaredField("items");
51 m_items.setAccessible(true);
52
53 m_cache = ConstPool.class.getDeclaredField("itemsCache");
54 m_cache.setAccessible(true);
55
56 m_numItems = ConstPool.class.getDeclaredField("numOfItems");
57 m_numItems.setAccessible(true);
58
59 m_objects = Class.forName("javassist.bytecode.LongVector").getDeclaredField("objects");
60 m_objects.setAccessible(true);
61
62 m_elements = Class.forName("javassist.bytecode.LongVector").getDeclaredField("elements");
63 m_elements.setAccessible(true);
64
65 m_methodWritePool = ConstPool.class.getDeclaredMethod("write", DataOutputStream.class);
66 m_methodWritePool.setAccessible(true);
67
68 m_constructorPool = ConstPool.class.getDeclaredConstructor(DataInputStream.class);
69 m_constructorPool.setAccessible(true);
70 } catch (Exception ex) {
71 throw new Error(ex);
72 }
73 }
74
75 private ConstPool m_pool;
76
77 public ConstPoolEditor(ConstPool pool) {
78 m_pool = pool;
79 }
80
81 public void writePool(DataOutputStream out) {
82 try {
83 m_methodWritePool.invoke(m_pool, out);
84 } catch (Exception ex) {
85 throw new Error(ex);
86 }
87 }
88
89 public static ConstPool readPool(DataInputStream in) {
90 try {
91 return m_constructorPool.newInstance(in);
92 } catch (Exception ex) {
93 throw new Error(ex);
94 }
95 }
96
97 public String getMemberrefClassname(int memberrefIndex) {
98 return Descriptor.toJvmName(m_pool.getClassInfo(m_pool.getMemberClass(memberrefIndex)));
99 }
100
101 public String getMemberrefName(int memberrefIndex) {
102 return m_pool.getUtf8Info(m_pool.getNameAndTypeName(m_pool.getMemberNameAndType(memberrefIndex)));
103 }
104
105 public String getMemberrefType(int memberrefIndex) {
106 return m_pool.getUtf8Info(m_pool.getNameAndTypeDescriptor(m_pool.getMemberNameAndType(memberrefIndex)));
107 }
108
109 public ConstInfoAccessor getItem(int index) {
110 try {
111 Object entry = m_getItem.invoke(m_pool, index);
112 if (entry == null) {
113 return null;
114 }
115 return new ConstInfoAccessor(entry);
116 } catch (Exception ex) {
117 throw new Error(ex);
118 }
119 }
120
121 public int addItem(Object item) {
122 try {
123 return (Integer)m_addItem.invoke(m_pool, item);
124 } catch (Exception ex) {
125 throw new Error(ex);
126 }
127 }
128
129 public int addItemForceNew(Object item) {
130 try {
131 return (Integer)m_addItem0.invoke(m_pool, item);
132 } catch (Exception ex) {
133 throw new Error(ex);
134 }
135 }
136
137 @SuppressWarnings("rawtypes")
138 public void removeLastItem() {
139 try {
140 // remove the item from the cache
141 HashMap cache = getCache();
142 if (cache != null) {
143 Object item = getItem(m_pool.getSize() - 1);
144 cache.remove(item);
145 }
146
147 // remove the actual item
148 // based off of LongVector.addElement()
149 Object items = m_items.get(m_pool);
150 Object[][] objects = (Object[][])m_objects.get(items);
151 int numElements = (Integer)m_elements.get(items) - 1;
152 int nth = numElements >> 7;
153 int offset = numElements & (128 - 1);
154 objects[nth][offset] = null;
155
156 // decrement the number of items
157 m_elements.set(items, numElements);
158 m_numItems.set(m_pool, (Integer)m_numItems.get(m_pool) - 1);
159 } catch (Exception ex) {
160 throw new Error(ex);
161 }
162 }
163
164 @SuppressWarnings("rawtypes")
165 public HashMap getCache() {
166 try {
167 return (HashMap)m_cache.get(m_pool);
168 } catch (Exception ex) {
169 throw new Error(ex);
170 }
171 }
172
173 @SuppressWarnings({ "rawtypes", "unchecked" })
174 public void changeMemberrefNameAndType(int memberrefIndex, String newName, String newType) {
175 // NOTE: when changing values, we always need to copy-on-write
176 try {
177 // get the memberref item
178 Object item = getItem(memberrefIndex).getItem();
179
180 // update the cache
181 HashMap cache = getCache();
182 if (cache != null) {
183 cache.remove(item);
184 }
185
186 new MemberRefInfoAccessor(item).setNameAndTypeIndex(m_pool.addNameAndTypeInfo(newName, newType));
187
188 // update the cache
189 if (cache != null) {
190 cache.put(item, item);
191 }
192 } catch (Exception ex) {
193 throw new Error(ex);
194 }
195
196 // make sure the change worked
197 assert (newName.equals(getMemberrefName(memberrefIndex)));
198 assert (newType.equals(getMemberrefType(memberrefIndex)));
199 }
200
201 @SuppressWarnings({ "rawtypes", "unchecked" })
202 public void changeClassName(int classNameIndex, String newName) {
203 // NOTE: when changing values, we always need to copy-on-write
204 try {
205 // get the class item
206 Object item = getItem(classNameIndex).getItem();
207
208 // update the cache
209 HashMap cache = getCache();
210 if (cache != null) {
211 cache.remove(item);
212 }
213
214 // add the new name and repoint the name-and-type to it
215 new ClassInfoAccessor(item).setNameIndex(m_pool.addUtf8Info(newName));
216
217 // update the cache
218 if (cache != null) {
219 cache.put(item, item);
220 }
221 } catch (Exception ex) {
222 throw new Error(ex);
223 }
224 }
225
226 public static ConstPool newConstPool() {
227 // const pool expects the name of a class to initialize itself
228 // but we want an empty pool
229 // so give it a bogus name, and then clear the entries afterwards
230 ConstPool pool = new ConstPool("a");
231
232 ConstPoolEditor editor = new ConstPoolEditor(pool);
233 int size = pool.getSize();
234 for (int i = 0; i < size - 1; i++) {
235 editor.removeLastItem();
236 }
237
238 // make sure the pool is actually empty
239 // although, in this case "empty" means one thing in it
240 // the JVM spec says index 0 should be reserved
241 assert (pool.getSize() == 1);
242 assert (editor.getItem(0) == null);
243 assert (editor.getItem(1) == null);
244 assert (editor.getItem(2) == null);
245 assert (editor.getItem(3) == null);
246
247 // also, clear the cache
248 editor.getCache().clear();
249
250 return pool;
251 }
252
253 public String dump() {
254 StringBuilder buf = new StringBuilder();
255 for (int i = 1; i < m_pool.getSize(); i++) {
256 buf.append(String.format("%4d", i));
257 buf.append(" ");
258 buf.append(getItem(i).toString());
259 buf.append("\n");
260 }
261 return buf.toString();
262 }
263}
diff --git a/src/cuchaz/enigma/bytecode/InfoType.java b/src/cuchaz/enigma/bytecode/InfoType.java
new file mode 100644
index 00000000..08f2b3e2
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/InfoType.java
@@ -0,0 +1,317 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.util.Collection;
14import java.util.List;
15import java.util.Map;
16
17import com.google.common.collect.Lists;
18import com.google.common.collect.Maps;
19
20import cuchaz.enigma.bytecode.accessors.ClassInfoAccessor;
21import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor;
22import cuchaz.enigma.bytecode.accessors.InvokeDynamicInfoAccessor;
23import cuchaz.enigma.bytecode.accessors.MemberRefInfoAccessor;
24import cuchaz.enigma.bytecode.accessors.MethodHandleInfoAccessor;
25import cuchaz.enigma.bytecode.accessors.MethodTypeInfoAccessor;
26import cuchaz.enigma.bytecode.accessors.NameAndTypeInfoAccessor;
27import cuchaz.enigma.bytecode.accessors.StringInfoAccessor;
28
29public enum InfoType {
30
31 Utf8Info( 1, 0 ),
32 IntegerInfo( 3, 0 ),
33 FloatInfo( 4, 0 ),
34 LongInfo( 5, 0 ),
35 DoubleInfo( 6, 0 ),
36 ClassInfo( 7, 1 ) {
37
38 @Override
39 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
40 ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem());
41 gatherIndexTree(indices, editor, accessor.getNameIndex());
42 }
43
44 @Override
45 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
46 ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem());
47 accessor.setNameIndex(remapIndex(map, accessor.getNameIndex()));
48 }
49
50 @Override
51 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
52 ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem());
53 ConstInfoAccessor nameEntry = pool.getItem(accessor.getNameIndex());
54 return nameEntry != null && nameEntry.getTag() == Utf8Info.getTag();
55 }
56 },
57 StringInfo( 8, 1 ) {
58
59 @Override
60 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
61 StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem());
62 gatherIndexTree(indices, editor, accessor.getStringIndex());
63 }
64
65 @Override
66 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
67 StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem());
68 accessor.setStringIndex(remapIndex(map, accessor.getStringIndex()));
69 }
70
71 @Override
72 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
73 StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem());
74 ConstInfoAccessor stringEntry = pool.getItem(accessor.getStringIndex());
75 return stringEntry != null && stringEntry.getTag() == Utf8Info.getTag();
76 }
77 },
78 FieldRefInfo( 9, 2 ) {
79
80 @Override
81 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
82 MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem());
83 gatherIndexTree(indices, editor, accessor.getClassIndex());
84 gatherIndexTree(indices, editor, accessor.getNameAndTypeIndex());
85 }
86
87 @Override
88 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
89 MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem());
90 accessor.setClassIndex(remapIndex(map, accessor.getClassIndex()));
91 accessor.setNameAndTypeIndex(remapIndex(map, accessor.getNameAndTypeIndex()));
92 }
93
94 @Override
95 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
96 MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem());
97 ConstInfoAccessor classEntry = pool.getItem(accessor.getClassIndex());
98 ConstInfoAccessor nameAndTypeEntry = pool.getItem(accessor.getNameAndTypeIndex());
99 return classEntry != null && classEntry.getTag() == ClassInfo.getTag() && nameAndTypeEntry != null && nameAndTypeEntry.getTag() == NameAndTypeInfo.getTag();
100 }
101 },
102 // same as FieldRefInfo
103 MethodRefInfo( 10, 2 ) {
104
105 @Override
106 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
107 FieldRefInfo.gatherIndexTree(indices, editor, entry);
108 }
109
110 @Override
111 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
112 FieldRefInfo.remapIndices(map, entry);
113 }
114
115 @Override
116 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
117 return FieldRefInfo.subIndicesAreValid(entry, pool);
118 }
119 },
120 // same as FieldRefInfo
121 InterfaceMethodRefInfo( 11, 2 ) {
122
123 @Override
124 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
125 FieldRefInfo.gatherIndexTree(indices, editor, entry);
126 }
127
128 @Override
129 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
130 FieldRefInfo.remapIndices(map, entry);
131 }
132
133 @Override
134 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
135 return FieldRefInfo.subIndicesAreValid(entry, pool);
136 }
137 },
138 NameAndTypeInfo( 12, 1 ) {
139
140 @Override
141 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
142 NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem());
143 gatherIndexTree(indices, editor, accessor.getNameIndex());
144 gatherIndexTree(indices, editor, accessor.getTypeIndex());
145 }
146
147 @Override
148 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
149 NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem());
150 accessor.setNameIndex(remapIndex(map, accessor.getNameIndex()));
151 accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex()));
152 }
153
154 @Override
155 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
156 NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem());
157 ConstInfoAccessor nameEntry = pool.getItem(accessor.getNameIndex());
158 ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex());
159 return nameEntry != null && nameEntry.getTag() == Utf8Info.getTag() && typeEntry != null && typeEntry.getTag() == Utf8Info.getTag();
160 }
161 },
162 MethodHandleInfo( 15, 3 ) {
163
164 @Override
165 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
166 MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem());
167 gatherIndexTree(indices, editor, accessor.getTypeIndex());
168 gatherIndexTree(indices, editor, accessor.getMethodRefIndex());
169 }
170
171 @Override
172 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
173 MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem());
174 accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex()));
175 accessor.setMethodRefIndex(remapIndex(map, accessor.getMethodRefIndex()));
176 }
177
178 @Override
179 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
180 MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem());
181 ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex());
182 ConstInfoAccessor methodRefEntry = pool.getItem(accessor.getMethodRefIndex());
183 return typeEntry != null && typeEntry.getTag() == Utf8Info.getTag() && methodRefEntry != null && methodRefEntry.getTag() == MethodRefInfo.getTag();
184 }
185 },
186 MethodTypeInfo( 16, 1 ) {
187
188 @Override
189 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
190 MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem());
191 gatherIndexTree(indices, editor, accessor.getTypeIndex());
192 }
193
194 @Override
195 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
196 MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem());
197 accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex()));
198 }
199
200 @Override
201 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
202 MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem());
203 ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex());
204 return typeEntry != null && typeEntry.getTag() == Utf8Info.getTag();
205 }
206 },
207 InvokeDynamicInfo( 18, 2 ) {
208
209 @Override
210 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
211 InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem());
212 gatherIndexTree(indices, editor, accessor.getBootstrapIndex());
213 gatherIndexTree(indices, editor, accessor.getNameAndTypeIndex());
214 }
215
216 @Override
217 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
218 InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem());
219 accessor.setBootstrapIndex(remapIndex(map, accessor.getBootstrapIndex()));
220 accessor.setNameAndTypeIndex(remapIndex(map, accessor.getNameAndTypeIndex()));
221 }
222
223 @Override
224 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
225 InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem());
226 ConstInfoAccessor bootstrapEntry = pool.getItem(accessor.getBootstrapIndex());
227 ConstInfoAccessor nameAndTypeEntry = pool.getItem(accessor.getNameAndTypeIndex());
228 return bootstrapEntry != null && bootstrapEntry.getTag() == Utf8Info.getTag() && nameAndTypeEntry != null && nameAndTypeEntry.getTag() == NameAndTypeInfo.getTag();
229 }
230 };
231
232 private static Map<Integer,InfoType> m_types;
233
234 static {
235 m_types = Maps.newTreeMap();
236 for (InfoType type : values()) {
237 m_types.put(type.getTag(), type);
238 }
239 }
240
241 private int m_tag;
242 private int m_level;
243
244 private InfoType(int tag, int level) {
245 m_tag = tag;
246 m_level = level;
247 }
248
249 public int getTag() {
250 return m_tag;
251 }
252
253 public int getLevel() {
254 return m_level;
255 }
256
257 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
258 // by default, do nothing
259 }
260
261 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
262 // by default, do nothing
263 }
264
265 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
266 // by default, everything is good
267 return true;
268 }
269
270 public boolean selfIndexIsValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
271 ConstInfoAccessor entryCheck = pool.getItem(entry.getIndex());
272 if (entryCheck == null) {
273 return false;
274 }
275 return entryCheck.getItem().equals(entry.getItem());
276 }
277
278 public static InfoType getByTag(int tag) {
279 return m_types.get(tag);
280 }
281
282 public static List<InfoType> getByLevel(int level) {
283 List<InfoType> types = Lists.newArrayList();
284 for (InfoType type : values()) {
285 if (type.getLevel() == level) {
286 types.add(type);
287 }
288 }
289 return types;
290 }
291
292 public static List<InfoType> getSortedByLevel() {
293 List<InfoType> types = Lists.newArrayList();
294 types.addAll(getByLevel(0));
295 types.addAll(getByLevel(1));
296 types.addAll(getByLevel(2));
297 types.addAll(getByLevel(3));
298 return types;
299 }
300
301 public static void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, int index) {
302 // add own index
303 indices.add(index);
304
305 // recurse
306 ConstInfoAccessor entry = editor.getItem(index);
307 entry.getType().gatherIndexTree(indices, editor, entry);
308 }
309
310 private static int remapIndex(Map<Integer,Integer> map, int index) {
311 Integer newIndex = map.get(index);
312 if (newIndex == null) {
313 newIndex = index;
314 }
315 return newIndex;
316 }
317}
diff --git a/src/cuchaz/enigma/bytecode/InnerClassWriter.java b/src/cuchaz/enigma/bytecode/InnerClassWriter.java
new file mode 100644
index 00000000..bdb1b5df
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/InnerClassWriter.java
@@ -0,0 +1,132 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.util.Collection;
14import java.util.List;
15
16import com.google.common.collect.Lists;
17
18import javassist.CtClass;
19import javassist.bytecode.AccessFlag;
20import javassist.bytecode.ConstPool;
21import javassist.bytecode.EnclosingMethodAttribute;
22import javassist.bytecode.InnerClassesAttribute;
23import cuchaz.enigma.analysis.JarIndex;
24import cuchaz.enigma.mapping.BehaviorEntry;
25import cuchaz.enigma.mapping.ClassEntry;
26import cuchaz.enigma.mapping.EntryFactory;
27
28public class InnerClassWriter {
29
30 private JarIndex m_index;
31
32 public InnerClassWriter(JarIndex index) {
33 m_index = index;
34 }
35
36 public void write(CtClass c) {
37
38 // don't change anything if there's already an attribute there
39 InnerClassesAttribute oldAttr = (InnerClassesAttribute)c.getClassFile().getAttribute(InnerClassesAttribute.tag);
40 if (oldAttr != null) {
41 // bail!
42 return;
43 }
44
45 ClassEntry obfClassEntry = EntryFactory.getClassEntry(c);
46 List<ClassEntry> obfClassChain = m_index.getObfClassChain(obfClassEntry);
47
48 boolean isInnerClass = obfClassChain.size() > 1;
49 if (isInnerClass) {
50
51 // it's an inner class, rename it to the fully qualified name
52 c.setName(obfClassEntry.buildClassEntry(obfClassChain).getName());
53
54 BehaviorEntry caller = m_index.getAnonymousClassCaller(obfClassEntry);
55 if (caller != null) {
56
57 // write the enclosing method attribute
58 if (caller.getName().equals("<clinit>")) {
59 c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName()));
60 } else {
61 c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName(), caller.getName(), caller.getSignature().toString()));
62 }
63 }
64 }
65
66 // does this class have any inner classes?
67 Collection<ClassEntry> obfInnerClassEntries = m_index.getInnerClasses(obfClassEntry);
68
69 if (isInnerClass || !obfInnerClassEntries.isEmpty()) {
70
71 // create an inner class attribute
72 InnerClassesAttribute attr = new InnerClassesAttribute(c.getClassFile().getConstPool());
73 c.getClassFile().addAttribute(attr);
74
75 // write the ancestry, but not the outermost class
76 for (int i=1; i<obfClassChain.size(); i++) {
77 ClassEntry obfInnerClassEntry = obfClassChain.get(i);
78 writeInnerClass(attr, obfClassChain, obfInnerClassEntry);
79
80 // update references to use the fully qualified inner class name
81 c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(obfClassChain).getName());
82 }
83
84 // write the inner classes
85 for (ClassEntry obfInnerClassEntry : obfInnerClassEntries) {
86
87 // extend the class chain
88 List<ClassEntry> extendedObfClassChain = Lists.newArrayList(obfClassChain);
89 extendedObfClassChain.add(obfInnerClassEntry);
90
91 writeInnerClass(attr, extendedObfClassChain, obfInnerClassEntry);
92
93 // update references to use the fully qualified inner class name
94 c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(extendedObfClassChain).getName());
95 }
96 }
97 }
98
99 private void writeInnerClass(InnerClassesAttribute attr, List<ClassEntry> obfClassChain, ClassEntry obfClassEntry) {
100
101 // get the new inner class name
102 ClassEntry obfInnerClassEntry = obfClassEntry.buildClassEntry(obfClassChain);
103 ClassEntry obfOuterClassEntry = obfInnerClassEntry.getOuterClassEntry();
104
105 // here's what the JVM spec says about the InnerClasses attribute
106 // append(inner, parent, 0 if anonymous else simple name, flags);
107
108 // update the attribute with this inner class
109 ConstPool constPool = attr.getConstPool();
110 int innerClassIndex = constPool.addClassInfo(obfInnerClassEntry.getName());
111 int parentClassIndex = constPool.addClassInfo(obfOuterClassEntry.getName());
112 int innerClassNameIndex = 0;
113 int accessFlags = AccessFlag.PUBLIC;
114 // TODO: need to figure out if we can put static or not
115 if (!m_index.isAnonymousClass(obfClassEntry)) {
116 innerClassNameIndex = constPool.addUtf8Info(obfInnerClassEntry.getInnermostClassName());
117 }
118
119 attr.append(innerClassIndex, parentClassIndex, innerClassNameIndex, accessFlags);
120
121 /* DEBUG
122 System.out.println(String.format("\tOBF: %s -> ATTR: %s,%s,%s (replace %s with %s)",
123 obfClassEntry,
124 attr.innerClass(attr.tableLength() - 1),
125 attr.outerClass(attr.tableLength() - 1),
126 attr.innerName(attr.tableLength() - 1),
127 Constants.NonePackage + "/" + obfInnerClassName,
128 obfClassEntry.getName()
129 ));
130 */
131 }
132}
diff --git a/src/cuchaz/enigma/bytecode/LocalVariableRenamer.java b/src/cuchaz/enigma/bytecode/LocalVariableRenamer.java
new file mode 100644
index 00000000..ae0455f9
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/LocalVariableRenamer.java
@@ -0,0 +1,123 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import javassist.CtBehavior;
14import javassist.CtClass;
15import javassist.bytecode.ByteArray;
16import javassist.bytecode.CodeAttribute;
17import javassist.bytecode.ConstPool;
18import javassist.bytecode.LocalVariableAttribute;
19import javassist.bytecode.LocalVariableTypeAttribute;
20import cuchaz.enigma.mapping.ArgumentEntry;
21import cuchaz.enigma.mapping.BehaviorEntry;
22import cuchaz.enigma.mapping.EntryFactory;
23import cuchaz.enigma.mapping.Translator;
24
25
26public class LocalVariableRenamer {
27
28 private Translator m_translator;
29
30 public LocalVariableRenamer(Translator translator) {
31 m_translator = translator;
32 }
33
34 public void rename(CtClass c) {
35 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
36
37 // if there's a local variable table, just rename everything to v1, v2, v3, ... for now
38 CodeAttribute codeAttribute = behavior.getMethodInfo().getCodeAttribute();
39 if (codeAttribute == null) {
40 continue;
41 }
42
43 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
44 ConstPool constants = c.getClassFile().getConstPool();
45
46 LocalVariableAttribute table = (LocalVariableAttribute)codeAttribute.getAttribute(LocalVariableAttribute.tag);
47 if (table != null) {
48 renameLVT(behaviorEntry, constants, table);
49 }
50
51 LocalVariableTypeAttribute typeTable = (LocalVariableTypeAttribute)codeAttribute.getAttribute(LocalVariableAttribute.typeTag);
52 if (typeTable != null) {
53 renameLVTT(typeTable, table);
54 }
55 }
56 }
57
58 // DEBUG
59 @SuppressWarnings("unused")
60 private void dumpTable(LocalVariableAttribute table) {
61 for (int i=0; i<table.tableLength(); i++) {
62 System.out.println(String.format("\t%d (%d): %s %s",
63 i, table.index(i), table.variableName(i), table.descriptor(i)
64 ));
65 }
66 }
67
68 private void renameLVT(BehaviorEntry behaviorEntry, ConstPool constants, LocalVariableAttribute table) {
69
70 // skip empty tables
71 if (table.tableLength() <= 0) {
72 return;
73 }
74
75 // where do we start counting variables?
76 int starti = 0;
77 if (table.variableName(0).equals("this")) {
78 // skip the "this" variable
79 starti = 1;
80 }
81
82 // rename method arguments first
83 int numArgs = 0;
84 if (behaviorEntry.getSignature() != null) {
85 numArgs = behaviorEntry.getSignature().getArgumentTypes().size();
86 for (int i=starti; i<starti + numArgs && i<table.tableLength(); i++) {
87 int argi = i - starti;
88 String argName = m_translator.translate(new ArgumentEntry(behaviorEntry, argi, ""));
89 if (argName == null) {
90 argName = "a" + (argi + 1);
91 }
92 renameVariable(table, i, constants.addUtf8Info(argName));
93 }
94 }
95
96 // then rename the rest of the args, if any
97 for (int i=starti + numArgs; i<table.tableLength(); i++) {
98 int firstIndex = table.index(starti + numArgs);
99 renameVariable(table, i, constants.addUtf8Info("v" + (table.index(i) - firstIndex + 1)));
100 }
101 }
102
103 private void renameLVTT(LocalVariableTypeAttribute typeTable, LocalVariableAttribute table) {
104 // rename args to the same names as in the LVT
105 for (int i=0; i<typeTable.tableLength(); i++) {
106 renameVariable(typeTable, i, getNameIndex(table, typeTable.index(i)));
107 }
108 }
109
110 private void renameVariable(LocalVariableAttribute table, int i, int stringId) {
111 // based off of LocalVariableAttribute.nameIndex()
112 ByteArray.write16bit(stringId, table.get(), i*10 + 6);
113 }
114
115 private int getNameIndex(LocalVariableAttribute table, int index) {
116 for (int i=0; i<table.tableLength(); i++) {
117 if (table.index(i) == index) {
118 return table.nameIndex(i);
119 }
120 }
121 return 0;
122 }
123}
diff --git a/src/cuchaz/enigma/bytecode/MethodParameterWriter.java b/src/cuchaz/enigma/bytecode/MethodParameterWriter.java
new file mode 100644
index 00000000..0bdf47a4
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/MethodParameterWriter.java
@@ -0,0 +1,70 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.util.ArrayList;
14import java.util.List;
15
16import javassist.CtBehavior;
17import javassist.CtClass;
18import javassist.bytecode.CodeAttribute;
19import javassist.bytecode.LocalVariableAttribute;
20import cuchaz.enigma.mapping.ArgumentEntry;
21import cuchaz.enigma.mapping.BehaviorEntry;
22import cuchaz.enigma.mapping.EntryFactory;
23import cuchaz.enigma.mapping.Signature;
24import cuchaz.enigma.mapping.Translator;
25
26public class MethodParameterWriter {
27
28 private Translator m_translator;
29
30 public MethodParameterWriter(Translator translator) {
31 m_translator = translator;
32 }
33
34 public void writeMethodArguments(CtClass c) {
35
36 // Procyon will read method arguments from the "MethodParameters" attribute, so write those
37 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
38
39 // if there's a local variable table here, don't write a MethodParameters attribute
40 // let the local variable writer deal with it instead
41 // procyon starts doing really weird things if we give it both attributes
42 CodeAttribute codeAttribute = behavior.getMethodInfo().getCodeAttribute();
43 if (codeAttribute != null && codeAttribute.getAttribute(LocalVariableAttribute.tag) != null) {
44 continue;
45 }
46
47 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
48
49 // get the number of arguments
50 Signature signature = behaviorEntry.getSignature();
51 if (signature == null) {
52 // static initializers have no signatures, or arguments
53 continue;
54 }
55 int numParams = signature.getArgumentTypes().size();
56 if (numParams <= 0) {
57 continue;
58 }
59
60 // get the list of argument names
61 List<String> names = new ArrayList<String>(numParams);
62 for (int i = 0; i < numParams; i++) {
63 names.add(m_translator.translate(new ArgumentEntry(behaviorEntry, i, "")));
64 }
65
66 // save the mappings to the class
67 MethodParametersAttribute.updateClass(behavior.getMethodInfo(), names);
68 }
69 }
70}
diff --git a/src/cuchaz/enigma/bytecode/MethodParametersAttribute.java b/src/cuchaz/enigma/bytecode/MethodParametersAttribute.java
new file mode 100644
index 00000000..512e65a0
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/MethodParametersAttribute.java
@@ -0,0 +1,86 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.io.ByteArrayOutputStream;
14import java.io.DataOutputStream;
15import java.io.IOException;
16import java.util.ArrayList;
17import java.util.List;
18
19import javassist.bytecode.AttributeInfo;
20import javassist.bytecode.ConstPool;
21import javassist.bytecode.MethodInfo;
22
23public class MethodParametersAttribute extends AttributeInfo {
24
25 private MethodParametersAttribute(ConstPool pool, List<Integer> parameterNameIndices) {
26 super(pool, "MethodParameters", writeStruct(parameterNameIndices));
27 }
28
29 public static void updateClass(MethodInfo info, List<String> names) {
30
31 // add the names to the class const pool
32 ConstPool constPool = info.getConstPool();
33 List<Integer> parameterNameIndices = new ArrayList<Integer>();
34 for (String name : names) {
35 if (name != null) {
36 parameterNameIndices.add(constPool.addUtf8Info(name));
37 } else {
38 parameterNameIndices.add(0);
39 }
40 }
41
42 // add the attribute to the method
43 info.addAttribute(new MethodParametersAttribute(constPool, parameterNameIndices));
44 }
45
46 private static byte[] writeStruct(List<Integer> parameterNameIndices) {
47 // JVM 8 Spec says the struct looks like this:
48 // http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.24
49 // uint8 num_params
50 // for each param:
51 // uint16 name_index -> points to UTF8 entry in constant pool, or 0 for no entry
52 // uint16 access_flags -> don't care, just set to 0
53
54 ByteArrayOutputStream buf = new ByteArrayOutputStream();
55 DataOutputStream out = new DataOutputStream(buf);
56
57 // NOTE: java hates unsigned integers, so we have to be careful here
58 // the writeShort(), writeByte() methods will read 16,8 low-order bits from the int argument
59 // as long as the int argument is in range of the unsigned short/byte type, it will be written as an unsigned short/byte
60 // if the int is out of range, the byte stream won't look the way we want and weird things will happen
61 final int SIZEOF_UINT8 = 1;
62 final int SIZEOF_UINT16 = 2;
63 final int MAX_UINT8 = (1 << 8) - 1;
64 final int MAX_UINT16 = (1 << 16) - 1;
65
66 try {
67 assert (parameterNameIndices.size() >= 0 && parameterNameIndices.size() <= MAX_UINT8);
68 out.writeByte(parameterNameIndices.size());
69
70 for (Integer index : parameterNameIndices) {
71 assert (index >= 0 && index <= MAX_UINT16);
72 out.writeShort(index);
73
74 // just write 0 for the access flags
75 out.writeShort(0);
76 }
77
78 out.close();
79 byte[] data = buf.toByteArray();
80 assert (data.length == SIZEOF_UINT8 + parameterNameIndices.size() * (SIZEOF_UINT16 + SIZEOF_UINT16));
81 return data;
82 } catch (IOException ex) {
83 throw new Error(ex);
84 }
85 }
86}
diff --git a/src/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java
new file mode 100644
index 00000000..9072c29a
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java
@@ -0,0 +1,55 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors;
12
13import java.lang.reflect.Field;
14
15public class ClassInfoAccessor {
16
17 private static Class<?> m_class;
18 private static Field m_nameIndex;
19
20 static {
21 try {
22 m_class = Class.forName("javassist.bytecode.ClassInfo");
23 m_nameIndex = m_class.getDeclaredField("name");
24 m_nameIndex.setAccessible(true);
25 } catch (Exception ex) {
26 throw new Error(ex);
27 }
28 }
29
30 public static boolean isType(ConstInfoAccessor accessor) {
31 return m_class.isAssignableFrom(accessor.getItem().getClass());
32 }
33
34 private Object m_item;
35
36 public ClassInfoAccessor(Object item) {
37 m_item = item;
38 }
39
40 public int getNameIndex() {
41 try {
42 return (Integer)m_nameIndex.get(m_item);
43 } catch (Exception ex) {
44 throw new Error(ex);
45 }
46 }
47
48 public void setNameIndex(int val) {
49 try {
50 m_nameIndex.set(m_item, val);
51 } catch (Exception ex) {
52 throw new Error(ex);
53 }
54 }
55}
diff --git a/src/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java
new file mode 100644
index 00000000..ede04738
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java
@@ -0,0 +1,156 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors;
12
13import java.io.ByteArrayInputStream;
14import java.io.ByteArrayOutputStream;
15import java.io.DataInputStream;
16import java.io.DataOutputStream;
17import java.io.IOException;
18import java.io.PrintWriter;
19import java.lang.reflect.Constructor;
20import java.lang.reflect.Field;
21import java.lang.reflect.Method;
22
23import cuchaz.enigma.bytecode.InfoType;
24
25public class ConstInfoAccessor {
26
27 private static Class<?> m_class;
28 private static Field m_index;
29 private static Method m_getTag;
30
31 static {
32 try {
33 m_class = Class.forName("javassist.bytecode.ConstInfo");
34 m_index = m_class.getDeclaredField("index");
35 m_index.setAccessible(true);
36 m_getTag = m_class.getMethod("getTag");
37 m_getTag.setAccessible(true);
38 } catch (Exception ex) {
39 throw new Error(ex);
40 }
41 }
42
43 private Object m_item;
44
45 public ConstInfoAccessor(Object item) {
46 if (item == null) {
47 throw new IllegalArgumentException("item cannot be null!");
48 }
49 m_item = item;
50 }
51
52 public ConstInfoAccessor(DataInputStream in) throws IOException {
53 try {
54 // read the entry
55 String className = in.readUTF();
56 int oldIndex = in.readInt();
57
58 // NOTE: ConstInfo instances write a type id (a "tag"), but they don't read it back
59 // so we have to read it here
60 in.readByte();
61
62 Constructor<?> constructor = Class.forName(className).getConstructor(DataInputStream.class, int.class);
63 constructor.setAccessible(true);
64 m_item = constructor.newInstance(in, oldIndex);
65 } catch (IOException ex) {
66 throw ex;
67 } catch (Exception ex) {
68 throw new Error(ex);
69 }
70 }
71
72 public Object getItem() {
73 return m_item;
74 }
75
76 public int getIndex() {
77 try {
78 return (Integer)m_index.get(m_item);
79 } catch (Exception ex) {
80 throw new Error(ex);
81 }
82 }
83
84 public void setIndex(int val) {
85 try {
86 m_index.set(m_item, val);
87 } catch (Exception ex) {
88 throw new Error(ex);
89 }
90 }
91
92 public int getTag() {
93 try {
94 return (Integer)m_getTag.invoke(m_item);
95 } catch (Exception ex) {
96 throw new Error(ex);
97 }
98 }
99
100 public ConstInfoAccessor copy() {
101 return new ConstInfoAccessor(copyItem());
102 }
103
104 public Object copyItem() {
105 // I don't know of a simpler way to copy one of these silly things...
106 try {
107 // serialize the item
108 ByteArrayOutputStream buf = new ByteArrayOutputStream();
109 DataOutputStream out = new DataOutputStream(buf);
110 write(out);
111
112 // deserialize the item
113 DataInputStream in = new DataInputStream(new ByteArrayInputStream(buf.toByteArray()));
114 Object item = new ConstInfoAccessor(in).getItem();
115 in.close();
116
117 return item;
118 } catch (Exception ex) {
119 throw new Error(ex);
120 }
121 }
122
123 public void write(DataOutputStream out) throws IOException {
124 try {
125 out.writeUTF(m_item.getClass().getName());
126 out.writeInt(getIndex());
127
128 Method method = m_item.getClass().getMethod("write", DataOutputStream.class);
129 method.setAccessible(true);
130 method.invoke(m_item, out);
131 } catch (IOException ex) {
132 throw ex;
133 } catch (Exception ex) {
134 throw new Error(ex);
135 }
136 }
137
138 @Override
139 public String toString() {
140 try {
141 ByteArrayOutputStream buf = new ByteArrayOutputStream();
142 PrintWriter out = new PrintWriter(buf);
143 Method print = m_item.getClass().getMethod("print", PrintWriter.class);
144 print.setAccessible(true);
145 print.invoke(m_item, out);
146 out.close();
147 return buf.toString().replace("\n", "");
148 } catch (Exception ex) {
149 throw new Error(ex);
150 }
151 }
152
153 public InfoType getType() {
154 return InfoType.getByTag(getTag());
155 }
156}
diff --git a/src/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java
new file mode 100644
index 00000000..82af0b99
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java
@@ -0,0 +1,74 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors;
12
13import java.lang.reflect.Field;
14
15public class InvokeDynamicInfoAccessor {
16
17 private static Class<?> m_class;
18 private static Field m_bootstrapIndex;
19 private static Field m_nameAndTypeIndex;
20
21 static {
22 try {
23 m_class = Class.forName("javassist.bytecode.InvokeDynamicInfo");
24 m_bootstrapIndex = m_class.getDeclaredField("bootstrap");
25 m_bootstrapIndex.setAccessible(true);
26 m_nameAndTypeIndex = m_class.getDeclaredField("nameAndType");
27 m_nameAndTypeIndex.setAccessible(true);
28 } catch (Exception ex) {
29 throw new Error(ex);
30 }
31 }
32
33 public static boolean isType(ConstInfoAccessor accessor) {
34 return m_class.isAssignableFrom(accessor.getItem().getClass());
35 }
36
37 private Object m_item;
38
39 public InvokeDynamicInfoAccessor(Object item) {
40 m_item = item;
41 }
42
43 public int getBootstrapIndex() {
44 try {
45 return (Integer)m_bootstrapIndex.get(m_item);
46 } catch (Exception ex) {
47 throw new Error(ex);
48 }
49 }
50
51 public void setBootstrapIndex(int val) {
52 try {
53 m_bootstrapIndex.set(m_item, val);
54 } catch (Exception ex) {
55 throw new Error(ex);
56 }
57 }
58
59 public int getNameAndTypeIndex() {
60 try {
61 return (Integer)m_nameAndTypeIndex.get(m_item);
62 } catch (Exception ex) {
63 throw new Error(ex);
64 }
65 }
66
67 public void setNameAndTypeIndex(int val) {
68 try {
69 m_nameAndTypeIndex.set(m_item, val);
70 } catch (Exception ex) {
71 throw new Error(ex);
72 }
73 }
74}
diff --git a/src/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java
new file mode 100644
index 00000000..71ee5b73
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java
@@ -0,0 +1,74 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors;
12
13import java.lang.reflect.Field;
14
15public class MemberRefInfoAccessor {
16
17 private static Class<?> m_class;
18 private static Field m_classIndex;
19 private static Field m_nameAndTypeIndex;
20
21 static {
22 try {
23 m_class = Class.forName("javassist.bytecode.MemberrefInfo");
24 m_classIndex = m_class.getDeclaredField("classIndex");
25 m_classIndex.setAccessible(true);
26 m_nameAndTypeIndex = m_class.getDeclaredField("nameAndTypeIndex");
27 m_nameAndTypeIndex.setAccessible(true);
28 } catch (Exception ex) {
29 throw new Error(ex);
30 }
31 }
32
33 public static boolean isType(ConstInfoAccessor accessor) {
34 return m_class.isAssignableFrom(accessor.getItem().getClass());
35 }
36
37 private Object m_item;
38
39 public MemberRefInfoAccessor(Object item) {
40 m_item = item;
41 }
42
43 public int getClassIndex() {
44 try {
45 return (Integer)m_classIndex.get(m_item);
46 } catch (Exception ex) {
47 throw new Error(ex);
48 }
49 }
50
51 public void setClassIndex(int val) {
52 try {
53 m_classIndex.set(m_item, val);
54 } catch (Exception ex) {
55 throw new Error(ex);
56 }
57 }
58
59 public int getNameAndTypeIndex() {
60 try {
61 return (Integer)m_nameAndTypeIndex.get(m_item);
62 } catch (Exception ex) {
63 throw new Error(ex);
64 }
65 }
66
67 public void setNameAndTypeIndex(int val) {
68 try {
69 m_nameAndTypeIndex.set(m_item, val);
70 } catch (Exception ex) {
71 throw new Error(ex);
72 }
73 }
74}
diff --git a/src/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java
new file mode 100644
index 00000000..172b0c51
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java
@@ -0,0 +1,74 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors;
12
13import java.lang.reflect.Field;
14
15public class MethodHandleInfoAccessor {
16
17 private static Class<?> m_class;
18 private static Field m_kindIndex;
19 private static Field m_indexIndex;
20
21 static {
22 try {
23 m_class = Class.forName("javassist.bytecode.MethodHandleInfo");
24 m_kindIndex = m_class.getDeclaredField("refKind");
25 m_kindIndex.setAccessible(true);
26 m_indexIndex = m_class.getDeclaredField("refIndex");
27 m_indexIndex.setAccessible(true);
28 } catch (Exception ex) {
29 throw new Error(ex);
30 }
31 }
32
33 public static boolean isType(ConstInfoAccessor accessor) {
34 return m_class.isAssignableFrom(accessor.getItem().getClass());
35 }
36
37 private Object m_item;
38
39 public MethodHandleInfoAccessor(Object item) {
40 m_item = item;
41 }
42
43 public int getTypeIndex() {
44 try {
45 return (Integer)m_kindIndex.get(m_item);
46 } catch (Exception ex) {
47 throw new Error(ex);
48 }
49 }
50
51 public void setTypeIndex(int val) {
52 try {
53 m_kindIndex.set(m_item, val);
54 } catch (Exception ex) {
55 throw new Error(ex);
56 }
57 }
58
59 public int getMethodRefIndex() {
60 try {
61 return (Integer)m_indexIndex.get(m_item);
62 } catch (Exception ex) {
63 throw new Error(ex);
64 }
65 }
66
67 public void setMethodRefIndex(int val) {
68 try {
69 m_indexIndex.set(m_item, val);
70 } catch (Exception ex) {
71 throw new Error(ex);
72 }
73 }
74}
diff --git a/src/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java
new file mode 100644
index 00000000..0099a843
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java
@@ -0,0 +1,55 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors;
12
13import java.lang.reflect.Field;
14
15public class MethodTypeInfoAccessor {
16
17 private static Class<?> m_class;
18 private static Field m_descriptorIndex;
19
20 static {
21 try {
22 m_class = Class.forName("javassist.bytecode.MethodTypeInfo");
23 m_descriptorIndex = m_class.getDeclaredField("descriptor");
24 m_descriptorIndex.setAccessible(true);
25 } catch (Exception ex) {
26 throw new Error(ex);
27 }
28 }
29
30 public static boolean isType(ConstInfoAccessor accessor) {
31 return m_class.isAssignableFrom(accessor.getItem().getClass());
32 }
33
34 private Object m_item;
35
36 public MethodTypeInfoAccessor(Object item) {
37 m_item = item;
38 }
39
40 public int getTypeIndex() {
41 try {
42 return (Integer)m_descriptorIndex.get(m_item);
43 } catch (Exception ex) {
44 throw new Error(ex);
45 }
46 }
47
48 public void setTypeIndex(int val) {
49 try {
50 m_descriptorIndex.set(m_item, val);
51 } catch (Exception ex) {
52 throw new Error(ex);
53 }
54 }
55}
diff --git a/src/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java
new file mode 100644
index 00000000..3ecc1297
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java
@@ -0,0 +1,74 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors;
12
13import java.lang.reflect.Field;
14
15public class NameAndTypeInfoAccessor {
16
17 private static Class<?> m_class;
18 private static Field m_nameIndex;
19 private static Field m_typeIndex;
20
21 static {
22 try {
23 m_class = Class.forName("javassist.bytecode.NameAndTypeInfo");
24 m_nameIndex = m_class.getDeclaredField("memberName");
25 m_nameIndex.setAccessible(true);
26 m_typeIndex = m_class.getDeclaredField("typeDescriptor");
27 m_typeIndex.setAccessible(true);
28 } catch (Exception ex) {
29 throw new Error(ex);
30 }
31 }
32
33 public static boolean isType(ConstInfoAccessor accessor) {
34 return m_class.isAssignableFrom(accessor.getItem().getClass());
35 }
36
37 private Object m_item;
38
39 public NameAndTypeInfoAccessor(Object item) {
40 m_item = item;
41 }
42
43 public int getNameIndex() {
44 try {
45 return (Integer)m_nameIndex.get(m_item);
46 } catch (Exception ex) {
47 throw new Error(ex);
48 }
49 }
50
51 public void setNameIndex(int val) {
52 try {
53 m_nameIndex.set(m_item, val);
54 } catch (Exception ex) {
55 throw new Error(ex);
56 }
57 }
58
59 public int getTypeIndex() {
60 try {
61 return (Integer)m_typeIndex.get(m_item);
62 } catch (Exception ex) {
63 throw new Error(ex);
64 }
65 }
66
67 public void setTypeIndex(int val) {
68 try {
69 m_typeIndex.set(m_item, val);
70 } catch (Exception ex) {
71 throw new Error(ex);
72 }
73 }
74}
diff --git a/src/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java
new file mode 100644
index 00000000..f150612e
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java
@@ -0,0 +1,55 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors;
12
13import java.lang.reflect.Field;
14
15public class StringInfoAccessor {
16
17 private static Class<?> m_class;
18 private static Field m_stringIndex;
19
20 static {
21 try {
22 m_class = Class.forName("javassist.bytecode.StringInfo");
23 m_stringIndex = m_class.getDeclaredField("string");
24 m_stringIndex.setAccessible(true);
25 } catch (Exception ex) {
26 throw new Error(ex);
27 }
28 }
29
30 public static boolean isType(ConstInfoAccessor accessor) {
31 return m_class.isAssignableFrom(accessor.getItem().getClass());
32 }
33
34 private Object m_item;
35
36 public StringInfoAccessor(Object item) {
37 m_item = item;
38 }
39
40 public int getStringIndex() {
41 try {
42 return (Integer)m_stringIndex.get(m_item);
43 } catch (Exception ex) {
44 throw new Error(ex);
45 }
46 }
47
48 public void setStringIndex(int val) {
49 try {
50 m_stringIndex.set(m_item, val);
51 } catch (Exception ex) {
52 throw new Error(ex);
53 }
54 }
55}
diff --git a/src/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java
new file mode 100644
index 00000000..38e8ff99
--- /dev/null
+++ b/src/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 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors;
12
13public class Utf8InfoAccessor {
14
15 private static Class<?> m_class;
16
17 static {
18 try {
19 m_class = Class.forName("javassist.bytecode.Utf8Info");
20 } catch (Exception ex) {
21 throw new Error(ex);
22 }
23 }
24
25 public static boolean isType(ConstInfoAccessor accessor) {
26 return m_class.isAssignableFrom(accessor.getItem().getClass());
27 }
28}
diff --git a/src/cuchaz/enigma/convert/ClassForest.java b/src/cuchaz/enigma/convert/ClassForest.java
new file mode 100644
index 00000000..0407730e
--- /dev/null
+++ b/src/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 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.util.Collection;
14
15import com.google.common.collect.HashMultimap;
16import com.google.common.collect.Multimap;
17
18import cuchaz.enigma.mapping.ClassEntry;
19
20
21public class ClassForest {
22
23 private ClassIdentifier m_identifier;
24 private Multimap<ClassIdentity,ClassEntry> m_forest;
25
26 public ClassForest(ClassIdentifier identifier) {
27 m_identifier = identifier;
28 m_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 m_forest.put(m_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 m_forest.keySet();
47 }
48
49 public Collection<ClassEntry> classes() {
50 return m_forest.values();
51 }
52
53 public Collection<ClassEntry> getClasses(ClassIdentity identity) {
54 return m_forest.get(identity);
55 }
56
57 public boolean containsIdentity(ClassIdentity identity) {
58 return m_forest.containsKey(identity);
59 }
60}
diff --git a/src/cuchaz/enigma/convert/ClassIdentifier.java b/src/cuchaz/enigma/convert/ClassIdentifier.java
new file mode 100644
index 00000000..ee5e9033
--- /dev/null
+++ b/src/cuchaz/enigma/convert/ClassIdentifier.java
@@ -0,0 +1,54 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.util.Map;
14import java.util.jar.JarFile;
15
16import com.google.common.collect.Maps;
17
18import javassist.CtClass;
19import cuchaz.enigma.TranslatingTypeLoader;
20import cuchaz.enigma.analysis.JarIndex;
21import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
22import cuchaz.enigma.mapping.ClassEntry;
23
24
25public class ClassIdentifier {
26
27 private JarIndex m_index;
28 private SidedClassNamer m_namer;
29 private boolean m_useReferences;
30 private TranslatingTypeLoader m_loader;
31 private Map<ClassEntry,ClassIdentity> m_cache;
32
33 public ClassIdentifier(JarFile jar, JarIndex index, SidedClassNamer namer, boolean useReferences) {
34 m_index = index;
35 m_namer = namer;
36 m_useReferences = useReferences;
37 m_loader = new TranslatingTypeLoader(jar, index);
38 m_cache = Maps.newHashMap();
39 }
40
41 public ClassIdentity identify(ClassEntry classEntry)
42 throws ClassNotFoundException {
43 ClassIdentity identity = m_cache.get(classEntry);
44 if (identity == null) {
45 CtClass c = m_loader.loadClass(classEntry.getName());
46 if (c == null) {
47 throw new ClassNotFoundException(classEntry.getName());
48 }
49 identity = new ClassIdentity(c, m_namer, m_index, m_useReferences);
50 m_cache.put(classEntry, identity);
51 }
52 return identity;
53 }
54}
diff --git a/src/cuchaz/enigma/convert/ClassIdentity.java b/src/cuchaz/enigma/convert/ClassIdentity.java
new file mode 100644
index 00000000..2e164ae7
--- /dev/null
+++ b/src/cuchaz/enigma/convert/ClassIdentity.java
@@ -0,0 +1,468 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.io.UnsupportedEncodingException;
14import java.security.MessageDigest;
15import java.security.NoSuchAlgorithmException;
16import java.util.Enumeration;
17import java.util.List;
18import java.util.Map;
19import java.util.Set;
20
21import javassist.CannotCompileException;
22import javassist.CtBehavior;
23import javassist.CtClass;
24import javassist.CtConstructor;
25import javassist.CtField;
26import javassist.CtMethod;
27import javassist.bytecode.BadBytecode;
28import javassist.bytecode.CodeIterator;
29import javassist.bytecode.ConstPool;
30import javassist.bytecode.Descriptor;
31import javassist.bytecode.Opcode;
32import javassist.expr.ConstructorCall;
33import javassist.expr.ExprEditor;
34import javassist.expr.FieldAccess;
35import javassist.expr.MethodCall;
36import javassist.expr.NewExpr;
37
38import com.google.common.collect.HashMultiset;
39import com.google.common.collect.Lists;
40import com.google.common.collect.Maps;
41import com.google.common.collect.Multiset;
42import com.google.common.collect.Sets;
43
44import cuchaz.enigma.Constants;
45import cuchaz.enigma.Util;
46import cuchaz.enigma.analysis.ClassImplementationsTreeNode;
47import cuchaz.enigma.analysis.EntryReference;
48import cuchaz.enigma.analysis.JarIndex;
49import cuchaz.enigma.bytecode.ConstPoolEditor;
50import cuchaz.enigma.bytecode.InfoType;
51import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor;
52import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
53import cuchaz.enigma.mapping.BehaviorEntry;
54import cuchaz.enigma.mapping.ClassEntry;
55import cuchaz.enigma.mapping.ClassNameReplacer;
56import cuchaz.enigma.mapping.Entry;
57import cuchaz.enigma.mapping.EntryFactory;
58import cuchaz.enigma.mapping.FieldEntry;
59import cuchaz.enigma.mapping.Signature;
60import cuchaz.enigma.mapping.Type;
61
62public class ClassIdentity {
63
64 private ClassEntry m_classEntry;
65 private SidedClassNamer m_namer;
66 private Multiset<String> m_fields;
67 private Multiset<String> m_methods;
68 private Multiset<String> m_constructors;
69 private String m_staticInitializer;
70 private String m_extends;
71 private Multiset<String> m_implements;
72 private Set<String> m_stringLiterals;
73 private Multiset<String> m_implementations;
74 private Multiset<String> m_references;
75 private String m_outer;
76
77 private final ClassNameReplacer m_classNameReplacer = new ClassNameReplacer() {
78
79 private Map<String,String> m_classNames = Maps.newHashMap();
80
81 @Override
82 public String replace(String className) {
83
84 // classes not in the none package can be passed through
85 ClassEntry classEntry = new ClassEntry(className);
86 if (!classEntry.getPackageName().equals(Constants.NonePackage)) {
87 return className;
88 }
89
90 // is this class ourself?
91 if (className.equals(m_classEntry.getName())) {
92 return "CSelf";
93 }
94
95 // try the namer
96 if (m_namer != null) {
97 String newName = m_namer.getName(className);
98 if (newName != null) {
99 return newName;
100 }
101 }
102
103 // otherwise, use local naming
104 if (!m_classNames.containsKey(className)) {
105 m_classNames.put(className, getNewClassName());
106 }
107 return m_classNames.get(className);
108 }
109
110 private String getNewClassName() {
111 return String.format("C%03d", m_classNames.size());
112 }
113 };
114
115 public ClassIdentity(CtClass c, SidedClassNamer namer, JarIndex index, boolean useReferences) {
116 m_namer = namer;
117
118 // stuff from the bytecode
119
120 m_classEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
121 m_fields = HashMultiset.create();
122 for (CtField field : c.getDeclaredFields()) {
123 m_fields.add(scrubType(field.getSignature()));
124 }
125 m_methods = HashMultiset.create();
126 for (CtMethod method : c.getDeclaredMethods()) {
127 m_methods.add(scrubSignature(method.getSignature()) + "0x" + getBehaviorSignature(method));
128 }
129 m_constructors = HashMultiset.create();
130 for (CtConstructor constructor : c.getDeclaredConstructors()) {
131 m_constructors.add(scrubSignature(constructor.getSignature()) + "0x" + getBehaviorSignature(constructor));
132 }
133 m_staticInitializer = "";
134 if (c.getClassInitializer() != null) {
135 m_staticInitializer = getBehaviorSignature(c.getClassInitializer());
136 }
137 m_extends = "";
138 if (c.getClassFile().getSuperclass() != null) {
139 m_extends = scrubClassName(Descriptor.toJvmName(c.getClassFile().getSuperclass()));
140 }
141 m_implements = HashMultiset.create();
142 for (String interfaceName : c.getClassFile().getInterfaces()) {
143 m_implements.add(scrubClassName(Descriptor.toJvmName(interfaceName)));
144 }
145
146 m_stringLiterals = Sets.newHashSet();
147 ConstPool constants = c.getClassFile().getConstPool();
148 for (int i=1; i<constants.getSize(); i++) {
149 if (constants.getTag(i) == ConstPool.CONST_String) {
150 m_stringLiterals.add(constants.getStringInfo(i));
151 }
152 }
153
154 // stuff from the jar index
155
156 m_implementations = HashMultiset.create();
157 ClassImplementationsTreeNode implementationsNode = index.getClassImplementations(null, m_classEntry);
158 if (implementationsNode != null) {
159 @SuppressWarnings("unchecked")
160 Enumeration<ClassImplementationsTreeNode> implementations = implementationsNode.children();
161 while (implementations.hasMoreElements()) {
162 ClassImplementationsTreeNode node = implementations.nextElement();
163 m_implementations.add(scrubClassName(node.getClassEntry().getName()));
164 }
165 }
166
167 m_references = HashMultiset.create();
168 if (useReferences) {
169 for (CtField field : c.getDeclaredFields()) {
170 FieldEntry fieldEntry = EntryFactory.getFieldEntry(field);
171 for (EntryReference<FieldEntry,BehaviorEntry> reference : index.getFieldReferences(fieldEntry)) {
172 addReference(reference);
173 }
174 }
175 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
176 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
177 for (EntryReference<BehaviorEntry,BehaviorEntry> reference : index.getBehaviorReferences(behaviorEntry)) {
178 addReference(reference);
179 }
180 }
181 }
182
183 m_outer = EntryFactory.getClassEntry(c).getOuterClassName();
184 }
185
186 private void addReference(EntryReference<? extends Entry,BehaviorEntry> reference) {
187 if (reference.context.getSignature() != null) {
188 m_references.add(String.format("%s_%s",
189 scrubClassName(reference.context.getClassName()),
190 scrubSignature(reference.context.getSignature())
191 ));
192 } else {
193 m_references.add(String.format("%s_<clinit>",
194 scrubClassName(reference.context.getClassName())
195 ));
196 }
197 }
198
199 public ClassEntry getClassEntry() {
200 return m_classEntry;
201 }
202
203 @Override
204 public String toString() {
205 StringBuilder buf = new StringBuilder();
206 buf.append("class: ");
207 buf.append(m_classEntry.getName());
208 buf.append(" ");
209 buf.append(hashCode());
210 buf.append("\n");
211 for (String field : m_fields) {
212 buf.append("\tfield ");
213 buf.append(field);
214 buf.append("\n");
215 }
216 for (String method : m_methods) {
217 buf.append("\tmethod ");
218 buf.append(method);
219 buf.append("\n");
220 }
221 for (String constructor : m_constructors) {
222 buf.append("\tconstructor ");
223 buf.append(constructor);
224 buf.append("\n");
225 }
226 if (m_staticInitializer.length() > 0) {
227 buf.append("\tinitializer ");
228 buf.append(m_staticInitializer);
229 buf.append("\n");
230 }
231 if (m_extends.length() > 0) {
232 buf.append("\textends ");
233 buf.append(m_extends);
234 buf.append("\n");
235 }
236 for (String interfaceName : m_implements) {
237 buf.append("\timplements ");
238 buf.append(interfaceName);
239 buf.append("\n");
240 }
241 for (String implementation : m_implementations) {
242 buf.append("\timplemented by ");
243 buf.append(implementation);
244 buf.append("\n");
245 }
246 for (String reference : m_references) {
247 buf.append("\treference ");
248 buf.append(reference);
249 buf.append("\n");
250 }
251 buf.append("\touter ");
252 buf.append(m_outer);
253 buf.append("\n");
254 return buf.toString();
255 }
256
257 private String scrubClassName(String className) {
258 return m_classNameReplacer.replace(className);
259 }
260
261 private String scrubType(String typeName) {
262 return scrubType(new Type(typeName)).toString();
263 }
264
265 private Type scrubType(Type type) {
266 if (type.hasClass()) {
267 return new Type(type, m_classNameReplacer);
268 } else {
269 return type;
270 }
271 }
272
273 private String scrubSignature(String signature) {
274 return scrubSignature(new Signature(signature)).toString();
275 }
276
277 private Signature scrubSignature(Signature signature) {
278 return new Signature(signature, m_classNameReplacer);
279 }
280
281 private boolean isClassMatchedUniquely(String className) {
282 return m_namer != null && m_namer.getName(Descriptor.toJvmName(className)) != null;
283 }
284
285 private String getBehaviorSignature(CtBehavior behavior) {
286 try {
287 // does this method have an implementation?
288 if (behavior.getMethodInfo().getCodeAttribute() == null) {
289 return "(none)";
290 }
291
292 // compute the hash from the opcodes
293 ConstPool constants = behavior.getMethodInfo().getConstPool();
294 final MessageDigest digest = MessageDigest.getInstance("MD5");
295 CodeIterator iter = behavior.getMethodInfo().getCodeAttribute().iterator();
296 while (iter.hasNext()) {
297 int pos = iter.next();
298
299 // update the hash with the opcode
300 int opcode = iter.byteAt(pos);
301 digest.update((byte)opcode);
302
303 switch (opcode) {
304 case Opcode.LDC: {
305 int constIndex = iter.byteAt(pos + 1);
306 updateHashWithConstant(digest, constants, constIndex);
307 }
308 break;
309
310 case Opcode.LDC_W:
311 case Opcode.LDC2_W: {
312 int constIndex = (iter.byteAt(pos + 1) << 8) | iter.byteAt(pos + 2);
313 updateHashWithConstant(digest, constants, constIndex);
314 }
315 break;
316 }
317 }
318
319 // update hash with method and field accesses
320 behavior.instrument(new ExprEditor() {
321 @Override
322 public void edit(MethodCall call) {
323 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName())));
324 updateHashWithString(digest, scrubSignature(call.getSignature()));
325 if (isClassMatchedUniquely(call.getClassName())) {
326 updateHashWithString(digest, call.getMethodName());
327 }
328 }
329
330 @Override
331 public void edit(FieldAccess access) {
332 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(access.getClassName())));
333 updateHashWithString(digest, scrubType(access.getSignature()));
334 if (isClassMatchedUniquely(access.getClassName())) {
335 updateHashWithString(digest, access.getFieldName());
336 }
337 }
338
339 @Override
340 public void edit(ConstructorCall call) {
341 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName())));
342 updateHashWithString(digest, scrubSignature(call.getSignature()));
343 }
344
345 @Override
346 public void edit(NewExpr expr) {
347 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(expr.getClassName())));
348 }
349 });
350
351 // convert the hash to a hex string
352 return toHex(digest.digest());
353 } catch (BadBytecode | NoSuchAlgorithmException | CannotCompileException ex) {
354 throw new Error(ex);
355 }
356 }
357
358 private void updateHashWithConstant(MessageDigest digest, ConstPool constants, int index) {
359 ConstPoolEditor editor = new ConstPoolEditor(constants);
360 ConstInfoAccessor item = editor.getItem(index);
361 if (item.getType() == InfoType.StringInfo) {
362 updateHashWithString(digest, constants.getStringInfo(index));
363 }
364 // TODO: other constants
365 }
366
367 private void updateHashWithString(MessageDigest digest, String val) {
368 try {
369 digest.update(val.getBytes("UTF8"));
370 } catch (UnsupportedEncodingException ex) {
371 throw new Error(ex);
372 }
373 }
374
375 private String toHex(byte[] bytes) {
376 // function taken from:
377 // http://stackoverflow.com/questions/9655181/convert-from-byte-array-to-hex-string-in-java
378 final char[] hexArray = "0123456789ABCDEF".toCharArray();
379 char[] hexChars = new char[bytes.length * 2];
380 for (int j = 0; j < bytes.length; j++) {
381 int v = bytes[j] & 0xFF;
382 hexChars[j * 2] = hexArray[v >>> 4];
383 hexChars[j * 2 + 1] = hexArray[v & 0x0F];
384 }
385 return new String(hexChars);
386 }
387
388 @Override
389 public boolean equals(Object other) {
390 if (other instanceof ClassIdentity) {
391 return equals((ClassIdentity)other);
392 }
393 return false;
394 }
395
396 public boolean equals(ClassIdentity other) {
397 return m_fields.equals(other.m_fields)
398 && m_methods.equals(other.m_methods)
399 && m_constructors.equals(other.m_constructors)
400 && m_staticInitializer.equals(other.m_staticInitializer)
401 && m_extends.equals(other.m_extends)
402 && m_implements.equals(other.m_implements)
403 && m_implementations.equals(other.m_implementations)
404 && m_references.equals(other.m_references);
405 }
406
407 @Override
408 public int hashCode() {
409 List<Object> objs = Lists.newArrayList();
410 objs.addAll(m_fields);
411 objs.addAll(m_methods);
412 objs.addAll(m_constructors);
413 objs.add(m_staticInitializer);
414 objs.add(m_extends);
415 objs.addAll(m_implements);
416 objs.addAll(m_implementations);
417 objs.addAll(m_references);
418 return Util.combineHashesOrdered(objs);
419 }
420
421 public int getMatchScore(ClassIdentity other) {
422 return 2*getNumMatches(m_extends, other.m_extends)
423 + 2*getNumMatches(m_outer, other.m_outer)
424 + 2*getNumMatches(m_implements, other.m_implements)
425 + getNumMatches(m_stringLiterals, other.m_stringLiterals)
426 + getNumMatches(m_fields, other.m_fields)
427 + getNumMatches(m_methods, other.m_methods)
428 + getNumMatches(m_constructors, other.m_constructors);
429 }
430
431 public int getMaxMatchScore() {
432 return 2 + 2 + 2*m_implements.size() + m_stringLiterals.size() + m_fields.size() + m_methods.size() + m_constructors.size();
433 }
434
435 public boolean matches(CtClass c) {
436 // just compare declaration counts
437 return m_fields.size() == c.getDeclaredFields().length
438 && m_methods.size() == c.getDeclaredMethods().length
439 && m_constructors.size() == c.getDeclaredConstructors().length;
440 }
441
442 private int getNumMatches(Set<String> a, Set<String> b) {
443 int numMatches = 0;
444 for (String val : a) {
445 if (b.contains(val)) {
446 numMatches++;
447 }
448 }
449 return numMatches;
450 }
451
452 private int getNumMatches(Multiset<String> a, Multiset<String> b) {
453 int numMatches = 0;
454 for (String val : a) {
455 if (b.contains(val)) {
456 numMatches++;
457 }
458 }
459 return numMatches;
460 }
461
462 private int getNumMatches(String a, String b) {
463 if (a.equals(b)) {
464 return 1;
465 }
466 return 0;
467 }
468}
diff --git a/src/cuchaz/enigma/convert/ClassMatch.java b/src/cuchaz/enigma/convert/ClassMatch.java
new file mode 100644
index 00000000..8c50a624
--- /dev/null
+++ b/src/cuchaz/enigma/convert/ClassMatch.java
@@ -0,0 +1,88 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.util.Collection;
14import java.util.Set;
15
16import com.google.common.collect.Sets;
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 if (other instanceof ClassMatch) {
79 return equals((ClassMatch)other);
80 }
81 return false;
82 }
83
84 public boolean equals(ClassMatch other) {
85 return this.sourceClasses.equals(other.sourceClasses)
86 && this.destClasses.equals(other.destClasses);
87 }
88}
diff --git a/src/cuchaz/enigma/convert/ClassMatches.java b/src/cuchaz/enigma/convert/ClassMatches.java
new file mode 100644
index 00000000..f70c1805
--- /dev/null
+++ b/src/cuchaz/enigma/convert/ClassMatches.java
@@ -0,0 +1,163 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.util.ArrayList;
14import java.util.Collection;
15import java.util.Iterator;
16import java.util.Map;
17import java.util.Set;
18
19import com.google.common.collect.BiMap;
20import com.google.common.collect.HashBiMap;
21import com.google.common.collect.Maps;
22import com.google.common.collect.Sets;
23
24import cuchaz.enigma.mapping.ClassEntry;
25
26
27public class ClassMatches implements Iterable<ClassMatch> {
28
29 Collection<ClassMatch> m_matches;
30 Map<ClassEntry,ClassMatch> m_matchesBySource;
31 Map<ClassEntry,ClassMatch> m_matchesByDest;
32 BiMap<ClassEntry,ClassEntry> m_uniqueMatches;
33 Map<ClassEntry,ClassMatch> m_ambiguousMatchesBySource;
34 Map<ClassEntry,ClassMatch> m_ambiguousMatchesByDest;
35 Set<ClassEntry> m_unmatchedSourceClasses;
36 Set<ClassEntry> m_unmatchedDestClasses;
37
38 public ClassMatches() {
39 this(new ArrayList<ClassMatch>());
40 }
41
42 public ClassMatches(Collection<ClassMatch> matches) {
43 m_matches = matches;
44 m_matchesBySource = Maps.newHashMap();
45 m_matchesByDest = Maps.newHashMap();
46 m_uniqueMatches = HashBiMap.create();
47 m_ambiguousMatchesBySource = Maps.newHashMap();
48 m_ambiguousMatchesByDest = Maps.newHashMap();
49 m_unmatchedSourceClasses = Sets.newHashSet();
50 m_unmatchedDestClasses = Sets.newHashSet();
51
52 for (ClassMatch match : matches) {
53 indexMatch(match);
54 }
55 }
56
57 public void add(ClassMatch match) {
58 m_matches.add(match);
59 indexMatch(match);
60 }
61
62 public void remove(ClassMatch match) {
63 for (ClassEntry sourceClass : match.sourceClasses) {
64 m_matchesBySource.remove(sourceClass);
65 m_uniqueMatches.remove(sourceClass);
66 m_ambiguousMatchesBySource.remove(sourceClass);
67 m_unmatchedSourceClasses.remove(sourceClass);
68 }
69 for (ClassEntry destClass : match.destClasses) {
70 m_matchesByDest.remove(destClass);
71 m_uniqueMatches.inverse().remove(destClass);
72 m_ambiguousMatchesByDest.remove(destClass);
73 m_unmatchedDestClasses.remove(destClass);
74 }
75 m_matches.remove(match);
76 }
77
78 public int size() {
79 return m_matches.size();
80 }
81
82 @Override
83 public Iterator<ClassMatch> iterator() {
84 return m_matches.iterator();
85 }
86
87 private void indexMatch(ClassMatch match) {
88 if (!match.isMatched()) {
89 // unmatched
90 m_unmatchedSourceClasses.addAll(match.sourceClasses);
91 m_unmatchedDestClasses.addAll(match.destClasses);
92 } else {
93 if (match.isAmbiguous()) {
94 // ambiguously matched
95 for (ClassEntry entry : match.sourceClasses) {
96 m_ambiguousMatchesBySource.put(entry, match);
97 }
98 for (ClassEntry entry : match.destClasses) {
99 m_ambiguousMatchesByDest.put(entry, match);
100 }
101 } else {
102 // uniquely matched
103 m_uniqueMatches.put(match.getUniqueSource(), match.getUniqueDest());
104 }
105 }
106 for (ClassEntry entry : match.sourceClasses) {
107 m_matchesBySource.put(entry, match);
108 }
109 for (ClassEntry entry : match.destClasses) {
110 m_matchesByDest.put(entry, match);
111 }
112 }
113
114 public BiMap<ClassEntry,ClassEntry> getUniqueMatches() {
115 return m_uniqueMatches;
116 }
117
118 public Set<ClassEntry> getUnmatchedSourceClasses() {
119 return m_unmatchedSourceClasses;
120 }
121
122 public Set<ClassEntry> getUnmatchedDestClasses() {
123 return m_unmatchedDestClasses;
124 }
125
126 public Set<ClassEntry> getAmbiguouslyMatchedSourceClasses() {
127 return m_ambiguousMatchesBySource.keySet();
128 }
129
130 public ClassMatch getAmbiguousMatchBySource(ClassEntry sourceClass) {
131 return m_ambiguousMatchesBySource.get(sourceClass);
132 }
133
134 public ClassMatch getMatchBySource(ClassEntry sourceClass) {
135 return m_matchesBySource.get(sourceClass);
136 }
137
138 public ClassMatch getMatchByDest(ClassEntry destClass) {
139 return m_matchesByDest.get(destClass);
140 }
141
142 public void removeSource(ClassEntry sourceClass) {
143 ClassMatch match = m_matchesBySource.get(sourceClass);
144 if (match != null) {
145 remove(match);
146 match.sourceClasses.remove(sourceClass);
147 if (!match.sourceClasses.isEmpty() || !match.destClasses.isEmpty()) {
148 add(match);
149 }
150 }
151 }
152
153 public void removeDest(ClassEntry destClass) {
154 ClassMatch match = m_matchesByDest.get(destClass);
155 if (match != null) {
156 remove(match);
157 match.destClasses.remove(destClass);
158 if (!match.sourceClasses.isEmpty() || !match.destClasses.isEmpty()) {
159 add(match);
160 }
161 }
162 }
163}
diff --git a/src/cuchaz/enigma/convert/ClassMatching.java b/src/cuchaz/enigma/convert/ClassMatching.java
new file mode 100644
index 00000000..633d1ac7
--- /dev/null
+++ b/src/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 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.util.ArrayList;
14import java.util.Collection;
15import java.util.List;
16import java.util.Map.Entry;
17import java.util.Set;
18
19import com.google.common.collect.BiMap;
20import com.google.common.collect.HashBiMap;
21import com.google.common.collect.Lists;
22import com.google.common.collect.Sets;
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<ClassEntry>(),
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/cuchaz/enigma/convert/ClassNamer.java b/src/cuchaz/enigma/convert/ClassNamer.java
new file mode 100644
index 00000000..e8fa7303
--- /dev/null
+++ b/src/cuchaz/enigma/convert/ClassNamer.java
@@ -0,0 +1,66 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.util.Map;
14
15import com.google.common.collect.BiMap;
16import com.google.common.collect.Maps;
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> m_sourceNames;
27 private Map<String,String> m_destNames;
28
29 public ClassNamer(BiMap<ClassEntry,ClassEntry> mappings) {
30 // convert the identity mappings to name maps
31 m_sourceNames = Maps.newHashMap();
32 m_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 m_sourceNames.put(entry.getKey().getName(), name);
37 m_destNames.put(entry.getValue().getName(), name);
38 }
39 }
40
41 public String getSourceName(String name) {
42 return m_sourceNames.get(name);
43 }
44
45 public String getDestName(String name) {
46 return m_destNames.get(name);
47 }
48
49 public SidedClassNamer getSourceNamer() {
50 return new SidedClassNamer() {
51 @Override
52 public String getName(String name) {
53 return getSourceName(name);
54 }
55 };
56 }
57
58 public SidedClassNamer getDestNamer() {
59 return new SidedClassNamer() {
60 @Override
61 public String getName(String name) {
62 return getDestName(name);
63 }
64 };
65 }
66}
diff --git a/src/cuchaz/enigma/convert/FieldMatches.java b/src/cuchaz/enigma/convert/FieldMatches.java
new file mode 100644
index 00000000..8439a84c
--- /dev/null
+++ b/src/cuchaz/enigma/convert/FieldMatches.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 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.util.Collection;
14import java.util.Set;
15
16import com.google.common.collect.BiMap;
17import com.google.common.collect.HashBiMap;
18import com.google.common.collect.HashMultimap;
19import com.google.common.collect.Multimap;
20import com.google.common.collect.Sets;
21
22import cuchaz.enigma.mapping.ClassEntry;
23import cuchaz.enigma.mapping.FieldEntry;
24
25
26public class FieldMatches {
27
28 private BiMap<FieldEntry,FieldEntry> m_matches;
29 private Multimap<ClassEntry,FieldEntry> m_matchedSourceFields;
30 private Multimap<ClassEntry,FieldEntry> m_unmatchedSourceFields;
31 private Multimap<ClassEntry,FieldEntry> m_unmatchedDestFields;
32 private Multimap<ClassEntry,FieldEntry> m_unmatchableSourceFields;
33
34 public FieldMatches() {
35 m_matches = HashBiMap.create();
36 m_matchedSourceFields = HashMultimap.create();
37 m_unmatchedSourceFields = HashMultimap.create();
38 m_unmatchedDestFields = HashMultimap.create();
39 m_unmatchableSourceFields = HashMultimap.create();
40 }
41
42 public void addMatch(FieldEntry srcField, FieldEntry destField) {
43 boolean wasAdded = m_matches.put(srcField, destField) == null;
44 assert (wasAdded);
45 wasAdded = m_matchedSourceFields.put(srcField.getClassEntry(), srcField);
46 assert (wasAdded);
47 }
48
49 public void addUnmatchedSourceField(FieldEntry fieldEntry) {
50 boolean wasAdded = m_unmatchedSourceFields.put(fieldEntry.getClassEntry(), fieldEntry);
51 assert (wasAdded);
52 }
53
54 public void addUnmatchedSourceFields(Iterable<FieldEntry> fieldEntries) {
55 for (FieldEntry fieldEntry : fieldEntries) {
56 addUnmatchedSourceField(fieldEntry);
57 }
58 }
59
60 public void addUnmatchedDestField(FieldEntry fieldEntry) {
61 boolean wasAdded = m_unmatchedDestFields.put(fieldEntry.getClassEntry(), fieldEntry);
62 assert (wasAdded);
63 }
64
65 public void addUnmatchedDestFields(Iterable<FieldEntry> fieldEntries) {
66 for (FieldEntry fieldEntry : fieldEntries) {
67 addUnmatchedDestField(fieldEntry);
68 }
69 }
70
71 public void addUnmatchableSourceField(FieldEntry sourceField) {
72 boolean wasAdded = m_unmatchableSourceFields.put(sourceField.getClassEntry(), sourceField);
73 assert (wasAdded);
74 }
75
76 public Set<ClassEntry> getSourceClassesWithUnmatchedFields() {
77 return m_unmatchedSourceFields.keySet();
78 }
79
80 public Collection<ClassEntry> getSourceClassesWithoutUnmatchedFields() {
81 Set<ClassEntry> out = Sets.newHashSet();
82 out.addAll(m_matchedSourceFields.keySet());
83 out.removeAll(m_unmatchedSourceFields.keySet());
84 return out;
85 }
86
87 public Collection<FieldEntry> getUnmatchedSourceFields() {
88 return m_unmatchedSourceFields.values();
89 }
90
91 public Collection<FieldEntry> getUnmatchedSourceFields(ClassEntry sourceClass) {
92 return m_unmatchedSourceFields.get(sourceClass);
93 }
94
95 public Collection<FieldEntry> getUnmatchedDestFields() {
96 return m_unmatchedDestFields.values();
97 }
98
99 public Collection<FieldEntry> getUnmatchedDestFields(ClassEntry destClass) {
100 return m_unmatchedDestFields.get(destClass);
101 }
102
103 public Collection<FieldEntry> getUnmatchableSourceFields() {
104 return m_unmatchableSourceFields.values();
105 }
106
107 public boolean hasSource(FieldEntry fieldEntry) {
108 return m_matches.containsKey(fieldEntry) || m_unmatchedSourceFields.containsValue(fieldEntry);
109 }
110
111 public boolean hasDest(FieldEntry fieldEntry) {
112 return m_matches.containsValue(fieldEntry) || m_unmatchedDestFields.containsValue(fieldEntry);
113 }
114
115 public BiMap<FieldEntry,FieldEntry> matches() {
116 return m_matches;
117 }
118
119 public boolean isMatchedSourceField(FieldEntry sourceField) {
120 return m_matches.containsKey(sourceField);
121 }
122
123 public boolean isMatchedDestField(FieldEntry destField) {
124 return m_matches.containsValue(destField);
125 }
126
127 public void makeMatch(FieldEntry sourceField, FieldEntry destField) {
128 boolean wasRemoved = m_unmatchedSourceFields.remove(sourceField.getClassEntry(), sourceField);
129 assert (wasRemoved);
130 wasRemoved = m_unmatchedDestFields.remove(destField.getClassEntry(), destField);
131 assert (wasRemoved);
132 addMatch(sourceField, destField);
133 }
134
135 public boolean isMatched(FieldEntry sourceField, FieldEntry destField) {
136 FieldEntry match = m_matches.get(sourceField);
137 return match != null && match.equals(destField);
138 }
139
140 public void unmakeMatch(FieldEntry sourceField, FieldEntry destField) {
141 boolean wasRemoved = m_matches.remove(sourceField) != null;
142 assert (wasRemoved);
143 wasRemoved = m_matchedSourceFields.remove(sourceField.getClassEntry(), sourceField);
144 assert (wasRemoved);
145 addUnmatchedSourceField(sourceField);
146 addUnmatchedDestField(destField);
147 }
148
149 public void makeSourceUnmatchable(FieldEntry sourceField) {
150 assert(!isMatchedSourceField(sourceField));
151 boolean wasRemoved = m_unmatchedSourceFields.remove(sourceField.getClassEntry(), sourceField);
152 assert (wasRemoved);
153 addUnmatchableSourceField(sourceField);
154 }
155}
diff --git a/src/cuchaz/enigma/convert/MappingsConverter.java b/src/cuchaz/enigma/convert/MappingsConverter.java
new file mode 100644
index 00000000..b457d6c4
--- /dev/null
+++ b/src/cuchaz/enigma/convert/MappingsConverter.java
@@ -0,0 +1,559 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.util.Arrays;
14import java.util.Collection;
15import java.util.Collections;
16import java.util.Iterator;
17import java.util.LinkedHashMap;
18import java.util.List;
19import java.util.Map;
20import java.util.Set;
21import java.util.jar.JarFile;
22
23import com.google.common.collect.BiMap;
24import com.google.common.collect.HashMultimap;
25import com.google.common.collect.Lists;
26import com.google.common.collect.Maps;
27import com.google.common.collect.Multimap;
28import com.google.common.collect.Sets;
29
30import cuchaz.enigma.Deobfuscator;
31import cuchaz.enigma.analysis.JarIndex;
32import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
33import cuchaz.enigma.mapping.BehaviorEntry;
34import cuchaz.enigma.mapping.ClassEntry;
35import cuchaz.enigma.mapping.ClassMapping;
36import cuchaz.enigma.mapping.ClassNameReplacer;
37import cuchaz.enigma.mapping.ConstructorEntry;
38import cuchaz.enigma.mapping.Entry;
39import cuchaz.enigma.mapping.FieldEntry;
40import cuchaz.enigma.mapping.FieldMapping;
41import cuchaz.enigma.mapping.Mappings;
42import cuchaz.enigma.mapping.MappingsChecker;
43import cuchaz.enigma.mapping.MemberMapping;
44import cuchaz.enigma.mapping.MethodEntry;
45import cuchaz.enigma.mapping.MethodMapping;
46import cuchaz.enigma.mapping.Signature;
47import cuchaz.enigma.mapping.Type;
48
49public class MappingsConverter {
50
51 public static ClassMatches computeClassMatches(JarFile sourceJar, JarFile destJar, Mappings mappings) {
52
53 // index jars
54 System.out.println("Indexing source jar...");
55 JarIndex sourceIndex = new JarIndex();
56 sourceIndex.indexJar(sourceJar, false);
57 System.out.println("Indexing dest jar...");
58 JarIndex destIndex = new JarIndex();
59 destIndex.indexJar(destJar, false);
60
61 // compute the matching
62 ClassMatching matching = computeMatching(sourceJar, sourceIndex, destJar, destIndex, null);
63 return new ClassMatches(matching.matches());
64 }
65
66 public static ClassMatching computeMatching(JarFile sourceJar, JarIndex sourceIndex, JarFile destJar, JarIndex destIndex, BiMap<ClassEntry,ClassEntry> knownMatches) {
67
68 System.out.println("Iteratively matching classes");
69
70 ClassMatching lastMatching = null;
71 int round = 0;
72 SidedClassNamer sourceNamer = null;
73 SidedClassNamer destNamer = null;
74 for (boolean useReferences : Arrays.asList(false, true)) {
75
76 int numUniqueMatchesLastTime = 0;
77 if (lastMatching != null) {
78 numUniqueMatchesLastTime = lastMatching.uniqueMatches().size();
79 }
80
81 while (true) {
82
83 System.out.println("Round " + (++round) + "...");
84
85 // init the matching with identity settings
86 ClassMatching matching = new ClassMatching(
87 new ClassIdentifier(sourceJar, sourceIndex, sourceNamer, useReferences),
88 new ClassIdentifier(destJar, destIndex, destNamer, useReferences)
89 );
90
91 if (knownMatches != null) {
92 matching.addKnownMatches(knownMatches);
93 }
94
95 if (lastMatching == null) {
96 // search all classes
97 matching.match(sourceIndex.getObfClassEntries(), destIndex.getObfClassEntries());
98 } else {
99 // we already know about these matches from last time
100 matching.addKnownMatches(lastMatching.uniqueMatches());
101
102 // search unmatched and ambiguously-matched classes
103 matching.match(lastMatching.unmatchedSourceClasses(), lastMatching.unmatchedDestClasses());
104 for (ClassMatch match : lastMatching.ambiguousMatches()) {
105 matching.match(match.sourceClasses, match.destClasses);
106 }
107 }
108 System.out.println(matching);
109 BiMap<ClassEntry,ClassEntry> uniqueMatches = matching.uniqueMatches();
110
111 // did we match anything new this time?
112 if (uniqueMatches.size() > numUniqueMatchesLastTime) {
113 numUniqueMatchesLastTime = uniqueMatches.size();
114 lastMatching = matching;
115 } else {
116 break;
117 }
118
119 // update the namers
120 ClassNamer namer = new ClassNamer(uniqueMatches);
121 sourceNamer = namer.getSourceNamer();
122 destNamer = namer.getDestNamer();
123 }
124 }
125
126 return lastMatching;
127 }
128
129 public static Mappings newMappings(ClassMatches matches, Mappings oldMappings, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
130
131 // sort the unique matches by size of inner class chain
132 Multimap<Integer,java.util.Map.Entry<ClassEntry,ClassEntry>> matchesByDestChainSize = HashMultimap.create();
133 for (java.util.Map.Entry<ClassEntry,ClassEntry> match : matches.getUniqueMatches().entrySet()) {
134 int chainSize = destDeobfuscator.getJarIndex().getObfClassChain(match.getValue()).size();
135 matchesByDestChainSize.put(chainSize, match);
136 }
137
138 // build the mappings (in order of small-to-large inner chains)
139 Mappings newMappings = new Mappings();
140 List<Integer> chainSizes = Lists.newArrayList(matchesByDestChainSize.keySet());
141 Collections.sort(chainSizes);
142 for (int chainSize : chainSizes) {
143 for (java.util.Map.Entry<ClassEntry,ClassEntry> match : matchesByDestChainSize.get(chainSize)) {
144
145 // get class info
146 ClassEntry obfSourceClassEntry = match.getKey();
147 ClassEntry obfDestClassEntry = match.getValue();
148 List<ClassEntry> destClassChain = destDeobfuscator.getJarIndex().getObfClassChain(obfDestClassEntry);
149
150 ClassMapping sourceMapping = sourceDeobfuscator.getMappings().getClassByObf(obfSourceClassEntry);
151 if (sourceMapping == null) {
152 // if this class was never deobfuscated, don't try to match it
153 continue;
154 }
155
156 // find out where to make the dest class mapping
157 if (destClassChain.size() == 1) {
158 // not an inner class, add directly to mappings
159 newMappings.addClassMapping(migrateClassMapping(obfDestClassEntry, sourceMapping, matches, false));
160 } else {
161 // inner class, find the outer class mapping
162 ClassMapping destMapping = null;
163 for (int i=0; i<destClassChain.size()-1; i++) {
164 ClassEntry destChainClassEntry = destClassChain.get(i);
165 if (destMapping == null) {
166 destMapping = newMappings.getClassByObf(destChainClassEntry);
167 if (destMapping == null) {
168 destMapping = new ClassMapping(destChainClassEntry.getName());
169 newMappings.addClassMapping(destMapping);
170 }
171 } else {
172 destMapping = destMapping.getInnerClassByObfSimple(destChainClassEntry.getInnermostClassName());
173 if (destMapping == null) {
174 destMapping = new ClassMapping(destChainClassEntry.getName());
175 destMapping.addInnerClassMapping(destMapping);
176 }
177 }
178 }
179 destMapping.addInnerClassMapping(migrateClassMapping(obfDestClassEntry, sourceMapping, matches, true));
180 }
181 }
182 }
183 return newMappings;
184 }
185
186 private static ClassMapping migrateClassMapping(ClassEntry newObfClass, ClassMapping oldClassMapping, final ClassMatches matches, boolean useSimpleName) {
187
188 ClassNameReplacer replacer = new ClassNameReplacer() {
189 @Override
190 public String replace(String className) {
191 ClassEntry newClassEntry = matches.getUniqueMatches().get(new ClassEntry(className));
192 if (newClassEntry != null) {
193 return newClassEntry.getName();
194 }
195 return null;
196 }
197 };
198
199 ClassMapping newClassMapping;
200 String deobfName = oldClassMapping.getDeobfName();
201 if (deobfName != null) {
202 if (useSimpleName) {
203 deobfName = new ClassEntry(deobfName).getSimpleName();
204 }
205 newClassMapping = new ClassMapping(newObfClass.getName(), deobfName);
206 } else {
207 newClassMapping = new ClassMapping(newObfClass.getName());
208 }
209
210 // copy fields
211 for (FieldMapping fieldMapping : oldClassMapping.fields()) {
212 newClassMapping.addFieldMapping(new FieldMapping(fieldMapping, replacer));
213 }
214
215 // copy methods
216 for (MethodMapping methodMapping : oldClassMapping.methods()) {
217 newClassMapping.addMethodMapping(new MethodMapping(methodMapping, replacer));
218 }
219
220 return newClassMapping;
221 }
222
223 public static void convertMappings(Mappings mappings, BiMap<ClassEntry,ClassEntry> changes) {
224
225 // sort the changes so classes are renamed in the correct order
226 // ie. if we have the mappings a->b, b->c, we have to apply b->c before a->b
227 LinkedHashMap<ClassEntry,ClassEntry> sortedChanges = Maps.newLinkedHashMap();
228 int numChangesLeft = changes.size();
229 while (!changes.isEmpty()) {
230 Iterator<Map.Entry<ClassEntry,ClassEntry>> iter = changes.entrySet().iterator();
231 while (iter.hasNext()) {
232 Map.Entry<ClassEntry,ClassEntry> change = iter.next();
233 if (changes.containsKey(change.getValue())) {
234 sortedChanges.put(change.getKey(), change.getValue());
235 iter.remove();
236 }
237 }
238
239 // did we remove any changes?
240 if (numChangesLeft - changes.size() > 0) {
241 // keep going
242 numChangesLeft = changes.size();
243 } else {
244 // can't sort anymore. There must be a loop
245 break;
246 }
247 }
248 if (!changes.isEmpty()) {
249 throw new Error("Unable to sort class changes! There must be a cycle.");
250 }
251
252 // convert the mappings in the correct class order
253 for (Map.Entry<ClassEntry,ClassEntry> entry : sortedChanges.entrySet()) {
254 mappings.renameObfClass(entry.getKey().getName(), entry.getValue().getName());
255 }
256 }
257
258 public static interface Doer<T extends Entry> {
259 Collection<T> getDroppedEntries(MappingsChecker checker);
260 Collection<T> getObfEntries(JarIndex jarIndex);
261 Collection<? extends MemberMapping<T>> getMappings(ClassMapping destClassMapping);
262 Set<T> filterEntries(Collection<T> obfEntries, T obfSourceEntry, ClassMatches classMatches);
263 void setUpdateObfMember(ClassMapping classMapping, MemberMapping<T> memberMapping, T newEntry);
264 boolean hasObfMember(ClassMapping classMapping, T obfEntry);
265 void removeMemberByObf(ClassMapping classMapping, T obfEntry);
266 }
267
268 public static Doer<FieldEntry> getFieldDoer() {
269 return new Doer<FieldEntry>() {
270
271 @Override
272 public Collection<FieldEntry> getDroppedEntries(MappingsChecker checker) {
273 return checker.getDroppedFieldMappings().keySet();
274 }
275
276 @Override
277 public Collection<FieldEntry> getObfEntries(JarIndex jarIndex) {
278 return jarIndex.getObfFieldEntries();
279 }
280
281 @Override
282 public Collection<? extends MemberMapping<FieldEntry>> getMappings(ClassMapping destClassMapping) {
283 return (Collection<? extends MemberMapping<FieldEntry>>)destClassMapping.fields();
284 }
285
286 @Override
287 public Set<FieldEntry> filterEntries(Collection<FieldEntry> obfDestFields, FieldEntry obfSourceField, ClassMatches classMatches) {
288 Set<FieldEntry> out = Sets.newHashSet();
289 for (FieldEntry obfDestField : obfDestFields) {
290 Type translatedDestType = translate(obfDestField.getType(), classMatches.getUniqueMatches().inverse());
291 if (translatedDestType.equals(obfSourceField.getType())) {
292 out.add(obfDestField);
293 }
294 }
295 return out;
296 }
297
298 @Override
299 public void setUpdateObfMember(ClassMapping classMapping, MemberMapping<FieldEntry> memberMapping, FieldEntry newField) {
300 FieldMapping fieldMapping = (FieldMapping)memberMapping;
301 classMapping.setFieldObfNameAndType(fieldMapping.getObfName(), fieldMapping.getObfType(), newField.getName(), newField.getType());
302 }
303
304 @Override
305 public boolean hasObfMember(ClassMapping classMapping, FieldEntry obfField) {
306 return classMapping.getFieldByObf(obfField.getName(), obfField.getType()) != null;
307 }
308
309 @Override
310 public void removeMemberByObf(ClassMapping classMapping, FieldEntry obfField) {
311 classMapping.removeFieldMapping(classMapping.getFieldByObf(obfField.getName(), obfField.getType()));
312 }
313 };
314 }
315
316 public static Doer<BehaviorEntry> getMethodDoer() {
317 return new Doer<BehaviorEntry>() {
318
319 @Override
320 public Collection<BehaviorEntry> getDroppedEntries(MappingsChecker checker) {
321 return checker.getDroppedMethodMappings().keySet();
322 }
323
324 @Override
325 public Collection<BehaviorEntry> getObfEntries(JarIndex jarIndex) {
326 return jarIndex.getObfBehaviorEntries();
327 }
328
329 @Override
330 public Collection<? extends MemberMapping<BehaviorEntry>> getMappings(ClassMapping destClassMapping) {
331 return (Collection<? extends MemberMapping<BehaviorEntry>>)destClassMapping.methods();
332 }
333
334 @Override
335 public Set<BehaviorEntry> filterEntries(Collection<BehaviorEntry> obfDestFields, BehaviorEntry obfSourceField, ClassMatches classMatches) {
336 Set<BehaviorEntry> out = Sets.newHashSet();
337 for (BehaviorEntry obfDestField : obfDestFields) {
338 Signature translatedDestSignature = translate(obfDestField.getSignature(), classMatches.getUniqueMatches().inverse());
339 if (translatedDestSignature == null && obfSourceField.getSignature() == null) {
340 out.add(obfDestField);
341 } else if (translatedDestSignature == null || obfSourceField.getSignature() == null) {
342 // skip it
343 } else if (translatedDestSignature.equals(obfSourceField.getSignature())) {
344 out.add(obfDestField);
345 }
346 }
347 return out;
348 }
349
350 @Override
351 public void setUpdateObfMember(ClassMapping classMapping, MemberMapping<BehaviorEntry> memberMapping, BehaviorEntry newBehavior) {
352 MethodMapping methodMapping = (MethodMapping)memberMapping;
353 classMapping.setMethodObfNameAndSignature(methodMapping.getObfName(), methodMapping.getObfSignature(), newBehavior.getName(), newBehavior.getSignature());
354 }
355
356 @Override
357 public boolean hasObfMember(ClassMapping classMapping, BehaviorEntry obfBehavior) {
358 return classMapping.getMethodByObf(obfBehavior.getName(), obfBehavior.getSignature()) != null;
359 }
360
361 @Override
362 public void removeMemberByObf(ClassMapping classMapping, BehaviorEntry obfBehavior) {
363 classMapping.removeMethodMapping(classMapping.getMethodByObf(obfBehavior.getName(), obfBehavior.getSignature()));
364 }
365 };
366 }
367
368 public static <T extends Entry> MemberMatches<T> computeMemberMatches(Deobfuscator destDeobfuscator, Mappings destMappings, ClassMatches classMatches, Doer<T> doer) {
369
370 MemberMatches<T> memberMatches = new MemberMatches<T>();
371
372 // unmatched source fields are easy
373 MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex());
374 checker.dropBrokenMappings(destMappings);
375 for (T destObfEntry : doer.getDroppedEntries(checker)) {
376 T srcObfEntry = translate(destObfEntry, classMatches.getUniqueMatches().inverse());
377 memberMatches.addUnmatchedSourceEntry(srcObfEntry);
378 }
379
380 // get matched fields (anything that's left after the checks/drops is matched(
381 for (ClassMapping classMapping : destMappings.classes()) {
382 collectMatchedFields(memberMatches, classMapping, classMatches, doer);
383 }
384
385 // get unmatched dest fields
386 for (T destEntry : doer.getObfEntries(destDeobfuscator.getJarIndex())) {
387 if (!memberMatches.isMatchedDestEntry(destEntry)) {
388 memberMatches.addUnmatchedDestEntry(destEntry);
389 }
390 }
391
392 System.out.println("Automatching " + memberMatches.getUnmatchedSourceEntries().size() + " unmatched source entries...");
393
394 // go through the unmatched source fields and try to pick out the easy matches
395 for (ClassEntry obfSourceClass : Lists.newArrayList(memberMatches.getSourceClassesWithUnmatchedEntries())) {
396 for (T obfSourceEntry : Lists.newArrayList(memberMatches.getUnmatchedSourceEntries(obfSourceClass))) {
397
398 // get the possible dest matches
399 ClassEntry obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass);
400
401 // filter by type/signature
402 Set<T> obfDestEntries = doer.filterEntries(memberMatches.getUnmatchedDestEntries(obfDestClass), obfSourceEntry, classMatches);
403
404 if (obfDestEntries.size() == 1) {
405 // make the easy match
406 memberMatches.makeMatch(obfSourceEntry, obfDestEntries.iterator().next());
407 } else if (obfDestEntries.isEmpty()) {
408 // no match is possible =(
409 memberMatches.makeSourceUnmatchable(obfSourceEntry);
410 }
411 }
412 }
413
414 System.out.println(String.format("Ended up with %d ambiguous and %d unmatchable source entries",
415 memberMatches.getUnmatchedSourceEntries().size(),
416 memberMatches.getUnmatchableSourceEntries().size()
417 ));
418
419 return memberMatches;
420 }
421
422 private static <T extends Entry> void collectMatchedFields(MemberMatches<T> memberMatches, ClassMapping destClassMapping, ClassMatches classMatches, Doer<T> doer) {
423
424 // get the fields for this class
425 for (MemberMapping<T> destEntryMapping : doer.getMappings(destClassMapping)) {
426 T destObfField = destEntryMapping.getObfEntry(destClassMapping.getObfEntry());
427 T srcObfField = translate(destObfField, classMatches.getUniqueMatches().inverse());
428 memberMatches.addMatch(srcObfField, destObfField);
429 }
430
431 // recurse
432 for (ClassMapping destInnerClassMapping : destClassMapping.innerClasses()) {
433 collectMatchedFields(memberMatches, destInnerClassMapping, classMatches, doer);
434 }
435 }
436
437 @SuppressWarnings("unchecked")
438 private static <T extends Entry> T translate(T in, BiMap<ClassEntry,ClassEntry> map) {
439 if (in instanceof FieldEntry) {
440 return (T)new FieldEntry(
441 map.get(in.getClassEntry()),
442 in.getName(),
443 translate(((FieldEntry)in).getType(), map)
444 );
445 } else if (in instanceof MethodEntry) {
446 return (T)new MethodEntry(
447 map.get(in.getClassEntry()),
448 in.getName(),
449 translate(((MethodEntry)in).getSignature(), map)
450 );
451 } else if (in instanceof ConstructorEntry) {
452 return (T)new ConstructorEntry(
453 map.get(in.getClassEntry()),
454 translate(((ConstructorEntry)in).getSignature(), map)
455 );
456 }
457 throw new Error("Unhandled entry type: " + in.getClass());
458 }
459
460 private static Type translate(Type type, final BiMap<ClassEntry,ClassEntry> map) {
461 return new Type(type, new ClassNameReplacer() {
462 @Override
463 public String replace(String inClassName) {
464 ClassEntry outClassEntry = map.get(new ClassEntry(inClassName));
465 if (outClassEntry == null) {
466 return null;
467 }
468 return outClassEntry.getName();
469 }
470 });
471 }
472
473 private static Signature translate(Signature signature, final BiMap<ClassEntry,ClassEntry> map) {
474 if (signature == null) {
475 return null;
476 }
477 return new Signature(signature, new ClassNameReplacer() {
478 @Override
479 public String replace(String inClassName) {
480 ClassEntry outClassEntry = map.get(new ClassEntry(inClassName));
481 if (outClassEntry == null) {
482 return null;
483 }
484 return outClassEntry.getName();
485 }
486 });
487 }
488
489 public static <T extends Entry> void applyMemberMatches(Mappings mappings, ClassMatches classMatches, MemberMatches<T> memberMatches, Doer<T> doer) {
490 for (ClassMapping classMapping : mappings.classes()) {
491 applyMemberMatches(classMapping, classMatches, memberMatches, doer);
492 }
493 }
494
495 private static <T extends Entry> void applyMemberMatches(ClassMapping classMapping, ClassMatches classMatches, MemberMatches<T> memberMatches, Doer<T> doer) {
496
497 // get the classes
498 ClassEntry obfDestClass = classMapping.getObfEntry();
499
500 // make a map of all the renames we need to make
501 Map<T,T> renames = Maps.newHashMap();
502 for (MemberMapping<T> memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) {
503 T obfOldDestEntry = memberMapping.getObfEntry(obfDestClass);
504 T obfSourceEntry = getSourceEntryFromDestMapping(memberMapping, obfDestClass, classMatches);
505
506 // but drop the unmatchable things
507 if (memberMatches.isUnmatchableSourceEntry(obfSourceEntry)) {
508 doer.removeMemberByObf(classMapping, obfOldDestEntry);
509 continue;
510 }
511
512 T obfNewDestEntry = memberMatches.matches().get(obfSourceEntry);
513 if (obfNewDestEntry != null && !obfOldDestEntry.getName().equals(obfNewDestEntry.getName())) {
514 renames.put(obfOldDestEntry, obfNewDestEntry);
515 }
516 }
517
518 if (!renames.isEmpty()) {
519
520 // apply to this class (should never need more than n passes)
521 int numRenamesAppliedThisRound;
522 do {
523 numRenamesAppliedThisRound = 0;
524
525 for (MemberMapping<T> memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) {
526 T obfOldDestEntry = memberMapping.getObfEntry(obfDestClass);
527 T obfNewDestEntry = renames.get(obfOldDestEntry);
528 if (obfNewDestEntry != null) {
529 // make sure this rename won't cause a collision
530 // otherwise, save it for the next round and try again next time
531 if (!doer.hasObfMember(classMapping, obfNewDestEntry)) {
532 doer.setUpdateObfMember(classMapping, memberMapping, obfNewDestEntry);
533 renames.remove(obfOldDestEntry);
534 numRenamesAppliedThisRound++;
535 }
536 }
537 }
538 } while(numRenamesAppliedThisRound > 0);
539
540 if (!renames.isEmpty()) {
541 System.err.println(String.format("WARNING: Couldn't apply all the renames for class %s. %d renames left.",
542 classMapping.getObfFullName(), renames.size()
543 ));
544 for (Map.Entry<T,T> entry : renames.entrySet()) {
545 System.err.println(String.format("\t%s -> %s", entry.getKey().getName(), entry.getValue().getName()));
546 }
547 }
548 }
549
550 // recurse
551 for (ClassMapping innerClassMapping : classMapping.innerClasses()) {
552 applyMemberMatches(innerClassMapping, classMatches, memberMatches, doer);
553 }
554 }
555
556 private static <T extends Entry> T getSourceEntryFromDestMapping(MemberMapping<T> destMemberMapping, ClassEntry obfDestClass, ClassMatches classMatches) {
557 return translate(destMemberMapping.getObfEntry(obfDestClass), classMatches.getUniqueMatches().inverse());
558 }
559}
diff --git a/src/cuchaz/enigma/convert/MatchesReader.java b/src/cuchaz/enigma/convert/MatchesReader.java
new file mode 100644
index 00000000..7514e2a9
--- /dev/null
+++ b/src/cuchaz/enigma/convert/MatchesReader.java
@@ -0,0 +1,113 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.io.BufferedReader;
14import java.io.File;
15import java.io.FileReader;
16import java.io.IOException;
17import java.util.Collection;
18import java.util.List;
19
20import com.google.common.collect.Lists;
21
22import cuchaz.enigma.mapping.ClassEntry;
23import cuchaz.enigma.mapping.Entry;
24import cuchaz.enigma.mapping.EntryFactory;
25import cuchaz.enigma.mapping.FieldEntry;
26import cuchaz.enigma.mapping.Type;
27
28
29public class MatchesReader {
30
31 public static ClassMatches readClasses(File file)
32 throws IOException {
33 try (BufferedReader in = new BufferedReader(new FileReader(file))) {
34 ClassMatches matches = new ClassMatches();
35 String line = null;
36 while ((line = in.readLine()) != null) {
37 matches.add(readClassMatch(line));
38 }
39 return matches;
40 }
41 }
42
43 private static ClassMatch readClassMatch(String line)
44 throws IOException {
45 String[] sides = line.split(":", 2);
46 return new ClassMatch(readClasses(sides[0]), readClasses(sides[1]));
47 }
48
49 private static Collection<ClassEntry> readClasses(String in) {
50 List<ClassEntry> entries = Lists.newArrayList();
51 for (String className : in.split(",")) {
52 className = className.trim();
53 if (className.length() > 0) {
54 entries.add(new ClassEntry(className));
55 }
56 }
57 return entries;
58 }
59
60 public static <T extends Entry> MemberMatches<T> readMembers(File file)
61 throws IOException {
62 try (BufferedReader in = new BufferedReader(new FileReader(file))) {
63 MemberMatches<T> matches = new MemberMatches<T>();
64 String line = null;
65 while ((line = in.readLine()) != null) {
66 readMemberMatch(matches, line);
67 }
68 return matches;
69 }
70 }
71
72 private static <T extends Entry> void readMemberMatch(MemberMatches<T> matches, String line) {
73 if (line.startsWith("!")) {
74 T source = readEntry(line.substring(1));
75 matches.addUnmatchableSourceEntry(source);
76 } else {
77 String[] parts = line.split(":", 2);
78 T source = readEntry(parts[0]);
79 T dest = readEntry(parts[1]);
80 if (source != null && dest != null) {
81 matches.addMatch(source, dest);
82 } else if (source != null) {
83 matches.addUnmatchedSourceEntry(source);
84 } else if (dest != null) {
85 matches.addUnmatchedDestEntry(dest);
86 }
87 }
88 }
89
90 @SuppressWarnings("unchecked")
91 private static <T extends Entry> T readEntry(String in) {
92 if (in.length() <= 0) {
93 return null;
94 }
95 String[] parts = in.split(" ");
96 if (parts.length == 3 && parts[2].indexOf('(') < 0) {
97 return (T)new FieldEntry(
98 new ClassEntry(parts[0]),
99 parts[1],
100 new Type(parts[2])
101 );
102 } else {
103 assert(parts.length == 2 || parts.length == 3);
104 if (parts.length == 2) {
105 return (T)EntryFactory.getBehaviorEntry(parts[0], parts[1]);
106 } else if (parts.length == 3) {
107 return (T)EntryFactory.getBehaviorEntry(parts[0], parts[1], parts[2]);
108 } else {
109 throw new Error("Malformed behavior entry: " + in);
110 }
111 }
112 }
113}
diff --git a/src/cuchaz/enigma/convert/MatchesWriter.java b/src/cuchaz/enigma/convert/MatchesWriter.java
new file mode 100644
index 00000000..42c6b61b
--- /dev/null
+++ b/src/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 *
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/cuchaz/enigma/convert/MemberMatches.java b/src/cuchaz/enigma/convert/MemberMatches.java
new file mode 100644
index 00000000..29def159
--- /dev/null
+++ b/src/cuchaz/enigma/convert/MemberMatches.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 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.util.Collection;
14import java.util.Set;
15
16import com.google.common.collect.BiMap;
17import com.google.common.collect.HashBiMap;
18import com.google.common.collect.HashMultimap;
19import com.google.common.collect.Multimap;
20import com.google.common.collect.Sets;
21
22import cuchaz.enigma.mapping.ClassEntry;
23import cuchaz.enigma.mapping.Entry;
24
25
26public class MemberMatches<T extends Entry> {
27
28 private BiMap<T,T> m_matches;
29 private Multimap<ClassEntry,T> m_matchedSourceEntries;
30 private Multimap<ClassEntry,T> m_unmatchedSourceEntries;
31 private Multimap<ClassEntry,T> m_unmatchedDestEntries;
32 private Multimap<ClassEntry,T> m_unmatchableSourceEntries;
33
34 public MemberMatches() {
35 m_matches = HashBiMap.create();
36 m_matchedSourceEntries = HashMultimap.create();
37 m_unmatchedSourceEntries = HashMultimap.create();
38 m_unmatchedDestEntries = HashMultimap.create();
39 m_unmatchableSourceEntries = HashMultimap.create();
40 }
41
42 public void addMatch(T srcEntry, T destEntry) {
43 boolean wasAdded = m_matches.put(srcEntry, destEntry) == null;
44 assert (wasAdded);
45 wasAdded = m_matchedSourceEntries.put(srcEntry.getClassEntry(), srcEntry);
46 assert (wasAdded);
47 }
48
49 public void addUnmatchedSourceEntry(T sourceEntry) {
50 boolean wasAdded = m_unmatchedSourceEntries.put(sourceEntry.getClassEntry(), sourceEntry);
51 assert (wasAdded);
52 }
53
54 public void addUnmatchedSourceEntries(Iterable<T> sourceEntries) {
55 for (T sourceEntry : sourceEntries) {
56 addUnmatchedSourceEntry(sourceEntry);
57 }
58 }
59
60 public void addUnmatchedDestEntry(T destEntry) {
61 boolean wasAdded = m_unmatchedDestEntries.put(destEntry.getClassEntry(), destEntry);
62 assert (wasAdded);
63 }
64
65 public void addUnmatchedDestEntries(Iterable<T> destEntriesntries) {
66 for (T entry : destEntriesntries) {
67 addUnmatchedDestEntry(entry);
68 }
69 }
70
71 public void addUnmatchableSourceEntry(T sourceEntry) {
72 boolean wasAdded = m_unmatchableSourceEntries.put(sourceEntry.getClassEntry(), sourceEntry);
73 assert (wasAdded);
74 }
75
76 public Set<ClassEntry> getSourceClassesWithUnmatchedEntries() {
77 return m_unmatchedSourceEntries.keySet();
78 }
79
80 public Collection<ClassEntry> getSourceClassesWithoutUnmatchedEntries() {
81 Set<ClassEntry> out = Sets.newHashSet();
82 out.addAll(m_matchedSourceEntries.keySet());
83 out.removeAll(m_unmatchedSourceEntries.keySet());
84 return out;
85 }
86
87 public Collection<T> getUnmatchedSourceEntries() {
88 return m_unmatchedSourceEntries.values();
89 }
90
91 public Collection<T> getUnmatchedSourceEntries(ClassEntry sourceClass) {
92 return m_unmatchedSourceEntries.get(sourceClass);
93 }
94
95 public Collection<T> getUnmatchedDestEntries() {
96 return m_unmatchedDestEntries.values();
97 }
98
99 public Collection<T> getUnmatchedDestEntries(ClassEntry destClass) {
100 return m_unmatchedDestEntries.get(destClass);
101 }
102
103 public Collection<T> getUnmatchableSourceEntries() {
104 return m_unmatchableSourceEntries.values();
105 }
106
107 public boolean hasSource(T sourceEntry) {
108 return m_matches.containsKey(sourceEntry) || m_unmatchedSourceEntries.containsValue(sourceEntry);
109 }
110
111 public boolean hasDest(T destEntry) {
112 return m_matches.containsValue(destEntry) || m_unmatchedDestEntries.containsValue(destEntry);
113 }
114
115 public BiMap<T,T> matches() {
116 return m_matches;
117 }
118
119 public boolean isMatchedSourceEntry(T sourceEntry) {
120 return m_matches.containsKey(sourceEntry);
121 }
122
123 public boolean isMatchedDestEntry(T destEntry) {
124 return m_matches.containsValue(destEntry);
125 }
126
127 public boolean isUnmatchableSourceEntry(T sourceEntry) {
128 return m_unmatchableSourceEntries.containsEntry(sourceEntry.getClassEntry(), sourceEntry);
129 }
130
131 public void makeMatch(T sourceEntry, T destEntry) {
132 boolean wasRemoved = m_unmatchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry);
133 assert (wasRemoved);
134 wasRemoved = m_unmatchedDestEntries.remove(destEntry.getClassEntry(), destEntry);
135 assert (wasRemoved);
136 addMatch(sourceEntry, destEntry);
137 }
138
139 public boolean isMatched(T sourceEntry, T destEntry) {
140 T match = m_matches.get(sourceEntry);
141 return match != null && match.equals(destEntry);
142 }
143
144 public void unmakeMatch(T sourceEntry, T destEntry) {
145 boolean wasRemoved = m_matches.remove(sourceEntry) != null;
146 assert (wasRemoved);
147 wasRemoved = m_matchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry);
148 assert (wasRemoved);
149 addUnmatchedSourceEntry(sourceEntry);
150 addUnmatchedDestEntry(destEntry);
151 }
152
153 public void makeSourceUnmatchable(T sourceEntry) {
154 assert(!isMatchedSourceEntry(sourceEntry));
155 boolean wasRemoved = m_unmatchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry);
156 assert (wasRemoved);
157 addUnmatchableSourceEntry(sourceEntry);
158 }
159}
diff --git a/src/cuchaz/enigma/gui/AboutDialog.java b/src/cuchaz/enigma/gui/AboutDialog.java
new file mode 100644
index 00000000..3eba1e50
--- /dev/null
+++ b/src/cuchaz/enigma/gui/AboutDialog.java
@@ -0,0 +1,86 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Color;
14import java.awt.Container;
15import java.awt.Cursor;
16import java.awt.FlowLayout;
17import java.awt.event.ActionEvent;
18import java.awt.event.ActionListener;
19import java.io.IOException;
20
21import javax.swing.JButton;
22import javax.swing.JFrame;
23import javax.swing.JLabel;
24import javax.swing.JPanel;
25import javax.swing.WindowConstants;
26
27import cuchaz.enigma.Constants;
28import cuchaz.enigma.Util;
29
30public class AboutDialog {
31
32 public static void show(JFrame parent) {
33 // init frame
34 final JFrame frame = new JFrame(Constants.Name + " - About");
35 final Container pane = frame.getContentPane();
36 pane.setLayout(new FlowLayout());
37
38 // load the content
39 try {
40 String html = Util.readResourceToString("/about.html");
41 html = String.format(html, Constants.Name, Constants.Version);
42 JLabel label = new JLabel(html);
43 label.setHorizontalAlignment(JLabel.CENTER);
44 pane.add(label);
45 } catch (IOException ex) {
46 throw new Error(ex);
47 }
48
49 // show the link
50 String html = "<html><a href=\"%s\">%s</a></html>";
51 html = String.format(html, Constants.Url, Constants.Url);
52 JButton link = new JButton(html);
53 link.addActionListener(new ActionListener() {
54 @Override
55 public void actionPerformed(ActionEvent event) {
56 Util.openUrl(Constants.Url);
57 }
58 });
59 link.setBorderPainted(false);
60 link.setOpaque(false);
61 link.setBackground(Color.WHITE);
62 link.setCursor(new Cursor(Cursor.HAND_CURSOR));
63 link.setFocusable(false);
64 JPanel linkPanel = new JPanel();
65 linkPanel.add(link);
66 pane.add(linkPanel);
67
68 // show ok button
69 JButton okButton = new JButton("Ok");
70 pane.add(okButton);
71 okButton.addActionListener(new ActionListener() {
72 @Override
73 public void actionPerformed(ActionEvent arg0) {
74 frame.dispose();
75 }
76 });
77
78 // show the frame
79 pane.doLayout();
80 frame.setSize(400, 220);
81 frame.setResizable(false);
82 frame.setLocationRelativeTo(parent);
83 frame.setVisible(true);
84 frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
85 }
86}
diff --git a/src/cuchaz/enigma/gui/BoxHighlightPainter.java b/src/cuchaz/enigma/gui/BoxHighlightPainter.java
new file mode 100644
index 00000000..e5e05571
--- /dev/null
+++ b/src/cuchaz/enigma/gui/BoxHighlightPainter.java
@@ -0,0 +1,64 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Color;
14import java.awt.Graphics;
15import java.awt.Rectangle;
16import java.awt.Shape;
17
18import javax.swing.text.BadLocationException;
19import javax.swing.text.Highlighter;
20import javax.swing.text.JTextComponent;
21
22public abstract class BoxHighlightPainter implements Highlighter.HighlightPainter {
23
24 private Color m_fillColor;
25 private Color m_borderColor;
26
27 protected BoxHighlightPainter(Color fillColor, Color borderColor) {
28 m_fillColor = fillColor;
29 m_borderColor = borderColor;
30 }
31
32 @Override
33 public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) {
34 Rectangle bounds = getBounds(text, start, end);
35
36 // fill the area
37 if (m_fillColor != null) {
38 g.setColor(m_fillColor);
39 g.fillRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4);
40 }
41
42 // draw a box around the area
43 g.setColor(m_borderColor);
44 g.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4);
45 }
46
47 protected static Rectangle getBounds(JTextComponent text, int start, int end) {
48 try {
49 // determine the bounds of the text
50 Rectangle bounds = text.modelToView(start).union(text.modelToView(end));
51
52 // adjust the box so it looks nice
53 bounds.x -= 2;
54 bounds.width += 2;
55 bounds.y += 1;
56 bounds.height -= 2;
57
58 return bounds;
59 } catch (BadLocationException ex) {
60 // don't care... just return something
61 return new Rectangle(0, 0, 0, 0);
62 }
63 }
64}
diff --git a/src/cuchaz/enigma/gui/BrowserCaret.java b/src/cuchaz/enigma/gui/BrowserCaret.java
new file mode 100644
index 00000000..6af4d248
--- /dev/null
+++ b/src/cuchaz/enigma/gui/BrowserCaret.java
@@ -0,0 +1,45 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Graphics;
14import java.awt.Shape;
15
16import javax.swing.text.DefaultCaret;
17import javax.swing.text.Highlighter;
18import javax.swing.text.JTextComponent;
19
20public class BrowserCaret extends DefaultCaret {
21
22 private static final long serialVersionUID = 1158977422507969940L;
23
24 private static final Highlighter.HighlightPainter m_selectionPainter = new Highlighter.HighlightPainter() {
25 @Override
26 public void paint(Graphics g, int p0, int p1, Shape bounds, JTextComponent c) {
27 // don't paint anything
28 }
29 };
30
31 @Override
32 public boolean isSelectionVisible() {
33 return false;
34 }
35
36 @Override
37 public boolean isVisible() {
38 return true;
39 }
40
41 @Override
42 public Highlighter.HighlightPainter getSelectionPainter() {
43 return m_selectionPainter;
44 }
45}
diff --git a/src/cuchaz/enigma/gui/ClassListCellRenderer.java b/src/cuchaz/enigma/gui/ClassListCellRenderer.java
new file mode 100644
index 00000000..cde3e4ca
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ClassListCellRenderer.java
@@ -0,0 +1,36 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Component;
14
15import javassist.bytecode.Descriptor;
16
17import javax.swing.DefaultListCellRenderer;
18import javax.swing.JLabel;
19import javax.swing.JList;
20import javax.swing.ListCellRenderer;
21
22public class ClassListCellRenderer implements ListCellRenderer<String> {
23
24 private DefaultListCellRenderer m_defaultRenderer;
25
26 public ClassListCellRenderer() {
27 m_defaultRenderer = new DefaultListCellRenderer();
28 }
29
30 @Override
31 public Component getListCellRendererComponent(JList<? extends String> list, String className, int index, boolean isSelected, boolean hasFocus) {
32 JLabel label = (JLabel)m_defaultRenderer.getListCellRendererComponent(list, className, index, isSelected, hasFocus);
33 label.setText(Descriptor.toJavaName(className));
34 return label;
35 }
36}
diff --git a/src/cuchaz/enigma/gui/ClassMatchingGui.java b/src/cuchaz/enigma/gui/ClassMatchingGui.java
new file mode 100644
index 00000000..89b19c3a
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ClassMatchingGui.java
@@ -0,0 +1,589 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.BorderLayout;
14import java.awt.Container;
15import java.awt.Dimension;
16import java.awt.FlowLayout;
17import java.awt.event.ActionEvent;
18import java.awt.event.ActionListener;
19import java.util.Collection;
20import java.util.List;
21import java.util.Map;
22
23import javax.swing.BoxLayout;
24import javax.swing.ButtonGroup;
25import javax.swing.JButton;
26import javax.swing.JCheckBox;
27import javax.swing.JFrame;
28import javax.swing.JLabel;
29import javax.swing.JPanel;
30import javax.swing.JRadioButton;
31import javax.swing.JScrollPane;
32import javax.swing.JSplitPane;
33import javax.swing.SwingConstants;
34import javax.swing.WindowConstants;
35
36import com.google.common.collect.BiMap;
37import com.google.common.collect.Lists;
38import com.google.common.collect.Maps;
39
40import cuchaz.enigma.Constants;
41import cuchaz.enigma.Deobfuscator;
42import cuchaz.enigma.convert.ClassIdentifier;
43import cuchaz.enigma.convert.ClassIdentity;
44import cuchaz.enigma.convert.ClassMatch;
45import cuchaz.enigma.convert.ClassMatches;
46import cuchaz.enigma.convert.ClassMatching;
47import cuchaz.enigma.convert.ClassNamer;
48import cuchaz.enigma.convert.MappingsConverter;
49import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener;
50import cuchaz.enigma.mapping.ClassEntry;
51import cuchaz.enigma.mapping.Mappings;
52import cuchaz.enigma.mapping.MappingsChecker;
53import de.sciss.syntaxpane.DefaultSyntaxKit;
54
55
56public class ClassMatchingGui {
57
58 private static enum SourceType {
59 Matched {
60
61 @Override
62 public Collection<ClassEntry> getSourceClasses(ClassMatches matches) {
63 return matches.getUniqueMatches().keySet();
64 }
65 },
66 Unmatched {
67
68 @Override
69 public Collection<ClassEntry> getSourceClasses(ClassMatches matches) {
70 return matches.getUnmatchedSourceClasses();
71 }
72 },
73 Ambiguous {
74
75 @Override
76 public Collection<ClassEntry> getSourceClasses(ClassMatches matches) {
77 return matches.getAmbiguouslyMatchedSourceClasses();
78 }
79 };
80
81 public JRadioButton newRadio(ActionListener listener, ButtonGroup group) {
82 JRadioButton button = new JRadioButton(name(), this == getDefault());
83 button.setActionCommand(name());
84 button.addActionListener(listener);
85 group.add(button);
86 return button;
87 }
88
89 public abstract Collection<ClassEntry> getSourceClasses(ClassMatches matches);
90
91 public static SourceType getDefault() {
92 return values()[0];
93 }
94 }
95
96 public static interface SaveListener {
97 public void save(ClassMatches matches);
98 }
99
100 // controls
101 private JFrame m_frame;
102 private ClassSelector m_sourceClasses;
103 private ClassSelector m_destClasses;
104 private CodeReader m_sourceReader;
105 private CodeReader m_destReader;
106 private JLabel m_sourceClassLabel;
107 private JLabel m_destClassLabel;
108 private JButton m_matchButton;
109 private Map<SourceType,JRadioButton> m_sourceTypeButtons;
110 private JCheckBox m_advanceCheck;
111
112 private ClassMatches m_classMatches;
113 private Deobfuscator m_sourceDeobfuscator;
114 private Deobfuscator m_destDeobfuscator;
115 private ClassEntry m_sourceClass;
116 private ClassEntry m_destClass;
117 private SourceType m_sourceType;
118 private SaveListener m_saveListener;
119
120 public ClassMatchingGui(ClassMatches matches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
121
122 m_classMatches = matches;
123 m_sourceDeobfuscator = sourceDeobfuscator;
124 m_destDeobfuscator = destDeobfuscator;
125
126 // init frame
127 m_frame = new JFrame(Constants.Name + " - Class Matcher");
128 final Container pane = m_frame.getContentPane();
129 pane.setLayout(new BorderLayout());
130
131 // init source side
132 JPanel sourcePanel = new JPanel();
133 sourcePanel.setLayout(new BoxLayout(sourcePanel, BoxLayout.PAGE_AXIS));
134 sourcePanel.setPreferredSize(new Dimension(200, 0));
135 pane.add(sourcePanel, BorderLayout.WEST);
136 sourcePanel.add(new JLabel("Source Classes"));
137
138 // init source type radios
139 JPanel sourceTypePanel = new JPanel();
140 sourcePanel.add(sourceTypePanel);
141 sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS));
142 ActionListener sourceTypeListener = new ActionListener() {
143 @Override
144 public void actionPerformed(ActionEvent event) {
145 setSourceType(SourceType.valueOf(event.getActionCommand()));
146 }
147 };
148 ButtonGroup sourceTypeButtons = new ButtonGroup();
149 m_sourceTypeButtons = Maps.newHashMap();
150 for (SourceType sourceType : SourceType.values()) {
151 JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons);
152 m_sourceTypeButtons.put(sourceType, button);
153 sourceTypePanel.add(button);
154 }
155
156 m_sourceClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator);
157 m_sourceClasses.setListener(new ClassSelectionListener() {
158 @Override
159 public void onSelectClass(ClassEntry classEntry) {
160 setSourceClass(classEntry);
161 }
162 });
163 JScrollPane sourceScroller = new JScrollPane(m_sourceClasses);
164 sourcePanel.add(sourceScroller);
165
166 // init dest side
167 JPanel destPanel = new JPanel();
168 destPanel.setLayout(new BoxLayout(destPanel, BoxLayout.PAGE_AXIS));
169 destPanel.setPreferredSize(new Dimension(200, 0));
170 pane.add(destPanel, BorderLayout.WEST);
171 destPanel.add(new JLabel("Destination Classes"));
172
173 m_destClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator);
174 m_destClasses.setListener(new ClassSelectionListener() {
175 @Override
176 public void onSelectClass(ClassEntry classEntry) {
177 setDestClass(classEntry);
178 }
179 });
180 JScrollPane destScroller = new JScrollPane(m_destClasses);
181 destPanel.add(destScroller);
182
183 JButton autoMatchButton = new JButton("AutoMatch");
184 autoMatchButton.addActionListener(new ActionListener() {
185 @Override
186 public void actionPerformed(ActionEvent event) {
187 autoMatch();
188 }
189 });
190 destPanel.add(autoMatchButton);
191
192 // init source panels
193 DefaultSyntaxKit.initKit();
194 m_sourceReader = new CodeReader();
195 m_destReader = new CodeReader();
196
197 // init all the splits
198 JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, sourcePanel, new JScrollPane(m_sourceReader));
199 splitLeft.setResizeWeight(0); // let the right side take all the slack
200 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(m_destReader), destPanel);
201 splitRight.setResizeWeight(1); // let the left side take all the slack
202 JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, splitLeft, splitRight);
203 splitCenter.setResizeWeight(0.5); // resize 50:50
204 pane.add(splitCenter, BorderLayout.CENTER);
205 splitCenter.resetToPreferredSizes();
206
207 // init bottom panel
208 JPanel bottomPanel = new JPanel();
209 bottomPanel.setLayout(new FlowLayout());
210
211 m_sourceClassLabel = new JLabel();
212 m_sourceClassLabel.setHorizontalAlignment(SwingConstants.RIGHT);
213 m_destClassLabel = new JLabel();
214 m_destClassLabel.setHorizontalAlignment(SwingConstants.LEFT);
215
216 m_matchButton = new JButton();
217
218 m_advanceCheck = new JCheckBox("Advance to next likely match");
219 m_advanceCheck.addActionListener(new ActionListener() {
220 @Override
221 public void actionPerformed(ActionEvent event) {
222 if (m_advanceCheck.isSelected()) {
223 advance();
224 }
225 }
226 });
227
228 bottomPanel.add(m_sourceClassLabel);
229 bottomPanel.add(m_matchButton);
230 bottomPanel.add(m_destClassLabel);
231 bottomPanel.add(m_advanceCheck);
232 pane.add(bottomPanel, BorderLayout.SOUTH);
233
234 // show the frame
235 pane.doLayout();
236 m_frame.setSize(1024, 576);
237 m_frame.setMinimumSize(new Dimension(640, 480));
238 m_frame.setVisible(true);
239 m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
240
241 // init state
242 updateDestMappings();
243 setSourceType(SourceType.getDefault());
244 updateMatchButton();
245 m_saveListener = null;
246 }
247
248 public void setSaveListener(SaveListener val) {
249 m_saveListener = val;
250 }
251
252 private void updateDestMappings() {
253
254 Mappings newMappings = MappingsConverter.newMappings(
255 m_classMatches,
256 m_sourceDeobfuscator.getMappings(),
257 m_sourceDeobfuscator,
258 m_destDeobfuscator
259 );
260
261 // look for dropped mappings
262 MappingsChecker checker = new MappingsChecker(m_destDeobfuscator.getJarIndex());
263 checker.dropBrokenMappings(newMappings);
264
265 // count them
266 int numDroppedFields = checker.getDroppedFieldMappings().size();
267 int numDroppedMethods = checker.getDroppedMethodMappings().size();
268 System.out.println(String.format(
269 "%d mappings from matched classes don't match the dest jar:\n\t%5d fields\n\t%5d methods",
270 numDroppedFields + numDroppedMethods,
271 numDroppedFields,
272 numDroppedMethods
273 ));
274
275 m_destDeobfuscator.setMappings(newMappings);
276 }
277
278 protected void setSourceType(SourceType val) {
279
280 // show the source classes
281 m_sourceType = val;
282 m_sourceClasses.setClasses(deobfuscateClasses(m_sourceType.getSourceClasses(m_classMatches), m_sourceDeobfuscator));
283
284 // update counts
285 for (SourceType sourceType : SourceType.values()) {
286 m_sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)",
287 sourceType.name(),
288 sourceType.getSourceClasses(m_classMatches).size()
289 ));
290 }
291 }
292
293 private Collection<ClassEntry> deobfuscateClasses(Collection<ClassEntry> in, Deobfuscator deobfuscator) {
294 List<ClassEntry> out = Lists.newArrayList();
295 for (ClassEntry entry : in) {
296
297 ClassEntry deobf = deobfuscator.deobfuscateEntry(entry);
298
299 // make sure we preserve any scores
300 if (entry instanceof ScoredClassEntry) {
301 deobf = new ScoredClassEntry(deobf, ((ScoredClassEntry)entry).getScore());
302 }
303
304 out.add(deobf);
305 }
306 return out;
307 }
308
309 protected void setSourceClass(ClassEntry classEntry) {
310
311 Runnable onGetDestClasses = null;
312 if (m_advanceCheck.isSelected()) {
313 onGetDestClasses = new Runnable() {
314 @Override
315 public void run() {
316 pickBestDestClass();
317 }
318 };
319 }
320
321 setSourceClass(classEntry, onGetDestClasses);
322 }
323
324 protected void setSourceClass(ClassEntry classEntry, final Runnable onGetDestClasses) {
325
326 // update the current source class
327 m_sourceClass = classEntry;
328 m_sourceClassLabel.setText(m_sourceClass != null ? m_sourceClass.getName() : "");
329
330 if (m_sourceClass != null) {
331
332 // show the dest class(es)
333 ClassMatch match = m_classMatches.getMatchBySource(m_sourceDeobfuscator.obfuscateEntry(m_sourceClass));
334 assert(match != null);
335 if (match.destClasses.isEmpty()) {
336
337 m_destClasses.setClasses(null);
338
339 // run in a separate thread to keep ui responsive
340 new Thread() {
341 @Override
342 public void run() {
343 m_destClasses.setClasses(deobfuscateClasses(getLikelyMatches(m_sourceClass), m_destDeobfuscator));
344 m_destClasses.expandAll();
345
346 if (onGetDestClasses != null) {
347 onGetDestClasses.run();
348 }
349 }
350 }.start();
351
352 } else {
353
354 m_destClasses.setClasses(deobfuscateClasses(match.destClasses, m_destDeobfuscator));
355 m_destClasses.expandAll();
356
357 if (onGetDestClasses != null) {
358 onGetDestClasses.run();
359 }
360 }
361 }
362
363 setDestClass(null);
364 m_sourceReader.decompileClass(m_sourceClass, m_sourceDeobfuscator, new Runnable() {
365 @Override
366 public void run() {
367 m_sourceReader.navigateToClassDeclaration(m_sourceClass);
368 }
369 });
370
371 updateMatchButton();
372 }
373
374 private Collection<ClassEntry> getLikelyMatches(ClassEntry sourceClass) {
375
376 ClassEntry obfSourceClass = m_sourceDeobfuscator.obfuscateEntry(sourceClass);
377
378 // set up identifiers
379 ClassNamer namer = new ClassNamer(m_classMatches.getUniqueMatches());
380 ClassIdentifier sourceIdentifier = new ClassIdentifier(
381 m_sourceDeobfuscator.getJar(), m_sourceDeobfuscator.getJarIndex(),
382 namer.getSourceNamer(), true
383 );
384 ClassIdentifier destIdentifier = new ClassIdentifier(
385 m_destDeobfuscator.getJar(), m_destDeobfuscator.getJarIndex(),
386 namer.getDestNamer(), true
387 );
388
389 try {
390
391 // rank all the unmatched dest classes against the source class
392 ClassIdentity sourceIdentity = sourceIdentifier.identify(obfSourceClass);
393 List<ClassEntry> scoredDestClasses = Lists.newArrayList();
394 for (ClassEntry unmatchedDestClass : m_classMatches.getUnmatchedDestClasses()) {
395 ClassIdentity destIdentity = destIdentifier.identify(unmatchedDestClass);
396 float score = 100.0f*(sourceIdentity.getMatchScore(destIdentity) + destIdentity.getMatchScore(sourceIdentity))
397 /(sourceIdentity.getMaxMatchScore() + destIdentity.getMaxMatchScore());
398 scoredDestClasses.add(new ScoredClassEntry(unmatchedDestClass, score));
399 }
400 return scoredDestClasses;
401
402 } catch (ClassNotFoundException ex) {
403 throw new Error("Unable to find class " + ex.getMessage());
404 }
405 }
406
407 protected void setDestClass(ClassEntry classEntry) {
408
409 // update the current source class
410 m_destClass = classEntry;
411 m_destClassLabel.setText(m_destClass != null ? m_destClass.getName() : "");
412
413 m_destReader.decompileClass(m_destClass, m_destDeobfuscator, new Runnable() {
414 @Override
415 public void run() {
416 m_destReader.navigateToClassDeclaration(m_destClass);
417 }
418 });
419
420 updateMatchButton();
421 }
422
423 private void updateMatchButton() {
424
425 ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass);
426 ClassEntry obfDest = m_destDeobfuscator.obfuscateEntry(m_destClass);
427
428 BiMap<ClassEntry,ClassEntry> uniqueMatches = m_classMatches.getUniqueMatches();
429 boolean twoSelected = m_sourceClass != null && m_destClass != null;
430 boolean isMatched = uniqueMatches.containsKey(obfSource) && uniqueMatches.containsValue(obfDest);
431 boolean canMatch = !uniqueMatches.containsKey(obfSource) && ! uniqueMatches.containsValue(obfDest);
432
433 GuiTricks.deactivateButton(m_matchButton);
434 if (twoSelected) {
435 if (isMatched) {
436 GuiTricks.activateButton(m_matchButton, "Unmatch", new ActionListener() {
437 @Override
438 public void actionPerformed(ActionEvent event) {
439 onUnmatchClick();
440 }
441 });
442 } else if (canMatch) {
443 GuiTricks.activateButton(m_matchButton, "Match", new ActionListener() {
444 @Override
445 public void actionPerformed(ActionEvent event) {
446 onMatchClick();
447 }
448 });
449 }
450 }
451 }
452
453 private void onMatchClick() {
454 // precondition: source and dest classes are set correctly
455
456 ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass);
457 ClassEntry obfDest = m_destDeobfuscator.obfuscateEntry(m_destClass);
458
459 // remove the classes from their match
460 m_classMatches.removeSource(obfSource);
461 m_classMatches.removeDest(obfDest);
462
463 // add them as matched classes
464 m_classMatches.add(new ClassMatch(obfSource, obfDest));
465
466 ClassEntry nextClass = null;
467 if (m_advanceCheck.isSelected()) {
468 nextClass = m_sourceClasses.getNextClass(m_sourceClass);
469 }
470
471 save();
472 updateMatches();
473
474 if (nextClass != null) {
475 advance(nextClass);
476 }
477 }
478
479 private void onUnmatchClick() {
480 // precondition: source and dest classes are set to a unique match
481
482 ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass);
483
484 // remove the source to break the match, then add the source back as unmatched
485 m_classMatches.removeSource(obfSource);
486 m_classMatches.add(new ClassMatch(obfSource, null));
487
488 save();
489 updateMatches();
490 }
491
492 private void updateMatches() {
493 updateDestMappings();
494 setDestClass(null);
495 m_destClasses.setClasses(null);
496 updateMatchButton();
497
498 // remember where we were in the source tree
499 String packageName = m_sourceClasses.getSelectedPackage();
500
501 setSourceType(m_sourceType);
502
503 m_sourceClasses.expandPackage(packageName);
504 }
505
506 private void save() {
507 if (m_saveListener != null) {
508 m_saveListener.save(m_classMatches);
509 }
510 }
511
512 private void autoMatch() {
513
514 System.out.println("Automatching...");
515
516 // compute a new matching
517 ClassMatching matching = MappingsConverter.computeMatching(
518 m_sourceDeobfuscator.getJar(), m_sourceDeobfuscator.getJarIndex(),
519 m_destDeobfuscator.getJar(), m_destDeobfuscator.getJarIndex(),
520 m_classMatches.getUniqueMatches()
521 );
522 ClassMatches newMatches = new ClassMatches(matching.matches());
523 System.out.println(String.format("Automatch found %d new matches",
524 newMatches.getUniqueMatches().size() - m_classMatches.getUniqueMatches().size()
525 ));
526
527 // update the current matches
528 m_classMatches = newMatches;
529 save();
530 updateMatches();
531 }
532
533 private void advance() {
534 advance(null);
535 }
536
537 private void advance(ClassEntry sourceClass) {
538
539 // make sure we have a source class
540 if (sourceClass == null) {
541 sourceClass = m_sourceClasses.getSelectedClass();
542 if (sourceClass != null) {
543 sourceClass = m_sourceClasses.getNextClass(sourceClass);
544 } else {
545 sourceClass = m_sourceClasses.getFirstClass();
546 }
547 }
548
549 // set the source class
550 setSourceClass(sourceClass, new Runnable() {
551 @Override
552 public void run() {
553 pickBestDestClass();
554 }
555 });
556 m_sourceClasses.setSelectionClass(sourceClass);
557 }
558
559 private void pickBestDestClass() {
560
561 // then, pick the best dest class
562 ClassEntry firstClass = null;
563 ScoredClassEntry bestDestClass = null;
564 for (ClassSelectorPackageNode packageNode : m_destClasses.packageNodes()) {
565 for (ClassSelectorClassNode classNode : m_destClasses.classNodes(packageNode)) {
566 if (firstClass == null) {
567 firstClass = classNode.getClassEntry();
568 }
569 if (classNode.getClassEntry() instanceof ScoredClassEntry) {
570 ScoredClassEntry scoredClass = (ScoredClassEntry)classNode.getClassEntry();
571 if (bestDestClass == null || bestDestClass.getScore() < scoredClass.getScore()) {
572 bestDestClass = scoredClass;
573 }
574 }
575 }
576 }
577
578 // pick the entry to show
579 ClassEntry destClass = null;
580 if (bestDestClass != null) {
581 destClass = bestDestClass;
582 } else if (firstClass != null) {
583 destClass = firstClass;
584 }
585
586 setDestClass(destClass);
587 m_destClasses.setSelectionClass(destClass);
588 }
589}
diff --git a/src/cuchaz/enigma/gui/ClassSelector.java b/src/cuchaz/enigma/gui/ClassSelector.java
new file mode 100644
index 00000000..11333a96
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ClassSelector.java
@@ -0,0 +1,293 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.event.MouseAdapter;
14import java.awt.event.MouseEvent;
15import java.util.Collection;
16import java.util.Collections;
17import java.util.Comparator;
18import java.util.Enumeration;
19import java.util.List;
20import java.util.Map;
21
22import javax.swing.JTree;
23import javax.swing.tree.DefaultMutableTreeNode;
24import javax.swing.tree.DefaultTreeModel;
25import javax.swing.tree.TreePath;
26
27import com.google.common.collect.ArrayListMultimap;
28import com.google.common.collect.Lists;
29import com.google.common.collect.Maps;
30import com.google.common.collect.Multimap;
31
32import cuchaz.enigma.mapping.ClassEntry;
33
34public class ClassSelector extends JTree {
35
36 private static final long serialVersionUID = -7632046902384775977L;
37
38 public interface ClassSelectionListener {
39 void onSelectClass(ClassEntry classEntry);
40 }
41
42 public static Comparator<ClassEntry> ObfuscatedClassEntryComparator;
43 public static Comparator<ClassEntry> DeobfuscatedClassEntryComparator;
44
45 static {
46 ObfuscatedClassEntryComparator = new Comparator<ClassEntry>() {
47 @Override
48 public int compare(ClassEntry a, ClassEntry b) {
49 String aname = a.getName();
50 String bname = a.getName();
51 if (aname.length() != bname.length()) {
52 return aname.length() - bname.length();
53 }
54 return aname.compareTo(bname);
55 }
56 };
57
58 DeobfuscatedClassEntryComparator = new Comparator<ClassEntry>() {
59 @Override
60 public int compare(ClassEntry a, ClassEntry b) {
61 if (a instanceof ScoredClassEntry && b instanceof ScoredClassEntry) {
62 return Float.compare(
63 ((ScoredClassEntry)b).getScore(),
64 ((ScoredClassEntry)a).getScore()
65 );
66 }
67 return a.getName().compareTo(b.getName());
68 }
69 };
70 }
71
72 private ClassSelectionListener m_listener;
73 private Comparator<ClassEntry> m_comparator;
74
75 public ClassSelector(Comparator<ClassEntry> comparator) {
76 m_comparator = comparator;
77
78 // configure the tree control
79 setRootVisible(false);
80 setShowsRootHandles(false);
81 setModel(null);
82
83 // hook events
84 addMouseListener(new MouseAdapter() {
85 @Override
86 public void mouseClicked(MouseEvent event) {
87 if (m_listener != null && event.getClickCount() == 2) {
88 // get the selected node
89 TreePath path = getSelectionPath();
90 if (path != null && path.getLastPathComponent() instanceof ClassSelectorClassNode) {
91 ClassSelectorClassNode node = (ClassSelectorClassNode)path.getLastPathComponent();
92 m_listener.onSelectClass(node.getClassEntry());
93 }
94 }
95 }
96 });
97
98 // init defaults
99 m_listener = null;
100 }
101
102 public void setListener(ClassSelectionListener val) {
103 m_listener = val;
104 }
105
106 public void setClasses(Collection<ClassEntry> classEntries) {
107 if (classEntries == null) {
108 setModel(null);
109 return;
110 }
111
112 // build the package names
113 Map<String,ClassSelectorPackageNode> packages = Maps.newHashMap();
114 for (ClassEntry classEntry : classEntries) {
115 packages.put(classEntry.getPackageName(), null);
116 }
117
118 // sort the packages
119 List<String> sortedPackageNames = Lists.newArrayList(packages.keySet());
120 Collections.sort(sortedPackageNames, new Comparator<String>() {
121 @Override
122 public int compare(String a, String b) {
123 // I can never keep this rule straight when writing these damn things...
124 // a < b => -1, a == b => 0, a > b => +1
125
126 String[] aparts = a.split("/");
127 String[] bparts = b.split("/");
128 for (int i = 0; true; i++) {
129 if (i >= aparts.length) {
130 return -1;
131 } else if (i >= bparts.length) {
132 return 1;
133 }
134
135 int result = aparts[i].compareTo(bparts[i]);
136 if (result != 0) {
137 return result;
138 }
139 }
140 }
141 });
142
143 // create the root node and the package nodes
144 DefaultMutableTreeNode root = new DefaultMutableTreeNode();
145 for (String packageName : sortedPackageNames) {
146 ClassSelectorPackageNode node = new ClassSelectorPackageNode(packageName);
147 packages.put(packageName, node);
148 root.add(node);
149 }
150
151 // put the classes into packages
152 Multimap<String,ClassEntry> packagedClassEntries = ArrayListMultimap.create();
153 for (ClassEntry classEntry : classEntries) {
154 packagedClassEntries.put(classEntry.getPackageName(), classEntry);
155 }
156
157 // build the class nodes
158 for (String packageName : packagedClassEntries.keySet()) {
159 // sort the class entries
160 List<ClassEntry> classEntriesInPackage = Lists.newArrayList(packagedClassEntries.get(packageName));
161 Collections.sort(classEntriesInPackage, m_comparator);
162
163 // create the nodes in order
164 for (ClassEntry classEntry : classEntriesInPackage) {
165 ClassSelectorPackageNode node = packages.get(packageName);
166 node.add(new ClassSelectorClassNode(classEntry));
167 }
168 }
169
170 // finally, update the tree control
171 setModel(new DefaultTreeModel(root));
172 }
173
174 public ClassEntry getSelectedClass() {
175 if (!isSelectionEmpty()) {
176 Object selectedNode = getSelectionPath().getLastPathComponent();
177 if (selectedNode instanceof ClassSelectorClassNode) {
178 ClassSelectorClassNode classNode = (ClassSelectorClassNode)selectedNode;
179 return classNode.getClassEntry();
180 }
181 }
182 return null;
183 }
184
185 public String getSelectedPackage() {
186 if (!isSelectionEmpty()) {
187 Object selectedNode = getSelectionPath().getLastPathComponent();
188 if (selectedNode instanceof ClassSelectorPackageNode) {
189 ClassSelectorPackageNode packageNode = (ClassSelectorPackageNode)selectedNode;
190 return packageNode.getPackageName();
191 } else if (selectedNode instanceof ClassSelectorClassNode) {
192 ClassSelectorClassNode classNode = (ClassSelectorClassNode)selectedNode;
193 return classNode.getClassEntry().getPackageName();
194 }
195 }
196 return null;
197 }
198
199 public Iterable<ClassSelectorPackageNode> packageNodes() {
200 List<ClassSelectorPackageNode> nodes = Lists.newArrayList();
201 DefaultMutableTreeNode root = (DefaultMutableTreeNode)getModel().getRoot();
202 Enumeration<?> children = root.children();
203 while (children.hasMoreElements()) {
204 ClassSelectorPackageNode packageNode = (ClassSelectorPackageNode)children.nextElement();
205 nodes.add(packageNode);
206 }
207 return nodes;
208 }
209
210 public Iterable<ClassSelectorClassNode> classNodes(ClassSelectorPackageNode packageNode) {
211 List<ClassSelectorClassNode> nodes = Lists.newArrayList();
212 Enumeration<?> children = packageNode.children();
213 while (children.hasMoreElements()) {
214 ClassSelectorClassNode classNode = (ClassSelectorClassNode)children.nextElement();
215 nodes.add(classNode);
216 }
217 return nodes;
218 }
219
220 public void expandPackage(String packageName) {
221 if (packageName == null) {
222 return;
223 }
224 for (ClassSelectorPackageNode packageNode : packageNodes()) {
225 if (packageNode.getPackageName().equals(packageName)) {
226 expandPath(new TreePath(new Object[] {getModel().getRoot(), packageNode}));
227 return;
228 }
229 }
230 }
231
232 public void expandAll() {
233 for (ClassSelectorPackageNode packageNode : packageNodes()) {
234 expandPath(new TreePath(new Object[] {getModel().getRoot(), packageNode}));
235 }
236 }
237
238 public ClassEntry getFirstClass() {
239 for (ClassSelectorPackageNode packageNode : packageNodes()) {
240 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
241 return classNode.getClassEntry();
242 }
243 }
244 return null;
245 }
246
247 public ClassSelectorPackageNode getPackageNode(ClassEntry entry) {
248 for (ClassSelectorPackageNode packageNode : packageNodes()) {
249 if (packageNode.getPackageName().equals(entry.getPackageName())) {
250 return packageNode;
251 }
252 }
253 return null;
254 }
255
256 public ClassEntry getNextClass(ClassEntry entry) {
257 boolean foundIt = false;
258 for (ClassSelectorPackageNode packageNode : packageNodes()) {
259 if (!foundIt) {
260 // skip to the package with our target in it
261 if (packageNode.getPackageName().equals(entry.getPackageName())) {
262 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
263 if (!foundIt) {
264 if (classNode.getClassEntry().equals(entry)) {
265 foundIt = true;
266 }
267 } else {
268 // return the next class
269 return classNode.getClassEntry();
270 }
271 }
272 }
273 } else {
274 // return the next class
275 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
276 return classNode.getClassEntry();
277 }
278 }
279 }
280 return null;
281 }
282
283 public void setSelectionClass(ClassEntry classEntry) {
284 expandPackage(classEntry.getPackageName());
285 for (ClassSelectorPackageNode packageNode : packageNodes()) {
286 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
287 if (classNode.getClassEntry().equals(classEntry)) {
288 setSelectionPath(new TreePath(new Object[] {getModel().getRoot(), packageNode, classNode}));
289 }
290 }
291 }
292 }
293}
diff --git a/src/cuchaz/enigma/gui/ClassSelectorClassNode.java b/src/cuchaz/enigma/gui/ClassSelectorClassNode.java
new file mode 100644
index 00000000..1219e890
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ClassSelectorClassNode.java
@@ -0,0 +1,50 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import javax.swing.tree.DefaultMutableTreeNode;
14
15import cuchaz.enigma.mapping.ClassEntry;
16
17public class ClassSelectorClassNode extends DefaultMutableTreeNode {
18
19 private static final long serialVersionUID = -8956754339813257380L;
20
21 private ClassEntry m_classEntry;
22
23 public ClassSelectorClassNode(ClassEntry classEntry) {
24 m_classEntry = classEntry;
25 }
26
27 public ClassEntry getClassEntry() {
28 return m_classEntry;
29 }
30
31 @Override
32 public String toString() {
33 if (m_classEntry instanceof ScoredClassEntry) {
34 return String.format("%d%% %s", (int)((ScoredClassEntry)m_classEntry).getScore(), m_classEntry.getSimpleName());
35 }
36 return m_classEntry.getSimpleName();
37 }
38
39 @Override
40 public boolean equals(Object other) {
41 if (other instanceof ClassSelectorClassNode) {
42 return equals((ClassSelectorClassNode)other);
43 }
44 return false;
45 }
46
47 public boolean equals(ClassSelectorClassNode other) {
48 return m_classEntry.equals(other.m_classEntry);
49 }
50}
diff --git a/src/cuchaz/enigma/gui/ClassSelectorPackageNode.java b/src/cuchaz/enigma/gui/ClassSelectorPackageNode.java
new file mode 100644
index 00000000..7259f54d
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ClassSelectorPackageNode.java
@@ -0,0 +1,45 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import javax.swing.tree.DefaultMutableTreeNode;
14
15public class ClassSelectorPackageNode extends DefaultMutableTreeNode {
16
17 private static final long serialVersionUID = -3730868701219548043L;
18
19 private String m_packageName;
20
21 public ClassSelectorPackageNode(String packageName) {
22 m_packageName = packageName;
23 }
24
25 public String getPackageName() {
26 return m_packageName;
27 }
28
29 @Override
30 public String toString() {
31 return m_packageName;
32 }
33
34 @Override
35 public boolean equals(Object other) {
36 if (other instanceof ClassSelectorPackageNode) {
37 return equals((ClassSelectorPackageNode)other);
38 }
39 return false;
40 }
41
42 public boolean equals(ClassSelectorPackageNode other) {
43 return m_packageName.equals(other.m_packageName);
44 }
45}
diff --git a/src/cuchaz/enigma/gui/CodeReader.java b/src/cuchaz/enigma/gui/CodeReader.java
new file mode 100644
index 00000000..5033a2cd
--- /dev/null
+++ b/src/cuchaz/enigma/gui/CodeReader.java
@@ -0,0 +1,222 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Rectangle;
14import java.awt.event.ActionEvent;
15import java.awt.event.ActionListener;
16
17import javax.swing.JEditorPane;
18import javax.swing.SwingUtilities;
19import javax.swing.Timer;
20import javax.swing.event.CaretEvent;
21import javax.swing.event.CaretListener;
22import javax.swing.text.BadLocationException;
23import javax.swing.text.Highlighter.HighlightPainter;
24
25import com.strobel.decompiler.languages.java.ast.CompilationUnit;
26
27import cuchaz.enigma.Deobfuscator;
28import cuchaz.enigma.analysis.EntryReference;
29import cuchaz.enigma.analysis.SourceIndex;
30import cuchaz.enigma.analysis.Token;
31import cuchaz.enigma.mapping.ClassEntry;
32import cuchaz.enigma.mapping.Entry;
33import de.sciss.syntaxpane.DefaultSyntaxKit;
34
35
36public class CodeReader extends JEditorPane {
37
38 private static final long serialVersionUID = 3673180950485748810L;
39
40 private static final Object m_lock = new Object();
41
42 public static interface SelectionListener {
43 void onSelect(EntryReference<Entry,Entry> reference);
44 }
45
46 private SelectionHighlightPainter m_selectionHighlightPainter;
47 private SourceIndex m_sourceIndex;
48 private SelectionListener m_selectionListener;
49
50 public CodeReader() {
51
52 setEditable(false);
53 setContentType("text/java");
54
55 // turn off token highlighting (it's wrong most of the time anyway...)
56 DefaultSyntaxKit kit = (DefaultSyntaxKit)getEditorKit();
57 kit.toggleComponent(this, "de.sciss.syntaxpane.components.TokenMarker");
58
59 // hook events
60 addCaretListener(new CaretListener() {
61 @Override
62 public void caretUpdate(CaretEvent event) {
63 if (m_selectionListener != null && m_sourceIndex != null) {
64 Token token = m_sourceIndex.getReferenceToken(event.getDot());
65 if (token != null) {
66 m_selectionListener.onSelect(m_sourceIndex.getDeobfReference(token));
67 } else {
68 m_selectionListener.onSelect(null);
69 }
70 }
71 }
72 });
73
74 m_selectionHighlightPainter = new SelectionHighlightPainter();
75 m_sourceIndex = null;
76 m_selectionListener = null;
77 }
78
79 public void setSelectionListener(SelectionListener val) {
80 m_selectionListener = val;
81 }
82
83 public void setCode(String code) {
84 // sadly, the java lexer is not thread safe, so we have to serialize all these calls
85 synchronized (m_lock) {
86 setText(code);
87 }
88 }
89
90 public SourceIndex getSourceIndex() {
91 return m_sourceIndex;
92 }
93
94 public void decompileClass(ClassEntry classEntry, Deobfuscator deobfuscator) {
95 decompileClass(classEntry, deobfuscator, null);
96 }
97
98 public void decompileClass(ClassEntry classEntry, Deobfuscator deobfuscator, Runnable callback) {
99 decompileClass(classEntry, deobfuscator, null, callback);
100 }
101
102 public void decompileClass(final ClassEntry classEntry, final Deobfuscator deobfuscator, final Boolean ignoreBadTokens, final Runnable callback) {
103
104 if (classEntry == null) {
105 setCode(null);
106 return;
107 }
108
109 setCode("(decompiling...)");
110
111 // run decompilation in a separate thread to keep ui responsive
112 new Thread() {
113 @Override
114 public void run() {
115
116 // decompile it
117 CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getOutermostClassName());
118 String source = deobfuscator.getSource(sourceTree);
119 setCode(source);
120 m_sourceIndex = deobfuscator.getSourceIndex(sourceTree, source, ignoreBadTokens);
121
122 if (callback != null) {
123 callback.run();
124 }
125 }
126 }.start();
127 }
128
129 public void navigateToClassDeclaration(ClassEntry classEntry) {
130
131 // navigate to the class declaration
132 Token token = m_sourceIndex.getDeclarationToken(classEntry);
133 if (token == null) {
134 // couldn't find the class declaration token, might be an anonymous class
135 // look for any declaration in that class instead
136 for (Entry entry : m_sourceIndex.declarations()) {
137 if (entry.getClassEntry().equals(classEntry)) {
138 token = m_sourceIndex.getDeclarationToken(entry);
139 break;
140 }
141 }
142 }
143
144 if (token != null) {
145 navigateToToken(token);
146 } else {
147 // couldn't find anything =(
148 System.out.println("Unable to find declaration in source for " + classEntry);
149 }
150 }
151
152 public void navigateToToken(final Token token) {
153 navigateToToken(this, token, m_selectionHighlightPainter);
154 }
155
156 // HACKHACK: someday we can update the main GUI to use this code reader
157 public static void navigateToToken(final JEditorPane editor, final Token token, final HighlightPainter highlightPainter) {
158
159 // set the caret position to the token
160 editor.setCaretPosition(token.start);
161 editor.grabFocus();
162
163 try {
164 // make sure the token is visible in the scroll window
165 Rectangle start = editor.modelToView(token.start);
166 Rectangle end = editor.modelToView(token.end);
167 final Rectangle show = start.union(end);
168 show.grow(start.width * 10, start.height * 6);
169 SwingUtilities.invokeLater(new Runnable() {
170 @Override
171 public void run() {
172 editor.scrollRectToVisible(show);
173 }
174 });
175 } catch (BadLocationException ex) {
176 throw new Error(ex);
177 }
178
179 // highlight the token momentarily
180 final Timer timer = new Timer(200, new ActionListener() {
181 private int m_counter = 0;
182 private Object m_highlight = null;
183
184 @Override
185 public void actionPerformed(ActionEvent event) {
186 if (m_counter % 2 == 0) {
187 try {
188 m_highlight = editor.getHighlighter().addHighlight(token.start, token.end, highlightPainter);
189 } catch (BadLocationException ex) {
190 // don't care
191 }
192 } else if (m_highlight != null) {
193 editor.getHighlighter().removeHighlight(m_highlight);
194 }
195
196 if (m_counter++ > 6) {
197 Timer timer = (Timer)event.getSource();
198 timer.stop();
199 }
200 }
201 });
202 timer.start();
203 }
204
205 public void setHighlightedTokens(Iterable<Token> tokens, HighlightPainter painter) {
206 for (Token token : tokens) {
207 setHighlightedToken(token, painter);
208 }
209 }
210
211 public void setHighlightedToken(Token token, HighlightPainter painter) {
212 try {
213 getHighlighter().addHighlight(token.start, token.end, painter);
214 } catch (BadLocationException ex) {
215 throw new IllegalArgumentException(ex);
216 }
217 }
218
219 public void clearHighlights() {
220 getHighlighter().removeAllHighlights();
221 }
222}
diff --git a/src/cuchaz/enigma/gui/CrashDialog.java b/src/cuchaz/enigma/gui/CrashDialog.java
new file mode 100644
index 00000000..904273c1
--- /dev/null
+++ b/src/cuchaz/enigma/gui/CrashDialog.java
@@ -0,0 +1,101 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.BorderLayout;
14import java.awt.Container;
15import java.awt.FlowLayout;
16import java.awt.event.ActionEvent;
17import java.awt.event.ActionListener;
18import java.io.PrintWriter;
19import java.io.StringWriter;
20
21import javax.swing.BorderFactory;
22import javax.swing.JButton;
23import javax.swing.JFrame;
24import javax.swing.JLabel;
25import javax.swing.JPanel;
26import javax.swing.JScrollPane;
27import javax.swing.JTextArea;
28import javax.swing.WindowConstants;
29
30import cuchaz.enigma.Constants;
31
32public class CrashDialog {
33
34 private static CrashDialog m_instance = null;
35
36 private JFrame m_frame;
37 private JTextArea m_text;
38
39 private CrashDialog(JFrame parent) {
40 // init frame
41 m_frame = new JFrame(Constants.Name + " - Crash Report");
42 final Container pane = m_frame.getContentPane();
43 pane.setLayout(new BorderLayout());
44
45 JLabel label = new JLabel(Constants.Name + " has crashed! =(");
46 label.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
47 pane.add(label, BorderLayout.NORTH);
48
49 // report panel
50 m_text = new JTextArea();
51 m_text.setTabSize(2);
52 pane.add(new JScrollPane(m_text), BorderLayout.CENTER);
53
54 // buttons panel
55 JPanel buttonsPanel = new JPanel();
56 FlowLayout buttonsLayout = new FlowLayout();
57 buttonsLayout.setAlignment(FlowLayout.RIGHT);
58 buttonsPanel.setLayout(buttonsLayout);
59 buttonsPanel.add(GuiTricks.unboldLabel(new JLabel("If you choose exit, you will lose any unsaved work.")));
60 JButton ignoreButton = new JButton("Ignore");
61 ignoreButton.addActionListener(new ActionListener() {
62 @Override
63 public void actionPerformed(ActionEvent event) {
64 // close (hide) the dialog
65 m_frame.setVisible(false);
66 }
67 });
68 buttonsPanel.add(ignoreButton);
69 JButton exitButton = new JButton("Exit");
70 exitButton.addActionListener(new ActionListener() {
71 @Override
72 public void actionPerformed(ActionEvent event) {
73 // exit enigma
74 System.exit(1);
75 }
76 });
77 buttonsPanel.add(exitButton);
78 pane.add(buttonsPanel, BorderLayout.SOUTH);
79
80 // show the frame
81 m_frame.setSize(600, 400);
82 m_frame.setLocationRelativeTo(parent);
83 m_frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
84 }
85
86 public static void init(JFrame parent) {
87 m_instance = new CrashDialog(parent);
88 }
89
90 public static void show(Throwable ex) {
91 // get the error report
92 StringWriter buf = new StringWriter();
93 ex.printStackTrace(new PrintWriter(buf));
94 String report = buf.toString();
95
96 // show it!
97 m_instance.m_text.setText(report);
98 m_instance.m_frame.doLayout();
99 m_instance.m_frame.setVisible(true);
100 }
101}
diff --git a/src/cuchaz/enigma/gui/DeobfuscatedHighlightPainter.java b/src/cuchaz/enigma/gui/DeobfuscatedHighlightPainter.java
new file mode 100644
index 00000000..57210a84
--- /dev/null
+++ b/src/cuchaz/enigma/gui/DeobfuscatedHighlightPainter.java
@@ -0,0 +1,21 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Color;
14
15public class DeobfuscatedHighlightPainter extends BoxHighlightPainter {
16
17 public DeobfuscatedHighlightPainter() {
18 // green ish
19 super(new Color(220, 255, 220), new Color(80, 160, 80));
20 }
21}
diff --git a/src/cuchaz/enigma/gui/Gui.java b/src/cuchaz/enigma/gui/Gui.java
new file mode 100644
index 00000000..f9192d31
--- /dev/null
+++ b/src/cuchaz/enigma/gui/Gui.java
@@ -0,0 +1,1122 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.BorderLayout;
14import java.awt.Color;
15import java.awt.Container;
16import java.awt.Dimension;
17import java.awt.FlowLayout;
18import java.awt.GridLayout;
19import java.awt.event.ActionEvent;
20import java.awt.event.ActionListener;
21import java.awt.event.InputEvent;
22import java.awt.event.KeyAdapter;
23import java.awt.event.KeyEvent;
24import java.awt.event.MouseAdapter;
25import java.awt.event.MouseEvent;
26import java.awt.event.WindowAdapter;
27import java.awt.event.WindowEvent;
28import java.io.File;
29import java.io.IOException;
30import java.lang.Thread.UncaughtExceptionHandler;
31import java.util.Collection;
32import java.util.Collections;
33import java.util.List;
34import java.util.Vector;
35import java.util.jar.JarFile;
36
37import javax.swing.BorderFactory;
38import javax.swing.JEditorPane;
39import javax.swing.JFileChooser;
40import javax.swing.JFrame;
41import javax.swing.JLabel;
42import javax.swing.JList;
43import javax.swing.JMenu;
44import javax.swing.JMenuBar;
45import javax.swing.JMenuItem;
46import javax.swing.JOptionPane;
47import javax.swing.JPanel;
48import javax.swing.JPopupMenu;
49import javax.swing.JScrollPane;
50import javax.swing.JSplitPane;
51import javax.swing.JTabbedPane;
52import javax.swing.JTextField;
53import javax.swing.JTree;
54import javax.swing.KeyStroke;
55import javax.swing.ListSelectionModel;
56import javax.swing.WindowConstants;
57import javax.swing.event.CaretEvent;
58import javax.swing.event.CaretListener;
59import javax.swing.text.BadLocationException;
60import javax.swing.text.Highlighter;
61import javax.swing.tree.DefaultTreeModel;
62import javax.swing.tree.TreeNode;
63import javax.swing.tree.TreePath;
64
65import com.google.common.collect.Lists;
66
67import cuchaz.enigma.Constants;
68import cuchaz.enigma.ExceptionIgnorer;
69import cuchaz.enigma.analysis.BehaviorReferenceTreeNode;
70import cuchaz.enigma.analysis.ClassImplementationsTreeNode;
71import cuchaz.enigma.analysis.ClassInheritanceTreeNode;
72import cuchaz.enigma.analysis.EntryReference;
73import cuchaz.enigma.analysis.FieldReferenceTreeNode;
74import cuchaz.enigma.analysis.MethodImplementationsTreeNode;
75import cuchaz.enigma.analysis.MethodInheritanceTreeNode;
76import cuchaz.enigma.analysis.ReferenceTreeNode;
77import cuchaz.enigma.analysis.Token;
78import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener;
79import cuchaz.enigma.mapping.ArgumentEntry;
80import cuchaz.enigma.mapping.ClassEntry;
81import cuchaz.enigma.mapping.ConstructorEntry;
82import cuchaz.enigma.mapping.Entry;
83import cuchaz.enigma.mapping.FieldEntry;
84import cuchaz.enigma.mapping.IllegalNameException;
85import cuchaz.enigma.mapping.MappingParseException;
86import cuchaz.enigma.mapping.MethodEntry;
87import cuchaz.enigma.mapping.Signature;
88import de.sciss.syntaxpane.DefaultSyntaxKit;
89
90public class Gui {
91
92 private GuiController m_controller;
93
94 // controls
95 private JFrame m_frame;
96 private ClassSelector m_obfClasses;
97 private ClassSelector m_deobfClasses;
98 private JEditorPane m_editor;
99 private JPanel m_classesPanel;
100 private JSplitPane m_splitClasses;
101 private JPanel m_infoPanel;
102 private ObfuscatedHighlightPainter m_obfuscatedHighlightPainter;
103 private DeobfuscatedHighlightPainter m_deobfuscatedHighlightPainter;
104 private OtherHighlightPainter m_otherHighlightPainter;
105 private SelectionHighlightPainter m_selectionHighlightPainter;
106 private JTree m_inheritanceTree;
107 private JTree m_implementationsTree;
108 private JTree m_callsTree;
109 private JList<Token> m_tokens;
110 private JTabbedPane m_tabs;
111
112 // dynamic menu items
113 private JMenuItem m_closeJarMenu;
114 private JMenuItem m_openMappingsMenu;
115 private JMenuItem m_saveMappingsMenu;
116 private JMenuItem m_saveMappingsAsMenu;
117 private JMenuItem m_closeMappingsMenu;
118 private JMenuItem m_renameMenu;
119 private JMenuItem m_showInheritanceMenu;
120 private JMenuItem m_openEntryMenu;
121 private JMenuItem m_openPreviousMenu;
122 private JMenuItem m_showCallsMenu;
123 private JMenuItem m_showImplementationsMenu;
124 private JMenuItem m_toggleMappingMenu;
125 private JMenuItem m_exportSourceMenu;
126 private JMenuItem m_exportJarMenu;
127
128 // state
129 private EntryReference<Entry,Entry> m_reference;
130 private JFileChooser m_jarFileChooser;
131 private JFileChooser m_mappingsFileChooser;
132 private JFileChooser m_exportSourceFileChooser;
133 private JFileChooser m_exportJarFileChooser;
134
135 public Gui() {
136
137 // init frame
138 m_frame = new JFrame(Constants.Name);
139 final Container pane = m_frame.getContentPane();
140 pane.setLayout(new BorderLayout());
141
142 if (Boolean.parseBoolean(System.getProperty("enigma.catchExceptions", "true"))) {
143 // install a global exception handler to the event thread
144 CrashDialog.init(m_frame);
145 Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
146 @Override
147 public void uncaughtException(Thread thread, Throwable t) {
148 t.printStackTrace(System.err);
149 if (!ExceptionIgnorer.shouldIgnore(t)) {
150 CrashDialog.show(t);
151 }
152 }
153 });
154 }
155
156 m_controller = new GuiController(this);
157
158 // init file choosers
159 m_jarFileChooser = new JFileChooser();
160 m_mappingsFileChooser = new JFileChooser();
161 m_exportSourceFileChooser = new JFileChooser();
162 m_exportSourceFileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
163 m_exportJarFileChooser = new JFileChooser();
164
165 // init obfuscated classes list
166 m_obfClasses = new ClassSelector(ClassSelector.ObfuscatedClassEntryComparator);
167 m_obfClasses.setListener(new ClassSelectionListener() {
168 @Override
169 public void onSelectClass(ClassEntry classEntry) {
170 navigateTo(classEntry);
171 }
172 });
173 JScrollPane obfScroller = new JScrollPane(m_obfClasses);
174 JPanel obfPanel = new JPanel();
175 obfPanel.setLayout(new BorderLayout());
176 obfPanel.add(new JLabel("Obfuscated Classes"), BorderLayout.NORTH);
177 obfPanel.add(obfScroller, BorderLayout.CENTER);
178
179 // init deobfuscated classes list
180 m_deobfClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator);
181 m_deobfClasses.setListener(new ClassSelectionListener() {
182 @Override
183 public void onSelectClass(ClassEntry classEntry) {
184 navigateTo(classEntry);
185 }
186 });
187 JScrollPane deobfScroller = new JScrollPane(m_deobfClasses);
188 JPanel deobfPanel = new JPanel();
189 deobfPanel.setLayout(new BorderLayout());
190 deobfPanel.add(new JLabel("De-obfuscated Classes"), BorderLayout.NORTH);
191 deobfPanel.add(deobfScroller, BorderLayout.CENTER);
192
193 // set up classes panel (don't add the splitter yet)
194 m_splitClasses = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, obfPanel, deobfPanel);
195 m_splitClasses.setResizeWeight(0.3);
196 m_classesPanel = new JPanel();
197 m_classesPanel.setLayout(new BorderLayout());
198 m_classesPanel.setPreferredSize(new Dimension(250, 0));
199
200 // init info panel
201 m_infoPanel = new JPanel();
202 m_infoPanel.setLayout(new GridLayout(4, 1, 0, 0));
203 m_infoPanel.setPreferredSize(new Dimension(0, 100));
204 m_infoPanel.setBorder(BorderFactory.createTitledBorder("Identifier Info"));
205 clearReference();
206
207 // init editor
208 DefaultSyntaxKit.initKit();
209 m_obfuscatedHighlightPainter = new ObfuscatedHighlightPainter();
210 m_deobfuscatedHighlightPainter = new DeobfuscatedHighlightPainter();
211 m_otherHighlightPainter = new OtherHighlightPainter();
212 m_selectionHighlightPainter = new SelectionHighlightPainter();
213 m_editor = new JEditorPane();
214 m_editor.setEditable(false);
215 m_editor.setCaret(new BrowserCaret());
216 JScrollPane sourceScroller = new JScrollPane(m_editor);
217 m_editor.setContentType("text/java");
218 m_editor.addCaretListener(new CaretListener() {
219 @Override
220 public void caretUpdate(CaretEvent event) {
221 onCaretMove(event.getDot());
222 }
223 });
224 m_editor.addKeyListener(new KeyAdapter() {
225 @Override
226 public void keyPressed(KeyEvent event) {
227 switch (event.getKeyCode()) {
228 case KeyEvent.VK_R:
229 m_renameMenu.doClick();
230 break;
231
232 case KeyEvent.VK_I:
233 m_showInheritanceMenu.doClick();
234 break;
235
236 case KeyEvent.VK_M:
237 m_showImplementationsMenu.doClick();
238 break;
239
240 case KeyEvent.VK_N:
241 m_openEntryMenu.doClick();
242 break;
243
244 case KeyEvent.VK_P:
245 m_openPreviousMenu.doClick();
246 break;
247
248 case KeyEvent.VK_C:
249 m_showCallsMenu.doClick();
250 break;
251
252 case KeyEvent.VK_T:
253 m_toggleMappingMenu.doClick();
254 break;
255 }
256 }
257 });
258
259 // turn off token highlighting (it's wrong most of the time anyway...)
260 DefaultSyntaxKit kit = (DefaultSyntaxKit)m_editor.getEditorKit();
261 kit.toggleComponent(m_editor, "de.sciss.syntaxpane.components.TokenMarker");
262
263 // init editor popup menu
264 JPopupMenu popupMenu = new JPopupMenu();
265 m_editor.setComponentPopupMenu(popupMenu);
266 {
267 JMenuItem menu = new JMenuItem("Rename");
268 menu.addActionListener(new ActionListener() {
269 @Override
270 public void actionPerformed(ActionEvent event) {
271 startRename();
272 }
273 });
274 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, 0));
275 menu.setEnabled(false);
276 popupMenu.add(menu);
277 m_renameMenu = menu;
278 }
279 {
280 JMenuItem menu = new JMenuItem("Show Inheritance");
281 menu.addActionListener(new ActionListener() {
282 @Override
283 public void actionPerformed(ActionEvent event) {
284 showInheritance();
285 }
286 });
287 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, 0));
288 menu.setEnabled(false);
289 popupMenu.add(menu);
290 m_showInheritanceMenu = menu;
291 }
292 {
293 JMenuItem menu = new JMenuItem("Show Implementations");
294 menu.addActionListener(new ActionListener() {
295 @Override
296 public void actionPerformed(ActionEvent event) {
297 showImplementations();
298 }
299 });
300 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, 0));
301 menu.setEnabled(false);
302 popupMenu.add(menu);
303 m_showImplementationsMenu = menu;
304 }
305 {
306 JMenuItem menu = new JMenuItem("Show Calls");
307 menu.addActionListener(new ActionListener() {
308 @Override
309 public void actionPerformed(ActionEvent event) {
310 showCalls();
311 }
312 });
313 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, 0));
314 menu.setEnabled(false);
315 popupMenu.add(menu);
316 m_showCallsMenu = menu;
317 }
318 {
319 JMenuItem menu = new JMenuItem("Go to Declaration");
320 menu.addActionListener(new ActionListener() {
321 @Override
322 public void actionPerformed(ActionEvent event) {
323 navigateTo(m_reference.entry);
324 }
325 });
326 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, 0));
327 menu.setEnabled(false);
328 popupMenu.add(menu);
329 m_openEntryMenu = menu;
330 }
331 {
332 JMenuItem menu = new JMenuItem("Go to previous");
333 menu.addActionListener(new ActionListener() {
334 @Override
335 public void actionPerformed(ActionEvent event) {
336 m_controller.openPreviousReference();
337 }
338 });
339 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, 0));
340 menu.setEnabled(false);
341 popupMenu.add(menu);
342 m_openPreviousMenu = menu;
343 }
344 {
345 JMenuItem menu = new JMenuItem("Mark as deobfuscated");
346 menu.addActionListener(new ActionListener() {
347 @Override
348 public void actionPerformed(ActionEvent event) {
349 toggleMapping();
350 }
351 });
352 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, 0));
353 menu.setEnabled(false);
354 popupMenu.add(menu);
355 m_toggleMappingMenu = menu;
356 }
357
358 // init inheritance panel
359 m_inheritanceTree = new JTree();
360 m_inheritanceTree.setModel(null);
361 m_inheritanceTree.addMouseListener(new MouseAdapter() {
362 @Override
363 public void mouseClicked(MouseEvent event) {
364 if (event.getClickCount() == 2) {
365 // get the selected node
366 TreePath path = m_inheritanceTree.getSelectionPath();
367 if (path == null) {
368 return;
369 }
370
371 Object node = path.getLastPathComponent();
372 if (node instanceof ClassInheritanceTreeNode) {
373 ClassInheritanceTreeNode classNode = (ClassInheritanceTreeNode)node;
374 navigateTo(new ClassEntry(classNode.getObfClassName()));
375 } else if (node instanceof MethodInheritanceTreeNode) {
376 MethodInheritanceTreeNode methodNode = (MethodInheritanceTreeNode)node;
377 if (methodNode.isImplemented()) {
378 navigateTo(methodNode.getMethodEntry());
379 }
380 }
381 }
382 }
383 });
384 JPanel inheritancePanel = new JPanel();
385 inheritancePanel.setLayout(new BorderLayout());
386 inheritancePanel.add(new JScrollPane(m_inheritanceTree));
387
388 // init implementations panel
389 m_implementationsTree = new JTree();
390 m_implementationsTree.setModel(null);
391 m_implementationsTree.addMouseListener(new MouseAdapter() {
392 @Override
393 public void mouseClicked(MouseEvent event) {
394 if (event.getClickCount() == 2) {
395 // get the selected node
396 TreePath path = m_implementationsTree.getSelectionPath();
397 if (path == null) {
398 return;
399 }
400
401 Object node = path.getLastPathComponent();
402 if (node instanceof ClassImplementationsTreeNode) {
403 ClassImplementationsTreeNode classNode = (ClassImplementationsTreeNode)node;
404 navigateTo(classNode.getClassEntry());
405 } else if (node instanceof MethodImplementationsTreeNode) {
406 MethodImplementationsTreeNode methodNode = (MethodImplementationsTreeNode)node;
407 navigateTo(methodNode.getMethodEntry());
408 }
409 }
410 }
411 });
412 JPanel implementationsPanel = new JPanel();
413 implementationsPanel.setLayout(new BorderLayout());
414 implementationsPanel.add(new JScrollPane(m_implementationsTree));
415
416 // init call panel
417 m_callsTree = new JTree();
418 m_callsTree.setModel(null);
419 m_callsTree.addMouseListener(new MouseAdapter() {
420 @SuppressWarnings("unchecked")
421 @Override
422 public void mouseClicked(MouseEvent event) {
423 if (event.getClickCount() == 2) {
424 // get the selected node
425 TreePath path = m_callsTree.getSelectionPath();
426 if (path == null) {
427 return;
428 }
429
430 Object node = path.getLastPathComponent();
431 if (node instanceof ReferenceTreeNode) {
432 ReferenceTreeNode<Entry,Entry> referenceNode = ((ReferenceTreeNode<Entry,Entry>)node);
433 if (referenceNode.getReference() != null) {
434 navigateTo(referenceNode.getReference());
435 } else {
436 navigateTo(referenceNode.getEntry());
437 }
438 }
439 }
440 }
441 });
442 m_tokens = new JList<Token>();
443 m_tokens.setCellRenderer(new TokenListCellRenderer(m_controller));
444 m_tokens.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
445 m_tokens.setLayoutOrientation(JList.VERTICAL);
446 m_tokens.addMouseListener(new MouseAdapter() {
447 @Override
448 public void mouseClicked(MouseEvent event) {
449 if (event.getClickCount() == 2) {
450 Token selected = m_tokens.getSelectedValue();
451 if (selected != null) {
452 showToken(selected);
453 }
454 }
455 }
456 });
457 m_tokens.setPreferredSize(new Dimension(0, 200));
458 m_tokens.setMinimumSize(new Dimension(0, 200));
459 JSplitPane callPanel = new JSplitPane(
460 JSplitPane.VERTICAL_SPLIT,
461 true,
462 new JScrollPane(m_callsTree),
463 new JScrollPane(m_tokens)
464 );
465 callPanel.setResizeWeight(1); // let the top side take all the slack
466 callPanel.resetToPreferredSizes();
467
468 // layout controls
469 JPanel centerPanel = new JPanel();
470 centerPanel.setLayout(new BorderLayout());
471 centerPanel.add(m_infoPanel, BorderLayout.NORTH);
472 centerPanel.add(sourceScroller, BorderLayout.CENTER);
473 m_tabs = new JTabbedPane();
474 m_tabs.setPreferredSize(new Dimension(250, 0));
475 m_tabs.addTab("Inheritance", inheritancePanel);
476 m_tabs.addTab("Implementations", implementationsPanel);
477 m_tabs.addTab("Call Graph", callPanel);
478 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, centerPanel, m_tabs);
479 splitRight.setResizeWeight(1); // let the left side take all the slack
480 splitRight.resetToPreferredSizes();
481 JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, m_classesPanel, splitRight);
482 splitCenter.setResizeWeight(0); // let the right side take all the slack
483 pane.add(splitCenter, BorderLayout.CENTER);
484
485 // init menus
486 JMenuBar menuBar = new JMenuBar();
487 m_frame.setJMenuBar(menuBar);
488 {
489 JMenu menu = new JMenu("File");
490 menuBar.add(menu);
491 {
492 JMenuItem item = new JMenuItem("Open Jar...");
493 menu.add(item);
494 item.addActionListener(new ActionListener() {
495 @Override
496 public void actionPerformed(ActionEvent event) {
497 if (m_jarFileChooser.showOpenDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
498 // load the jar in a separate thread
499 new Thread() {
500 @Override
501 public void run() {
502 try {
503 m_controller.openJar(new JarFile(m_jarFileChooser.getSelectedFile()));
504 } catch (IOException ex) {
505 throw new Error(ex);
506 }
507 }
508 }.start();
509 }
510 }
511 });
512 }
513 {
514 JMenuItem item = new JMenuItem("Close Jar");
515 menu.add(item);
516 item.addActionListener(new ActionListener() {
517 @Override
518 public void actionPerformed(ActionEvent event) {
519 m_controller.closeJar();
520 }
521 });
522 m_closeJarMenu = item;
523 }
524 menu.addSeparator();
525 {
526 JMenuItem item = new JMenuItem("Open Mappings...");
527 menu.add(item);
528 item.addActionListener(new ActionListener() {
529 @Override
530 public void actionPerformed(ActionEvent event) {
531 if (m_mappingsFileChooser.showOpenDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
532 try {
533 m_controller.openMappings(m_mappingsFileChooser.getSelectedFile());
534 } catch (IOException ex) {
535 throw new Error(ex);
536 } catch (MappingParseException ex) {
537 JOptionPane.showMessageDialog(m_frame, ex.getMessage());
538 }
539 }
540 }
541 });
542 m_openMappingsMenu = item;
543 }
544 {
545 JMenuItem item = new JMenuItem("Save Mappings");
546 menu.add(item);
547 item.addActionListener(new ActionListener() {
548 @Override
549 public void actionPerformed(ActionEvent event) {
550 try {
551 m_controller.saveMappings(m_mappingsFileChooser.getSelectedFile());
552 } catch (IOException ex) {
553 throw new Error(ex);
554 }
555 }
556 });
557 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK));
558 m_saveMappingsMenu = item;
559 }
560 {
561 JMenuItem item = new JMenuItem("Save Mappings As...");
562 menu.add(item);
563 item.addActionListener(new ActionListener() {
564 @Override
565 public void actionPerformed(ActionEvent event) {
566 if (m_mappingsFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
567 try {
568 m_controller.saveMappings(m_mappingsFileChooser.getSelectedFile());
569 m_saveMappingsMenu.setEnabled(true);
570 } catch (IOException ex) {
571 throw new Error(ex);
572 }
573 }
574 }
575 });
576 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK));
577 m_saveMappingsAsMenu = item;
578 }
579 {
580 JMenuItem item = new JMenuItem("Close Mappings");
581 menu.add(item);
582 item.addActionListener(new ActionListener() {
583 @Override
584 public void actionPerformed(ActionEvent event) {
585 m_controller.closeMappings();
586 }
587 });
588 m_closeMappingsMenu = item;
589 }
590 menu.addSeparator();
591 {
592 JMenuItem item = new JMenuItem("Export Source...");
593 menu.add(item);
594 item.addActionListener(new ActionListener() {
595 @Override
596 public void actionPerformed(ActionEvent event) {
597 if (m_exportSourceFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
598 m_controller.exportSource(m_exportSourceFileChooser.getSelectedFile());
599 }
600 }
601 });
602 m_exportSourceMenu = item;
603 }
604 {
605 JMenuItem item = new JMenuItem("Export Jar...");
606 menu.add(item);
607 item.addActionListener(new ActionListener() {
608 @Override
609 public void actionPerformed(ActionEvent event) {
610 if (m_exportJarFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
611 m_controller.exportJar(m_exportJarFileChooser.getSelectedFile());
612 }
613 }
614 });
615 m_exportJarMenu = item;
616 }
617 menu.addSeparator();
618 {
619 JMenuItem item = new JMenuItem("Exit");
620 menu.add(item);
621 item.addActionListener(new ActionListener() {
622 @Override
623 public void actionPerformed(ActionEvent event) {
624 close();
625 }
626 });
627 }
628 }
629 {
630 JMenu menu = new JMenu("Help");
631 menuBar.add(menu);
632 {
633 JMenuItem item = new JMenuItem("About");
634 menu.add(item);
635 item.addActionListener(new ActionListener() {
636 @Override
637 public void actionPerformed(ActionEvent event) {
638 AboutDialog.show(m_frame);
639 }
640 });
641 }
642 }
643
644 // init state
645 onCloseJar();
646
647 m_frame.addWindowListener(new WindowAdapter() {
648 @Override
649 public void windowClosing(WindowEvent event) {
650 close();
651 }
652 });
653
654 // show the frame
655 pane.doLayout();
656 m_frame.setSize(1024, 576);
657 m_frame.setMinimumSize(new Dimension(640, 480));
658 m_frame.setVisible(true);
659 m_frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
660 }
661
662 public JFrame getFrame() {
663 return m_frame;
664 }
665
666 public GuiController getController() {
667 return m_controller;
668 }
669
670 public void onStartOpenJar() {
671 m_classesPanel.removeAll();
672 JPanel panel = new JPanel();
673 panel.setLayout(new FlowLayout());
674 panel.add(new JLabel("Loading..."));
675 m_classesPanel.add(panel);
676 redraw();
677 }
678
679 public void onFinishOpenJar(String jarName) {
680 // update gui
681 m_frame.setTitle(Constants.Name + " - " + jarName);
682 m_classesPanel.removeAll();
683 m_classesPanel.add(m_splitClasses);
684 setSource(null);
685
686 // update menu
687 m_closeJarMenu.setEnabled(true);
688 m_openMappingsMenu.setEnabled(true);
689 m_saveMappingsMenu.setEnabled(false);
690 m_saveMappingsAsMenu.setEnabled(true);
691 m_closeMappingsMenu.setEnabled(true);
692 m_exportSourceMenu.setEnabled(true);
693 m_exportJarMenu.setEnabled(true);
694
695 redraw();
696 }
697
698 public void onCloseJar() {
699 // update gui
700 m_frame.setTitle(Constants.Name);
701 setObfClasses(null);
702 setDeobfClasses(null);
703 setSource(null);
704 m_classesPanel.removeAll();
705
706 // update menu
707 m_closeJarMenu.setEnabled(false);
708 m_openMappingsMenu.setEnabled(false);
709 m_saveMappingsMenu.setEnabled(false);
710 m_saveMappingsAsMenu.setEnabled(false);
711 m_closeMappingsMenu.setEnabled(false);
712 m_exportSourceMenu.setEnabled(false);
713 m_exportJarMenu.setEnabled(false);
714
715 redraw();
716 }
717
718 public void setObfClasses(Collection<ClassEntry> obfClasses) {
719 m_obfClasses.setClasses(obfClasses);
720 }
721
722 public void setDeobfClasses(Collection<ClassEntry> deobfClasses) {
723 m_deobfClasses.setClasses(deobfClasses);
724 }
725
726 public void setMappingsFile(File file) {
727 m_mappingsFileChooser.setSelectedFile(file);
728 m_saveMappingsMenu.setEnabled(file != null);
729 }
730
731 public void setSource(String source) {
732 m_editor.getHighlighter().removeAllHighlights();
733 m_editor.setText(source);
734 }
735
736 public void showToken(final Token token) {
737 if (token == null) {
738 throw new IllegalArgumentException("Token cannot be null!");
739 }
740 CodeReader.navigateToToken(m_editor, token, m_selectionHighlightPainter);
741 redraw();
742 }
743
744 public void showTokens(Collection<Token> tokens) {
745 Vector<Token> sortedTokens = new Vector<Token>(tokens);
746 Collections.sort(sortedTokens);
747 if (sortedTokens.size() > 1) {
748 // sort the tokens and update the tokens panel
749 m_tokens.setListData(sortedTokens);
750 m_tokens.setSelectedIndex(0);
751 } else {
752 m_tokens.setListData(new Vector<Token>());
753 }
754
755 // show the first token
756 showToken(sortedTokens.get(0));
757 }
758
759 public void setHighlightedTokens(Iterable<Token> obfuscatedTokens, Iterable<Token> deobfuscatedTokens, Iterable<Token> otherTokens) {
760
761 // remove any old highlighters
762 m_editor.getHighlighter().removeAllHighlights();
763
764 // color things based on the index
765 if (obfuscatedTokens != null) {
766 setHighlightedTokens(obfuscatedTokens, m_obfuscatedHighlightPainter);
767 }
768 if (deobfuscatedTokens != null) {
769 setHighlightedTokens(deobfuscatedTokens, m_deobfuscatedHighlightPainter);
770 }
771 if (otherTokens != null) {
772 setHighlightedTokens(otherTokens, m_otherHighlightPainter);
773 }
774
775 redraw();
776 }
777
778 private void setHighlightedTokens(Iterable<Token> tokens, Highlighter.HighlightPainter painter) {
779 for (Token token : tokens) {
780 try {
781 m_editor.getHighlighter().addHighlight(token.start, token.end, painter);
782 } catch (BadLocationException ex) {
783 throw new IllegalArgumentException(ex);
784 }
785 }
786 }
787
788 private void clearReference() {
789 m_infoPanel.removeAll();
790 JLabel label = new JLabel("No identifier selected");
791 GuiTricks.unboldLabel(label);
792 label.setHorizontalAlignment(JLabel.CENTER);
793 m_infoPanel.add(label);
794
795 redraw();
796 }
797
798 private void showReference(EntryReference<Entry,Entry> reference) {
799 if (reference == null) {
800 clearReference();
801 return;
802 }
803
804 m_reference = reference;
805
806 m_infoPanel.removeAll();
807 if (reference.entry instanceof ClassEntry) {
808 showClassEntry((ClassEntry)m_reference.entry);
809 } else if (m_reference.entry instanceof FieldEntry) {
810 showFieldEntry((FieldEntry)m_reference.entry);
811 } else if (m_reference.entry instanceof MethodEntry) {
812 showMethodEntry((MethodEntry)m_reference.entry);
813 } else if (m_reference.entry instanceof ConstructorEntry) {
814 showConstructorEntry((ConstructorEntry)m_reference.entry);
815 } else if (m_reference.entry instanceof ArgumentEntry) {
816 showArgumentEntry((ArgumentEntry)m_reference.entry);
817 } else {
818 throw new Error("Unknown entry type: " + m_reference.entry.getClass().getName());
819 }
820
821 redraw();
822 }
823
824 private void showClassEntry(ClassEntry entry) {
825 addNameValue(m_infoPanel, "Class", entry.getName());
826 }
827
828 private void showFieldEntry(FieldEntry entry) {
829 addNameValue(m_infoPanel, "Field", entry.getName());
830 addNameValue(m_infoPanel, "Class", entry.getClassEntry().getName());
831 addNameValue(m_infoPanel, "Type", entry.getType().toString());
832 }
833
834 private void showMethodEntry(MethodEntry entry) {
835 addNameValue(m_infoPanel, "Method", entry.getName());
836 addNameValue(m_infoPanel, "Class", entry.getClassEntry().getName());
837 addNameValue(m_infoPanel, "Signature", entry.getSignature().toString());
838 }
839
840 private void showConstructorEntry(ConstructorEntry entry) {
841 addNameValue(m_infoPanel, "Constructor", entry.getClassEntry().getName());
842 if (!entry.isStatic()) {
843 addNameValue(m_infoPanel, "Signature", entry.getSignature().toString());
844 }
845 }
846
847 private void showArgumentEntry(ArgumentEntry entry) {
848 addNameValue(m_infoPanel, "Argument", entry.getName());
849 addNameValue(m_infoPanel, "Class", entry.getClassEntry().getName());
850 addNameValue(m_infoPanel, "Method", entry.getBehaviorEntry().getName());
851 addNameValue(m_infoPanel, "Index", Integer.toString(entry.getIndex()));
852 }
853
854 private void addNameValue(JPanel container, String name, String value) {
855 JPanel panel = new JPanel();
856 panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0));
857 container.add(panel);
858
859 JLabel label = new JLabel(name + ":", JLabel.RIGHT);
860 label.setPreferredSize(new Dimension(100, label.getPreferredSize().height));
861 panel.add(label);
862
863 panel.add(GuiTricks.unboldLabel(new JLabel(value, JLabel.LEFT)));
864 }
865
866 private void onCaretMove(int pos) {
867
868 Token token = m_controller.getToken(pos);
869 boolean isToken = token != null;
870
871 m_reference = m_controller.getDeobfReference(token);
872 boolean isClassEntry = isToken && m_reference.entry instanceof ClassEntry;
873 boolean isFieldEntry = isToken && m_reference.entry instanceof FieldEntry;
874 boolean isMethodEntry = isToken && m_reference.entry instanceof MethodEntry;
875 boolean isConstructorEntry = isToken && m_reference.entry instanceof ConstructorEntry;
876 boolean isInJar = isToken && m_controller.entryIsInJar(m_reference.entry);
877 boolean isRenameable = isToken && m_controller.referenceIsRenameable(m_reference);
878
879 if (isToken) {
880 showReference(m_reference);
881 } else {
882 clearReference();
883 }
884
885 m_renameMenu.setEnabled(isRenameable && isToken);
886 m_showInheritanceMenu.setEnabled(isClassEntry || isMethodEntry || isConstructorEntry);
887 m_showImplementationsMenu.setEnabled(isClassEntry || isMethodEntry);
888 m_showCallsMenu.setEnabled(isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry);
889 m_openEntryMenu.setEnabled(isInJar && (isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry));
890 m_openPreviousMenu.setEnabled(m_controller.hasPreviousLocation());
891 m_toggleMappingMenu.setEnabled(isRenameable && isToken);
892
893 if (isToken && m_controller.entryHasDeobfuscatedName(m_reference.entry)) {
894 m_toggleMappingMenu.setText("Reset to obfuscated");
895 } else {
896 m_toggleMappingMenu.setText("Mark as deobfuscated");
897 }
898 }
899
900 private void navigateTo(Entry entry) {
901 if (!m_controller.entryIsInJar(entry)) {
902 // entry is not in the jar. Ignore it
903 return;
904 }
905 if (m_reference != null) {
906 m_controller.savePreviousReference(m_reference);
907 }
908 m_controller.openDeclaration(entry);
909 }
910
911 private void navigateTo(EntryReference<Entry,Entry> reference) {
912 if (!m_controller.entryIsInJar(reference.getLocationClassEntry())) {
913 // reference is not in the jar. Ignore it
914 return;
915 }
916 if (m_reference != null) {
917 m_controller.savePreviousReference(m_reference);
918 }
919 m_controller.openReference(reference);
920 }
921
922 private void startRename() {
923
924 // init the text box
925 final JTextField text = new JTextField();
926 text.setText(m_reference.getNamableName());
927 text.setPreferredSize(new Dimension(360, text.getPreferredSize().height));
928 text.addKeyListener(new KeyAdapter() {
929 @Override
930 public void keyPressed(KeyEvent event) {
931 switch (event.getKeyCode()) {
932 case KeyEvent.VK_ENTER:
933 finishRename(text, true);
934 break;
935
936 case KeyEvent.VK_ESCAPE:
937 finishRename(text, false);
938 break;
939 }
940 }
941 });
942
943 // find the label with the name and replace it with the text box
944 JPanel panel = (JPanel)m_infoPanel.getComponent(0);
945 panel.remove(panel.getComponentCount() - 1);
946 panel.add(text);
947 text.grabFocus();
948 text.selectAll();
949
950 redraw();
951 }
952
953 private void finishRename(JTextField text, boolean saveName) {
954 String newName = text.getText();
955 if (saveName && newName != null && newName.length() > 0) {
956 try {
957 m_controller.rename(m_reference, newName);
958 } catch (IllegalNameException ex) {
959 text.setBorder(BorderFactory.createLineBorder(Color.red, 1));
960 text.setToolTipText(ex.getReason());
961 GuiTricks.showToolTipNow(text);
962 }
963 return;
964 }
965
966 // abort the rename
967 JPanel panel = (JPanel)m_infoPanel.getComponent(0);
968 panel.remove(panel.getComponentCount() - 1);
969 panel.add(GuiTricks.unboldLabel(new JLabel(m_reference.getNamableName(), JLabel.LEFT)));
970
971 m_editor.grabFocus();
972
973 redraw();
974 }
975
976 private void showInheritance() {
977
978 if (m_reference == null) {
979 return;
980 }
981
982 m_inheritanceTree.setModel(null);
983
984 if (m_reference.entry instanceof ClassEntry) {
985 // get the class inheritance
986 ClassInheritanceTreeNode classNode = m_controller.getClassInheritance((ClassEntry)m_reference.entry);
987
988 // show the tree at the root
989 TreePath path = getPathToRoot(classNode);
990 m_inheritanceTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0)));
991 m_inheritanceTree.expandPath(path);
992 m_inheritanceTree.setSelectionRow(m_inheritanceTree.getRowForPath(path));
993 } else if (m_reference.entry instanceof MethodEntry) {
994 // get the method inheritance
995 MethodInheritanceTreeNode classNode = m_controller.getMethodInheritance((MethodEntry)m_reference.entry);
996
997 // show the tree at the root
998 TreePath path = getPathToRoot(classNode);
999 m_inheritanceTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0)));
1000 m_inheritanceTree.expandPath(path);
1001 m_inheritanceTree.setSelectionRow(m_inheritanceTree.getRowForPath(path));
1002 }
1003
1004 m_tabs.setSelectedIndex(0);
1005 redraw();
1006 }
1007
1008 private void showImplementations() {
1009
1010 if (m_reference == null) {
1011 return;
1012 }
1013
1014 m_implementationsTree.setModel(null);
1015
1016 if (m_reference.entry instanceof ClassEntry) {
1017 // get the class implementations
1018 ClassImplementationsTreeNode node = m_controller.getClassImplementations((ClassEntry)m_reference.entry);
1019 if (node != null) {
1020 // show the tree at the root
1021 TreePath path = getPathToRoot(node);
1022 m_implementationsTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0)));
1023 m_implementationsTree.expandPath(path);
1024 m_implementationsTree.setSelectionRow(m_implementationsTree.getRowForPath(path));
1025 }
1026 } else if (m_reference.entry instanceof MethodEntry) {
1027 // get the method implementations
1028 MethodImplementationsTreeNode node = m_controller.getMethodImplementations((MethodEntry)m_reference.entry);
1029 if (node != null) {
1030 // show the tree at the root
1031 TreePath path = getPathToRoot(node);
1032 m_implementationsTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0)));
1033 m_implementationsTree.expandPath(path);
1034 m_implementationsTree.setSelectionRow(m_implementationsTree.getRowForPath(path));
1035 }
1036 }
1037
1038 m_tabs.setSelectedIndex(1);
1039 redraw();
1040 }
1041
1042 private void showCalls() {
1043
1044 if (m_reference == null) {
1045 return;
1046 }
1047
1048 if (m_reference.entry instanceof ClassEntry) {
1049 // look for calls to the default constructor
1050 // TODO: get a list of all the constructors and find calls to all of them
1051 BehaviorReferenceTreeNode node = m_controller.getMethodReferences(new ConstructorEntry((ClassEntry)m_reference.entry, new Signature("()V")));
1052 m_callsTree.setModel(new DefaultTreeModel(node));
1053 } else if (m_reference.entry instanceof FieldEntry) {
1054 FieldReferenceTreeNode node = m_controller.getFieldReferences((FieldEntry)m_reference.entry);
1055 m_callsTree.setModel(new DefaultTreeModel(node));
1056 } else if (m_reference.entry instanceof MethodEntry) {
1057 BehaviorReferenceTreeNode node = m_controller.getMethodReferences((MethodEntry)m_reference.entry);
1058 m_callsTree.setModel(new DefaultTreeModel(node));
1059 } else if (m_reference.entry instanceof ConstructorEntry) {
1060 BehaviorReferenceTreeNode node = m_controller.getMethodReferences((ConstructorEntry)m_reference.entry);
1061 m_callsTree.setModel(new DefaultTreeModel(node));
1062 }
1063
1064 m_tabs.setSelectedIndex(2);
1065 redraw();
1066 }
1067
1068 private void toggleMapping() {
1069 if (m_controller.entryHasDeobfuscatedName(m_reference.entry)) {
1070 m_controller.removeMapping(m_reference);
1071 } else {
1072 m_controller.markAsDeobfuscated(m_reference);
1073 }
1074 }
1075
1076 private TreePath getPathToRoot(TreeNode node) {
1077 List<TreeNode> nodes = Lists.newArrayList();
1078 TreeNode n = node;
1079 do {
1080 nodes.add(n);
1081 n = n.getParent();
1082 } while (n != null);
1083 Collections.reverse(nodes);
1084 return new TreePath(nodes.toArray());
1085 }
1086
1087 private void close() {
1088 if (!m_controller.isDirty()) {
1089 // everything is saved, we can exit safely
1090 m_frame.dispose();
1091 } else {
1092 // ask to save before closing
1093 String[] options = { "Save and exit", "Discard changes", "Cancel" };
1094 int response = JOptionPane.showOptionDialog(m_frame, "Your mappings have not been saved yet. Do you want to save?", "Save your changes?", JOptionPane.YES_NO_CANCEL_OPTION,
1095 JOptionPane.QUESTION_MESSAGE, null, options, options[2]);
1096 switch (response) {
1097 case JOptionPane.YES_OPTION: // save and exit
1098 if (m_mappingsFileChooser.getSelectedFile() != null || m_mappingsFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
1099 try {
1100 m_controller.saveMappings(m_mappingsFileChooser.getSelectedFile());
1101 m_frame.dispose();
1102 } catch (IOException ex) {
1103 throw new Error(ex);
1104 }
1105 }
1106 break;
1107
1108 case JOptionPane.NO_OPTION:
1109 // don't save, exit
1110 m_frame.dispose();
1111 break;
1112
1113 // cancel means do nothing
1114 }
1115 }
1116 }
1117
1118 private void redraw() {
1119 m_frame.validate();
1120 m_frame.repaint();
1121 }
1122}
diff --git a/src/cuchaz/enigma/gui/GuiController.java b/src/cuchaz/enigma/gui/GuiController.java
new file mode 100644
index 00000000..66906227
--- /dev/null
+++ b/src/cuchaz/enigma/gui/GuiController.java
@@ -0,0 +1,358 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.io.File;
14import java.io.FileReader;
15import java.io.FileWriter;
16import java.io.IOException;
17import java.util.Collection;
18import java.util.Deque;
19import java.util.List;
20import java.util.jar.JarFile;
21
22import com.google.common.collect.Lists;
23import com.google.common.collect.Queues;
24import com.strobel.decompiler.languages.java.ast.CompilationUnit;
25
26import cuchaz.enigma.Deobfuscator;
27import cuchaz.enigma.Deobfuscator.ProgressListener;
28import cuchaz.enigma.analysis.BehaviorReferenceTreeNode;
29import cuchaz.enigma.analysis.ClassImplementationsTreeNode;
30import cuchaz.enigma.analysis.ClassInheritanceTreeNode;
31import cuchaz.enigma.analysis.EntryReference;
32import cuchaz.enigma.analysis.FieldReferenceTreeNode;
33import cuchaz.enigma.analysis.MethodImplementationsTreeNode;
34import cuchaz.enigma.analysis.MethodInheritanceTreeNode;
35import cuchaz.enigma.analysis.SourceIndex;
36import cuchaz.enigma.analysis.Token;
37import cuchaz.enigma.gui.ProgressDialog.ProgressRunnable;
38import cuchaz.enigma.mapping.BehaviorEntry;
39import cuchaz.enigma.mapping.ClassEntry;
40import cuchaz.enigma.mapping.Entry;
41import cuchaz.enigma.mapping.FieldEntry;
42import cuchaz.enigma.mapping.MappingParseException;
43import cuchaz.enigma.mapping.MappingsReader;
44import cuchaz.enigma.mapping.MappingsWriter;
45import cuchaz.enigma.mapping.MethodEntry;
46import cuchaz.enigma.mapping.TranslationDirection;
47
48public class GuiController {
49
50 private Deobfuscator m_deobfuscator;
51 private Gui m_gui;
52 private SourceIndex m_index;
53 private ClassEntry m_currentObfClass;
54 private boolean m_isDirty;
55 private Deque<EntryReference<Entry,Entry>> m_referenceStack;
56
57 public GuiController(Gui gui) {
58 m_gui = gui;
59 m_deobfuscator = null;
60 m_index = null;
61 m_currentObfClass = null;
62 m_isDirty = false;
63 m_referenceStack = Queues.newArrayDeque();
64 }
65
66 public boolean isDirty() {
67 return m_isDirty;
68 }
69
70 public void openJar(final JarFile jar) throws IOException {
71 m_gui.onStartOpenJar();
72 m_deobfuscator = new Deobfuscator(jar);
73 m_gui.onFinishOpenJar(m_deobfuscator.getJarName());
74 refreshClasses();
75 }
76
77 public void closeJar() {
78 m_deobfuscator = null;
79 m_gui.onCloseJar();
80 }
81
82 public void openMappings(File file) throws IOException, MappingParseException {
83 FileReader in = new FileReader(file);
84 m_deobfuscator.setMappings(new MappingsReader().read(in));
85 in.close();
86 m_isDirty = false;
87 m_gui.setMappingsFile(file);
88 refreshClasses();
89 refreshCurrentClass();
90 }
91
92 public void saveMappings(File file) throws IOException {
93 FileWriter out = new FileWriter(file);
94 new MappingsWriter().write(out, m_deobfuscator.getMappings());
95 out.close();
96 m_isDirty = false;
97 }
98
99 public void closeMappings() {
100 m_deobfuscator.setMappings(null);
101 m_gui.setMappingsFile(null);
102 refreshClasses();
103 refreshCurrentClass();
104 }
105
106 public void exportSource(final File dirOut) {
107 ProgressDialog.runInThread(m_gui.getFrame(), new ProgressRunnable() {
108 @Override
109 public void run(ProgressListener progress) throws Exception {
110 m_deobfuscator.writeSources(dirOut, progress);
111 }
112 });
113 }
114
115 public void exportJar(final File fileOut) {
116 ProgressDialog.runInThread(m_gui.getFrame(), new ProgressRunnable() {
117 @Override
118 public void run(ProgressListener progress) {
119 m_deobfuscator.writeJar(fileOut, progress);
120 }
121 });
122 }
123
124 public Token getToken(int pos) {
125 if (m_index == null) {
126 return null;
127 }
128 return m_index.getReferenceToken(pos);
129 }
130
131 public EntryReference<Entry,Entry> getDeobfReference(Token token) {
132 if (m_index == null) {
133 return null;
134 }
135 return m_index.getDeobfReference(token);
136 }
137
138 public ReadableToken getReadableToken(Token token) {
139 if (m_index == null) {
140 return null;
141 }
142 return new ReadableToken(
143 m_index.getLineNumber(token.start),
144 m_index.getColumnNumber(token.start),
145 m_index.getColumnNumber(token.end)
146 );
147 }
148
149 public boolean entryHasDeobfuscatedName(Entry deobfEntry) {
150 return m_deobfuscator.hasDeobfuscatedName(m_deobfuscator.obfuscateEntry(deobfEntry));
151 }
152
153 public boolean entryIsInJar(Entry deobfEntry) {
154 return m_deobfuscator.isObfuscatedIdentifier(m_deobfuscator.obfuscateEntry(deobfEntry));
155 }
156
157 public boolean referenceIsRenameable(EntryReference<Entry,Entry> deobfReference) {
158 return m_deobfuscator.isRenameable(m_deobfuscator.obfuscateReference(deobfReference));
159 }
160
161 public ClassInheritanceTreeNode getClassInheritance(ClassEntry deobfClassEntry) {
162 ClassEntry obfClassEntry = m_deobfuscator.obfuscateEntry(deobfClassEntry);
163 ClassInheritanceTreeNode rootNode = m_deobfuscator.getJarIndex().getClassInheritance(
164 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
165 obfClassEntry
166 );
167 return ClassInheritanceTreeNode.findNode(rootNode, obfClassEntry);
168 }
169
170 public ClassImplementationsTreeNode getClassImplementations(ClassEntry deobfClassEntry) {
171 ClassEntry obfClassEntry = m_deobfuscator.obfuscateEntry(deobfClassEntry);
172 return m_deobfuscator.getJarIndex().getClassImplementations(
173 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
174 obfClassEntry
175 );
176 }
177
178 public MethodInheritanceTreeNode getMethodInheritance(MethodEntry deobfMethodEntry) {
179 MethodEntry obfMethodEntry = m_deobfuscator.obfuscateEntry(deobfMethodEntry);
180 MethodInheritanceTreeNode rootNode = m_deobfuscator.getJarIndex().getMethodInheritance(
181 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
182 obfMethodEntry
183 );
184 return MethodInheritanceTreeNode.findNode(rootNode, obfMethodEntry);
185 }
186
187 public MethodImplementationsTreeNode getMethodImplementations(MethodEntry deobfMethodEntry) {
188 MethodEntry obfMethodEntry = m_deobfuscator.obfuscateEntry(deobfMethodEntry);
189 List<MethodImplementationsTreeNode> rootNodes = m_deobfuscator.getJarIndex().getMethodImplementations(
190 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
191 obfMethodEntry
192 );
193 if (rootNodes.isEmpty()) {
194 return null;
195 }
196 if (rootNodes.size() > 1) {
197 System.err.println("WARNING: Method " + deobfMethodEntry + " implements multiple interfaces. Only showing first one.");
198 }
199 return MethodImplementationsTreeNode.findNode(rootNodes.get(0), obfMethodEntry);
200 }
201
202 public FieldReferenceTreeNode getFieldReferences(FieldEntry deobfFieldEntry) {
203 FieldEntry obfFieldEntry = m_deobfuscator.obfuscateEntry(deobfFieldEntry);
204 FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(
205 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
206 obfFieldEntry
207 );
208 rootNode.load(m_deobfuscator.getJarIndex(), true);
209 return rootNode;
210 }
211
212 public BehaviorReferenceTreeNode getMethodReferences(BehaviorEntry deobfBehaviorEntry) {
213 BehaviorEntry obfBehaviorEntry = m_deobfuscator.obfuscateEntry(deobfBehaviorEntry);
214 BehaviorReferenceTreeNode rootNode = new BehaviorReferenceTreeNode(
215 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
216 obfBehaviorEntry
217 );
218 rootNode.load(m_deobfuscator.getJarIndex(), true);
219 return rootNode;
220 }
221
222 public void rename(EntryReference<Entry,Entry> deobfReference, String newName) {
223 EntryReference<Entry,Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference);
224 m_deobfuscator.rename(obfReference.getNameableEntry(), newName);
225 m_isDirty = true;
226 refreshClasses();
227 refreshCurrentClass(obfReference);
228 }
229
230 public void removeMapping(EntryReference<Entry,Entry> deobfReference) {
231 EntryReference<Entry,Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference);
232 m_deobfuscator.removeMapping(obfReference.getNameableEntry());
233 m_isDirty = true;
234 refreshClasses();
235 refreshCurrentClass(obfReference);
236 }
237
238 public void markAsDeobfuscated(EntryReference<Entry,Entry> deobfReference) {
239 EntryReference<Entry,Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference);
240 m_deobfuscator.markAsDeobfuscated(obfReference.getNameableEntry());
241 m_isDirty = true;
242 refreshClasses();
243 refreshCurrentClass(obfReference);
244 }
245
246 public void openDeclaration(Entry deobfEntry) {
247 if (deobfEntry == null) {
248 throw new IllegalArgumentException("Entry cannot be null!");
249 }
250 openReference(new EntryReference<Entry,Entry>(deobfEntry, deobfEntry.getName()));
251 }
252
253 public void openReference(EntryReference<Entry,Entry> deobfReference) {
254 if (deobfReference == null) {
255 throw new IllegalArgumentException("Reference cannot be null!");
256 }
257
258 // get the reference target class
259 EntryReference<Entry,Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference);
260 ClassEntry obfClassEntry = obfReference.getLocationClassEntry().getOutermostClassEntry();
261 if (!m_deobfuscator.isObfuscatedIdentifier(obfClassEntry)) {
262 throw new IllegalArgumentException("Obfuscated class " + obfClassEntry + " was not found in the jar!");
263 }
264 if (m_currentObfClass == null || !m_currentObfClass.equals(obfClassEntry)) {
265 // deobfuscate the class, then navigate to the reference
266 m_currentObfClass = obfClassEntry;
267 deobfuscate(m_currentObfClass, obfReference);
268 } else {
269 showReference(obfReference);
270 }
271 }
272
273 private void showReference(EntryReference<Entry,Entry> obfReference) {
274 EntryReference<Entry,Entry> deobfReference = m_deobfuscator.deobfuscateReference(obfReference);
275 Collection<Token> tokens = m_index.getReferenceTokens(deobfReference);
276 if (tokens.isEmpty()) {
277 // DEBUG
278 System.err.println(String.format("WARNING: no tokens found for %s in %s", deobfReference, m_currentObfClass));
279 } else {
280 m_gui.showTokens(tokens);
281 }
282 }
283
284 public void savePreviousReference(EntryReference<Entry,Entry> deobfReference) {
285 m_referenceStack.push(m_deobfuscator.obfuscateReference(deobfReference));
286 }
287
288 public void openPreviousReference() {
289 if (hasPreviousLocation()) {
290 openReference(m_deobfuscator.deobfuscateReference(m_referenceStack.pop()));
291 }
292 }
293
294 public boolean hasPreviousLocation() {
295 return !m_referenceStack.isEmpty();
296 }
297
298 private void refreshClasses() {
299 List<ClassEntry> obfClasses = Lists.newArrayList();
300 List<ClassEntry> deobfClasses = Lists.newArrayList();
301 m_deobfuscator.getSeparatedClasses(obfClasses, deobfClasses);
302 m_gui.setObfClasses(obfClasses);
303 m_gui.setDeobfClasses(deobfClasses);
304 }
305
306 private void refreshCurrentClass() {
307 refreshCurrentClass(null);
308 }
309
310 private void refreshCurrentClass(EntryReference<Entry,Entry> obfReference) {
311 if (m_currentObfClass != null) {
312 deobfuscate(m_currentObfClass, obfReference);
313 }
314 }
315
316 private void deobfuscate(final ClassEntry classEntry, final EntryReference<Entry,Entry> obfReference) {
317
318 m_gui.setSource("(deobfuscating...)");
319
320 // run the deobfuscator in a separate thread so we don't block the GUI event queue
321 new Thread() {
322 @Override
323 public void run() {
324 // decompile,deobfuscate the bytecode
325 CompilationUnit sourceTree = m_deobfuscator.getSourceTree(classEntry.getClassName());
326 if (sourceTree == null) {
327 // decompilation of this class is not supported
328 m_gui.setSource("Unable to find class: " + classEntry);
329 return;
330 }
331 String source = m_deobfuscator.getSource(sourceTree);
332 m_index = m_deobfuscator.getSourceIndex(sourceTree, source);
333 m_gui.setSource(m_index.getSource());
334 if (obfReference != null) {
335 showReference(obfReference);
336 }
337
338 // set the highlighted tokens
339 List<Token> obfuscatedTokens = Lists.newArrayList();
340 List<Token> deobfuscatedTokens = Lists.newArrayList();
341 List<Token> otherTokens = Lists.newArrayList();
342 for (Token token : m_index.referenceTokens()) {
343 EntryReference<Entry,Entry> reference = m_index.getDeobfReference(token);
344 if (referenceIsRenameable(reference)) {
345 if (entryHasDeobfuscatedName(reference.getNameableEntry())) {
346 deobfuscatedTokens.add(token);
347 } else {
348 obfuscatedTokens.add(token);
349 }
350 } else {
351 otherTokens.add(token);
352 }
353 }
354 m_gui.setHighlightedTokens(obfuscatedTokens, deobfuscatedTokens, otherTokens);
355 }
356 }.start();
357 }
358}
diff --git a/src/cuchaz/enigma/gui/GuiTricks.java b/src/cuchaz/enigma/gui/GuiTricks.java
new file mode 100644
index 00000000..5dc3ffb3
--- /dev/null
+++ b/src/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 *
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/cuchaz/enigma/gui/MemberMatchingGui.java b/src/cuchaz/enigma/gui/MemberMatchingGui.java
new file mode 100644
index 00000000..150eaadb
--- /dev/null
+++ b/src/cuchaz/enigma/gui/MemberMatchingGui.java
@@ -0,0 +1,499 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.BorderLayout;
14import java.awt.Container;
15import java.awt.Dimension;
16import java.awt.FlowLayout;
17import java.awt.event.ActionEvent;
18import java.awt.event.ActionListener;
19import java.awt.event.KeyAdapter;
20import java.awt.event.KeyEvent;
21import java.util.Collection;
22import java.util.List;
23import java.util.Map;
24
25import javax.swing.BoxLayout;
26import javax.swing.ButtonGroup;
27import javax.swing.JButton;
28import javax.swing.JFrame;
29import javax.swing.JLabel;
30import javax.swing.JPanel;
31import javax.swing.JRadioButton;
32import javax.swing.JScrollPane;
33import javax.swing.JSplitPane;
34import javax.swing.WindowConstants;
35import javax.swing.text.Highlighter.HighlightPainter;
36
37import com.google.common.collect.Lists;
38import com.google.common.collect.Maps;
39
40import cuchaz.enigma.Constants;
41import cuchaz.enigma.Deobfuscator;
42import cuchaz.enigma.analysis.EntryReference;
43import cuchaz.enigma.analysis.SourceIndex;
44import cuchaz.enigma.analysis.Token;
45import cuchaz.enigma.convert.ClassMatches;
46import cuchaz.enigma.convert.MemberMatches;
47import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener;
48import cuchaz.enigma.mapping.ClassEntry;
49import cuchaz.enigma.mapping.Entry;
50import de.sciss.syntaxpane.DefaultSyntaxKit;
51
52
53public class MemberMatchingGui<T extends Entry> {
54
55 private static enum SourceType {
56 Matched {
57
58 @Override
59 public <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches) {
60 return matches.getSourceClassesWithoutUnmatchedEntries();
61 }
62 },
63 Unmatched {
64
65 @Override
66 public <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches) {
67 return matches.getSourceClassesWithUnmatchedEntries();
68 }
69 };
70
71 public JRadioButton newRadio(ActionListener listener, ButtonGroup group) {
72 JRadioButton button = new JRadioButton(name(), this == getDefault());
73 button.setActionCommand(name());
74 button.addActionListener(listener);
75 group.add(button);
76 return button;
77 }
78
79 public abstract <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches);
80
81 public static SourceType getDefault() {
82 return values()[0];
83 }
84 }
85
86 public static interface SaveListener<T extends Entry> {
87 public void save(MemberMatches<T> matches);
88 }
89
90 // controls
91 private JFrame m_frame;
92 private Map<SourceType,JRadioButton> m_sourceTypeButtons;
93 private ClassSelector m_sourceClasses;
94 private CodeReader m_sourceReader;
95 private CodeReader m_destReader;
96 private JButton m_matchButton;
97 private JButton m_unmatchableButton;
98 private JLabel m_sourceLabel;
99 private JLabel m_destLabel;
100 private HighlightPainter m_unmatchedHighlightPainter;
101 private HighlightPainter m_matchedHighlightPainter;
102
103 private ClassMatches m_classMatches;
104 private MemberMatches<T> m_memberMatches;
105 private Deobfuscator m_sourceDeobfuscator;
106 private Deobfuscator m_destDeobfuscator;
107 private SaveListener<T> m_saveListener;
108 private SourceType m_sourceType;
109 private ClassEntry m_obfSourceClass;
110 private ClassEntry m_obfDestClass;
111 private T m_obfSourceEntry;
112 private T m_obfDestEntry;
113
114 public MemberMatchingGui(ClassMatches classMatches, MemberMatches<T> fieldMatches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
115
116 m_classMatches = classMatches;
117 m_memberMatches = fieldMatches;
118 m_sourceDeobfuscator = sourceDeobfuscator;
119 m_destDeobfuscator = destDeobfuscator;
120
121 // init frame
122 m_frame = new JFrame(Constants.Name + " - Member Matcher");
123 final Container pane = m_frame.getContentPane();
124 pane.setLayout(new BorderLayout());
125
126 // init classes side
127 JPanel classesPanel = new JPanel();
128 classesPanel.setLayout(new BoxLayout(classesPanel, BoxLayout.PAGE_AXIS));
129 classesPanel.setPreferredSize(new Dimension(200, 0));
130 pane.add(classesPanel, BorderLayout.WEST);
131 classesPanel.add(new JLabel("Classes"));
132
133 // init source type radios
134 JPanel sourceTypePanel = new JPanel();
135 classesPanel.add(sourceTypePanel);
136 sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS));
137 ActionListener sourceTypeListener = new ActionListener() {
138 @Override
139 public void actionPerformed(ActionEvent event) {
140 setSourceType(SourceType.valueOf(event.getActionCommand()));
141 }
142 };
143 ButtonGroup sourceTypeButtons = new ButtonGroup();
144 m_sourceTypeButtons = Maps.newHashMap();
145 for (SourceType sourceType : SourceType.values()) {
146 JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons);
147 m_sourceTypeButtons.put(sourceType, button);
148 sourceTypePanel.add(button);
149 }
150
151 m_sourceClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator);
152 m_sourceClasses.setListener(new ClassSelectionListener() {
153 @Override
154 public void onSelectClass(ClassEntry classEntry) {
155 setSourceClass(classEntry);
156 }
157 });
158 JScrollPane sourceScroller = new JScrollPane(m_sourceClasses);
159 classesPanel.add(sourceScroller);
160
161 // init readers
162 DefaultSyntaxKit.initKit();
163 m_sourceReader = new CodeReader();
164 m_sourceReader.setSelectionListener(new CodeReader.SelectionListener() {
165 @Override
166 public void onSelect(EntryReference<Entry,Entry> reference) {
167 if (reference != null) {
168 onSelectSource(reference.entry);
169 } else {
170 onSelectSource(null);
171 }
172 }
173 });
174 m_destReader = new CodeReader();
175 m_destReader.setSelectionListener(new CodeReader.SelectionListener() {
176 @Override
177 public void onSelect(EntryReference<Entry,Entry> reference) {
178 if (reference != null) {
179 onSelectDest(reference.entry);
180 } else {
181 onSelectDest(null);
182 }
183 }
184 });
185
186 // add key bindings
187 KeyAdapter keyListener = new KeyAdapter() {
188 @Override
189 public void keyPressed(KeyEvent event) {
190 switch (event.getKeyCode()) {
191 case KeyEvent.VK_M:
192 m_matchButton.doClick();
193 break;
194 }
195 }
196 };
197 m_sourceReader.addKeyListener(keyListener);
198 m_destReader.addKeyListener(keyListener);
199
200 // init all the splits
201 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(m_sourceReader), new JScrollPane(m_destReader));
202 splitRight.setResizeWeight(0.5); // resize 50:50
203 JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, classesPanel, splitRight);
204 splitLeft.setResizeWeight(0); // let the right side take all the slack
205 pane.add(splitLeft, BorderLayout.CENTER);
206 splitLeft.resetToPreferredSizes();
207
208 // init bottom panel
209 JPanel bottomPanel = new JPanel();
210 bottomPanel.setLayout(new FlowLayout());
211 pane.add(bottomPanel, BorderLayout.SOUTH);
212
213 m_matchButton = new JButton();
214 m_unmatchableButton = new JButton();
215
216 m_sourceLabel = new JLabel();
217 bottomPanel.add(m_sourceLabel);
218 bottomPanel.add(m_matchButton);
219 bottomPanel.add(m_unmatchableButton);
220 m_destLabel = new JLabel();
221 bottomPanel.add(m_destLabel);
222
223 // show the frame
224 pane.doLayout();
225 m_frame.setSize(1024, 576);
226 m_frame.setMinimumSize(new Dimension(640, 480));
227 m_frame.setVisible(true);
228 m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
229
230 m_unmatchedHighlightPainter = new ObfuscatedHighlightPainter();
231 m_matchedHighlightPainter = new DeobfuscatedHighlightPainter();
232
233 // init state
234 m_saveListener = null;
235 m_obfSourceClass = null;
236 m_obfDestClass = null;
237 m_obfSourceEntry = null;
238 m_obfDestEntry = null;
239 setSourceType(SourceType.getDefault());
240 updateButtons();
241 }
242
243 protected void setSourceType(SourceType val) {
244 m_sourceType = val;
245 updateSourceClasses();
246 }
247
248 public void setSaveListener(SaveListener<T> val) {
249 m_saveListener = val;
250 }
251
252 private void updateSourceClasses() {
253
254 String selectedPackage = m_sourceClasses.getSelectedPackage();
255
256 List<ClassEntry> deobfClassEntries = Lists.newArrayList();
257 for (ClassEntry entry : m_sourceType.getObfSourceClasses(m_memberMatches)) {
258 deobfClassEntries.add(m_sourceDeobfuscator.deobfuscateEntry(entry));
259 }
260 m_sourceClasses.setClasses(deobfClassEntries);
261
262 if (selectedPackage != null) {
263 m_sourceClasses.expandPackage(selectedPackage);
264 }
265
266 for (SourceType sourceType : SourceType.values()) {
267 m_sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)",
268 sourceType.name(), sourceType.getObfSourceClasses(m_memberMatches).size()
269 ));
270 }
271 }
272
273 protected void setSourceClass(ClassEntry sourceClass) {
274
275 m_obfSourceClass = m_sourceDeobfuscator.obfuscateEntry(sourceClass);
276 m_obfDestClass = m_classMatches.getUniqueMatches().get(m_obfSourceClass);
277 if (m_obfDestClass == null) {
278 throw new Error("No matching dest class for source class: " + m_obfSourceClass);
279 }
280
281 m_sourceReader.decompileClass(m_obfSourceClass, m_sourceDeobfuscator, false, new Runnable() {
282 @Override
283 public void run() {
284 updateSourceHighlights();
285 }
286 });
287 m_destReader.decompileClass(m_obfDestClass, m_destDeobfuscator, false, new Runnable() {
288 @Override
289 public void run() {
290 updateDestHighlights();
291 }
292 });
293 }
294
295 protected void updateSourceHighlights() {
296 highlightEntries(m_sourceReader, m_sourceDeobfuscator, m_memberMatches.matches().keySet(), m_memberMatches.getUnmatchedSourceEntries());
297 }
298
299 protected void updateDestHighlights() {
300 highlightEntries(m_destReader, m_destDeobfuscator, m_memberMatches.matches().values(), m_memberMatches.getUnmatchedDestEntries());
301 }
302
303 private void highlightEntries(CodeReader reader, Deobfuscator deobfuscator, Collection<T> obfMatchedEntries, Collection<T> obfUnmatchedEntries) {
304 reader.clearHighlights();
305 SourceIndex index = reader.getSourceIndex();
306
307 // matched fields
308 for (T obfT : obfMatchedEntries) {
309 T deobfT = deobfuscator.deobfuscateEntry(obfT);
310 Token token = index.getDeclarationToken(deobfT);
311 if (token != null) {
312 reader.setHighlightedToken(token, m_matchedHighlightPainter);
313 }
314 }
315
316 // unmatched fields
317 for (T obfT : obfUnmatchedEntries) {
318 T deobfT = deobfuscator.deobfuscateEntry(obfT);
319 Token token = index.getDeclarationToken(deobfT);
320 if (token != null) {
321 reader.setHighlightedToken(token, m_unmatchedHighlightPainter);
322 }
323 }
324 }
325
326 private boolean isSelectionMatched() {
327 return m_obfSourceEntry != null && m_obfDestEntry != null
328 && m_memberMatches.isMatched(m_obfSourceEntry, m_obfDestEntry);
329 }
330
331 protected void onSelectSource(Entry source) {
332
333 // start with no selection
334 if (isSelectionMatched()) {
335 setDest(null);
336 }
337 setSource(null);
338
339 // then look for a valid source selection
340 if (source != null) {
341
342 // this looks really scary, but it's actually ok
343 // Deobfuscator.obfuscateEntry can handle all implementations of Entry
344 // and MemberMatches.hasSource() will only pass entries that actually match T
345 @SuppressWarnings("unchecked")
346 T sourceEntry = (T)source;
347
348 T obfSourceEntry = m_sourceDeobfuscator.obfuscateEntry(sourceEntry);
349 if (m_memberMatches.hasSource(obfSourceEntry)) {
350 setSource(obfSourceEntry);
351
352 // look for a matched dest too
353 T obfDestEntry = m_memberMatches.matches().get(obfSourceEntry);
354 if (obfDestEntry != null) {
355 setDest(obfDestEntry);
356 }
357 }
358 }
359
360 updateButtons();
361 }
362
363 protected void onSelectDest(Entry dest) {
364
365 // start with no selection
366 if (isSelectionMatched()) {
367 setSource(null);
368 }
369 setDest(null);
370
371 // then look for a valid dest selection
372 if (dest != null) {
373
374 // this looks really scary, but it's actually ok
375 // Deobfuscator.obfuscateEntry can handle all implementations of Entry
376 // and MemberMatches.hasSource() will only pass entries that actually match T
377 @SuppressWarnings("unchecked")
378 T destEntry = (T)dest;
379
380 T obfDestEntry = m_destDeobfuscator.obfuscateEntry(destEntry);
381 if (m_memberMatches.hasDest(obfDestEntry)) {
382 setDest(obfDestEntry);
383
384 // look for a matched source too
385 T obfSourceEntry = m_memberMatches.matches().inverse().get(obfDestEntry);
386 if (obfSourceEntry != null) {
387 setSource(obfSourceEntry);
388 }
389 }
390 }
391
392 updateButtons();
393 }
394
395 private void setSource(T obfEntry) {
396 if (obfEntry == null) {
397 m_obfSourceEntry = obfEntry;
398 m_sourceLabel.setText("");
399 } else {
400 m_obfSourceEntry = obfEntry;
401 m_sourceLabel.setText(getEntryLabel(obfEntry, m_sourceDeobfuscator));
402 }
403 }
404
405 private void setDest(T obfEntry) {
406 if (obfEntry == null) {
407 m_obfDestEntry = obfEntry;
408 m_destLabel.setText("");
409 } else {
410 m_obfDestEntry = obfEntry;
411 m_destLabel.setText(getEntryLabel(obfEntry, m_destDeobfuscator));
412 }
413 }
414
415 private String getEntryLabel(T obfEntry, Deobfuscator deobfuscator) {
416 // show obfuscated and deobfuscated names, but no types/signatures
417 T deobfEntry = deobfuscator.deobfuscateEntry(obfEntry);
418 return String.format("%s (%s)", deobfEntry.getName(), obfEntry.getName());
419 }
420
421 private void updateButtons() {
422
423 GuiTricks.deactivateButton(m_matchButton);
424 GuiTricks.deactivateButton(m_unmatchableButton);
425
426 if (m_obfSourceEntry != null && m_obfDestEntry != null) {
427 if (m_memberMatches.isMatched(m_obfSourceEntry, m_obfDestEntry)) {
428 GuiTricks.activateButton(m_matchButton, "Unmatch", new ActionListener() {
429 @Override
430 public void actionPerformed(ActionEvent event) {
431 unmatch();
432 }
433 });
434 } else if (!m_memberMatches.isMatchedSourceEntry(m_obfSourceEntry) && !m_memberMatches.isMatchedDestEntry(m_obfDestEntry)) {
435 GuiTricks.activateButton(m_matchButton, "Match", new ActionListener() {
436 @Override
437 public void actionPerformed(ActionEvent event) {
438 match();
439 }
440 });
441 }
442 } else if (m_obfSourceEntry != null) {
443 GuiTricks.activateButton(m_unmatchableButton, "Set Unmatchable", new ActionListener() {
444 @Override
445 public void actionPerformed(ActionEvent event) {
446 unmatchable();
447 }
448 });
449 }
450 }
451
452 protected void match() {
453
454 // update the field matches
455 m_memberMatches.makeMatch(m_obfSourceEntry, m_obfDestEntry);
456 save();
457
458 // update the ui
459 onSelectSource(null);
460 onSelectDest(null);
461 updateSourceHighlights();
462 updateDestHighlights();
463 updateSourceClasses();
464 }
465
466 protected void unmatch() {
467
468 // update the field matches
469 m_memberMatches.unmakeMatch(m_obfSourceEntry, m_obfDestEntry);
470 save();
471
472 // update the ui
473 onSelectSource(null);
474 onSelectDest(null);
475 updateSourceHighlights();
476 updateDestHighlights();
477 updateSourceClasses();
478 }
479
480 protected void unmatchable() {
481
482 // update the field matches
483 m_memberMatches.makeSourceUnmatchable(m_obfSourceEntry);
484 save();
485
486 // update the ui
487 onSelectSource(null);
488 onSelectDest(null);
489 updateSourceHighlights();
490 updateDestHighlights();
491 updateSourceClasses();
492 }
493
494 private void save() {
495 if (m_saveListener != null) {
496 m_saveListener.save(m_memberMatches);
497 }
498 }
499}
diff --git a/src/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java b/src/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java
new file mode 100644
index 00000000..4c3714a9
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java
@@ -0,0 +1,21 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Color;
14
15public class ObfuscatedHighlightPainter extends BoxHighlightPainter {
16
17 public ObfuscatedHighlightPainter() {
18 // red ish
19 super(new Color(255, 220, 220), new Color(160, 80, 80));
20 }
21}
diff --git a/src/cuchaz/enigma/gui/OtherHighlightPainter.java b/src/cuchaz/enigma/gui/OtherHighlightPainter.java
new file mode 100644
index 00000000..8d3fbe86
--- /dev/null
+++ b/src/cuchaz/enigma/gui/OtherHighlightPainter.java
@@ -0,0 +1,21 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Color;
14
15public class OtherHighlightPainter extends BoxHighlightPainter {
16
17 public OtherHighlightPainter() {
18 // grey
19 super(null, new Color(180, 180, 180));
20 }
21}
diff --git a/src/cuchaz/enigma/gui/ProgressDialog.java b/src/cuchaz/enigma/gui/ProgressDialog.java
new file mode 100644
index 00000000..1c20f10b
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ProgressDialog.java
@@ -0,0 +1,105 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.BorderLayout;
14import java.awt.Container;
15import java.awt.Dimension;
16import java.awt.FlowLayout;
17
18import javax.swing.BorderFactory;
19import javax.swing.JFrame;
20import javax.swing.JLabel;
21import javax.swing.JPanel;
22import javax.swing.JProgressBar;
23import javax.swing.WindowConstants;
24
25import cuchaz.enigma.Constants;
26import cuchaz.enigma.Deobfuscator.ProgressListener;
27
28public class ProgressDialog implements ProgressListener, AutoCloseable {
29
30 private JFrame m_frame;
31 private JLabel m_title;
32 private JLabel m_text;
33 private JProgressBar m_progress;
34
35 public ProgressDialog(JFrame parent) {
36
37 // init frame
38 m_frame = new JFrame(Constants.Name + " - Operation in progress");
39 final Container pane = m_frame.getContentPane();
40 FlowLayout layout = new FlowLayout();
41 layout.setAlignment(FlowLayout.LEFT);
42 pane.setLayout(layout);
43
44 m_title = new JLabel();
45 pane.add(m_title);
46
47 // set up the progress bar
48 JPanel panel = new JPanel();
49 pane.add(panel);
50 panel.setLayout(new BorderLayout());
51 m_text = GuiTricks.unboldLabel(new JLabel());
52 m_progress = new JProgressBar();
53 m_text.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0));
54 panel.add(m_text, BorderLayout.NORTH);
55 panel.add(m_progress, BorderLayout.CENTER);
56 panel.setPreferredSize(new Dimension(360, 50));
57
58 // show the frame
59 pane.doLayout();
60 m_frame.setSize(400, 120);
61 m_frame.setResizable(false);
62 m_frame.setLocationRelativeTo(parent);
63 m_frame.setVisible(true);
64 m_frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
65 }
66
67 public void close() {
68 m_frame.dispose();
69 }
70
71 @Override
72 public void init(int totalWork, String title) {
73 m_title.setText(title);
74 m_progress.setMinimum(0);
75 m_progress.setMaximum(totalWork);
76 m_progress.setValue(0);
77 }
78
79 @Override
80 public void onProgress(int numDone, String message) {
81 m_text.setText(message);
82 m_progress.setValue(numDone);
83
84 // update the frame
85 m_frame.validate();
86 m_frame.repaint();
87 }
88
89 public static interface ProgressRunnable {
90 void run(ProgressListener listener) throws Exception;
91 }
92
93 public static void runInThread(final JFrame parent, final ProgressRunnable runnable) {
94 new Thread() {
95 @Override
96 public void run() {
97 try (ProgressDialog progress = new ProgressDialog(parent)) {
98 runnable.run(progress);
99 } catch (Exception ex) {
100 throw new Error(ex);
101 }
102 }
103 }.start();
104 }
105}
diff --git a/src/cuchaz/enigma/gui/ReadableToken.java b/src/cuchaz/enigma/gui/ReadableToken.java
new file mode 100644
index 00000000..0741af39
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ReadableToken.java
@@ -0,0 +1,36 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13public class ReadableToken {
14
15 public int line;
16 public int startColumn;
17 public int endColumn;
18
19 public ReadableToken(int line, int startColumn, int endColumn) {
20 this.line = line;
21 this.startColumn = startColumn;
22 this.endColumn = endColumn;
23 }
24
25 @Override
26 public String toString() {
27 StringBuilder buf = new StringBuilder();
28 buf.append("line ");
29 buf.append(line);
30 buf.append(" columns ");
31 buf.append(startColumn);
32 buf.append("-");
33 buf.append(endColumn);
34 return buf.toString();
35 }
36}
diff --git a/src/cuchaz/enigma/gui/RenameListener.java b/src/cuchaz/enigma/gui/RenameListener.java
new file mode 100644
index 00000000..8b515bbd
--- /dev/null
+++ b/src/cuchaz/enigma/gui/RenameListener.java
@@ -0,0 +1,17 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import cuchaz.enigma.mapping.Entry;
14
15public interface RenameListener {
16 void rename(Entry obfEntry, String newName);
17}
diff --git a/src/cuchaz/enigma/gui/ScoredClassEntry.java b/src/cuchaz/enigma/gui/ScoredClassEntry.java
new file mode 100644
index 00000000..60704528
--- /dev/null
+++ b/src/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 *
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/cuchaz/enigma/gui/SelectionHighlightPainter.java b/src/cuchaz/enigma/gui/SelectionHighlightPainter.java
new file mode 100644
index 00000000..4165da4a
--- /dev/null
+++ b/src/cuchaz/enigma/gui/SelectionHighlightPainter.java
@@ -0,0 +1,34 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.BasicStroke;
14import java.awt.Color;
15import java.awt.Graphics;
16import java.awt.Graphics2D;
17import java.awt.Rectangle;
18import java.awt.Shape;
19
20import javax.swing.text.Highlighter;
21import javax.swing.text.JTextComponent;
22
23public class SelectionHighlightPainter implements Highlighter.HighlightPainter {
24
25 @Override
26 public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) {
27 // draw a thick border
28 Graphics2D g2d = (Graphics2D)g;
29 Rectangle bounds = BoxHighlightPainter.getBounds(text, start, end);
30 g2d.setColor(Color.black);
31 g2d.setStroke(new BasicStroke(2.0f));
32 g2d.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4);
33 }
34}
diff --git a/src/cuchaz/enigma/gui/TokenListCellRenderer.java b/src/cuchaz/enigma/gui/TokenListCellRenderer.java
new file mode 100644
index 00000000..e4f7c873
--- /dev/null
+++ b/src/cuchaz/enigma/gui/TokenListCellRenderer.java
@@ -0,0 +1,38 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Component;
14
15import javax.swing.DefaultListCellRenderer;
16import javax.swing.JLabel;
17import javax.swing.JList;
18import javax.swing.ListCellRenderer;
19
20import cuchaz.enigma.analysis.Token;
21
22public class TokenListCellRenderer implements ListCellRenderer<Token> {
23
24 private GuiController m_controller;
25 private DefaultListCellRenderer m_defaultRenderer;
26
27 public TokenListCellRenderer(GuiController controller) {
28 m_controller = controller;
29 m_defaultRenderer = new DefaultListCellRenderer();
30 }
31
32 @Override
33 public Component getListCellRendererComponent(JList<? extends Token> list, Token token, int index, boolean isSelected, boolean hasFocus) {
34 JLabel label = (JLabel)m_defaultRenderer.getListCellRendererComponent(list, token, index, isSelected, hasFocus);
35 label.setText(m_controller.getReadableToken(token).toString());
36 return label;
37 }
38}
diff --git a/src/cuchaz/enigma/mapping/ArgumentEntry.java b/src/cuchaz/enigma/mapping/ArgumentEntry.java
new file mode 100644
index 00000000..9d99016e
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/ArgumentEntry.java
@@ -0,0 +1,116 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14
15import cuchaz.enigma.Util;
16
17public class ArgumentEntry implements Entry, Serializable {
18
19 private static final long serialVersionUID = 4472172468162696006L;
20
21 private BehaviorEntry m_behaviorEntry;
22 private int m_index;
23 private String m_name;
24
25 public ArgumentEntry(BehaviorEntry behaviorEntry, int index, String name) {
26 if (behaviorEntry == null) {
27 throw new IllegalArgumentException("Behavior cannot be null!");
28 }
29 if (index < 0) {
30 throw new IllegalArgumentException("Index must be non-negative!");
31 }
32 if (name == null) {
33 throw new IllegalArgumentException("Argument name cannot be null!");
34 }
35
36 m_behaviorEntry = behaviorEntry;
37 m_index = index;
38 m_name = name;
39 }
40
41 public ArgumentEntry(ArgumentEntry other) {
42 m_behaviorEntry = (BehaviorEntry)m_behaviorEntry.cloneToNewClass(getClassEntry());
43 m_index = other.m_index;
44 m_name = other.m_name;
45 }
46
47 public ArgumentEntry(ArgumentEntry other, String newClassName) {
48 m_behaviorEntry = (BehaviorEntry)other.m_behaviorEntry.cloneToNewClass(new ClassEntry(newClassName));
49 m_index = other.m_index;
50 m_name = other.m_name;
51 }
52
53 public BehaviorEntry getBehaviorEntry() {
54 return m_behaviorEntry;
55 }
56
57 public int getIndex() {
58 return m_index;
59 }
60
61 @Override
62 public String getName() {
63 return m_name;
64 }
65
66 @Override
67 public ClassEntry getClassEntry() {
68 return m_behaviorEntry.getClassEntry();
69 }
70
71 @Override
72 public String getClassName() {
73 return m_behaviorEntry.getClassName();
74 }
75
76 @Override
77 public ArgumentEntry cloneToNewClass(ClassEntry classEntry) {
78 return new ArgumentEntry(this, classEntry.getName());
79 }
80
81 public String getMethodName() {
82 return m_behaviorEntry.getName();
83 }
84
85 public Signature getMethodSignature() {
86 return m_behaviorEntry.getSignature();
87 }
88
89 @Override
90 public int hashCode() {
91 return Util.combineHashesOrdered(
92 m_behaviorEntry,
93 Integer.valueOf(m_index).hashCode(),
94 m_name.hashCode()
95 );
96 }
97
98 @Override
99 public boolean equals(Object other) {
100 if (other instanceof ArgumentEntry) {
101 return equals((ArgumentEntry)other);
102 }
103 return false;
104 }
105
106 public boolean equals(ArgumentEntry other) {
107 return m_behaviorEntry.equals(other.m_behaviorEntry)
108 && m_index == other.m_index
109 && m_name.equals(other.m_name);
110 }
111
112 @Override
113 public String toString() {
114 return m_behaviorEntry.toString() + "(" + m_index + ":" + m_name + ")";
115 }
116}
diff --git a/src/cuchaz/enigma/mapping/ArgumentMapping.java b/src/cuchaz/enigma/mapping/ArgumentMapping.java
new file mode 100644
index 00000000..a0055a63
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/ArgumentMapping.java
@@ -0,0 +1,49 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14
15public class ArgumentMapping implements Serializable, Comparable<ArgumentMapping> {
16
17 private static final long serialVersionUID = 8610742471440861315L;
18
19 private int m_index;
20 private String m_name;
21
22 // NOTE: this argument order is important for the MethodReader/MethodWriter
23 public ArgumentMapping(int index, String name) {
24 m_index = index;
25 m_name = NameValidator.validateArgumentName(name);
26 }
27
28 public ArgumentMapping(ArgumentMapping other) {
29 m_index = other.m_index;
30 m_name = other.m_name;
31 }
32
33 public int getIndex() {
34 return m_index;
35 }
36
37 public String getName() {
38 return m_name;
39 }
40
41 public void setName(String val) {
42 m_name = NameValidator.validateArgumentName(val);
43 }
44
45 @Override
46 public int compareTo(ArgumentMapping other) {
47 return Integer.compare(m_index, other.m_index);
48 }
49}
diff --git a/src/cuchaz/enigma/mapping/BehaviorEntry.java b/src/cuchaz/enigma/mapping/BehaviorEntry.java
new file mode 100644
index 00000000..031d2670
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/BehaviorEntry.java
@@ -0,0 +1,15 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13public interface BehaviorEntry extends Entry {
14 Signature getSignature();
15}
diff --git a/src/cuchaz/enigma/mapping/ClassEntry.java b/src/cuchaz/enigma/mapping/ClassEntry.java
new file mode 100644
index 00000000..373203f0
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/ClassEntry.java
@@ -0,0 +1,172 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14import java.util.List;
15
16import com.google.common.collect.Lists;
17
18public class ClassEntry implements Entry, Serializable {
19
20 private static final long serialVersionUID = 4235460580973955811L;
21
22 private String m_name;
23
24 public ClassEntry(String className) {
25 if (className == null) {
26 throw new IllegalArgumentException("Class name cannot be null!");
27 }
28 if (className.indexOf('.') >= 0) {
29 throw new IllegalArgumentException("Class name must be in JVM format. ie, path/to/package/class$inner : " + className);
30 }
31
32 m_name = className;
33
34 if (isInnerClass() && getInnermostClassName().indexOf('/') >= 0) {
35 throw new IllegalArgumentException("Inner class must not have a package: " + className);
36 }
37 }
38
39 public ClassEntry(ClassEntry other) {
40 m_name = other.m_name;
41 }
42
43 @Override
44 public String getName() {
45 return m_name;
46 }
47
48 @Override
49 public String getClassName() {
50 return m_name;
51 }
52
53 @Override
54 public ClassEntry getClassEntry() {
55 return this;
56 }
57
58 @Override
59 public ClassEntry cloneToNewClass(ClassEntry classEntry) {
60 return classEntry;
61 }
62
63 @Override
64 public int hashCode() {
65 return m_name.hashCode();
66 }
67
68 @Override
69 public boolean equals(Object other) {
70 if (other instanceof ClassEntry) {
71 return equals((ClassEntry)other);
72 }
73 return false;
74 }
75
76 public boolean equals(ClassEntry other) {
77 return m_name.equals(other.m_name);
78 }
79
80 @Override
81 public String toString() {
82 return m_name;
83 }
84
85 public boolean isInnerClass() {
86 return m_name.lastIndexOf('$') >= 0;
87 }
88
89 public List<String> getClassChainNames() {
90 return Lists.newArrayList(m_name.split("\\$"));
91 }
92
93 public List<ClassEntry> getClassChain() {
94 List<ClassEntry> entries = Lists.newArrayList();
95 StringBuilder buf = new StringBuilder();
96 for (String name : getClassChainNames()) {
97 if (buf.length() > 0) {
98 buf.append("$");
99 }
100 buf.append(name);
101 entries.add(new ClassEntry(buf.toString()));
102 }
103 return entries;
104 }
105
106 public String getOutermostClassName() {
107 if (isInnerClass()) {
108 return m_name.substring(0, m_name.indexOf('$'));
109 }
110 return m_name;
111 }
112
113 public ClassEntry getOutermostClassEntry() {
114 return new ClassEntry(getOutermostClassName());
115 }
116
117 public String getOuterClassName() {
118 if (!isInnerClass()) {
119 throw new Error("This is not an inner class!");
120 }
121 return m_name.substring(0, m_name.lastIndexOf('$'));
122 }
123
124 public ClassEntry getOuterClassEntry() {
125 return new ClassEntry(getOuterClassName());
126 }
127
128 public String getInnermostClassName() {
129 if (!isInnerClass()) {
130 throw new Error("This is not an inner class!");
131 }
132 return m_name.substring(m_name.lastIndexOf('$') + 1);
133 }
134
135 public boolean isInDefaultPackage() {
136 return m_name.indexOf('/') < 0;
137 }
138
139 public String getPackageName() {
140 int pos = m_name.lastIndexOf('/');
141 if (pos > 0) {
142 return m_name.substring(0, pos);
143 }
144 return null;
145 }
146
147 public String getSimpleName() {
148 int pos = m_name.lastIndexOf('/');
149 if (pos > 0) {
150 return m_name.substring(pos + 1);
151 }
152 return m_name;
153 }
154
155 public ClassEntry buildClassEntry(List<ClassEntry> classChain) {
156 assert(classChain.contains(this));
157 StringBuilder buf = new StringBuilder();
158 for (ClassEntry chainEntry : classChain) {
159 if (buf.length() == 0) {
160 buf.append(chainEntry.getName());
161 } else {
162 buf.append("$");
163 buf.append(chainEntry.isInnerClass() ? chainEntry.getInnermostClassName() : chainEntry.getSimpleName());
164 }
165
166 if (chainEntry == this) {
167 break;
168 }
169 }
170 return new ClassEntry(buf.toString());
171 }
172}
diff --git a/src/cuchaz/enigma/mapping/ClassMapping.java b/src/cuchaz/enigma/mapping/ClassMapping.java
new file mode 100644
index 00000000..0b0105ec
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/ClassMapping.java
@@ -0,0 +1,460 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14import java.util.ArrayList;
15import java.util.Map;
16
17import com.google.common.collect.Maps;
18
19public class ClassMapping implements Serializable, Comparable<ClassMapping> {
20
21 private static final long serialVersionUID = -5148491146902340107L;
22
23 private String m_obfFullName;
24 private String m_obfSimpleName;
25 private String m_deobfName;
26 private Map<String,ClassMapping> m_innerClassesByObfSimple;
27 private Map<String,ClassMapping> m_innerClassesByDeobf;
28 private Map<String,FieldMapping> m_fieldsByObf;
29 private Map<String,FieldMapping> m_fieldsByDeobf;
30 private Map<String,MethodMapping> m_methodsByObf;
31 private Map<String,MethodMapping> m_methodsByDeobf;
32
33 public ClassMapping(String obfFullName) {
34 this(obfFullName, null);
35 }
36
37 public ClassMapping(String obfFullName, String deobfName) {
38 m_obfFullName = obfFullName;
39 ClassEntry classEntry = new ClassEntry(obfFullName);
40 m_obfSimpleName = classEntry.isInnerClass() ? classEntry.getInnermostClassName() : classEntry.getSimpleName();
41 m_deobfName = NameValidator.validateClassName(deobfName, false);
42 m_innerClassesByObfSimple = Maps.newHashMap();
43 m_innerClassesByDeobf = Maps.newHashMap();
44 m_fieldsByObf = Maps.newHashMap();
45 m_fieldsByDeobf = Maps.newHashMap();
46 m_methodsByObf = Maps.newHashMap();
47 m_methodsByDeobf = Maps.newHashMap();
48 }
49
50 public String getObfFullName() {
51 return m_obfFullName;
52 }
53
54 public String getObfSimpleName() {
55 return m_obfSimpleName;
56 }
57
58 public String getDeobfName() {
59 return m_deobfName;
60 }
61
62 public void setDeobfName(String val) {
63 m_deobfName = NameValidator.validateClassName(val, false);
64 }
65
66 //// INNER CLASSES ////////
67
68 public Iterable<ClassMapping> innerClasses() {
69 assert (m_innerClassesByObfSimple.size() >= m_innerClassesByDeobf.size());
70 return m_innerClassesByObfSimple.values();
71 }
72
73 public void addInnerClassMapping(ClassMapping classMapping) {
74 boolean obfWasAdded = m_innerClassesByObfSimple.put(classMapping.getObfSimpleName(), classMapping) == null;
75 assert (obfWasAdded);
76 if (classMapping.getDeobfName() != null) {
77 assert (isSimpleClassName(classMapping.getDeobfName()));
78 boolean deobfWasAdded = m_innerClassesByDeobf.put(classMapping.getDeobfName(), classMapping) == null;
79 assert (deobfWasAdded);
80 }
81 }
82
83 public void removeInnerClassMapping(ClassMapping classMapping) {
84 boolean obfWasRemoved = m_innerClassesByObfSimple.remove(classMapping.getObfSimpleName()) != null;
85 assert (obfWasRemoved);
86 if (classMapping.getDeobfName() != null) {
87 boolean deobfWasRemoved = m_innerClassesByDeobf.remove(classMapping.getDeobfName()) != null;
88 assert (deobfWasRemoved);
89 }
90 }
91
92 public ClassMapping getOrCreateInnerClass(ClassEntry obfInnerClass) {
93 ClassMapping classMapping = m_innerClassesByObfSimple.get(obfInnerClass.getInnermostClassName());
94 if (classMapping == null) {
95 classMapping = new ClassMapping(obfInnerClass.getName());
96 boolean wasAdded = m_innerClassesByObfSimple.put(classMapping.getObfSimpleName(), classMapping) == null;
97 assert (wasAdded);
98 }
99 return classMapping;
100 }
101
102 public ClassMapping getInnerClassByObfSimple(String obfSimpleName) {
103 assert (isSimpleClassName(obfSimpleName));
104 return m_innerClassesByObfSimple.get(obfSimpleName);
105 }
106
107 public ClassMapping getInnerClassByDeobf(String deobfName) {
108 assert (isSimpleClassName(deobfName));
109 return m_innerClassesByDeobf.get(deobfName);
110 }
111
112 public ClassMapping getInnerClassByDeobfThenObfSimple(String name) {
113 ClassMapping classMapping = getInnerClassByDeobf(name);
114 if (classMapping == null) {
115 classMapping = getInnerClassByObfSimple(name);
116 }
117 return classMapping;
118 }
119
120 public String getDeobfInnerClassName(String obfSimpleName) {
121 assert (isSimpleClassName(obfSimpleName));
122 ClassMapping classMapping = m_innerClassesByObfSimple.get(obfSimpleName);
123 if (classMapping != null) {
124 return classMapping.getDeobfName();
125 }
126 return null;
127 }
128
129 public void setInnerClassName(ClassEntry obfInnerClass, String deobfName) {
130 ClassMapping classMapping = getOrCreateInnerClass(obfInnerClass);
131 if (classMapping.getDeobfName() != null) {
132 boolean wasRemoved = m_innerClassesByDeobf.remove(classMapping.getDeobfName()) != null;
133 assert (wasRemoved);
134 }
135 classMapping.setDeobfName(deobfName);
136 if (deobfName != null) {
137 assert (isSimpleClassName(deobfName));
138 boolean wasAdded = m_innerClassesByDeobf.put(deobfName, classMapping) == null;
139 assert (wasAdded);
140 }
141 }
142
143 public boolean hasInnerClassByObfSimple(String obfSimpleName) {
144 return m_innerClassesByObfSimple.containsKey(obfSimpleName);
145 }
146
147 public boolean hasInnerClassByDeobf(String deobfName) {
148 return m_innerClassesByDeobf.containsKey(deobfName);
149 }
150
151
152 //// FIELDS ////////
153
154 public Iterable<FieldMapping> fields() {
155 assert (m_fieldsByObf.size() == m_fieldsByDeobf.size());
156 return m_fieldsByObf.values();
157 }
158
159 public boolean containsObfField(String obfName, Type obfType) {
160 return m_fieldsByObf.containsKey(getFieldKey(obfName, obfType));
161 }
162
163 public boolean containsDeobfField(String deobfName, Type deobfType) {
164 return m_fieldsByDeobf.containsKey(getFieldKey(deobfName, deobfType));
165 }
166
167 public void addFieldMapping(FieldMapping fieldMapping) {
168 String obfKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType());
169 if (m_fieldsByObf.containsKey(obfKey)) {
170 throw new Error("Already have mapping for " + m_obfFullName + "." + obfKey);
171 }
172 String deobfKey = getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfType());
173 if (m_fieldsByDeobf.containsKey(deobfKey)) {
174 throw new Error("Already have mapping for " + m_deobfName + "." + deobfKey);
175 }
176 boolean obfWasAdded = m_fieldsByObf.put(obfKey, fieldMapping) == null;
177 assert (obfWasAdded);
178 boolean deobfWasAdded = m_fieldsByDeobf.put(deobfKey, fieldMapping) == null;
179 assert (deobfWasAdded);
180 assert (m_fieldsByObf.size() == m_fieldsByDeobf.size());
181 }
182
183 public void removeFieldMapping(FieldMapping fieldMapping) {
184 boolean obfWasRemoved = m_fieldsByObf.remove(getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType())) != null;
185 assert (obfWasRemoved);
186 if (fieldMapping.getDeobfName() != null) {
187 boolean deobfWasRemoved = m_fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfType())) != null;
188 assert (deobfWasRemoved);
189 }
190 }
191
192 public FieldMapping getFieldByObf(String obfName, Type obfType) {
193 return m_fieldsByObf.get(getFieldKey(obfName, obfType));
194 }
195
196 public FieldMapping getFieldByDeobf(String deobfName, Type obfType) {
197 return m_fieldsByDeobf.get(getFieldKey(deobfName, obfType));
198 }
199
200 public String getObfFieldName(String deobfName, Type obfType) {
201 FieldMapping fieldMapping = m_fieldsByDeobf.get(getFieldKey(deobfName, obfType));
202 if (fieldMapping != null) {
203 return fieldMapping.getObfName();
204 }
205 return null;
206 }
207
208 public String getDeobfFieldName(String obfName, Type obfType) {
209 FieldMapping fieldMapping = m_fieldsByObf.get(getFieldKey(obfName, obfType));
210 if (fieldMapping != null) {
211 return fieldMapping.getDeobfName();
212 }
213 return null;
214 }
215
216 private String getFieldKey(String name, Type type) {
217 if (name == null) {
218 throw new IllegalArgumentException("name cannot be null!");
219 }
220 if (type == null) {
221 throw new IllegalArgumentException("type cannot be null!");
222 }
223 return name + ":" + type;
224 }
225
226
227 public void setFieldName(String obfName, Type obfType, String deobfName) {
228 assert(deobfName != null);
229 FieldMapping fieldMapping = m_fieldsByObf.get(getFieldKey(obfName, obfType));
230 if (fieldMapping == null) {
231 fieldMapping = new FieldMapping(obfName, obfType, deobfName);
232 boolean obfWasAdded = m_fieldsByObf.put(getFieldKey(obfName, obfType), fieldMapping) == null;
233 assert (obfWasAdded);
234 } else {
235 boolean wasRemoved = m_fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), obfType)) != null;
236 assert (wasRemoved);
237 }
238 fieldMapping.setDeobfName(deobfName);
239 if (deobfName != null) {
240 boolean wasAdded = m_fieldsByDeobf.put(getFieldKey(deobfName, obfType), fieldMapping) == null;
241 assert (wasAdded);
242 }
243 }
244
245 public void setFieldObfNameAndType(String oldObfName, Type obfType, String newObfName, Type newObfType) {
246 assert(newObfName != null);
247 FieldMapping fieldMapping = m_fieldsByObf.remove(getFieldKey(oldObfName, obfType));
248 assert(fieldMapping != null);
249 fieldMapping.setObfName(newObfName);
250 fieldMapping.setObfType(newObfType);
251 boolean obfWasAdded = m_fieldsByObf.put(getFieldKey(newObfName, newObfType), fieldMapping) == null;
252 assert(obfWasAdded);
253 }
254
255
256 //// METHODS ////////
257
258 public Iterable<MethodMapping> methods() {
259 assert (m_methodsByObf.size() >= m_methodsByDeobf.size());
260 return m_methodsByObf.values();
261 }
262
263 public boolean containsObfMethod(String obfName, Signature obfSignature) {
264 return m_methodsByObf.containsKey(getMethodKey(obfName, obfSignature));
265 }
266
267 public boolean containsDeobfMethod(String deobfName, Signature obfSignature) {
268 return m_methodsByDeobf.containsKey(getMethodKey(deobfName, obfSignature));
269 }
270
271 public void addMethodMapping(MethodMapping methodMapping) {
272 String obfKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature());
273 if (m_methodsByObf.containsKey(obfKey)) {
274 throw new Error("Already have mapping for " + m_obfFullName + "." + obfKey);
275 }
276 boolean wasAdded = m_methodsByObf.put(obfKey, methodMapping) == null;
277 assert (wasAdded);
278 if (methodMapping.getDeobfName() != null) {
279 String deobfKey = getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature());
280 if (m_methodsByDeobf.containsKey(deobfKey)) {
281 throw new Error("Already have mapping for " + m_deobfName + "." + deobfKey);
282 }
283 boolean deobfWasAdded = m_methodsByDeobf.put(deobfKey, methodMapping) == null;
284 assert (deobfWasAdded);
285 }
286 assert (m_methodsByObf.size() >= m_methodsByDeobf.size());
287 }
288
289 public void removeMethodMapping(MethodMapping methodMapping) {
290 boolean obfWasRemoved = m_methodsByObf.remove(getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature())) != null;
291 assert (obfWasRemoved);
292 if (methodMapping.getDeobfName() != null) {
293 boolean deobfWasRemoved = m_methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature())) != null;
294 assert (deobfWasRemoved);
295 }
296 }
297
298 public MethodMapping getMethodByObf(String obfName, Signature obfSignature) {
299 return m_methodsByObf.get(getMethodKey(obfName, obfSignature));
300 }
301
302 public MethodMapping getMethodByDeobf(String deobfName, Signature obfSignature) {
303 return m_methodsByDeobf.get(getMethodKey(deobfName, obfSignature));
304 }
305
306 private String getMethodKey(String name, Signature signature) {
307 if (name == null) {
308 throw new IllegalArgumentException("name cannot be null!");
309 }
310 if (signature == null) {
311 throw new IllegalArgumentException("signature cannot be null!");
312 }
313 return name + signature;
314 }
315
316 public void setMethodName(String obfName, Signature obfSignature, String deobfName) {
317 MethodMapping methodMapping = m_methodsByObf.get(getMethodKey(obfName, obfSignature));
318 if (methodMapping == null) {
319 methodMapping = createMethodMapping(obfName, obfSignature);
320 } else if (methodMapping.getDeobfName() != null) {
321 boolean wasRemoved = m_methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature())) != null;
322 assert (wasRemoved);
323 }
324 methodMapping.setDeobfName(deobfName);
325 if (deobfName != null) {
326 boolean wasAdded = m_methodsByDeobf.put(getMethodKey(deobfName, obfSignature), methodMapping) == null;
327 assert (wasAdded);
328 }
329 }
330
331 public void setMethodObfNameAndSignature(String oldObfName, Signature obfSignature, String newObfName, Signature newObfSignature) {
332 assert(newObfName != null);
333 MethodMapping methodMapping = m_methodsByObf.remove(getMethodKey(oldObfName, obfSignature));
334 assert(methodMapping != null);
335 methodMapping.setObfName(newObfName);
336 methodMapping.setObfSignature(newObfSignature);
337 boolean obfWasAdded = m_methodsByObf.put(getMethodKey(newObfName, newObfSignature), methodMapping) == null;
338 assert(obfWasAdded);
339 }
340
341 //// ARGUMENTS ////////
342
343 public void setArgumentName(String obfMethodName, Signature obfMethodSignature, int argumentIndex, String argumentName) {
344 assert(argumentName != null);
345 MethodMapping methodMapping = m_methodsByObf.get(getMethodKey(obfMethodName, obfMethodSignature));
346 if (methodMapping == null) {
347 methodMapping = createMethodMapping(obfMethodName, obfMethodSignature);
348 }
349 methodMapping.setArgumentName(argumentIndex, argumentName);
350 }
351
352 public void removeArgumentName(String obfMethodName, Signature obfMethodSignature, int argumentIndex) {
353 m_methodsByObf.get(getMethodKey(obfMethodName, obfMethodSignature)).removeArgumentName(argumentIndex);
354 }
355
356 private MethodMapping createMethodMapping(String obfName, Signature obfSignature) {
357 MethodMapping methodMapping = new MethodMapping(obfName, obfSignature);
358 boolean wasAdded = m_methodsByObf.put(getMethodKey(obfName, obfSignature), methodMapping) == null;
359 assert (wasAdded);
360 return methodMapping;
361 }
362
363 @Override
364 public String toString() {
365 StringBuilder buf = new StringBuilder();
366 buf.append(m_obfFullName);
367 buf.append(" <-> ");
368 buf.append(m_deobfName);
369 buf.append("\n");
370 buf.append("Fields:\n");
371 for (FieldMapping fieldMapping : fields()) {
372 buf.append("\t");
373 buf.append(fieldMapping.getObfName());
374 buf.append(" <-> ");
375 buf.append(fieldMapping.getDeobfName());
376 buf.append("\n");
377 }
378 buf.append("Methods:\n");
379 for (MethodMapping methodMapping : m_methodsByObf.values()) {
380 buf.append(methodMapping.toString());
381 buf.append("\n");
382 }
383 buf.append("Inner Classes:\n");
384 for (ClassMapping classMapping : m_innerClassesByObfSimple.values()) {
385 buf.append("\t");
386 buf.append(classMapping.getObfSimpleName());
387 buf.append(" <-> ");
388 buf.append(classMapping.getDeobfName());
389 buf.append("\n");
390 }
391 return buf.toString();
392 }
393
394 @Override
395 public int compareTo(ClassMapping other) {
396 // sort by a, b, c, ... aa, ab, etc
397 if (m_obfFullName.length() != other.m_obfFullName.length()) {
398 return m_obfFullName.length() - other.m_obfFullName.length();
399 }
400 return m_obfFullName.compareTo(other.m_obfFullName);
401 }
402
403 public boolean renameObfClass(String oldObfClassName, String newObfClassName) {
404
405 // rename inner classes
406 for (ClassMapping innerClassMapping : new ArrayList<ClassMapping>(m_innerClassesByObfSimple.values())) {
407 if (innerClassMapping.renameObfClass(oldObfClassName, newObfClassName)) {
408 boolean wasRemoved = m_innerClassesByObfSimple.remove(oldObfClassName) != null;
409 assert (wasRemoved);
410 boolean wasAdded = m_innerClassesByObfSimple.put(newObfClassName, innerClassMapping) == null;
411 assert (wasAdded);
412 }
413 }
414
415 // rename field types
416 for (FieldMapping fieldMapping : new ArrayList<FieldMapping>(m_fieldsByObf.values())) {
417 String oldFieldKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType());
418 if (fieldMapping.renameObfClass(oldObfClassName, newObfClassName)) {
419 boolean wasRemoved = m_fieldsByObf.remove(oldFieldKey) != null;
420 assert (wasRemoved);
421 boolean wasAdded = m_fieldsByObf.put(getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType()), fieldMapping) == null;
422 assert (wasAdded);
423 }
424 }
425
426 // rename method signatures
427 for (MethodMapping methodMapping : new ArrayList<MethodMapping>(m_methodsByObf.values())) {
428 String oldMethodKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature());
429 if (methodMapping.renameObfClass(oldObfClassName, newObfClassName)) {
430 boolean wasRemoved = m_methodsByObf.remove(oldMethodKey) != null;
431 assert (wasRemoved);
432 boolean wasAdded = m_methodsByObf.put(getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()), methodMapping) == null;
433 assert (wasAdded);
434 }
435 }
436
437 if (m_obfFullName.equals(oldObfClassName)) {
438 // rename this class
439 m_obfFullName = newObfClassName;
440 return true;
441 }
442 return false;
443 }
444
445 public boolean containsArgument(BehaviorEntry obfBehaviorEntry, String name) {
446 MethodMapping methodMapping = m_methodsByObf.get(getMethodKey(obfBehaviorEntry.getName(), obfBehaviorEntry.getSignature()));
447 if (methodMapping != null) {
448 return methodMapping.containsArgument(name);
449 }
450 return false;
451 }
452
453 public static boolean isSimpleClassName(String name) {
454 return name.indexOf('/') < 0 && name.indexOf('$') < 0;
455 }
456
457 public ClassEntry getObfEntry() {
458 return new ClassEntry(m_obfFullName);
459 }
460}
diff --git a/src/cuchaz/enigma/mapping/ClassNameReplacer.java b/src/cuchaz/enigma/mapping/ClassNameReplacer.java
new file mode 100644
index 00000000..f00d811e
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/ClassNameReplacer.java
@@ -0,0 +1,15 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13public interface ClassNameReplacer {
14 String replace(String className);
15}
diff --git a/src/cuchaz/enigma/mapping/ConstructorEntry.java b/src/cuchaz/enigma/mapping/ConstructorEntry.java
new file mode 100644
index 00000000..7cde8f65
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/ConstructorEntry.java
@@ -0,0 +1,116 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14
15import cuchaz.enigma.Util;
16
17public class ConstructorEntry implements BehaviorEntry, Serializable {
18
19 private static final long serialVersionUID = -868346075317366758L;
20
21 private ClassEntry m_classEntry;
22 private Signature m_signature;
23
24 public ConstructorEntry(ClassEntry classEntry) {
25 this(classEntry, null);
26 }
27
28 public ConstructorEntry(ClassEntry classEntry, Signature signature) {
29 if (classEntry == null) {
30 throw new IllegalArgumentException("Class cannot be null!");
31 }
32
33 m_classEntry = classEntry;
34 m_signature = signature;
35 }
36
37 public ConstructorEntry(ConstructorEntry other) {
38 m_classEntry = new ClassEntry(other.m_classEntry);
39 m_signature = other.m_signature;
40 }
41
42 public ConstructorEntry(ConstructorEntry other, String newClassName) {
43 m_classEntry = new ClassEntry(newClassName);
44 m_signature = other.m_signature;
45 }
46
47 @Override
48 public ClassEntry getClassEntry() {
49 return m_classEntry;
50 }
51
52 @Override
53 public String getName() {
54 if (isStatic()) {
55 return "<clinit>";
56 }
57 return "<init>";
58 }
59
60 public boolean isStatic() {
61 return m_signature == null;
62 }
63
64 @Override
65 public Signature getSignature() {
66 return m_signature;
67 }
68
69 @Override
70 public String getClassName() {
71 return m_classEntry.getName();
72 }
73
74 @Override
75 public ConstructorEntry cloneToNewClass(ClassEntry classEntry) {
76 return new ConstructorEntry(this, classEntry.getName());
77 }
78
79 @Override
80 public int hashCode() {
81 if (isStatic()) {
82 return Util.combineHashesOrdered(m_classEntry);
83 } else {
84 return Util.combineHashesOrdered(m_classEntry, m_signature);
85 }
86 }
87
88 @Override
89 public boolean equals(Object other) {
90 if (other instanceof ConstructorEntry) {
91 return equals((ConstructorEntry)other);
92 }
93 return false;
94 }
95
96 public boolean equals(ConstructorEntry other) {
97 if (isStatic() != other.isStatic()) {
98 return false;
99 }
100
101 if (isStatic()) {
102 return m_classEntry.equals(other.m_classEntry);
103 } else {
104 return m_classEntry.equals(other.m_classEntry) && m_signature.equals(other.m_signature);
105 }
106 }
107
108 @Override
109 public String toString() {
110 if (isStatic()) {
111 return m_classEntry.getName() + "." + getName();
112 } else {
113 return m_classEntry.getName() + "." + getName() + m_signature;
114 }
115 }
116}
diff --git a/src/cuchaz/enigma/mapping/Entry.java b/src/cuchaz/enigma/mapping/Entry.java
new file mode 100644
index 00000000..3c94a95a
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/Entry.java
@@ -0,0 +1,18 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13public interface Entry {
14 String getName();
15 String getClassName();
16 ClassEntry getClassEntry();
17 Entry cloneToNewClass(ClassEntry classEntry);
18}
diff --git a/src/cuchaz/enigma/mapping/EntryFactory.java b/src/cuchaz/enigma/mapping/EntryFactory.java
new file mode 100644
index 00000000..03d97ba1
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/EntryFactory.java
@@ -0,0 +1,166 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import javassist.CtBehavior;
14import javassist.CtClass;
15import javassist.CtConstructor;
16import javassist.CtField;
17import javassist.CtMethod;
18import javassist.bytecode.Descriptor;
19import javassist.expr.ConstructorCall;
20import javassist.expr.FieldAccess;
21import javassist.expr.MethodCall;
22import javassist.expr.NewExpr;
23
24import cuchaz.enigma.analysis.JarIndex;
25
26public class EntryFactory {
27
28 public static ClassEntry getClassEntry(CtClass c) {
29 return new ClassEntry(Descriptor.toJvmName(c.getName()));
30 }
31
32 public static ClassEntry getObfClassEntry(JarIndex jarIndex, ClassMapping classMapping) {
33 ClassEntry obfClassEntry = new ClassEntry(classMapping.getObfFullName());
34 return obfClassEntry.buildClassEntry(jarIndex.getObfClassChain(obfClassEntry));
35 }
36
37 private static ClassEntry getObfClassEntry(ClassMapping classMapping) {
38 return new ClassEntry(classMapping.getObfFullName());
39 }
40
41 public static ClassEntry getDeobfClassEntry(ClassMapping classMapping) {
42 return new ClassEntry(classMapping.getDeobfName());
43 }
44
45 public static ClassEntry getSuperclassEntry(CtClass c) {
46 return new ClassEntry(Descriptor.toJvmName(c.getClassFile().getSuperclass()));
47 }
48
49 public static FieldEntry getFieldEntry(CtField field) {
50 return new FieldEntry(
51 getClassEntry(field.getDeclaringClass()),
52 field.getName(),
53 new Type(field.getFieldInfo().getDescriptor())
54 );
55 }
56
57 public static FieldEntry getFieldEntry(FieldAccess call) {
58 return new FieldEntry(
59 new ClassEntry(Descriptor.toJvmName(call.getClassName())),
60 call.getFieldName(),
61 new Type(call.getSignature())
62 );
63 }
64
65 public static FieldEntry getFieldEntry(String className, String name, String type) {
66 return new FieldEntry(new ClassEntry(className), name, new Type(type));
67 }
68
69 public static FieldEntry getObfFieldEntry(ClassMapping classMapping, FieldMapping fieldMapping) {
70 return new FieldEntry(
71 getObfClassEntry(classMapping),
72 fieldMapping.getObfName(),
73 fieldMapping.getObfType()
74 );
75 }
76
77 public static MethodEntry getMethodEntry(CtMethod method) {
78 return new MethodEntry(
79 getClassEntry(method.getDeclaringClass()),
80 method.getName(),
81 new Signature(method.getMethodInfo().getDescriptor())
82 );
83 }
84
85 public static MethodEntry getMethodEntry(MethodCall call) {
86 return new MethodEntry(
87 new ClassEntry(Descriptor.toJvmName(call.getClassName())),
88 call.getMethodName(),
89 new Signature(call.getSignature())
90 );
91 }
92
93 public static ConstructorEntry getConstructorEntry(CtConstructor constructor) {
94 if (constructor.isClassInitializer()) {
95 return new ConstructorEntry(
96 getClassEntry(constructor.getDeclaringClass())
97 );
98 } else {
99 return new ConstructorEntry(
100 getClassEntry(constructor.getDeclaringClass()),
101 new Signature(constructor.getMethodInfo().getDescriptor())
102 );
103 }
104 }
105
106 public static ConstructorEntry getConstructorEntry(ConstructorCall call) {
107 return new ConstructorEntry(
108 new ClassEntry(Descriptor.toJvmName(call.getClassName())),
109 new Signature(call.getSignature())
110 );
111 }
112
113 public static ConstructorEntry getConstructorEntry(NewExpr call) {
114 return new ConstructorEntry(
115 new ClassEntry(Descriptor.toJvmName(call.getClassName())),
116 new Signature(call.getSignature())
117 );
118 }
119
120 public static BehaviorEntry getBehaviorEntry(CtBehavior behavior) {
121 if (behavior instanceof CtMethod) {
122 return getMethodEntry((CtMethod)behavior);
123 } else if (behavior instanceof CtConstructor) {
124 return getConstructorEntry((CtConstructor)behavior);
125 }
126 throw new Error("behavior is neither Method nor Constructor!");
127 }
128
129 public static BehaviorEntry getBehaviorEntry(String className, String behaviorName, String behaviorSignature) {
130 return getBehaviorEntry(new ClassEntry(className), behaviorName, new Signature(behaviorSignature));
131 }
132
133 public static BehaviorEntry getBehaviorEntry(String className, String behaviorName) {
134 return getBehaviorEntry(new ClassEntry(className), behaviorName);
135 }
136
137 public static BehaviorEntry getBehaviorEntry(String className) {
138 return new ConstructorEntry(new ClassEntry(className));
139 }
140
141 public static BehaviorEntry getBehaviorEntry(ClassEntry classEntry, String behaviorName, Signature behaviorSignature) {
142 if (behaviorName.equals("<init>")) {
143 return new ConstructorEntry(classEntry, behaviorSignature);
144 } else if(behaviorName.equals("<clinit>")) {
145 return new ConstructorEntry(classEntry);
146 } else {
147 return new MethodEntry(classEntry, behaviorName, behaviorSignature);
148 }
149 }
150
151 public static BehaviorEntry getBehaviorEntry(ClassEntry classEntry, String behaviorName) {
152 if(behaviorName.equals("<clinit>")) {
153 return new ConstructorEntry(classEntry);
154 } else {
155 throw new IllegalArgumentException("Only class initializers don't have signatures");
156 }
157 }
158
159 public static BehaviorEntry getObfBehaviorEntry(ClassEntry classEntry, MethodMapping methodMapping) {
160 return getBehaviorEntry(classEntry, methodMapping.getObfName(), methodMapping.getObfSignature());
161 }
162
163 public static BehaviorEntry getObfBehaviorEntry(ClassMapping classMapping, MethodMapping methodMapping) {
164 return getObfBehaviorEntry(getObfClassEntry(classMapping), methodMapping);
165 }
166}
diff --git a/src/cuchaz/enigma/mapping/EntryPair.java b/src/cuchaz/enigma/mapping/EntryPair.java
new file mode 100644
index 00000000..82b28cd1
--- /dev/null
+++ b/src/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 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13public class EntryPair<T extends Entry> {
14
15 public T obf;
16 public T deobf;
17
18 public EntryPair(T obf, T deobf) {
19 this.obf = obf;
20 this.deobf = deobf;
21 }
22}
diff --git a/src/cuchaz/enigma/mapping/FieldEntry.java b/src/cuchaz/enigma/mapping/FieldEntry.java
new file mode 100644
index 00000000..e4a74f4f
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/FieldEntry.java
@@ -0,0 +1,99 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14
15import cuchaz.enigma.Util;
16
17public class FieldEntry implements Entry, Serializable {
18
19 private static final long serialVersionUID = 3004663582802885451L;
20
21 private ClassEntry m_classEntry;
22 private String m_name;
23 private Type m_type;
24
25 // NOTE: this argument order is important for the MethodReader/MethodWriter
26 public FieldEntry(ClassEntry classEntry, String name, Type type) {
27 if (classEntry == null) {
28 throw new IllegalArgumentException("Class cannot be null!");
29 }
30 if (name == null) {
31 throw new IllegalArgumentException("Field name cannot be null!");
32 }
33 if (type == null) {
34 throw new IllegalArgumentException("Field type cannot be null!");
35 }
36
37 m_classEntry = classEntry;
38 m_name = name;
39 m_type = type;
40 }
41
42 public FieldEntry(FieldEntry other) {
43 this(other, new ClassEntry(other.m_classEntry));
44 }
45
46 public FieldEntry(FieldEntry other, ClassEntry newClassEntry) {
47 m_classEntry = newClassEntry;
48 m_name = other.m_name;
49 m_type = other.m_type;
50 }
51
52 @Override
53 public ClassEntry getClassEntry() {
54 return m_classEntry;
55 }
56
57 @Override
58 public String getName() {
59 return m_name;
60 }
61
62 @Override
63 public String getClassName() {
64 return m_classEntry.getName();
65 }
66
67 public Type getType() {
68 return m_type;
69 }
70
71 @Override
72 public FieldEntry cloneToNewClass(ClassEntry classEntry) {
73 return new FieldEntry(this, classEntry);
74 }
75
76 @Override
77 public int hashCode() {
78 return Util.combineHashesOrdered(m_classEntry, m_name, m_type);
79 }
80
81 @Override
82 public boolean equals(Object other) {
83 if (other instanceof FieldEntry) {
84 return equals((FieldEntry)other);
85 }
86 return false;
87 }
88
89 public boolean equals(FieldEntry other) {
90 return m_classEntry.equals(other.m_classEntry)
91 && m_name.equals(other.m_name)
92 && m_type.equals(other.m_type);
93 }
94
95 @Override
96 public String toString() {
97 return m_classEntry.getName() + "." + m_name + ":" + m_type;
98 }
99}
diff --git a/src/cuchaz/enigma/mapping/FieldMapping.java b/src/cuchaz/enigma/mapping/FieldMapping.java
new file mode 100644
index 00000000..28557406
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/FieldMapping.java
@@ -0,0 +1,89 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14
15public class FieldMapping implements Serializable, Comparable<FieldMapping>, MemberMapping<FieldEntry> {
16
17 private static final long serialVersionUID = 8610742471440861315L;
18
19 private String m_obfName;
20 private String m_deobfName;
21 private Type m_obfType;
22
23 public FieldMapping(String obfName, Type obfType, String deobfName) {
24 m_obfName = obfName;
25 m_deobfName = NameValidator.validateFieldName(deobfName);
26 m_obfType = obfType;
27 }
28
29 public FieldMapping(FieldMapping other, ClassNameReplacer obfClassNameReplacer) {
30 m_obfName = other.m_obfName;
31 m_deobfName = other.m_deobfName;
32 m_obfType = new Type(other.m_obfType, obfClassNameReplacer);
33 }
34
35 @Override
36 public String getObfName() {
37 return m_obfName;
38 }
39
40 public void setObfName(String val) {
41 m_obfName = NameValidator.validateFieldName(val);
42 }
43
44 public String getDeobfName() {
45 return m_deobfName;
46 }
47
48 public void setDeobfName(String val) {
49 m_deobfName = NameValidator.validateFieldName(val);
50 }
51
52 public Type getObfType() {
53 return m_obfType;
54 }
55
56 public void setObfType(Type val) {
57 m_obfType = val;
58 }
59
60 @Override
61 public int compareTo(FieldMapping other) {
62 return (m_obfName + m_obfType).compareTo(other.m_obfName + other.m_obfType);
63 }
64
65 public boolean renameObfClass(final String oldObfClassName, final String newObfClassName) {
66
67 // rename obf classes in the type
68 Type newType = new Type(m_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(m_obfType)) {
79 m_obfType = newType;
80 return true;
81 }
82 return false;
83 }
84
85 @Override
86 public FieldEntry getObfEntry(ClassEntry classEntry) {
87 return new FieldEntry(classEntry, m_obfName, new Type(m_obfType));
88 }
89}
diff --git a/src/cuchaz/enigma/mapping/IllegalNameException.java b/src/cuchaz/enigma/mapping/IllegalNameException.java
new file mode 100644
index 00000000..f62df7c4
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/IllegalNameException.java
@@ -0,0 +1,44 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13public class IllegalNameException extends RuntimeException {
14
15 private static final long serialVersionUID = -2279910052561114323L;
16
17 private String m_name;
18 private String m_reason;
19
20 public IllegalNameException(String name) {
21 this(name, null);
22 }
23
24 public IllegalNameException(String name, String reason) {
25 m_name = name;
26 m_reason = reason;
27 }
28
29 public String getReason() {
30 return m_reason;
31 }
32
33 @Override
34 public String getMessage() {
35 StringBuilder buf = new StringBuilder();
36 buf.append("Illegal name: ");
37 buf.append(m_name);
38 if (m_reason != null) {
39 buf.append(" because ");
40 buf.append(m_reason);
41 }
42 return buf.toString();
43 }
44}
diff --git a/src/cuchaz/enigma/mapping/MappingParseException.java b/src/cuchaz/enigma/mapping/MappingParseException.java
new file mode 100644
index 00000000..73fca94a
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MappingParseException.java
@@ -0,0 +1,29 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13public class MappingParseException extends Exception {
14
15 private static final long serialVersionUID = -5487280332892507236L;
16
17 private int m_line;
18 private String m_message;
19
20 public MappingParseException(int line, String message) {
21 m_line = line;
22 m_message = message;
23 }
24
25 @Override
26 public String getMessage() {
27 return "Line " + m_line + ": " + m_message;
28 }
29}
diff --git a/src/cuchaz/enigma/mapping/Mappings.java b/src/cuchaz/enigma/mapping/Mappings.java
new file mode 100644
index 00000000..11ed5d0c
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/Mappings.java
@@ -0,0 +1,216 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14import java.util.ArrayList;
15import java.util.Collection;
16import java.util.List;
17import java.util.Map;
18import java.util.Set;
19
20import com.google.common.collect.Lists;
21import com.google.common.collect.Maps;
22import com.google.common.collect.Sets;
23
24import cuchaz.enigma.analysis.TranslationIndex;
25
26public class Mappings implements Serializable {
27
28 private static final long serialVersionUID = 4649790259460259026L;
29
30 protected Map<String,ClassMapping> m_classesByObf;
31 protected Map<String,ClassMapping> m_classesByDeobf;
32
33 public Mappings() {
34 m_classesByObf = Maps.newHashMap();
35 m_classesByDeobf = Maps.newHashMap();
36 }
37
38 public Mappings(Iterable<ClassMapping> classes) {
39 this();
40
41 for (ClassMapping classMapping : classes) {
42 m_classesByObf.put(classMapping.getObfFullName(), classMapping);
43 if (classMapping.getDeobfName() != null) {
44 m_classesByDeobf.put(classMapping.getDeobfName(), classMapping);
45 }
46 }
47 }
48
49 public Collection<ClassMapping> classes() {
50 assert (m_classesByObf.size() >= m_classesByDeobf.size());
51 return m_classesByObf.values();
52 }
53
54 public void addClassMapping(ClassMapping classMapping) {
55 if (m_classesByObf.containsKey(classMapping.getObfFullName())) {
56 throw new Error("Already have mapping for " + classMapping.getObfFullName());
57 }
58 boolean obfWasAdded = m_classesByObf.put(classMapping.getObfFullName(), classMapping) == null;
59 assert (obfWasAdded);
60 if (classMapping.getDeobfName() != null) {
61 if (m_classesByDeobf.containsKey(classMapping.getDeobfName())) {
62 throw new Error("Already have mapping for " + classMapping.getDeobfName());
63 }
64 boolean deobfWasAdded = m_classesByDeobf.put(classMapping.getDeobfName(), classMapping) == null;
65 assert (deobfWasAdded);
66 }
67 }
68
69 public void removeClassMapping(ClassMapping classMapping) {
70 boolean obfWasRemoved = m_classesByObf.remove(classMapping.getObfFullName()) != null;
71 assert (obfWasRemoved);
72 if (classMapping.getDeobfName() != null) {
73 boolean deobfWasRemoved = m_classesByDeobf.remove(classMapping.getDeobfName()) != null;
74 assert (deobfWasRemoved);
75 }
76 }
77
78 public ClassMapping getClassByObf(ClassEntry entry) {
79 return getClassByObf(entry.getName());
80 }
81
82 public ClassMapping getClassByObf(String obfName) {
83 return m_classesByObf.get(obfName);
84 }
85
86 public ClassMapping getClassByDeobf(ClassEntry entry) {
87 return getClassByDeobf(entry.getName());
88 }
89
90 public ClassMapping getClassByDeobf(String deobfName) {
91 return m_classesByDeobf.get(deobfName);
92 }
93
94 public void setClassDeobfName(ClassMapping classMapping, String deobfName) {
95 if (classMapping.getDeobfName() != null) {
96 boolean wasRemoved = m_classesByDeobf.remove(classMapping.getDeobfName()) != null;
97 assert (wasRemoved);
98 }
99 classMapping.setDeobfName(deobfName);
100 if (deobfName != null) {
101 boolean wasAdded = m_classesByDeobf.put(deobfName, classMapping) == null;
102 assert (wasAdded);
103 }
104 }
105
106 public Translator getTranslator(TranslationDirection direction, TranslationIndex index) {
107 switch (direction) {
108 case Deobfuscating:
109
110 return new Translator(direction, m_classesByObf, index);
111
112 case Obfuscating:
113
114 // fill in the missing deobf class entries with obf entries
115 Map<String,ClassMapping> classes = Maps.newHashMap();
116 for (ClassMapping classMapping : classes()) {
117 if (classMapping.getDeobfName() != null) {
118 classes.put(classMapping.getDeobfName(), classMapping);
119 } else {
120 classes.put(classMapping.getObfFullName(), classMapping);
121 }
122 }
123
124 // translate the translation index
125 // NOTE: this isn't actually recursive
126 TranslationIndex deobfIndex = new TranslationIndex(index, getTranslator(TranslationDirection.Deobfuscating, index));
127
128 return new Translator(direction, classes, deobfIndex);
129
130 default:
131 throw new Error("Invalid translation direction!");
132 }
133 }
134
135 @Override
136 public String toString() {
137 StringBuilder buf = new StringBuilder();
138 for (ClassMapping classMapping : m_classesByObf.values()) {
139 buf.append(classMapping.toString());
140 buf.append("\n");
141 }
142 return buf.toString();
143 }
144
145 public void renameObfClass(String oldObfName, String newObfName) {
146 for (ClassMapping classMapping : new ArrayList<ClassMapping>(classes())) {
147 if (classMapping.renameObfClass(oldObfName, newObfName)) {
148 boolean wasRemoved = m_classesByObf.remove(oldObfName) != null;
149 assert (wasRemoved);
150 boolean wasAdded = m_classesByObf.put(newObfName, classMapping) == null;
151 assert (wasAdded);
152 }
153 }
154 }
155
156 public Set<String> getAllObfClassNames() {
157 final Set<String> classNames = Sets.newHashSet();
158 for (ClassMapping classMapping : classes()) {
159
160 // add the class name
161 classNames.add(classMapping.getObfFullName());
162
163 // add classes from method signatures
164 for (MethodMapping methodMapping : classMapping.methods()) {
165 for (Type type : methodMapping.getObfSignature().types()) {
166 if (type.hasClass()) {
167 classNames.add(type.getClassEntry().getClassName());
168 }
169 }
170 }
171 }
172 return classNames;
173 }
174
175 public boolean containsDeobfClass(String deobfName) {
176 return m_classesByDeobf.containsKey(deobfName);
177 }
178
179 public boolean containsDeobfField(ClassEntry obfClassEntry, String deobfName, Type obfType) {
180 ClassMapping classMapping = m_classesByObf.get(obfClassEntry.getName());
181 if (classMapping != null) {
182 return classMapping.containsDeobfField(deobfName, obfType);
183 }
184 return false;
185 }
186
187 public boolean containsDeobfMethod(ClassEntry obfClassEntry, String deobfName, Signature deobfSignature) {
188 ClassMapping classMapping = m_classesByObf.get(obfClassEntry.getName());
189 if (classMapping != null) {
190 return classMapping.containsDeobfMethod(deobfName, deobfSignature);
191 }
192 return false;
193 }
194
195 public boolean containsArgument(BehaviorEntry obfBehaviorEntry, String name) {
196 ClassMapping classMapping = m_classesByObf.get(obfBehaviorEntry.getClassName());
197 if (classMapping != null) {
198 return classMapping.containsArgument(obfBehaviorEntry, name);
199 }
200 return false;
201 }
202
203 public List<ClassMapping> getClassMappingChain(ClassEntry obfClass) {
204 List<ClassMapping> mappingChain = Lists.newArrayList();
205 ClassMapping classMapping = null;
206 for (ClassEntry obfClassEntry : obfClass.getClassChain()) {
207 if (mappingChain.isEmpty()) {
208 classMapping = m_classesByObf.get(obfClassEntry.getName());
209 } else if (classMapping != null) {
210 classMapping = classMapping.getInnerClassByObfSimple(obfClassEntry.getInnermostClassName());
211 }
212 mappingChain.add(classMapping);
213 }
214 return mappingChain;
215 }
216}
diff --git a/src/cuchaz/enigma/mapping/MappingsChecker.java b/src/cuchaz/enigma/mapping/MappingsChecker.java
new file mode 100644
index 00000000..b25ea3cf
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MappingsChecker.java
@@ -0,0 +1,107 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.util.Map;
14
15import com.google.common.collect.Lists;
16import com.google.common.collect.Maps;
17
18import cuchaz.enigma.analysis.JarIndex;
19import cuchaz.enigma.analysis.RelatedMethodChecker;
20
21
22public class MappingsChecker {
23
24 private JarIndex m_index;
25 private RelatedMethodChecker m_relatedMethodChecker;
26 private Map<ClassEntry,ClassMapping> m_droppedClassMappings;
27 private Map<ClassEntry,ClassMapping> m_droppedInnerClassMappings;
28 private Map<FieldEntry,FieldMapping> m_droppedFieldMappings;
29 private Map<BehaviorEntry,MethodMapping> m_droppedMethodMappings;
30
31 public MappingsChecker(JarIndex index) {
32 m_index = index;
33 m_relatedMethodChecker = new RelatedMethodChecker(m_index);
34 m_droppedClassMappings = Maps.newHashMap();
35 m_droppedInnerClassMappings = Maps.newHashMap();
36 m_droppedFieldMappings = Maps.newHashMap();
37 m_droppedMethodMappings = Maps.newHashMap();
38 }
39
40 public RelatedMethodChecker getRelatedMethodChecker() {
41 return m_relatedMethodChecker;
42 }
43
44 public Map<ClassEntry,ClassMapping> getDroppedClassMappings() {
45 return m_droppedClassMappings;
46 }
47
48 public Map<ClassEntry,ClassMapping> getDroppedInnerClassMappings() {
49 return m_droppedInnerClassMappings;
50 }
51
52 public Map<FieldEntry,FieldMapping> getDroppedFieldMappings() {
53 return m_droppedFieldMappings;
54 }
55
56 public Map<BehaviorEntry,MethodMapping> getDroppedMethodMappings() {
57 return m_droppedMethodMappings;
58 }
59
60 public void dropBrokenMappings(Mappings mappings) {
61 for (ClassMapping classMapping : Lists.newArrayList(mappings.classes())) {
62 if (!checkClassMapping(classMapping)) {
63 mappings.removeClassMapping(classMapping);
64 m_droppedClassMappings.put(EntryFactory.getObfClassEntry(m_index, classMapping), classMapping);
65 }
66 }
67 }
68
69 private boolean checkClassMapping(ClassMapping classMapping) {
70
71 // check the class
72 ClassEntry classEntry = EntryFactory.getObfClassEntry(m_index, classMapping);
73 if (!m_index.getObfClassEntries().contains(classEntry)) {
74 return false;
75 }
76
77 // check the fields
78 for (FieldMapping fieldMapping : Lists.newArrayList(classMapping.fields())) {
79 FieldEntry obfFieldEntry = EntryFactory.getObfFieldEntry(classMapping, fieldMapping);
80 if (!m_index.containsObfField(obfFieldEntry)) {
81 classMapping.removeFieldMapping(fieldMapping);
82 m_droppedFieldMappings.put(obfFieldEntry, fieldMapping);
83 }
84 }
85
86 // check methods
87 for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) {
88 BehaviorEntry obfBehaviorEntry = EntryFactory.getObfBehaviorEntry(classEntry, methodMapping);
89 if (!m_index.containsObfBehavior(obfBehaviorEntry)) {
90 classMapping.removeMethodMapping(methodMapping);
91 m_droppedMethodMappings.put(obfBehaviorEntry, methodMapping);
92 }
93
94 m_relatedMethodChecker.checkMethod(classEntry, methodMapping);
95 }
96
97 // check inner classes
98 for (ClassMapping innerClassMapping : Lists.newArrayList(classMapping.innerClasses())) {
99 if (!checkClassMapping(innerClassMapping)) {
100 classMapping.removeInnerClassMapping(innerClassMapping);
101 m_droppedInnerClassMappings.put(EntryFactory.getObfClassEntry(m_index, innerClassMapping), innerClassMapping);
102 }
103 }
104
105 return true;
106 }
107}
diff --git a/src/cuchaz/enigma/mapping/MappingsReader.java b/src/cuchaz/enigma/mapping/MappingsReader.java
new file mode 100644
index 00000000..0a4b117e
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MappingsReader.java
@@ -0,0 +1,134 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.BufferedReader;
14import java.io.IOException;
15import java.io.Reader;
16import java.util.Deque;
17
18import com.google.common.collect.Queues;
19
20public class MappingsReader {
21
22 public Mappings read(Reader in)
23 throws IOException, MappingParseException {
24 return read(new BufferedReader(in));
25 }
26
27 public Mappings read(BufferedReader in)
28 throws IOException, MappingParseException {
29 Mappings mappings = new Mappings();
30 Deque<Object> mappingStack = Queues.newArrayDeque();
31
32 int lineNumber = 0;
33 String line = null;
34 while ( (line = in.readLine()) != null) {
35 lineNumber++;
36
37 // strip comments
38 int commentPos = line.indexOf('#');
39 if (commentPos >= 0) {
40 line = line.substring(0, commentPos);
41 }
42
43 // skip blank lines
44 if (line.trim().length() <= 0) {
45 continue;
46 }
47
48 // get the indent of this line
49 int indent = 0;
50 for (int i = 0; i < line.length(); i++) {
51 if (line.charAt(i) != '\t') {
52 break;
53 }
54 indent++;
55 }
56
57 // handle stack pops
58 while (indent < mappingStack.size()) {
59 mappingStack.pop();
60 }
61
62 String[] parts = line.trim().split("\\s");
63 try {
64 // read the first token
65 String token = parts[0];
66
67 if (token.equalsIgnoreCase("CLASS")) {
68 ClassMapping classMapping;
69 if (indent <= 0) {
70 // outer class
71 classMapping = readClass(parts, false);
72 mappings.addClassMapping(classMapping);
73 } else {
74
75 // inner class
76 if (!(mappingStack.peek() instanceof ClassMapping)) {
77 throw new MappingParseException(lineNumber, "Unexpected CLASS entry here!");
78 }
79
80 classMapping = readClass(parts, true);
81 ((ClassMapping)mappingStack.peek()).addInnerClassMapping(classMapping);
82 }
83 mappingStack.push(classMapping);
84 } else if (token.equalsIgnoreCase("FIELD")) {
85 if (mappingStack.isEmpty() || ! (mappingStack.peek() instanceof ClassMapping)) {
86 throw new MappingParseException(lineNumber, "Unexpected FIELD entry here!");
87 }
88 ((ClassMapping)mappingStack.peek()).addFieldMapping(readField(parts));
89 } else if (token.equalsIgnoreCase("METHOD")) {
90 if (mappingStack.isEmpty() || ! (mappingStack.peek() instanceof ClassMapping)) {
91 throw new MappingParseException(lineNumber, "Unexpected METHOD entry here!");
92 }
93 MethodMapping methodMapping = readMethod(parts);
94 ((ClassMapping)mappingStack.peek()).addMethodMapping(methodMapping);
95 mappingStack.push(methodMapping);
96 } else if (token.equalsIgnoreCase("ARG")) {
97 if (mappingStack.isEmpty() || ! (mappingStack.peek() instanceof MethodMapping)) {
98 throw new MappingParseException(lineNumber, "Unexpected ARG entry here!");
99 }
100 ((MethodMapping)mappingStack.peek()).addArgumentMapping(readArgument(parts));
101 }
102 } catch (ArrayIndexOutOfBoundsException | IllegalArgumentException ex) {
103 throw new MappingParseException(lineNumber, "Malformed line:\n" + line);
104 }
105 }
106
107 return mappings;
108 }
109
110 private ArgumentMapping readArgument(String[] parts) {
111 return new ArgumentMapping(Integer.parseInt(parts[1]), parts[2]);
112 }
113
114 private ClassMapping readClass(String[] parts, boolean makeSimple) {
115 if (parts.length == 2) {
116 return new ClassMapping(parts[1]);
117 } else {
118 return new ClassMapping(parts[1], parts[2]);
119 }
120 }
121
122 /* TEMP */
123 protected FieldMapping readField(String[] parts) {
124 return new FieldMapping(parts[1], new Type(parts[3]), parts[2]);
125 }
126
127 private MethodMapping readMethod(String[] parts) {
128 if (parts.length == 3) {
129 return new MethodMapping(parts[1], new Signature(parts[2]));
130 } else {
131 return new MethodMapping(parts[1], new Signature(parts[3]), parts[2]);
132 }
133 }
134}
diff --git a/src/cuchaz/enigma/mapping/MappingsRenamer.java b/src/cuchaz/enigma/mapping/MappingsRenamer.java
new file mode 100644
index 00000000..47e5738c
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MappingsRenamer.java
@@ -0,0 +1,237 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.IOException;
14import java.io.ObjectOutputStream;
15import java.io.OutputStream;
16import java.util.List;
17import java.util.Set;
18import java.util.zip.GZIPOutputStream;
19
20import cuchaz.enigma.analysis.JarIndex;
21
22public class MappingsRenamer {
23
24 private JarIndex m_index;
25 private Mappings m_mappings;
26
27 public MappingsRenamer(JarIndex index, Mappings mappings) {
28 m_index = index;
29 m_mappings = mappings;
30 }
31
32 public void setClassName(ClassEntry obf, String deobfName) {
33
34 deobfName = NameValidator.validateClassName(deobfName, !obf.isInnerClass());
35
36 List<ClassMapping> mappingChain = getOrCreateClassMappingChain(obf);
37 if (mappingChain.size() == 1) {
38
39 if (deobfName != null) {
40 // make sure we don't rename to an existing obf or deobf class
41 if (m_mappings.containsDeobfClass(deobfName) || m_index.containsObfClass(new ClassEntry(deobfName))) {
42 throw new IllegalNameException(deobfName, "There is already a class with that name");
43 }
44 }
45
46 ClassMapping classMapping = mappingChain.get(0);
47 m_mappings.setClassDeobfName(classMapping, deobfName);
48
49 } else {
50
51 ClassMapping outerClassMapping = mappingChain.get(mappingChain.size() - 2);
52
53 if (deobfName != null) {
54 // make sure we don't rename to an existing obf or deobf inner class
55 if (outerClassMapping.hasInnerClassByDeobf(deobfName) || outerClassMapping.hasInnerClassByObfSimple(deobfName)) {
56 throw new IllegalNameException(deobfName, "There is already a class with that name");
57 }
58 }
59
60 outerClassMapping.setInnerClassName(obf, deobfName);
61 }
62 }
63
64 public void removeClassMapping(ClassEntry obf) {
65 setClassName(obf, null);
66 }
67
68 public void markClassAsDeobfuscated(ClassEntry obf) {
69 String deobfName = obf.isInnerClass() ? obf.getInnermostClassName() : obf.getName();
70 List<ClassMapping> mappingChain = getOrCreateClassMappingChain(obf);
71 if (mappingChain.size() == 1) {
72 ClassMapping classMapping = mappingChain.get(0);
73 m_mappings.setClassDeobfName(classMapping, deobfName);
74 } else {
75 ClassMapping outerClassMapping = mappingChain.get(mappingChain.size() - 2);
76 outerClassMapping.setInnerClassName(obf, deobfName);
77 }
78 }
79
80 public void setFieldName(FieldEntry obf, String deobfName) {
81 deobfName = NameValidator.validateFieldName(deobfName);
82 FieldEntry targetEntry = new FieldEntry(obf.getClassEntry(), deobfName, obf.getType());
83 if (m_mappings.containsDeobfField(obf.getClassEntry(), deobfName, obf.getType()) || m_index.containsObfField(targetEntry)) {
84 throw new IllegalNameException(deobfName, "There is already a field with that name");
85 }
86
87 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
88 classMapping.setFieldName(obf.getName(), obf.getType(), deobfName);
89 }
90
91 public void removeFieldMapping(FieldEntry obf) {
92 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
93 classMapping.removeFieldMapping(classMapping.getFieldByObf(obf.getName(), obf.getType()));
94 }
95
96 public void markFieldAsDeobfuscated(FieldEntry obf) {
97 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
98 classMapping.setFieldName(obf.getName(), obf.getType(), obf.getName());
99 }
100
101 public void setMethodTreeName(MethodEntry obf, String deobfName) {
102 Set<MethodEntry> implementations = m_index.getRelatedMethodImplementations(obf);
103
104 deobfName = NameValidator.validateMethodName(deobfName);
105 for (MethodEntry entry : implementations) {
106 Signature deobfSignature = m_mappings.getTranslator(TranslationDirection.Deobfuscating, m_index.getTranslationIndex()).translateSignature(obf.getSignature());
107 MethodEntry targetEntry = new MethodEntry(entry.getClassEntry(), deobfName, deobfSignature);
108 if (m_mappings.containsDeobfMethod(entry.getClassEntry(), deobfName, entry.getSignature()) || m_index.containsObfBehavior(targetEntry)) {
109 String deobfClassName = m_mappings.getTranslator(TranslationDirection.Deobfuscating, m_index.getTranslationIndex()).translateClass(entry.getClassName());
110 throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName);
111 }
112 }
113
114 for (MethodEntry entry : implementations) {
115 setMethodName(entry, deobfName);
116 }
117 }
118
119 public void setMethodName(MethodEntry obf, String deobfName) {
120 deobfName = NameValidator.validateMethodName(deobfName);
121 MethodEntry targetEntry = new MethodEntry(obf.getClassEntry(), deobfName, obf.getSignature());
122 if (m_mappings.containsDeobfMethod(obf.getClassEntry(), deobfName, obf.getSignature()) || m_index.containsObfBehavior(targetEntry)) {
123 String deobfClassName = m_mappings.getTranslator(TranslationDirection.Deobfuscating, m_index.getTranslationIndex()).translateClass(obf.getClassName());
124 throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName);
125 }
126
127 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
128 classMapping.setMethodName(obf.getName(), obf.getSignature(), deobfName);
129 }
130
131 public void removeMethodTreeMapping(MethodEntry obf) {
132 for (MethodEntry implementation : m_index.getRelatedMethodImplementations(obf)) {
133 removeMethodMapping(implementation);
134 }
135 }
136
137 public void removeMethodMapping(MethodEntry obf) {
138 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
139 classMapping.setMethodName(obf.getName(), obf.getSignature(), null);
140 }
141
142 public void markMethodTreeAsDeobfuscated(MethodEntry obf) {
143 for (MethodEntry implementation : m_index.getRelatedMethodImplementations(obf)) {
144 markMethodAsDeobfuscated(implementation);
145 }
146 }
147
148 public void markMethodAsDeobfuscated(MethodEntry obf) {
149 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
150 classMapping.setMethodName(obf.getName(), obf.getSignature(), obf.getName());
151 }
152
153 public void setArgumentName(ArgumentEntry obf, String deobfName) {
154 deobfName = NameValidator.validateArgumentName(deobfName);
155 // NOTE: don't need to check arguments for name collisions with names determined by Procyon
156 if (m_mappings.containsArgument(obf.getBehaviorEntry(), deobfName)) {
157 throw new IllegalNameException(deobfName, "There is already an argument with that name");
158 }
159
160 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
161 classMapping.setArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex(), deobfName);
162 }
163
164 public void removeArgumentMapping(ArgumentEntry obf) {
165 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
166 classMapping.removeArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex());
167 }
168
169 public void markArgumentAsDeobfuscated(ArgumentEntry obf) {
170 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
171 classMapping.setArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex(), obf.getName());
172 }
173
174 public boolean moveFieldToObfClass(ClassMapping classMapping, FieldMapping fieldMapping, ClassEntry obfClass) {
175 classMapping.removeFieldMapping(fieldMapping);
176 ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass);
177 if (!targetClassMapping.containsObfField(fieldMapping.getObfName(), fieldMapping.getObfType())) {
178 if (!targetClassMapping.containsDeobfField(fieldMapping.getDeobfName(), fieldMapping.getObfType())) {
179 targetClassMapping.addFieldMapping(fieldMapping);
180 return true;
181 } else {
182 System.err.println("WARNING: deobf field was already there: " + obfClass + "." + fieldMapping.getDeobfName());
183 }
184 }
185 return false;
186 }
187
188 public boolean moveMethodToObfClass(ClassMapping classMapping, MethodMapping methodMapping, ClassEntry obfClass) {
189 classMapping.removeMethodMapping(methodMapping);
190 ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass);
191 if (!targetClassMapping.containsObfMethod(methodMapping.getObfName(), methodMapping.getObfSignature())) {
192 if (!targetClassMapping.containsDeobfMethod(methodMapping.getDeobfName(), methodMapping.getObfSignature())) {
193 targetClassMapping.addMethodMapping(methodMapping);
194 return true;
195 } else {
196 System.err.println("WARNING: deobf method was already there: " + obfClass + "." + methodMapping.getDeobfName() + methodMapping.getObfSignature());
197 }
198 }
199 return false;
200 }
201
202 public void write(OutputStream out) throws IOException {
203 // TEMP: just use the object output for now. We can find a more efficient storage format later
204 GZIPOutputStream gzipout = new GZIPOutputStream(out);
205 ObjectOutputStream oout = new ObjectOutputStream(gzipout);
206 oout.writeObject(this);
207 gzipout.finish();
208 }
209
210 private ClassMapping getOrCreateClassMapping(ClassEntry obfClassEntry) {
211 List<ClassMapping> mappingChain = getOrCreateClassMappingChain(obfClassEntry);
212 return mappingChain.get(mappingChain.size() - 1);
213 }
214
215 private List<ClassMapping> getOrCreateClassMappingChain(ClassEntry obfClassEntry) {
216 List<ClassEntry> classChain = obfClassEntry.getClassChain();
217 List<ClassMapping> mappingChain = m_mappings.getClassMappingChain(obfClassEntry);
218 for (int i=0; i<classChain.size(); i++) {
219 ClassEntry classEntry = classChain.get(i);
220 ClassMapping classMapping = mappingChain.get(i);
221 if (classMapping == null) {
222
223 // create it
224 classMapping = new ClassMapping(classEntry.getName());
225 mappingChain.set(i, classMapping);
226
227 // add it to the right parent
228 if (i == 0) {
229 m_mappings.addClassMapping(classMapping);
230 } else {
231 mappingChain.get(i-1).addInnerClassMapping(classMapping);
232 }
233 }
234 }
235 return mappingChain;
236 }
237}
diff --git a/src/cuchaz/enigma/mapping/MappingsWriter.java b/src/cuchaz/enigma/mapping/MappingsWriter.java
new file mode 100644
index 00000000..1ebefefa
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MappingsWriter.java
@@ -0,0 +1,88 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.IOException;
14import java.io.PrintWriter;
15import java.io.Writer;
16import java.util.ArrayList;
17import java.util.Collections;
18import java.util.List;
19
20public class MappingsWriter {
21
22 public void write(Writer out, Mappings mappings) throws IOException {
23 write(new PrintWriter(out), mappings);
24 }
25
26 public void write(PrintWriter out, Mappings mappings) throws IOException {
27 for (ClassMapping classMapping : sorted(mappings.classes())) {
28 write(out, classMapping, 0);
29 }
30 }
31
32 private void write(PrintWriter out, ClassMapping classMapping, int depth) throws IOException {
33 if (classMapping.getDeobfName() == null) {
34 out.format("%sCLASS %s\n", getIndent(depth), classMapping.getObfFullName());
35 } else {
36 out.format("%sCLASS %s %s\n", getIndent(depth), classMapping.getObfFullName(), classMapping.getDeobfName());
37 }
38
39 for (ClassMapping innerClassMapping : sorted(classMapping.innerClasses())) {
40 write(out, innerClassMapping, depth + 1);
41 }
42
43 for (FieldMapping fieldMapping : sorted(classMapping.fields())) {
44 write(out, fieldMapping, depth + 1);
45 }
46
47 for (MethodMapping methodMapping : sorted(classMapping.methods())) {
48 write(out, methodMapping, depth + 1);
49 }
50 }
51
52 private void write(PrintWriter out, FieldMapping fieldMapping, int depth) throws IOException {
53 out.format("%sFIELD %s %s %s\n", getIndent(depth), fieldMapping.getObfName(), fieldMapping.getDeobfName(), fieldMapping.getObfType().toString());
54 }
55
56 private void write(PrintWriter out, MethodMapping methodMapping, int depth) throws IOException {
57 if (methodMapping.getDeobfName() == null) {
58 out.format("%sMETHOD %s %s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getObfSignature());
59 } else {
60 out.format("%sMETHOD %s %s %s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getDeobfName(), methodMapping.getObfSignature());
61 }
62
63 for (ArgumentMapping argumentMapping : sorted(methodMapping.arguments())) {
64 write(out, argumentMapping, depth + 1);
65 }
66 }
67
68 private void write(PrintWriter out, ArgumentMapping argumentMapping, int depth) throws IOException {
69 out.format("%sARG %d %s\n", getIndent(depth), argumentMapping.getIndex(), argumentMapping.getName());
70 }
71
72 private <T extends Comparable<T>> List<T> sorted(Iterable<T> classes) {
73 List<T> out = new ArrayList<T>();
74 for (T t : classes) {
75 out.add(t);
76 }
77 Collections.sort(out);
78 return out;
79 }
80
81 private String getIndent(int depth) {
82 StringBuilder buf = new StringBuilder();
83 for (int i = 0; i < depth; i++) {
84 buf.append("\t");
85 }
86 return buf.toString();
87 }
88}
diff --git a/src/cuchaz/enigma/mapping/MemberMapping.java b/src/cuchaz/enigma/mapping/MemberMapping.java
new file mode 100644
index 00000000..83782975
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MemberMapping.java
@@ -0,0 +1,17 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13
14public interface MemberMapping<T extends Entry> {
15 T getObfEntry(ClassEntry classEntry);
16 String getObfName();
17}
diff --git a/src/cuchaz/enigma/mapping/MethodEntry.java b/src/cuchaz/enigma/mapping/MethodEntry.java
new file mode 100644
index 00000000..eb9e2043
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MethodEntry.java
@@ -0,0 +1,104 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14
15import cuchaz.enigma.Util;
16
17public class MethodEntry implements BehaviorEntry, Serializable {
18
19 private static final long serialVersionUID = 4770915224467247458L;
20
21 private ClassEntry m_classEntry;
22 private String m_name;
23 private Signature m_signature;
24
25 public MethodEntry(ClassEntry classEntry, String name, Signature signature) {
26 if (classEntry == null) {
27 throw new IllegalArgumentException("Class cannot be null!");
28 }
29 if (name == null) {
30 throw new IllegalArgumentException("Method name cannot be null!");
31 }
32 if (signature == null) {
33 throw new IllegalArgumentException("Method signature cannot be null!");
34 }
35 if (name.startsWith("<")) {
36 throw new IllegalArgumentException("Don't use MethodEntry for a constructor!");
37 }
38
39 m_classEntry = classEntry;
40 m_name = name;
41 m_signature = signature;
42 }
43
44 public MethodEntry(MethodEntry other) {
45 m_classEntry = new ClassEntry(other.m_classEntry);
46 m_name = other.m_name;
47 m_signature = other.m_signature;
48 }
49
50 public MethodEntry(MethodEntry other, String newClassName) {
51 m_classEntry = new ClassEntry(newClassName);
52 m_name = other.m_name;
53 m_signature = other.m_signature;
54 }
55
56 @Override
57 public ClassEntry getClassEntry() {
58 return m_classEntry;
59 }
60
61 @Override
62 public String getName() {
63 return m_name;
64 }
65
66 @Override
67 public Signature getSignature() {
68 return m_signature;
69 }
70
71 @Override
72 public String getClassName() {
73 return m_classEntry.getName();
74 }
75
76 @Override
77 public MethodEntry cloneToNewClass(ClassEntry classEntry) {
78 return new MethodEntry(this, classEntry.getName());
79 }
80
81 @Override
82 public int hashCode() {
83 return Util.combineHashesOrdered(m_classEntry, m_name, m_signature);
84 }
85
86 @Override
87 public boolean equals(Object other) {
88 if (other instanceof MethodEntry) {
89 return equals((MethodEntry)other);
90 }
91 return false;
92 }
93
94 public boolean equals(MethodEntry other) {
95 return m_classEntry.equals(other.m_classEntry)
96 && m_name.equals(other.m_name)
97 && m_signature.equals(other.m_signature);
98 }
99
100 @Override
101 public String toString() {
102 return m_classEntry.getName() + "." + m_name + m_signature;
103 }
104}
diff --git a/src/cuchaz/enigma/mapping/MethodMapping.java b/src/cuchaz/enigma/mapping/MethodMapping.java
new file mode 100644
index 00000000..055e1fe1
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MethodMapping.java
@@ -0,0 +1,191 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14import java.util.Map;
15import java.util.Map.Entry;
16
17import com.google.common.collect.Maps;
18
19public class MethodMapping implements Serializable, Comparable<MethodMapping>, MemberMapping<BehaviorEntry> {
20
21 private static final long serialVersionUID = -4409570216084263978L;
22
23 private String m_obfName;
24 private String m_deobfName;
25 private Signature m_obfSignature;
26 private Map<Integer,ArgumentMapping> m_arguments;
27
28 public MethodMapping(String obfName, Signature obfSignature) {
29 this(obfName, obfSignature, null);
30 }
31
32 public MethodMapping(String obfName, Signature obfSignature, String deobfName) {
33 if (obfName == null) {
34 throw new IllegalArgumentException("obf name cannot be null!");
35 }
36 if (obfSignature == null) {
37 throw new IllegalArgumentException("obf signature cannot be null!");
38 }
39 m_obfName = obfName;
40 m_deobfName = NameValidator.validateMethodName(deobfName);
41 m_obfSignature = obfSignature;
42 m_arguments = Maps.newTreeMap();
43 }
44
45 public MethodMapping(MethodMapping other, ClassNameReplacer obfClassNameReplacer) {
46 m_obfName = other.m_obfName;
47 m_deobfName = other.m_deobfName;
48 m_obfSignature = new Signature(other.m_obfSignature, obfClassNameReplacer);
49 m_arguments = Maps.newTreeMap();
50 for (Entry<Integer,ArgumentMapping> entry : other.m_arguments.entrySet()) {
51 m_arguments.put(entry.getKey(), new ArgumentMapping(entry.getValue()));
52 }
53 }
54
55 @Override
56 public String getObfName() {
57 return m_obfName;
58 }
59
60 public void setObfName(String val) {
61 m_obfName = NameValidator.validateMethodName(val);
62 }
63
64 public String getDeobfName() {
65 return m_deobfName;
66 }
67
68 public void setDeobfName(String val) {
69 m_deobfName = NameValidator.validateMethodName(val);
70 }
71
72 public Signature getObfSignature() {
73 return m_obfSignature;
74 }
75
76 public void setObfSignature(Signature val) {
77 m_obfSignature = val;
78 }
79
80 public Iterable<ArgumentMapping> arguments() {
81 return m_arguments.values();
82 }
83
84 public boolean isConstructor() {
85 return m_obfName.startsWith("<");
86 }
87
88 public void addArgumentMapping(ArgumentMapping argumentMapping) {
89 boolean wasAdded = m_arguments.put(argumentMapping.getIndex(), argumentMapping) == null;
90 assert (wasAdded);
91 }
92
93 public String getObfArgumentName(int index) {
94 ArgumentMapping argumentMapping = m_arguments.get(index);
95 if (argumentMapping != null) {
96 return argumentMapping.getName();
97 }
98
99 return null;
100 }
101
102 public String getDeobfArgumentName(int index) {
103 ArgumentMapping argumentMapping = m_arguments.get(index);
104 if (argumentMapping != null) {
105 return argumentMapping.getName();
106 }
107
108 return null;
109 }
110
111 public void setArgumentName(int index, String name) {
112 ArgumentMapping argumentMapping = m_arguments.get(index);
113 if (argumentMapping == null) {
114 argumentMapping = new ArgumentMapping(index, name);
115 boolean wasAdded = m_arguments.put(index, argumentMapping) == null;
116 assert (wasAdded);
117 } else {
118 argumentMapping.setName(name);
119 }
120 }
121
122 public void removeArgumentName(int index) {
123 boolean wasRemoved = m_arguments.remove(index) != null;
124 assert (wasRemoved);
125 }
126
127 @Override
128 public String toString() {
129 StringBuilder buf = new StringBuilder();
130 buf.append("\t");
131 buf.append(m_obfName);
132 buf.append(" <-> ");
133 buf.append(m_deobfName);
134 buf.append("\n");
135 buf.append("\t");
136 buf.append(m_obfSignature);
137 buf.append("\n");
138 buf.append("\tArguments:\n");
139 for (ArgumentMapping argumentMapping : m_arguments.values()) {
140 buf.append("\t\t");
141 buf.append(argumentMapping.getIndex());
142 buf.append(" -> ");
143 buf.append(argumentMapping.getName());
144 buf.append("\n");
145 }
146 return buf.toString();
147 }
148
149 @Override
150 public int compareTo(MethodMapping other) {
151 return (m_obfName + m_obfSignature).compareTo(other.m_obfName + other.m_obfSignature);
152 }
153
154 public boolean renameObfClass(final String oldObfClassName, final String newObfClassName) {
155
156 // rename obf classes in the signature
157 Signature newSignature = new Signature(m_obfSignature, new ClassNameReplacer() {
158 @Override
159 public String replace(String className) {
160 if (className.equals(oldObfClassName)) {
161 return newObfClassName;
162 }
163 return null;
164 }
165 });
166
167 if (!newSignature.equals(m_obfSignature)) {
168 m_obfSignature = newSignature;
169 return true;
170 }
171 return false;
172 }
173
174 public boolean containsArgument(String name) {
175 for (ArgumentMapping argumentMapping : m_arguments.values()) {
176 if (argumentMapping.getName().equals(name)) {
177 return true;
178 }
179 }
180 return false;
181 }
182
183 @Override
184 public BehaviorEntry getObfEntry(ClassEntry classEntry) {
185 if (isConstructor()) {
186 return new ConstructorEntry(classEntry, m_obfSignature);
187 } else {
188 return new MethodEntry(classEntry, m_obfName, m_obfSignature);
189 }
190 }
191}
diff --git a/src/cuchaz/enigma/mapping/NameValidator.java b/src/cuchaz/enigma/mapping/NameValidator.java
new file mode 100644
index 00000000..12520e12
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/NameValidator.java
@@ -0,0 +1,80 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.util.Arrays;
14import java.util.List;
15import java.util.regex.Pattern;
16
17import javassist.bytecode.Descriptor;
18
19public class NameValidator {
20
21 private static final Pattern IdentifierPattern;
22 private static final Pattern ClassPattern;
23 private static final List<String> ReservedWords = Arrays.asList(
24 "abstract", "continue", "for", "new", "switch", "assert", "default", "goto", "package", "synchronized",
25 "boolean", "do", "if", "private", "this", "break", "double", "implements", "protected", "throw", "byte",
26 "else", "import", "public", "throws", "case", "enum", "instanceof", "return", "transient", "catch",
27 "extends", "int", "short", "try", "char", "final", "interface", "static", "void", "class", "finally",
28 "long", "strictfp", "volatile", "const", "float", "native", "super", "while"
29 );
30
31 static {
32
33 // java allows all kinds of weird characters...
34 StringBuilder startChars = new StringBuilder();
35 StringBuilder partChars = new StringBuilder();
36 for (int i = Character.MIN_CODE_POINT; i <= Character.MAX_CODE_POINT; i++) {
37 if (Character.isJavaIdentifierStart(i)) {
38 startChars.appendCodePoint(i);
39 }
40 if (Character.isJavaIdentifierPart(i)) {
41 partChars.appendCodePoint(i);
42 }
43 }
44
45 String identifierRegex = "[A-Za-z_<][A-Za-z0-9_>]*";
46 IdentifierPattern = Pattern.compile(identifierRegex);
47 ClassPattern = Pattern.compile(String.format("^(%s(\\.|/))*(%s)$", identifierRegex, identifierRegex));
48 }
49
50 public static String validateClassName(String name, boolean packageRequired) {
51 if (name == null) {
52 return null;
53 }
54 if (!ClassPattern.matcher(name).matches() || ReservedWords.contains(name)) {
55 throw new IllegalNameException(name, "This doesn't look like a legal class name");
56 }
57 if (packageRequired && new ClassEntry(name).getPackageName() == null) {
58 throw new IllegalNameException(name, "Class must be in a package");
59 }
60 return Descriptor.toJvmName(name);
61 }
62
63 public static String validateFieldName(String name) {
64 if (name == null) {
65 return null;
66 }
67 if (!IdentifierPattern.matcher(name).matches() || ReservedWords.contains(name)) {
68 throw new IllegalNameException(name, "This doesn't look like a legal identifier");
69 }
70 return name;
71 }
72
73 public static String validateMethodName(String name) {
74 return validateFieldName(name);
75 }
76
77 public static String validateArgumentName(String name) {
78 return validateFieldName(name);
79 }
80}
diff --git a/src/cuchaz/enigma/mapping/ProcyonEntryFactory.java b/src/cuchaz/enigma/mapping/ProcyonEntryFactory.java
new file mode 100644
index 00000000..777a12e4
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/ProcyonEntryFactory.java
@@ -0,0 +1,55 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import com.strobel.assembler.metadata.FieldDefinition;
14import com.strobel.assembler.metadata.MethodDefinition;
15
16
17public class ProcyonEntryFactory {
18
19 public static FieldEntry getFieldEntry(FieldDefinition def) {
20 return new FieldEntry(
21 new ClassEntry(def.getDeclaringType().getInternalName()),
22 def.getName(),
23 new Type(def.getErasedSignature())
24 );
25 }
26
27 public static MethodEntry getMethodEntry(MethodDefinition def) {
28 return new MethodEntry(
29 new ClassEntry(def.getDeclaringType().getInternalName()),
30 def.getName(),
31 new Signature(def.getErasedSignature())
32 );
33 }
34
35 public static ConstructorEntry getConstructorEntry(MethodDefinition def) {
36 if (def.isTypeInitializer()) {
37 return new ConstructorEntry(
38 new ClassEntry(def.getDeclaringType().getInternalName())
39 );
40 } else {
41 return new ConstructorEntry(
42 new ClassEntry(def.getDeclaringType().getInternalName()),
43 new Signature(def.getErasedSignature())
44 );
45 }
46 }
47
48 public static BehaviorEntry getBehaviorEntry(MethodDefinition def) {
49 if (def.isConstructor() || def.isTypeInitializer()) {
50 return getConstructorEntry(def);
51 } else {
52 return getMethodEntry(def);
53 }
54 }
55}
diff --git a/src/cuchaz/enigma/mapping/Signature.java b/src/cuchaz/enigma/mapping/Signature.java
new file mode 100644
index 00000000..8f2b6b2e
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/Signature.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 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14import java.util.List;
15
16import com.google.common.collect.Lists;
17
18import cuchaz.enigma.Util;
19
20public class Signature implements Serializable {
21
22 private static final long serialVersionUID = -5843719505729497539L;
23
24 private List<Type> m_argumentTypes;
25 private Type m_returnType;
26
27 public Signature(String signature) {
28 try {
29 m_argumentTypes = Lists.newArrayList();
30 int i=0;
31 while (i<signature.length()) {
32 char c = signature.charAt(i);
33 if (c == '(') {
34 assert(m_argumentTypes.isEmpty());
35 assert(m_returnType == null);
36 i++;
37 } else if (c == ')') {
38 i++;
39 break;
40 } else {
41 String type = Type.parseFirst(signature.substring(i));
42 m_argumentTypes.add(new Type(type));
43 i += type.length();
44 }
45 }
46 m_returnType = new Type(Type.parseFirst(signature.substring(i)));
47 } catch (Exception ex) {
48 throw new IllegalArgumentException("Unable to parse signature: " + signature, ex);
49 }
50 }
51
52 public Signature(Signature other) {
53 m_argumentTypes = Lists.newArrayList(other.m_argumentTypes);
54 m_returnType = new Type(other.m_returnType);
55 }
56
57 public Signature(Signature other, ClassNameReplacer replacer) {
58 m_argumentTypes = Lists.newArrayList(other.m_argumentTypes);
59 for (int i=0; i<m_argumentTypes.size(); i++) {
60 m_argumentTypes.set(i, new Type(m_argumentTypes.get(i), replacer));
61 }
62 m_returnType = new Type(other.m_returnType, replacer);
63 }
64
65 public List<Type> getArgumentTypes() {
66 return m_argumentTypes;
67 }
68
69 public Type getReturnType() {
70 return m_returnType;
71 }
72
73 @Override
74 public String toString() {
75 StringBuilder buf = new StringBuilder();
76 buf.append("(");
77 for (Type type : m_argumentTypes) {
78 buf.append(type.toString());
79 }
80 buf.append(")");
81 buf.append(m_returnType.toString());
82 return buf.toString();
83 }
84
85 public Iterable<Type> types() {
86 List<Type> types = Lists.newArrayList();
87 types.addAll(m_argumentTypes);
88 types.add(m_returnType);
89 return types;
90 }
91
92 @Override
93 public boolean equals(Object other) {
94 if (other instanceof Signature) {
95 return equals((Signature)other);
96 }
97 return false;
98 }
99
100 public boolean equals(Signature other) {
101 return m_argumentTypes.equals(other.m_argumentTypes) && m_returnType.equals(other.m_returnType);
102 }
103
104 @Override
105 public int hashCode() {
106 return Util.combineHashesOrdered(m_argumentTypes.hashCode(), m_returnType.hashCode());
107 }
108
109 public boolean hasClass(ClassEntry classEntry) {
110 for (Type type : types()) {
111 if (type.hasClass() && type.getClassEntry().equals(classEntry)) {
112 return true;
113 }
114 }
115 return false;
116 }
117}
diff --git a/src/cuchaz/enigma/mapping/SignatureUpdater.java b/src/cuchaz/enigma/mapping/SignatureUpdater.java
new file mode 100644
index 00000000..eb53233e
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/SignatureUpdater.java
@@ -0,0 +1,94 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.IOException;
14import java.io.StringReader;
15import java.util.List;
16
17import com.google.common.collect.Lists;
18
19public class SignatureUpdater {
20
21 public interface ClassNameUpdater {
22 String update(String className);
23 }
24
25 public static String update(String signature, ClassNameUpdater updater) {
26 try {
27 StringBuilder buf = new StringBuilder();
28
29 // read the signature character-by-character
30 StringReader reader = new StringReader(signature);
31 int i = -1;
32 while ( (i = reader.read()) != -1) {
33 char c = (char)i;
34
35 // does this character start a class name?
36 if (c == 'L') {
37 // update the class name and add it to the buffer
38 buf.append('L');
39 String className = readClass(reader);
40 if (className == null) {
41 throw new IllegalArgumentException("Malformed signature: " + signature);
42 }
43 buf.append(updater.update(className));
44 buf.append(';');
45 } else {
46 // copy the character into the buffer
47 buf.append(c);
48 }
49 }
50
51 return buf.toString();
52 } catch (IOException ex) {
53 // I'm pretty sure a StringReader will never throw one of these
54 throw new Error(ex);
55 }
56 }
57
58 private static String readClass(StringReader reader) throws IOException {
59 // read all the characters in the buffer until we hit a ';'
60 // remember to treat generics correctly
61 StringBuilder buf = new StringBuilder();
62 int depth = 0;
63 int i = -1;
64 while ( (i = reader.read()) != -1) {
65 char c = (char)i;
66
67 if (c == '<') {
68 depth++;
69 } else if (c == '>') {
70 depth--;
71 } else if (depth == 0) {
72 if (c == ';') {
73 return buf.toString();
74 } else {
75 buf.append(c);
76 }
77 }
78 }
79
80 return null;
81 }
82
83 public static List<String> getClasses(String signature) {
84 final List<String> classNames = Lists.newArrayList();
85 update(signature, new ClassNameUpdater() {
86 @Override
87 public String update(String className) {
88 classNames.add(className);
89 return className;
90 }
91 });
92 return classNames;
93 }
94}
diff --git a/src/cuchaz/enigma/mapping/TranslationDirection.java b/src/cuchaz/enigma/mapping/TranslationDirection.java
new file mode 100644
index 00000000..bc3aaa13
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/TranslationDirection.java
@@ -0,0 +1,29 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13public enum TranslationDirection {
14
15 Deobfuscating {
16 @Override
17 public <T> T choose(T deobfChoice, T obfChoice) {
18 return deobfChoice;
19 }
20 },
21 Obfuscating {
22 @Override
23 public <T> T choose(T deobfChoice, T obfChoice) {
24 return obfChoice;
25 }
26 };
27
28 public abstract <T> T choose(T deobfChoice, T obfChoice);
29}
diff --git a/src/cuchaz/enigma/mapping/Translator.java b/src/cuchaz/enigma/mapping/Translator.java
new file mode 100644
index 00000000..41c7d7cc
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/Translator.java
@@ -0,0 +1,289 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.util.List;
14import java.util.Map;
15
16import com.google.common.collect.Lists;
17import com.google.common.collect.Maps;
18
19import cuchaz.enigma.analysis.TranslationIndex;
20
21public class Translator {
22
23 private TranslationDirection m_direction;
24 private Map<String,ClassMapping> m_classes;
25 private TranslationIndex m_index;
26
27 private ClassNameReplacer m_classNameReplacer = new ClassNameReplacer() {
28 @Override
29 public String replace(String className) {
30 return translateEntry(new ClassEntry(className)).getName();
31 }
32 };
33
34 public Translator() {
35 m_direction = null;
36 m_classes = Maps.newHashMap();
37 m_index = new TranslationIndex();
38 }
39
40 public Translator(TranslationDirection direction, Map<String,ClassMapping> classes, TranslationIndex index) {
41 m_direction = direction;
42 m_classes = classes;
43 m_index = index;
44 }
45
46 public TranslationDirection getDirection() {
47 return m_direction;
48 }
49
50 public TranslationIndex getTranslationIndex() {
51 return m_index;
52 }
53
54 @SuppressWarnings("unchecked")
55 public <T extends Entry> T translateEntry(T entry) {
56 if (entry instanceof ClassEntry) {
57 return (T)translateEntry((ClassEntry)entry);
58 } else if (entry instanceof FieldEntry) {
59 return (T)translateEntry((FieldEntry)entry);
60 } else if (entry instanceof MethodEntry) {
61 return (T)translateEntry((MethodEntry)entry);
62 } else if (entry instanceof ConstructorEntry) {
63 return (T)translateEntry((ConstructorEntry)entry);
64 } else if (entry instanceof ArgumentEntry) {
65 return (T)translateEntry((ArgumentEntry)entry);
66 } else {
67 throw new Error("Unknown entry type: " + entry.getClass().getName());
68 }
69 }
70
71 public <T extends Entry> String translate(T entry) {
72 if (entry instanceof ClassEntry) {
73 return translate((ClassEntry)entry);
74 } else if (entry instanceof FieldEntry) {
75 return translate((FieldEntry)entry);
76 } else if (entry instanceof MethodEntry) {
77 return translate((MethodEntry)entry);
78 } else if (entry instanceof ConstructorEntry) {
79 return translate((ConstructorEntry)entry);
80 } else if (entry instanceof ArgumentEntry) {
81 return translate((ArgumentEntry)entry);
82 } else {
83 throw new Error("Unknown entry type: " + entry.getClass().getName());
84 }
85 }
86
87 public String translate(ClassEntry in) {
88 ClassEntry translated = translateEntry(in);
89 if (translated.equals(in)) {
90 return null;
91 }
92 return translated.getName();
93 }
94
95 public String translateClass(String className) {
96 return translate(new ClassEntry(className));
97 }
98
99 public ClassEntry translateEntry(ClassEntry in) {
100
101 if (in.isInnerClass()) {
102
103 // translate as much of the class chain as we can
104 List<ClassMapping> mappingsChain = getClassMappingChain(in);
105 String[] obfClassNames = in.getName().split("\\$");
106 StringBuilder buf = new StringBuilder();
107 for (int i=0; i<obfClassNames.length; i++) {
108 boolean isFirstClass = buf.length() == 0;
109 String className = null;
110 ClassMapping classMapping = mappingsChain.get(i);
111 if (classMapping != null) {
112 className = m_direction.choose(
113 classMapping.getDeobfName(),
114 isFirstClass ? classMapping.getObfFullName() : classMapping.getObfSimpleName()
115 );
116 }
117 if (className == null) {
118 className = obfClassNames[i];
119 }
120 if (!isFirstClass) {
121 buf.append("$");
122 }
123 buf.append(className);
124 }
125 return new ClassEntry(buf.toString());
126
127 } else {
128
129 // normal classes are easy
130 ClassMapping classMapping = m_classes.get(in.getName());
131 if (classMapping == null) {
132 return in;
133 }
134 return m_direction.choose(
135 classMapping.getDeobfName() != null ? new ClassEntry(classMapping.getDeobfName()) : in,
136 new ClassEntry(classMapping.getObfFullName())
137 );
138 }
139 }
140
141 public String translate(FieldEntry in) {
142
143 // resolve the class entry
144 ClassEntry resolvedClassEntry = m_index.resolveEntryClass(in);
145 if (resolvedClassEntry != null) {
146
147 // look for the class
148 ClassMapping classMapping = findClassMapping(resolvedClassEntry);
149 if (classMapping != null) {
150
151 // look for the field
152 String translatedName = m_direction.choose(
153 classMapping.getDeobfFieldName(in.getName(), in.getType()),
154 classMapping.getObfFieldName(in.getName(), translateType(in.getType()))
155 );
156 if (translatedName != null) {
157 return translatedName;
158 }
159 }
160 }
161 return null;
162 }
163
164 public FieldEntry translateEntry(FieldEntry in) {
165 String name = translate(in);
166 if (name == null) {
167 name = in.getName();
168 }
169 return new FieldEntry(translateEntry(in.getClassEntry()), name, translateType(in.getType()));
170 }
171
172 public String translate(MethodEntry in) {
173
174 // resolve the class entry
175 ClassEntry resolvedClassEntry = m_index.resolveEntryClass(in);
176 if (resolvedClassEntry != null) {
177
178 // look for class
179 ClassMapping classMapping = findClassMapping(resolvedClassEntry);
180 if (classMapping != null) {
181
182 // look for the method
183 MethodMapping methodMapping = m_direction.choose(
184 classMapping.getMethodByObf(in.getName(), in.getSignature()),
185 classMapping.getMethodByDeobf(in.getName(), translateSignature(in.getSignature()))
186 );
187 if (methodMapping != null) {
188 return m_direction.choose(methodMapping.getDeobfName(), methodMapping.getObfName());
189 }
190 }
191 }
192 return null;
193 }
194
195 public MethodEntry translateEntry(MethodEntry in) {
196 String name = translate(in);
197 if (name == null) {
198 name = in.getName();
199 }
200 return new MethodEntry(translateEntry(in.getClassEntry()), name, translateSignature(in.getSignature()));
201 }
202
203 public ConstructorEntry translateEntry(ConstructorEntry in) {
204 if (in.isStatic()) {
205 return new ConstructorEntry(translateEntry(in.getClassEntry()));
206 } else {
207 return new ConstructorEntry(translateEntry(in.getClassEntry()), translateSignature(in.getSignature()));
208 }
209 }
210
211 public BehaviorEntry translateEntry(BehaviorEntry in) {
212 if (in instanceof MethodEntry) {
213 return translateEntry((MethodEntry)in);
214 } else if (in instanceof ConstructorEntry) {
215 return translateEntry((ConstructorEntry)in);
216 }
217 throw new Error("Wrong entry type!");
218 }
219
220 public String translate(ArgumentEntry in) {
221
222 // look for the class
223 ClassMapping classMapping = findClassMapping(in.getClassEntry());
224 if (classMapping != null) {
225
226 // look for the method
227 MethodMapping methodMapping = m_direction.choose(
228 classMapping.getMethodByObf(in.getMethodName(), in.getMethodSignature()),
229 classMapping.getMethodByDeobf(in.getMethodName(), translateSignature(in.getMethodSignature()))
230 );
231 if (methodMapping != null) {
232 return m_direction.choose(
233 methodMapping.getDeobfArgumentName(in.getIndex()),
234 methodMapping.getObfArgumentName(in.getIndex())
235 );
236 }
237 }
238 return null;
239 }
240
241 public ArgumentEntry translateEntry(ArgumentEntry in) {
242 String name = translate(in);
243 if (name == null) {
244 name = in.getName();
245 }
246 return new ArgumentEntry(translateEntry(in.getBehaviorEntry()), in.getIndex(), name);
247 }
248
249 public Type translateType(Type type) {
250 return new Type(type, m_classNameReplacer);
251 }
252
253 public Signature translateSignature(Signature signature) {
254 return new Signature(signature, m_classNameReplacer);
255 }
256
257 private ClassMapping findClassMapping(ClassEntry in) {
258 List<ClassMapping> mappingChain = getClassMappingChain(in);
259 return mappingChain.get(mappingChain.size() - 1);
260 }
261
262 private List<ClassMapping> getClassMappingChain(ClassEntry in) {
263
264 // get a list of all the classes in the hierarchy
265 String[] parts = in.getName().split("\\$");
266 List<ClassMapping> mappingsChain = Lists.newArrayList();
267
268 // get mappings for the outer class
269 ClassMapping outerClassMapping = m_classes.get(parts[0]);
270 mappingsChain.add(outerClassMapping);
271
272 for (int i=1; i<parts.length; i++) {
273
274 // get mappings for the inner class
275 ClassMapping innerClassMapping = null;
276 if (outerClassMapping != null) {
277 innerClassMapping = m_direction.choose(
278 outerClassMapping.getInnerClassByObfSimple(parts[i]),
279 outerClassMapping.getInnerClassByDeobfThenObfSimple(parts[i])
280 );
281 }
282 mappingsChain.add(innerClassMapping);
283 outerClassMapping = innerClassMapping;
284 }
285
286 assert(mappingsChain.size() == parts.length);
287 return mappingsChain;
288 }
289}
diff --git a/src/cuchaz/enigma/mapping/Type.java b/src/cuchaz/enigma/mapping/Type.java
new file mode 100644
index 00000000..f86a5ccc
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/Type.java
@@ -0,0 +1,247 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14import java.util.Map;
15
16import com.google.common.collect.Maps;
17
18public class Type implements Serializable {
19
20 private static final long serialVersionUID = 7862257669347104063L;
21
22 public enum Primitive {
23 Byte('B'),
24 Character('C'),
25 Short('S'),
26 Integer('I'),
27 Long('J'),
28 Float('F'),
29 Double('D'),
30 Boolean('Z');
31
32 private static final Map<Character,Primitive> m_lookup;
33
34 static {
35 m_lookup = Maps.newTreeMap();
36 for (Primitive val : values()) {
37 m_lookup.put(val.getCode(), val);
38 }
39 }
40
41 public static Primitive get(char code) {
42 return m_lookup.get(code);
43 }
44
45 private char m_code;
46
47 private Primitive(char code) {
48 m_code = code;
49 }
50
51 public char getCode() {
52 return m_code;
53 }
54 }
55
56 public static String parseFirst(String in) {
57
58 if (in == null || in.length() <= 0) {
59 throw new IllegalArgumentException("No type to parse, input is empty!");
60 }
61
62 // read one type from the input
63
64 char c = in.charAt(0);
65
66 // first check for void
67 if (c == 'V') {
68 return "V";
69 }
70
71 // then check for primitives
72 Primitive primitive = Primitive.get(c);
73 if (primitive != null) {
74 return in.substring(0, 1);
75 }
76
77 // then check for classes
78 if (c == 'L') {
79 return readClass(in);
80 }
81
82 // then check for templates
83 if (c == 'T') {
84 return readClass(in);
85 }
86
87 // then check for arrays
88 int dim = countArrayDimension(in);
89 if (dim > 0) {
90 String arrayType = Type.parseFirst(in.substring(dim));
91 return in.substring(0, dim + arrayType.length());
92 }
93
94 throw new IllegalArgumentException("don't know how to parse: " + in);
95 }
96
97 protected String m_name;
98
99 public Type(String name) {
100
101 // don't deal with generics
102 // this is just for raw jvm types
103 if (name.charAt(0) == 'T' || name.indexOf('<') >= 0 || name.indexOf('>') >= 0) {
104 throw new IllegalArgumentException("don't use with generic types or templates: " + name);
105 }
106
107 m_name = name;
108 }
109
110 public Type(Type other) {
111 m_name = other.m_name;
112 }
113
114 public Type(ClassEntry classEntry) {
115 m_name = "L" + classEntry.getClassName() + ";";
116 }
117
118 public Type(Type other, ClassNameReplacer replacer) {
119 m_name = other.m_name;
120 if (other.isClass()) {
121 String replacedName = replacer.replace(other.getClassEntry().getClassName());
122 if (replacedName != null) {
123 m_name = "L" + replacedName + ";";
124 }
125 } else if (other.isArray() && other.hasClass()) {
126 String replacedName = replacer.replace(other.getClassEntry().getClassName());
127 if (replacedName != null) {
128 m_name = Type.getArrayPrefix(other.getArrayDimension()) + "L" + replacedName + ";";
129 }
130 }
131 }
132
133 @Override
134 public String toString() {
135 return m_name;
136 }
137
138 public boolean isVoid() {
139 return m_name.length() == 1 && m_name.charAt(0) == 'V';
140 }
141
142 public boolean isPrimitive() {
143 return m_name.length() == 1 && Primitive.get(m_name.charAt(0)) != null;
144 }
145
146 public Primitive getPrimitive() {
147 if (!isPrimitive()) {
148 throw new IllegalStateException("not a primitive");
149 }
150 return Primitive.get(m_name.charAt(0));
151 }
152
153 public boolean isClass() {
154 return m_name.charAt(0) == 'L' && m_name.charAt(m_name.length() - 1) == ';';
155 }
156
157 public ClassEntry getClassEntry() {
158 if (isClass()) {
159 String name = m_name.substring(1, m_name.length() - 1);
160
161 int pos = name.indexOf('<');
162 if (pos >= 0) {
163 // remove the parameters from the class name
164 name = name.substring(0, pos);
165 }
166
167 return new ClassEntry(name);
168
169 } else if (isArray() && getArrayType().isClass()) {
170 return getArrayType().getClassEntry();
171 } else {
172 throw new IllegalStateException("type doesn't have a class");
173 }
174 }
175
176 public boolean isArray() {
177 return m_name.charAt(0) == '[';
178 }
179
180 public int getArrayDimension() {
181 if (!isArray()) {
182 throw new IllegalStateException("not an array");
183 }
184 return countArrayDimension(m_name);
185 }
186
187 public Type getArrayType() {
188 if (!isArray()) {
189 throw new IllegalStateException("not an array");
190 }
191 return new Type(m_name.substring(getArrayDimension(), m_name.length()));
192 }
193
194 private static String getArrayPrefix(int dimension) {
195 StringBuilder buf = new StringBuilder();
196 for (int i=0; i<dimension; i++) {
197 buf.append("[");
198 }
199 return buf.toString();
200 }
201
202 public boolean hasClass() {
203 return isClass() || (isArray() && getArrayType().hasClass());
204 }
205
206 @Override
207 public boolean equals(Object other) {
208 if (other instanceof Type) {
209 return equals((Type)other);
210 }
211 return false;
212 }
213
214 public boolean equals(Type other) {
215 return m_name.equals(other.m_name);
216 }
217
218 public int hashCode() {
219 return m_name.hashCode();
220 }
221
222 private static int countArrayDimension(String in) {
223 int i=0;
224 for(; i < in.length() && in.charAt(i) == '['; i++);
225 return i;
226 }
227
228 private static String readClass(String in) {
229 // read all the characters in the buffer until we hit a ';'
230 // include the parameters too
231 StringBuilder buf = new StringBuilder();
232 int depth = 0;
233 for (int i=0; i<in.length(); i++) {
234 char c = in.charAt(i);
235 buf.append(c);
236
237 if (c == '<') {
238 depth++;
239 } else if (c == '>') {
240 depth--;
241 } else if (depth == 0 && c == ';') {
242 return buf.toString();
243 }
244 }
245 return null;
246 }
247}