diff options
Diffstat (limited to 'src')
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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma; | ||
| 12 | |||
| 13 | import java.io.File; | ||
| 14 | import java.io.FileReader; | ||
| 15 | import java.util.jar.JarFile; | ||
| 16 | |||
| 17 | import cuchaz.enigma.Deobfuscator.ProgressListener; | ||
| 18 | import cuchaz.enigma.mapping.Mappings; | ||
| 19 | import cuchaz.enigma.mapping.MappingsReader; | ||
| 20 | |||
| 21 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma; | ||
| 12 | |||
| 13 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma; | ||
| 12 | |||
| 13 | import java.io.File; | ||
| 14 | import java.io.FileReader; | ||
| 15 | import java.io.FileWriter; | ||
| 16 | import java.io.IOException; | ||
| 17 | import java.util.jar.JarFile; | ||
| 18 | |||
| 19 | import cuchaz.enigma.convert.ClassMatches; | ||
| 20 | import cuchaz.enigma.convert.MappingsConverter; | ||
| 21 | import cuchaz.enigma.convert.MatchesReader; | ||
| 22 | import cuchaz.enigma.convert.MatchesWriter; | ||
| 23 | import cuchaz.enigma.convert.MemberMatches; | ||
| 24 | import cuchaz.enigma.gui.ClassMatchingGui; | ||
| 25 | import cuchaz.enigma.gui.MemberMatchingGui; | ||
| 26 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 27 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 28 | import cuchaz.enigma.mapping.ClassMapping; | ||
| 29 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 30 | import cuchaz.enigma.mapping.FieldMapping; | ||
| 31 | import cuchaz.enigma.mapping.MappingParseException; | ||
| 32 | import cuchaz.enigma.mapping.Mappings; | ||
| 33 | import cuchaz.enigma.mapping.MappingsChecker; | ||
| 34 | import cuchaz.enigma.mapping.MappingsReader; | ||
| 35 | import cuchaz.enigma.mapping.MappingsWriter; | ||
| 36 | import cuchaz.enigma.mapping.MethodMapping; | ||
| 37 | |||
| 38 | |||
| 39 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma; | ||
| 12 | |||
| 13 | import java.io.File; | ||
| 14 | import java.io.FileOutputStream; | ||
| 15 | import java.io.FileWriter; | ||
| 16 | import java.io.IOException; | ||
| 17 | import java.io.StringWriter; | ||
| 18 | import java.util.List; | ||
| 19 | import java.util.Map; | ||
| 20 | import java.util.Set; | ||
| 21 | import java.util.jar.JarEntry; | ||
| 22 | import java.util.jar.JarFile; | ||
| 23 | import java.util.jar.JarOutputStream; | ||
| 24 | |||
| 25 | import javassist.CtClass; | ||
| 26 | import javassist.bytecode.Descriptor; | ||
| 27 | |||
| 28 | import com.google.common.collect.Maps; | ||
| 29 | import com.google.common.collect.Sets; | ||
| 30 | import com.strobel.assembler.metadata.MetadataSystem; | ||
| 31 | import com.strobel.assembler.metadata.TypeDefinition; | ||
| 32 | import com.strobel.assembler.metadata.TypeReference; | ||
| 33 | import com.strobel.decompiler.DecompilerContext; | ||
| 34 | import com.strobel.decompiler.DecompilerSettings; | ||
| 35 | import com.strobel.decompiler.PlainTextOutput; | ||
| 36 | import com.strobel.decompiler.languages.java.JavaOutputVisitor; | ||
| 37 | import com.strobel.decompiler.languages.java.ast.AstBuilder; | ||
| 38 | import com.strobel.decompiler.languages.java.ast.CompilationUnit; | ||
| 39 | import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor; | ||
| 40 | |||
| 41 | import cuchaz.enigma.analysis.EntryReference; | ||
| 42 | import cuchaz.enigma.analysis.JarClassIterator; | ||
| 43 | import cuchaz.enigma.analysis.JarIndex; | ||
| 44 | import cuchaz.enigma.analysis.SourceIndex; | ||
| 45 | import cuchaz.enigma.analysis.SourceIndexVisitor; | ||
| 46 | import cuchaz.enigma.analysis.Token; | ||
| 47 | import cuchaz.enigma.bytecode.ClassProtectifier; | ||
| 48 | import cuchaz.enigma.bytecode.ClassPublifier; | ||
| 49 | import cuchaz.enigma.mapping.ArgumentEntry; | ||
| 50 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 51 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 52 | import cuchaz.enigma.mapping.ClassMapping; | ||
| 53 | import cuchaz.enigma.mapping.ConstructorEntry; | ||
| 54 | import cuchaz.enigma.mapping.Entry; | ||
| 55 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 56 | import cuchaz.enigma.mapping.FieldMapping; | ||
| 57 | import cuchaz.enigma.mapping.Mappings; | ||
| 58 | import cuchaz.enigma.mapping.MappingsChecker; | ||
| 59 | import cuchaz.enigma.mapping.MappingsRenamer; | ||
| 60 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 61 | import cuchaz.enigma.mapping.MethodMapping; | ||
| 62 | import cuchaz.enigma.mapping.TranslationDirection; | ||
| 63 | import cuchaz.enigma.mapping.Translator; | ||
| 64 | |||
| 65 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma; | ||
| 12 | |||
| 13 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma; | ||
| 12 | |||
| 13 | import java.io.File; | ||
| 14 | import java.util.jar.JarFile; | ||
| 15 | |||
| 16 | import cuchaz.enigma.gui.Gui; | ||
| 17 | |||
| 18 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma; | ||
| 12 | |||
| 13 | import java.io.File; | ||
| 14 | import java.io.FileReader; | ||
| 15 | import java.io.FileWriter; | ||
| 16 | import java.lang.reflect.Field; | ||
| 17 | import java.util.Map; | ||
| 18 | import java.util.jar.JarFile; | ||
| 19 | |||
| 20 | import javassist.CtClass; | ||
| 21 | import javassist.CtField; | ||
| 22 | |||
| 23 | import com.google.common.collect.Maps; | ||
| 24 | |||
| 25 | import cuchaz.enigma.analysis.JarClassIterator; | ||
| 26 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 27 | import cuchaz.enigma.mapping.ClassMapping; | ||
| 28 | import cuchaz.enigma.mapping.ClassNameReplacer; | ||
| 29 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 30 | import cuchaz.enigma.mapping.FieldMapping; | ||
| 31 | import cuchaz.enigma.mapping.EntryFactory; | ||
| 32 | import cuchaz.enigma.mapping.Mappings; | ||
| 33 | import cuchaz.enigma.mapping.MappingsReader; | ||
| 34 | import cuchaz.enigma.mapping.MappingsWriter; | ||
| 35 | import cuchaz.enigma.mapping.Type; | ||
| 36 | |||
| 37 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma; | ||
| 12 | |||
| 13 | import java.io.ByteArrayOutputStream; | ||
| 14 | import java.io.IOException; | ||
| 15 | import java.io.InputStream; | ||
| 16 | import java.util.List; | ||
| 17 | import java.util.Map; | ||
| 18 | import java.util.jar.JarEntry; | ||
| 19 | import java.util.jar.JarFile; | ||
| 20 | |||
| 21 | import javassist.ByteArrayClassPath; | ||
| 22 | import javassist.CannotCompileException; | ||
| 23 | import javassist.ClassPool; | ||
| 24 | import javassist.CtClass; | ||
| 25 | import javassist.NotFoundException; | ||
| 26 | import javassist.bytecode.Descriptor; | ||
| 27 | |||
| 28 | import com.google.common.collect.Lists; | ||
| 29 | import com.google.common.collect.Maps; | ||
| 30 | import com.strobel.assembler.metadata.Buffer; | ||
| 31 | import com.strobel.assembler.metadata.ClasspathTypeLoader; | ||
| 32 | import com.strobel.assembler.metadata.ITypeLoader; | ||
| 33 | |||
| 34 | import cuchaz.enigma.analysis.BridgeMarker; | ||
| 35 | import cuchaz.enigma.analysis.JarIndex; | ||
| 36 | import cuchaz.enigma.bytecode.ClassRenamer; | ||
| 37 | import cuchaz.enigma.bytecode.ClassTranslator; | ||
| 38 | import cuchaz.enigma.bytecode.InnerClassWriter; | ||
| 39 | import cuchaz.enigma.bytecode.LocalVariableRenamer; | ||
| 40 | import cuchaz.enigma.bytecode.MethodParameterWriter; | ||
| 41 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 42 | import cuchaz.enigma.mapping.Translator; | ||
| 43 | |||
| 44 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma; | ||
| 12 | |||
| 13 | import java.awt.Desktop; | ||
| 14 | import java.io.Closeable; | ||
| 15 | import java.io.File; | ||
| 16 | import java.io.FileOutputStream; | ||
| 17 | import java.io.IOException; | ||
| 18 | import java.io.InputStream; | ||
| 19 | import java.io.InputStreamReader; | ||
| 20 | import java.net.URI; | ||
| 21 | import java.net.URISyntaxException; | ||
| 22 | import java.util.Arrays; | ||
| 23 | import java.util.jar.JarFile; | ||
| 24 | |||
| 25 | import javassist.CannotCompileException; | ||
| 26 | import javassist.CtClass; | ||
| 27 | import javassist.bytecode.Descriptor; | ||
| 28 | |||
| 29 | import com.google.common.io.CharStreams; | ||
| 30 | |||
| 31 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import java.lang.reflect.Modifier; | ||
| 14 | |||
| 15 | import javassist.CtBehavior; | ||
| 16 | import javassist.CtField; | ||
| 17 | |||
| 18 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import java.util.Set; | ||
| 14 | |||
| 15 | import javax.swing.tree.DefaultMutableTreeNode; | ||
| 16 | import javax.swing.tree.TreeNode; | ||
| 17 | |||
| 18 | import com.google.common.collect.Sets; | ||
| 19 | |||
| 20 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 21 | import cuchaz.enigma.mapping.Entry; | ||
| 22 | import cuchaz.enigma.mapping.Translator; | ||
| 23 | |||
| 24 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import javassist.CtClass; | ||
| 14 | import javassist.CtMethod; | ||
| 15 | import javassist.bytecode.AccessFlag; | ||
| 16 | import cuchaz.enigma.mapping.EntryFactory; | ||
| 17 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 18 | |||
| 19 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import java.util.List; | ||
| 14 | |||
| 15 | import javax.swing.tree.DefaultMutableTreeNode; | ||
| 16 | |||
| 17 | import com.google.common.collect.Lists; | ||
| 18 | |||
| 19 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 20 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 21 | import cuchaz.enigma.mapping.Translator; | ||
| 22 | |||
| 23 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import java.util.List; | ||
| 14 | |||
| 15 | import javax.swing.tree.DefaultMutableTreeNode; | ||
| 16 | |||
| 17 | import com.google.common.collect.Lists; | ||
| 18 | |||
| 19 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 20 | import cuchaz.enigma.mapping.Translator; | ||
| 21 | |||
| 22 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import java.util.Arrays; | ||
| 14 | import java.util.List; | ||
| 15 | |||
| 16 | import cuchaz.enigma.Util; | ||
| 17 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 18 | import cuchaz.enigma.mapping.ConstructorEntry; | ||
| 19 | import cuchaz.enigma.mapping.Entry; | ||
| 20 | |||
| 21 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import java.util.AbstractMap; | ||
| 14 | import java.util.List; | ||
| 15 | import java.util.Map; | ||
| 16 | import java.util.Set; | ||
| 17 | |||
| 18 | import com.google.common.collect.Lists; | ||
| 19 | import com.google.common.collect.Multimap; | ||
| 20 | import com.google.common.collect.Sets; | ||
| 21 | |||
| 22 | import cuchaz.enigma.mapping.ArgumentEntry; | ||
| 23 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 24 | import cuchaz.enigma.mapping.ClassNameReplacer; | ||
| 25 | import cuchaz.enigma.mapping.ConstructorEntry; | ||
| 26 | import cuchaz.enigma.mapping.Entry; | ||
| 27 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 28 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 29 | import cuchaz.enigma.mapping.Signature; | ||
| 30 | import cuchaz.enigma.mapping.Type; | ||
| 31 | |||
| 32 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import javax.swing.tree.DefaultMutableTreeNode; | ||
| 14 | |||
| 15 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 16 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 17 | import cuchaz.enigma.mapping.Translator; | ||
| 18 | |||
| 19 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import java.io.ByteArrayOutputStream; | ||
| 14 | import java.io.IOException; | ||
| 15 | import java.io.InputStream; | ||
| 16 | import java.util.Enumeration; | ||
| 17 | import java.util.Iterator; | ||
| 18 | import java.util.List; | ||
| 19 | import java.util.jar.JarEntry; | ||
| 20 | import java.util.jar.JarFile; | ||
| 21 | |||
| 22 | import javassist.ByteArrayClassPath; | ||
| 23 | import javassist.ClassPool; | ||
| 24 | import javassist.CtClass; | ||
| 25 | import javassist.NotFoundException; | ||
| 26 | import javassist.bytecode.Descriptor; | ||
| 27 | |||
| 28 | import com.google.common.collect.Lists; | ||
| 29 | |||
| 30 | import cuchaz.enigma.Constants; | ||
| 31 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 32 | |||
| 33 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import java.lang.reflect.Modifier; | ||
| 14 | import java.util.Collection; | ||
| 15 | import java.util.Collections; | ||
| 16 | import java.util.HashSet; | ||
| 17 | import java.util.List; | ||
| 18 | import java.util.Map; | ||
| 19 | import java.util.Set; | ||
| 20 | import java.util.jar.JarFile; | ||
| 21 | |||
| 22 | import javassist.CannotCompileException; | ||
| 23 | import javassist.CtBehavior; | ||
| 24 | import javassist.CtClass; | ||
| 25 | import javassist.CtConstructor; | ||
| 26 | import javassist.CtField; | ||
| 27 | import javassist.CtMethod; | ||
| 28 | import javassist.NotFoundException; | ||
| 29 | import javassist.bytecode.AccessFlag; | ||
| 30 | import javassist.bytecode.Descriptor; | ||
| 31 | import javassist.bytecode.EnclosingMethodAttribute; | ||
| 32 | import javassist.bytecode.FieldInfo; | ||
| 33 | import javassist.bytecode.InnerClassesAttribute; | ||
| 34 | import javassist.expr.ConstructorCall; | ||
| 35 | import javassist.expr.ExprEditor; | ||
| 36 | import javassist.expr.FieldAccess; | ||
| 37 | import javassist.expr.MethodCall; | ||
| 38 | import javassist.expr.NewExpr; | ||
| 39 | |||
| 40 | import com.google.common.collect.HashMultimap; | ||
| 41 | import com.google.common.collect.Lists; | ||
| 42 | import com.google.common.collect.Maps; | ||
| 43 | import com.google.common.collect.Multimap; | ||
| 44 | import com.google.common.collect.Sets; | ||
| 45 | |||
| 46 | import cuchaz.enigma.Constants; | ||
| 47 | import cuchaz.enigma.bytecode.ClassRenamer; | ||
| 48 | import cuchaz.enigma.mapping.ArgumentEntry; | ||
| 49 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 50 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 51 | import cuchaz.enigma.mapping.ConstructorEntry; | ||
| 52 | import cuchaz.enigma.mapping.Entry; | ||
| 53 | import cuchaz.enigma.mapping.EntryFactory; | ||
| 54 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 55 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 56 | import cuchaz.enigma.mapping.Translator; | ||
| 57 | |||
| 58 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import java.util.List; | ||
| 14 | |||
| 15 | import javax.swing.tree.DefaultMutableTreeNode; | ||
| 16 | |||
| 17 | import com.google.common.collect.Lists; | ||
| 18 | |||
| 19 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 20 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 21 | import cuchaz.enigma.mapping.Translator; | ||
| 22 | |||
| 23 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import java.util.List; | ||
| 14 | |||
| 15 | import javax.swing.tree.DefaultMutableTreeNode; | ||
| 16 | |||
| 17 | import com.google.common.collect.Lists; | ||
| 18 | |||
| 19 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 20 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 21 | import cuchaz.enigma.mapping.Translator; | ||
| 22 | |||
| 23 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import cuchaz.enigma.mapping.Entry; | ||
| 14 | |||
| 15 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import java.util.Map; | ||
| 14 | import java.util.Set; | ||
| 15 | |||
| 16 | import com.google.common.collect.Maps; | ||
| 17 | import com.google.common.collect.Sets; | ||
| 18 | |||
| 19 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 20 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 21 | import cuchaz.enigma.mapping.EntryFactory; | ||
| 22 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 23 | import cuchaz.enigma.mapping.MethodMapping; | ||
| 24 | |||
| 25 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import java.util.Collection; | ||
| 14 | import java.util.List; | ||
| 15 | import java.util.Map; | ||
| 16 | import java.util.TreeMap; | ||
| 17 | |||
| 18 | import com.google.common.collect.HashMultimap; | ||
| 19 | import com.google.common.collect.Lists; | ||
| 20 | import com.google.common.collect.Maps; | ||
| 21 | import com.google.common.collect.Multimap; | ||
| 22 | import com.strobel.decompiler.languages.Region; | ||
| 23 | import com.strobel.decompiler.languages.java.ast.AstNode; | ||
| 24 | import com.strobel.decompiler.languages.java.ast.Identifier; | ||
| 25 | |||
| 26 | import cuchaz.enigma.mapping.Entry; | ||
| 27 | |||
| 28 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import com.strobel.assembler.metadata.MemberReference; | ||
| 14 | import com.strobel.assembler.metadata.MethodDefinition; | ||
| 15 | import com.strobel.assembler.metadata.MethodReference; | ||
| 16 | import com.strobel.assembler.metadata.ParameterDefinition; | ||
| 17 | import com.strobel.assembler.metadata.TypeReference; | ||
| 18 | import com.strobel.decompiler.languages.TextLocation; | ||
| 19 | import com.strobel.decompiler.languages.java.ast.AstNode; | ||
| 20 | import com.strobel.decompiler.languages.java.ast.IdentifierExpression; | ||
| 21 | import com.strobel.decompiler.languages.java.ast.InvocationExpression; | ||
| 22 | import com.strobel.decompiler.languages.java.ast.Keys; | ||
| 23 | import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression; | ||
| 24 | import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression; | ||
| 25 | import com.strobel.decompiler.languages.java.ast.ParameterDeclaration; | ||
| 26 | import com.strobel.decompiler.languages.java.ast.SimpleType; | ||
| 27 | import com.strobel.decompiler.languages.java.ast.SuperReferenceExpression; | ||
| 28 | import com.strobel.decompiler.languages.java.ast.ThisReferenceExpression; | ||
| 29 | |||
| 30 | import cuchaz.enigma.mapping.ArgumentEntry; | ||
| 31 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 32 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 33 | import cuchaz.enigma.mapping.ConstructorEntry; | ||
| 34 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 35 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 36 | import cuchaz.enigma.mapping.ProcyonEntryFactory; | ||
| 37 | import cuchaz.enigma.mapping.Signature; | ||
| 38 | import cuchaz.enigma.mapping.Type; | ||
| 39 | |||
| 40 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import com.strobel.assembler.metadata.FieldDefinition; | ||
| 14 | import com.strobel.assembler.metadata.MethodDefinition; | ||
| 15 | import com.strobel.assembler.metadata.TypeDefinition; | ||
| 16 | import com.strobel.assembler.metadata.TypeReference; | ||
| 17 | import com.strobel.decompiler.languages.TextLocation; | ||
| 18 | import com.strobel.decompiler.languages.java.ast.AstNode; | ||
| 19 | import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration; | ||
| 20 | import com.strobel.decompiler.languages.java.ast.EnumValueDeclaration; | ||
| 21 | import com.strobel.decompiler.languages.java.ast.FieldDeclaration; | ||
| 22 | import com.strobel.decompiler.languages.java.ast.Keys; | ||
| 23 | import com.strobel.decompiler.languages.java.ast.MethodDeclaration; | ||
| 24 | import com.strobel.decompiler.languages.java.ast.SimpleType; | ||
| 25 | import com.strobel.decompiler.languages.java.ast.TypeDeclaration; | ||
| 26 | import com.strobel.decompiler.languages.java.ast.VariableInitializer; | ||
| 27 | |||
| 28 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 29 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 30 | import cuchaz.enigma.mapping.ConstructorEntry; | ||
| 31 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 32 | import cuchaz.enigma.mapping.ProcyonEntryFactory; | ||
| 33 | |||
| 34 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import com.strobel.assembler.metadata.TypeDefinition; | ||
| 14 | import com.strobel.decompiler.languages.java.ast.Annotation; | ||
| 15 | import com.strobel.decompiler.languages.java.ast.AnonymousObjectCreationExpression; | ||
| 16 | import com.strobel.decompiler.languages.java.ast.ArrayCreationExpression; | ||
| 17 | import com.strobel.decompiler.languages.java.ast.ArrayInitializerExpression; | ||
| 18 | import com.strobel.decompiler.languages.java.ast.ArraySpecifier; | ||
| 19 | import com.strobel.decompiler.languages.java.ast.AssertStatement; | ||
| 20 | import com.strobel.decompiler.languages.java.ast.AssignmentExpression; | ||
| 21 | import com.strobel.decompiler.languages.java.ast.AstNode; | ||
| 22 | import com.strobel.decompiler.languages.java.ast.BinaryOperatorExpression; | ||
| 23 | import com.strobel.decompiler.languages.java.ast.BlockStatement; | ||
| 24 | import com.strobel.decompiler.languages.java.ast.BreakStatement; | ||
| 25 | import com.strobel.decompiler.languages.java.ast.CaseLabel; | ||
| 26 | import com.strobel.decompiler.languages.java.ast.CastExpression; | ||
| 27 | import com.strobel.decompiler.languages.java.ast.CatchClause; | ||
| 28 | import com.strobel.decompiler.languages.java.ast.ClassOfExpression; | ||
| 29 | import com.strobel.decompiler.languages.java.ast.Comment; | ||
| 30 | import com.strobel.decompiler.languages.java.ast.CompilationUnit; | ||
| 31 | import com.strobel.decompiler.languages.java.ast.ComposedType; | ||
| 32 | import com.strobel.decompiler.languages.java.ast.ConditionalExpression; | ||
| 33 | import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration; | ||
| 34 | import com.strobel.decompiler.languages.java.ast.ContinueStatement; | ||
| 35 | import com.strobel.decompiler.languages.java.ast.DoWhileStatement; | ||
| 36 | import com.strobel.decompiler.languages.java.ast.EmptyStatement; | ||
| 37 | import com.strobel.decompiler.languages.java.ast.EnumValueDeclaration; | ||
| 38 | import com.strobel.decompiler.languages.java.ast.ExpressionStatement; | ||
| 39 | import com.strobel.decompiler.languages.java.ast.FieldDeclaration; | ||
| 40 | import com.strobel.decompiler.languages.java.ast.ForEachStatement; | ||
| 41 | import com.strobel.decompiler.languages.java.ast.ForStatement; | ||
| 42 | import com.strobel.decompiler.languages.java.ast.GotoStatement; | ||
| 43 | import com.strobel.decompiler.languages.java.ast.IAstVisitor; | ||
| 44 | import com.strobel.decompiler.languages.java.ast.Identifier; | ||
| 45 | import com.strobel.decompiler.languages.java.ast.IdentifierExpression; | ||
| 46 | import com.strobel.decompiler.languages.java.ast.IfElseStatement; | ||
| 47 | import com.strobel.decompiler.languages.java.ast.ImportDeclaration; | ||
| 48 | import com.strobel.decompiler.languages.java.ast.IndexerExpression; | ||
| 49 | import com.strobel.decompiler.languages.java.ast.InstanceInitializer; | ||
| 50 | import com.strobel.decompiler.languages.java.ast.InstanceOfExpression; | ||
| 51 | import com.strobel.decompiler.languages.java.ast.InvocationExpression; | ||
| 52 | import com.strobel.decompiler.languages.java.ast.JavaTokenNode; | ||
| 53 | import com.strobel.decompiler.languages.java.ast.Keys; | ||
| 54 | import com.strobel.decompiler.languages.java.ast.LabelStatement; | ||
| 55 | import com.strobel.decompiler.languages.java.ast.LabeledStatement; | ||
| 56 | import com.strobel.decompiler.languages.java.ast.LambdaExpression; | ||
| 57 | import com.strobel.decompiler.languages.java.ast.LocalTypeDeclarationStatement; | ||
| 58 | import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression; | ||
| 59 | import com.strobel.decompiler.languages.java.ast.MethodDeclaration; | ||
| 60 | import com.strobel.decompiler.languages.java.ast.MethodGroupExpression; | ||
| 61 | import com.strobel.decompiler.languages.java.ast.NewLineNode; | ||
| 62 | import com.strobel.decompiler.languages.java.ast.NullReferenceExpression; | ||
| 63 | import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression; | ||
| 64 | import com.strobel.decompiler.languages.java.ast.PackageDeclaration; | ||
| 65 | import com.strobel.decompiler.languages.java.ast.ParameterDeclaration; | ||
| 66 | import com.strobel.decompiler.languages.java.ast.ParenthesizedExpression; | ||
| 67 | import com.strobel.decompiler.languages.java.ast.PrimitiveExpression; | ||
| 68 | import com.strobel.decompiler.languages.java.ast.ReturnStatement; | ||
| 69 | import com.strobel.decompiler.languages.java.ast.SimpleType; | ||
| 70 | import com.strobel.decompiler.languages.java.ast.SuperReferenceExpression; | ||
| 71 | import com.strobel.decompiler.languages.java.ast.SwitchSection; | ||
| 72 | import com.strobel.decompiler.languages.java.ast.SwitchStatement; | ||
| 73 | import com.strobel.decompiler.languages.java.ast.SynchronizedStatement; | ||
| 74 | import com.strobel.decompiler.languages.java.ast.TextNode; | ||
| 75 | import com.strobel.decompiler.languages.java.ast.ThisReferenceExpression; | ||
| 76 | import com.strobel.decompiler.languages.java.ast.ThrowStatement; | ||
| 77 | import com.strobel.decompiler.languages.java.ast.TryCatchStatement; | ||
| 78 | import com.strobel.decompiler.languages.java.ast.TypeDeclaration; | ||
| 79 | import com.strobel.decompiler.languages.java.ast.TypeParameterDeclaration; | ||
| 80 | import com.strobel.decompiler.languages.java.ast.TypeReferenceExpression; | ||
| 81 | import com.strobel.decompiler.languages.java.ast.UnaryOperatorExpression; | ||
| 82 | import com.strobel.decompiler.languages.java.ast.VariableDeclarationStatement; | ||
| 83 | import com.strobel.decompiler.languages.java.ast.VariableInitializer; | ||
| 84 | import com.strobel.decompiler.languages.java.ast.WhileStatement; | ||
| 85 | import com.strobel.decompiler.languages.java.ast.WildcardType; | ||
| 86 | import com.strobel.decompiler.patterns.Pattern; | ||
| 87 | |||
| 88 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 89 | |||
| 90 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import java.io.IOException; | ||
| 14 | import java.io.InputStream; | ||
| 15 | import java.io.ObjectInputStream; | ||
| 16 | import java.io.ObjectOutputStream; | ||
| 17 | import java.io.OutputStream; | ||
| 18 | import java.io.Serializable; | ||
| 19 | import java.util.Collection; | ||
| 20 | import java.util.HashMap; | ||
| 21 | import java.util.List; | ||
| 22 | import java.util.Map; | ||
| 23 | import java.util.Set; | ||
| 24 | import java.util.zip.GZIPInputStream; | ||
| 25 | import java.util.zip.GZIPOutputStream; | ||
| 26 | |||
| 27 | import javassist.CtBehavior; | ||
| 28 | import javassist.CtClass; | ||
| 29 | import javassist.CtField; | ||
| 30 | import javassist.bytecode.Descriptor; | ||
| 31 | |||
| 32 | import com.google.common.collect.HashMultimap; | ||
| 33 | import com.google.common.collect.Lists; | ||
| 34 | import com.google.common.collect.Maps; | ||
| 35 | import com.google.common.collect.Multimap; | ||
| 36 | |||
| 37 | import cuchaz.enigma.mapping.ArgumentEntry; | ||
| 38 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 39 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 40 | import cuchaz.enigma.mapping.Entry; | ||
| 41 | import cuchaz.enigma.mapping.EntryFactory; | ||
| 42 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 43 | import cuchaz.enigma.mapping.Translator; | ||
| 44 | |||
| 45 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import java.io.File; | ||
| 14 | import java.io.FileWriter; | ||
| 15 | import java.io.IOException; | ||
| 16 | import java.io.Writer; | ||
| 17 | |||
| 18 | import com.strobel.componentmodel.Key; | ||
| 19 | import com.strobel.decompiler.languages.java.ast.Annotation; | ||
| 20 | import com.strobel.decompiler.languages.java.ast.AnonymousObjectCreationExpression; | ||
| 21 | import com.strobel.decompiler.languages.java.ast.ArrayCreationExpression; | ||
| 22 | import com.strobel.decompiler.languages.java.ast.ArrayInitializerExpression; | ||
| 23 | import com.strobel.decompiler.languages.java.ast.ArraySpecifier; | ||
| 24 | import com.strobel.decompiler.languages.java.ast.AssertStatement; | ||
| 25 | import com.strobel.decompiler.languages.java.ast.AssignmentExpression; | ||
| 26 | import com.strobel.decompiler.languages.java.ast.AstNode; | ||
| 27 | import com.strobel.decompiler.languages.java.ast.BinaryOperatorExpression; | ||
| 28 | import com.strobel.decompiler.languages.java.ast.BlockStatement; | ||
| 29 | import com.strobel.decompiler.languages.java.ast.BreakStatement; | ||
| 30 | import com.strobel.decompiler.languages.java.ast.CaseLabel; | ||
| 31 | import com.strobel.decompiler.languages.java.ast.CastExpression; | ||
| 32 | import com.strobel.decompiler.languages.java.ast.CatchClause; | ||
| 33 | import com.strobel.decompiler.languages.java.ast.ClassOfExpression; | ||
| 34 | import com.strobel.decompiler.languages.java.ast.Comment; | ||
| 35 | import com.strobel.decompiler.languages.java.ast.CompilationUnit; | ||
| 36 | import com.strobel.decompiler.languages.java.ast.ComposedType; | ||
| 37 | import com.strobel.decompiler.languages.java.ast.ConditionalExpression; | ||
| 38 | import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration; | ||
| 39 | import com.strobel.decompiler.languages.java.ast.ContinueStatement; | ||
| 40 | import com.strobel.decompiler.languages.java.ast.DoWhileStatement; | ||
| 41 | import com.strobel.decompiler.languages.java.ast.EmptyStatement; | ||
| 42 | import com.strobel.decompiler.languages.java.ast.EnumValueDeclaration; | ||
| 43 | import com.strobel.decompiler.languages.java.ast.ExpressionStatement; | ||
| 44 | import com.strobel.decompiler.languages.java.ast.FieldDeclaration; | ||
| 45 | import com.strobel.decompiler.languages.java.ast.ForEachStatement; | ||
| 46 | import com.strobel.decompiler.languages.java.ast.ForStatement; | ||
| 47 | import com.strobel.decompiler.languages.java.ast.GotoStatement; | ||
| 48 | import com.strobel.decompiler.languages.java.ast.IAstVisitor; | ||
| 49 | import com.strobel.decompiler.languages.java.ast.Identifier; | ||
| 50 | import com.strobel.decompiler.languages.java.ast.IdentifierExpression; | ||
| 51 | import com.strobel.decompiler.languages.java.ast.IfElseStatement; | ||
| 52 | import com.strobel.decompiler.languages.java.ast.ImportDeclaration; | ||
| 53 | import com.strobel.decompiler.languages.java.ast.IndexerExpression; | ||
| 54 | import com.strobel.decompiler.languages.java.ast.InstanceInitializer; | ||
| 55 | import com.strobel.decompiler.languages.java.ast.InstanceOfExpression; | ||
| 56 | import com.strobel.decompiler.languages.java.ast.InvocationExpression; | ||
| 57 | import com.strobel.decompiler.languages.java.ast.JavaTokenNode; | ||
| 58 | import com.strobel.decompiler.languages.java.ast.Keys; | ||
| 59 | import com.strobel.decompiler.languages.java.ast.LabelStatement; | ||
| 60 | import com.strobel.decompiler.languages.java.ast.LabeledStatement; | ||
| 61 | import com.strobel.decompiler.languages.java.ast.LambdaExpression; | ||
| 62 | import com.strobel.decompiler.languages.java.ast.LocalTypeDeclarationStatement; | ||
| 63 | import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression; | ||
| 64 | import com.strobel.decompiler.languages.java.ast.MethodDeclaration; | ||
| 65 | import com.strobel.decompiler.languages.java.ast.MethodGroupExpression; | ||
| 66 | import com.strobel.decompiler.languages.java.ast.NewLineNode; | ||
| 67 | import com.strobel.decompiler.languages.java.ast.NullReferenceExpression; | ||
| 68 | import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression; | ||
| 69 | import com.strobel.decompiler.languages.java.ast.PackageDeclaration; | ||
| 70 | import com.strobel.decompiler.languages.java.ast.ParameterDeclaration; | ||
| 71 | import com.strobel.decompiler.languages.java.ast.ParenthesizedExpression; | ||
| 72 | import com.strobel.decompiler.languages.java.ast.PrimitiveExpression; | ||
| 73 | import com.strobel.decompiler.languages.java.ast.ReturnStatement; | ||
| 74 | import com.strobel.decompiler.languages.java.ast.SimpleType; | ||
| 75 | import com.strobel.decompiler.languages.java.ast.SuperReferenceExpression; | ||
| 76 | import com.strobel.decompiler.languages.java.ast.SwitchSection; | ||
| 77 | import com.strobel.decompiler.languages.java.ast.SwitchStatement; | ||
| 78 | import com.strobel.decompiler.languages.java.ast.SynchronizedStatement; | ||
| 79 | import com.strobel.decompiler.languages.java.ast.TextNode; | ||
| 80 | import com.strobel.decompiler.languages.java.ast.ThisReferenceExpression; | ||
| 81 | import com.strobel.decompiler.languages.java.ast.ThrowStatement; | ||
| 82 | import com.strobel.decompiler.languages.java.ast.TryCatchStatement; | ||
| 83 | import com.strobel.decompiler.languages.java.ast.TypeDeclaration; | ||
| 84 | import com.strobel.decompiler.languages.java.ast.TypeParameterDeclaration; | ||
| 85 | import com.strobel.decompiler.languages.java.ast.TypeReferenceExpression; | ||
| 86 | import com.strobel.decompiler.languages.java.ast.UnaryOperatorExpression; | ||
| 87 | import com.strobel.decompiler.languages.java.ast.VariableDeclarationStatement; | ||
| 88 | import com.strobel.decompiler.languages.java.ast.VariableInitializer; | ||
| 89 | import com.strobel.decompiler.languages.java.ast.WhileStatement; | ||
| 90 | import com.strobel.decompiler.languages.java.ast.WildcardType; | ||
| 91 | import com.strobel.decompiler.patterns.Pattern; | ||
| 92 | |||
| 93 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode; | ||
| 12 | |||
| 13 | import java.util.Iterator; | ||
| 14 | |||
| 15 | import javassist.bytecode.BadBytecode; | ||
| 16 | import javassist.bytecode.CodeAttribute; | ||
| 17 | import javassist.bytecode.CodeIterator; | ||
| 18 | import javassist.bytecode.ConstPool; | ||
| 19 | import javassist.bytecode.Descriptor; | ||
| 20 | import javassist.bytecode.Opcode; | ||
| 21 | import cuchaz.enigma.bytecode.CheckCastIterator.CheckCast; | ||
| 22 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 23 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 24 | import cuchaz.enigma.mapping.Signature; | ||
| 25 | |||
| 26 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode; | ||
| 12 | |||
| 13 | import javassist.CtBehavior; | ||
| 14 | import javassist.CtClass; | ||
| 15 | import javassist.CtField; | ||
| 16 | import javassist.bytecode.AccessFlag; | ||
| 17 | import javassist.bytecode.InnerClassesAttribute; | ||
| 18 | |||
| 19 | |||
| 20 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode; | ||
| 12 | |||
| 13 | import javassist.CtBehavior; | ||
| 14 | import javassist.CtClass; | ||
| 15 | import javassist.CtField; | ||
| 16 | import javassist.bytecode.AccessFlag; | ||
| 17 | import javassist.bytecode.InnerClassesAttribute; | ||
| 18 | |||
| 19 | |||
| 20 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode; | ||
| 12 | |||
| 13 | import java.lang.reflect.InvocationTargetException; | ||
| 14 | import java.lang.reflect.Method; | ||
| 15 | import java.util.Arrays; | ||
| 16 | import java.util.HashMap; | ||
| 17 | import java.util.List; | ||
| 18 | import java.util.Map; | ||
| 19 | |||
| 20 | import javassist.CtClass; | ||
| 21 | import javassist.bytecode.AttributeInfo; | ||
| 22 | import javassist.bytecode.BadBytecode; | ||
| 23 | import javassist.bytecode.ByteArray; | ||
| 24 | import javassist.bytecode.ClassFile; | ||
| 25 | import javassist.bytecode.CodeAttribute; | ||
| 26 | import javassist.bytecode.ConstPool; | ||
| 27 | import javassist.bytecode.Descriptor; | ||
| 28 | import javassist.bytecode.FieldInfo; | ||
| 29 | import javassist.bytecode.InnerClassesAttribute; | ||
| 30 | import javassist.bytecode.LocalVariableTypeAttribute; | ||
| 31 | import javassist.bytecode.MethodInfo; | ||
| 32 | import javassist.bytecode.SignatureAttribute; | ||
| 33 | import javassist.bytecode.SignatureAttribute.ArrayType; | ||
| 34 | import javassist.bytecode.SignatureAttribute.BaseType; | ||
| 35 | import javassist.bytecode.SignatureAttribute.ClassSignature; | ||
| 36 | import javassist.bytecode.SignatureAttribute.ClassType; | ||
| 37 | import javassist.bytecode.SignatureAttribute.MethodSignature; | ||
| 38 | import javassist.bytecode.SignatureAttribute.NestedClassType; | ||
| 39 | import javassist.bytecode.SignatureAttribute.ObjectType; | ||
| 40 | import javassist.bytecode.SignatureAttribute.Type; | ||
| 41 | import javassist.bytecode.SignatureAttribute.TypeArgument; | ||
| 42 | import javassist.bytecode.SignatureAttribute.TypeParameter; | ||
| 43 | import javassist.bytecode.SignatureAttribute.TypeVariable; | ||
| 44 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 45 | import cuchaz.enigma.mapping.ClassNameReplacer; | ||
| 46 | import cuchaz.enigma.mapping.Translator; | ||
| 47 | |||
| 48 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode; | ||
| 12 | |||
| 13 | import javassist.CtBehavior; | ||
| 14 | import javassist.CtClass; | ||
| 15 | import javassist.CtField; | ||
| 16 | import javassist.CtMethod; | ||
| 17 | import javassist.bytecode.ConstPool; | ||
| 18 | import javassist.bytecode.Descriptor; | ||
| 19 | import javassist.bytecode.EnclosingMethodAttribute; | ||
| 20 | import javassist.bytecode.SourceFileAttribute; | ||
| 21 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 22 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 23 | import cuchaz.enigma.mapping.EntryFactory; | ||
| 24 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 25 | import cuchaz.enigma.mapping.Signature; | ||
| 26 | import cuchaz.enigma.mapping.Translator; | ||
| 27 | import cuchaz.enigma.mapping.Type; | ||
| 28 | |||
| 29 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode; | ||
| 12 | |||
| 13 | import java.io.DataInputStream; | ||
| 14 | import java.io.DataOutputStream; | ||
| 15 | import java.lang.reflect.Constructor; | ||
| 16 | import java.lang.reflect.Field; | ||
| 17 | import java.lang.reflect.Method; | ||
| 18 | import java.util.HashMap; | ||
| 19 | |||
| 20 | import javassist.bytecode.ConstPool; | ||
| 21 | import javassist.bytecode.Descriptor; | ||
| 22 | import cuchaz.enigma.bytecode.accessors.ClassInfoAccessor; | ||
| 23 | import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor; | ||
| 24 | import cuchaz.enigma.bytecode.accessors.MemberRefInfoAccessor; | ||
| 25 | |||
| 26 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode; | ||
| 12 | |||
| 13 | import java.util.Collection; | ||
| 14 | import java.util.List; | ||
| 15 | import java.util.Map; | ||
| 16 | |||
| 17 | import com.google.common.collect.Lists; | ||
| 18 | import com.google.common.collect.Maps; | ||
| 19 | |||
| 20 | import cuchaz.enigma.bytecode.accessors.ClassInfoAccessor; | ||
| 21 | import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor; | ||
| 22 | import cuchaz.enigma.bytecode.accessors.InvokeDynamicInfoAccessor; | ||
| 23 | import cuchaz.enigma.bytecode.accessors.MemberRefInfoAccessor; | ||
| 24 | import cuchaz.enigma.bytecode.accessors.MethodHandleInfoAccessor; | ||
| 25 | import cuchaz.enigma.bytecode.accessors.MethodTypeInfoAccessor; | ||
| 26 | import cuchaz.enigma.bytecode.accessors.NameAndTypeInfoAccessor; | ||
| 27 | import cuchaz.enigma.bytecode.accessors.StringInfoAccessor; | ||
| 28 | |||
| 29 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode; | ||
| 12 | |||
| 13 | import java.util.Collection; | ||
| 14 | import java.util.List; | ||
| 15 | |||
| 16 | import com.google.common.collect.Lists; | ||
| 17 | |||
| 18 | import javassist.CtClass; | ||
| 19 | import javassist.bytecode.AccessFlag; | ||
| 20 | import javassist.bytecode.ConstPool; | ||
| 21 | import javassist.bytecode.EnclosingMethodAttribute; | ||
| 22 | import javassist.bytecode.InnerClassesAttribute; | ||
| 23 | import cuchaz.enigma.analysis.JarIndex; | ||
| 24 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 25 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 26 | import cuchaz.enigma.mapping.EntryFactory; | ||
| 27 | |||
| 28 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode; | ||
| 12 | |||
| 13 | import javassist.CtBehavior; | ||
| 14 | import javassist.CtClass; | ||
| 15 | import javassist.bytecode.ByteArray; | ||
| 16 | import javassist.bytecode.CodeAttribute; | ||
| 17 | import javassist.bytecode.ConstPool; | ||
| 18 | import javassist.bytecode.LocalVariableAttribute; | ||
| 19 | import javassist.bytecode.LocalVariableTypeAttribute; | ||
| 20 | import cuchaz.enigma.mapping.ArgumentEntry; | ||
| 21 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 22 | import cuchaz.enigma.mapping.EntryFactory; | ||
| 23 | import cuchaz.enigma.mapping.Translator; | ||
| 24 | |||
| 25 | |||
| 26 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode; | ||
| 12 | |||
| 13 | import java.util.ArrayList; | ||
| 14 | import java.util.List; | ||
| 15 | |||
| 16 | import javassist.CtBehavior; | ||
| 17 | import javassist.CtClass; | ||
| 18 | import javassist.bytecode.CodeAttribute; | ||
| 19 | import javassist.bytecode.LocalVariableAttribute; | ||
| 20 | import cuchaz.enigma.mapping.ArgumentEntry; | ||
| 21 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 22 | import cuchaz.enigma.mapping.EntryFactory; | ||
| 23 | import cuchaz.enigma.mapping.Signature; | ||
| 24 | import cuchaz.enigma.mapping.Translator; | ||
| 25 | |||
| 26 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode; | ||
| 12 | |||
| 13 | import java.io.ByteArrayOutputStream; | ||
| 14 | import java.io.DataOutputStream; | ||
| 15 | import java.io.IOException; | ||
| 16 | import java.util.ArrayList; | ||
| 17 | import java.util.List; | ||
| 18 | |||
| 19 | import javassist.bytecode.AttributeInfo; | ||
| 20 | import javassist.bytecode.ConstPool; | ||
| 21 | import javassist.bytecode.MethodInfo; | ||
| 22 | |||
| 23 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode.accessors; | ||
| 12 | |||
| 13 | import java.lang.reflect.Field; | ||
| 14 | |||
| 15 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode.accessors; | ||
| 12 | |||
| 13 | import java.io.ByteArrayInputStream; | ||
| 14 | import java.io.ByteArrayOutputStream; | ||
| 15 | import java.io.DataInputStream; | ||
| 16 | import java.io.DataOutputStream; | ||
| 17 | import java.io.IOException; | ||
| 18 | import java.io.PrintWriter; | ||
| 19 | import java.lang.reflect.Constructor; | ||
| 20 | import java.lang.reflect.Field; | ||
| 21 | import java.lang.reflect.Method; | ||
| 22 | |||
| 23 | import cuchaz.enigma.bytecode.InfoType; | ||
| 24 | |||
| 25 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode.accessors; | ||
| 12 | |||
| 13 | import java.lang.reflect.Field; | ||
| 14 | |||
| 15 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode.accessors; | ||
| 12 | |||
| 13 | import java.lang.reflect.Field; | ||
| 14 | |||
| 15 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode.accessors; | ||
| 12 | |||
| 13 | import java.lang.reflect.Field; | ||
| 14 | |||
| 15 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode.accessors; | ||
| 12 | |||
| 13 | import java.lang.reflect.Field; | ||
| 14 | |||
| 15 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode.accessors; | ||
| 12 | |||
| 13 | import java.lang.reflect.Field; | ||
| 14 | |||
| 15 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode.accessors; | ||
| 12 | |||
| 13 | import java.lang.reflect.Field; | ||
| 14 | |||
| 15 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode.accessors; | ||
| 12 | |||
| 13 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.convert; | ||
| 12 | |||
| 13 | import java.util.Collection; | ||
| 14 | |||
| 15 | import com.google.common.collect.HashMultimap; | ||
| 16 | import com.google.common.collect.Multimap; | ||
| 17 | |||
| 18 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 19 | |||
| 20 | |||
| 21 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.convert; | ||
| 12 | |||
| 13 | import java.util.Map; | ||
| 14 | import java.util.jar.JarFile; | ||
| 15 | |||
| 16 | import com.google.common.collect.Maps; | ||
| 17 | |||
| 18 | import javassist.CtClass; | ||
| 19 | import cuchaz.enigma.TranslatingTypeLoader; | ||
| 20 | import cuchaz.enigma.analysis.JarIndex; | ||
| 21 | import cuchaz.enigma.convert.ClassNamer.SidedClassNamer; | ||
| 22 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 23 | |||
| 24 | |||
| 25 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.convert; | ||
| 12 | |||
| 13 | import java.io.UnsupportedEncodingException; | ||
| 14 | import java.security.MessageDigest; | ||
| 15 | import java.security.NoSuchAlgorithmException; | ||
| 16 | import java.util.Enumeration; | ||
| 17 | import java.util.List; | ||
| 18 | import java.util.Map; | ||
| 19 | import java.util.Set; | ||
| 20 | |||
| 21 | import javassist.CannotCompileException; | ||
| 22 | import javassist.CtBehavior; | ||
| 23 | import javassist.CtClass; | ||
| 24 | import javassist.CtConstructor; | ||
| 25 | import javassist.CtField; | ||
| 26 | import javassist.CtMethod; | ||
| 27 | import javassist.bytecode.BadBytecode; | ||
| 28 | import javassist.bytecode.CodeIterator; | ||
| 29 | import javassist.bytecode.ConstPool; | ||
| 30 | import javassist.bytecode.Descriptor; | ||
| 31 | import javassist.bytecode.Opcode; | ||
| 32 | import javassist.expr.ConstructorCall; | ||
| 33 | import javassist.expr.ExprEditor; | ||
| 34 | import javassist.expr.FieldAccess; | ||
| 35 | import javassist.expr.MethodCall; | ||
| 36 | import javassist.expr.NewExpr; | ||
| 37 | |||
| 38 | import com.google.common.collect.HashMultiset; | ||
| 39 | import com.google.common.collect.Lists; | ||
| 40 | import com.google.common.collect.Maps; | ||
| 41 | import com.google.common.collect.Multiset; | ||
| 42 | import com.google.common.collect.Sets; | ||
| 43 | |||
| 44 | import cuchaz.enigma.Constants; | ||
| 45 | import cuchaz.enigma.Util; | ||
| 46 | import cuchaz.enigma.analysis.ClassImplementationsTreeNode; | ||
| 47 | import cuchaz.enigma.analysis.EntryReference; | ||
| 48 | import cuchaz.enigma.analysis.JarIndex; | ||
| 49 | import cuchaz.enigma.bytecode.ConstPoolEditor; | ||
| 50 | import cuchaz.enigma.bytecode.InfoType; | ||
| 51 | import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor; | ||
| 52 | import cuchaz.enigma.convert.ClassNamer.SidedClassNamer; | ||
| 53 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 54 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 55 | import cuchaz.enigma.mapping.ClassNameReplacer; | ||
| 56 | import cuchaz.enigma.mapping.Entry; | ||
| 57 | import cuchaz.enigma.mapping.EntryFactory; | ||
| 58 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 59 | import cuchaz.enigma.mapping.Signature; | ||
| 60 | import cuchaz.enigma.mapping.Type; | ||
| 61 | |||
| 62 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.convert; | ||
| 12 | |||
| 13 | import java.util.Collection; | ||
| 14 | import java.util.Set; | ||
| 15 | |||
| 16 | import com.google.common.collect.Sets; | ||
| 17 | |||
| 18 | import cuchaz.enigma.Util; | ||
| 19 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 20 | |||
| 21 | |||
| 22 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.convert; | ||
| 12 | |||
| 13 | import java.util.ArrayList; | ||
| 14 | import java.util.Collection; | ||
| 15 | import java.util.Iterator; | ||
| 16 | import java.util.Map; | ||
| 17 | import java.util.Set; | ||
| 18 | |||
| 19 | import com.google.common.collect.BiMap; | ||
| 20 | import com.google.common.collect.HashBiMap; | ||
| 21 | import com.google.common.collect.Maps; | ||
| 22 | import com.google.common.collect.Sets; | ||
| 23 | |||
| 24 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 25 | |||
| 26 | |||
| 27 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.convert; | ||
| 12 | |||
| 13 | import java.util.ArrayList; | ||
| 14 | import java.util.Collection; | ||
| 15 | import java.util.List; | ||
| 16 | import java.util.Map.Entry; | ||
| 17 | import java.util.Set; | ||
| 18 | |||
| 19 | import com.google.common.collect.BiMap; | ||
| 20 | import com.google.common.collect.HashBiMap; | ||
| 21 | import com.google.common.collect.Lists; | ||
| 22 | import com.google.common.collect.Sets; | ||
| 23 | |||
| 24 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 25 | |||
| 26 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.convert; | ||
| 12 | |||
| 13 | import java.util.Map; | ||
| 14 | |||
| 15 | import com.google.common.collect.BiMap; | ||
| 16 | import com.google.common.collect.Maps; | ||
| 17 | |||
| 18 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 19 | |||
| 20 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.convert; | ||
| 12 | |||
| 13 | import java.util.Collection; | ||
| 14 | import java.util.Set; | ||
| 15 | |||
| 16 | import com.google.common.collect.BiMap; | ||
| 17 | import com.google.common.collect.HashBiMap; | ||
| 18 | import com.google.common.collect.HashMultimap; | ||
| 19 | import com.google.common.collect.Multimap; | ||
| 20 | import com.google.common.collect.Sets; | ||
| 21 | |||
| 22 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 23 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 24 | |||
| 25 | |||
| 26 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.convert; | ||
| 12 | |||
| 13 | import java.util.Arrays; | ||
| 14 | import java.util.Collection; | ||
| 15 | import java.util.Collections; | ||
| 16 | import java.util.Iterator; | ||
| 17 | import java.util.LinkedHashMap; | ||
| 18 | import java.util.List; | ||
| 19 | import java.util.Map; | ||
| 20 | import java.util.Set; | ||
| 21 | import java.util.jar.JarFile; | ||
| 22 | |||
| 23 | import com.google.common.collect.BiMap; | ||
| 24 | import com.google.common.collect.HashMultimap; | ||
| 25 | import com.google.common.collect.Lists; | ||
| 26 | import com.google.common.collect.Maps; | ||
| 27 | import com.google.common.collect.Multimap; | ||
| 28 | import com.google.common.collect.Sets; | ||
| 29 | |||
| 30 | import cuchaz.enigma.Deobfuscator; | ||
| 31 | import cuchaz.enigma.analysis.JarIndex; | ||
| 32 | import cuchaz.enigma.convert.ClassNamer.SidedClassNamer; | ||
| 33 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 34 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 35 | import cuchaz.enigma.mapping.ClassMapping; | ||
| 36 | import cuchaz.enigma.mapping.ClassNameReplacer; | ||
| 37 | import cuchaz.enigma.mapping.ConstructorEntry; | ||
| 38 | import cuchaz.enigma.mapping.Entry; | ||
| 39 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 40 | import cuchaz.enigma.mapping.FieldMapping; | ||
| 41 | import cuchaz.enigma.mapping.Mappings; | ||
| 42 | import cuchaz.enigma.mapping.MappingsChecker; | ||
| 43 | import cuchaz.enigma.mapping.MemberMapping; | ||
| 44 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 45 | import cuchaz.enigma.mapping.MethodMapping; | ||
| 46 | import cuchaz.enigma.mapping.Signature; | ||
| 47 | import cuchaz.enigma.mapping.Type; | ||
| 48 | |||
| 49 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.convert; | ||
| 12 | |||
| 13 | import java.io.BufferedReader; | ||
| 14 | import java.io.File; | ||
| 15 | import java.io.FileReader; | ||
| 16 | import java.io.IOException; | ||
| 17 | import java.util.Collection; | ||
| 18 | import java.util.List; | ||
| 19 | |||
| 20 | import com.google.common.collect.Lists; | ||
| 21 | |||
| 22 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 23 | import cuchaz.enigma.mapping.Entry; | ||
| 24 | import cuchaz.enigma.mapping.EntryFactory; | ||
| 25 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 26 | import cuchaz.enigma.mapping.Type; | ||
| 27 | |||
| 28 | |||
| 29 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.convert; | ||
| 12 | |||
| 13 | import java.io.File; | ||
| 14 | import java.io.FileWriter; | ||
| 15 | import java.io.IOException; | ||
| 16 | import java.util.Map; | ||
| 17 | |||
| 18 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 19 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 20 | import cuchaz.enigma.mapping.Entry; | ||
| 21 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 22 | |||
| 23 | |||
| 24 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.convert; | ||
| 12 | |||
| 13 | import java.util.Collection; | ||
| 14 | import java.util.Set; | ||
| 15 | |||
| 16 | import com.google.common.collect.BiMap; | ||
| 17 | import com.google.common.collect.HashBiMap; | ||
| 18 | import com.google.common.collect.HashMultimap; | ||
| 19 | import com.google.common.collect.Multimap; | ||
| 20 | import com.google.common.collect.Sets; | ||
| 21 | |||
| 22 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 23 | import cuchaz.enigma.mapping.Entry; | ||
| 24 | |||
| 25 | |||
| 26 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import java.awt.Color; | ||
| 14 | import java.awt.Container; | ||
| 15 | import java.awt.Cursor; | ||
| 16 | import java.awt.FlowLayout; | ||
| 17 | import java.awt.event.ActionEvent; | ||
| 18 | import java.awt.event.ActionListener; | ||
| 19 | import java.io.IOException; | ||
| 20 | |||
| 21 | import javax.swing.JButton; | ||
| 22 | import javax.swing.JFrame; | ||
| 23 | import javax.swing.JLabel; | ||
| 24 | import javax.swing.JPanel; | ||
| 25 | import javax.swing.WindowConstants; | ||
| 26 | |||
| 27 | import cuchaz.enigma.Constants; | ||
| 28 | import cuchaz.enigma.Util; | ||
| 29 | |||
| 30 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import java.awt.Color; | ||
| 14 | import java.awt.Graphics; | ||
| 15 | import java.awt.Rectangle; | ||
| 16 | import java.awt.Shape; | ||
| 17 | |||
| 18 | import javax.swing.text.BadLocationException; | ||
| 19 | import javax.swing.text.Highlighter; | ||
| 20 | import javax.swing.text.JTextComponent; | ||
| 21 | |||
| 22 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import java.awt.Graphics; | ||
| 14 | import java.awt.Shape; | ||
| 15 | |||
| 16 | import javax.swing.text.DefaultCaret; | ||
| 17 | import javax.swing.text.Highlighter; | ||
| 18 | import javax.swing.text.JTextComponent; | ||
| 19 | |||
| 20 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import java.awt.Component; | ||
| 14 | |||
| 15 | import javassist.bytecode.Descriptor; | ||
| 16 | |||
| 17 | import javax.swing.DefaultListCellRenderer; | ||
| 18 | import javax.swing.JLabel; | ||
| 19 | import javax.swing.JList; | ||
| 20 | import javax.swing.ListCellRenderer; | ||
| 21 | |||
| 22 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import java.awt.BorderLayout; | ||
| 14 | import java.awt.Container; | ||
| 15 | import java.awt.Dimension; | ||
| 16 | import java.awt.FlowLayout; | ||
| 17 | import java.awt.event.ActionEvent; | ||
| 18 | import java.awt.event.ActionListener; | ||
| 19 | import java.util.Collection; | ||
| 20 | import java.util.List; | ||
| 21 | import java.util.Map; | ||
| 22 | |||
| 23 | import javax.swing.BoxLayout; | ||
| 24 | import javax.swing.ButtonGroup; | ||
| 25 | import javax.swing.JButton; | ||
| 26 | import javax.swing.JCheckBox; | ||
| 27 | import javax.swing.JFrame; | ||
| 28 | import javax.swing.JLabel; | ||
| 29 | import javax.swing.JPanel; | ||
| 30 | import javax.swing.JRadioButton; | ||
| 31 | import javax.swing.JScrollPane; | ||
| 32 | import javax.swing.JSplitPane; | ||
| 33 | import javax.swing.SwingConstants; | ||
| 34 | import javax.swing.WindowConstants; | ||
| 35 | |||
| 36 | import com.google.common.collect.BiMap; | ||
| 37 | import com.google.common.collect.Lists; | ||
| 38 | import com.google.common.collect.Maps; | ||
| 39 | |||
| 40 | import cuchaz.enigma.Constants; | ||
| 41 | import cuchaz.enigma.Deobfuscator; | ||
| 42 | import cuchaz.enigma.convert.ClassIdentifier; | ||
| 43 | import cuchaz.enigma.convert.ClassIdentity; | ||
| 44 | import cuchaz.enigma.convert.ClassMatch; | ||
| 45 | import cuchaz.enigma.convert.ClassMatches; | ||
| 46 | import cuchaz.enigma.convert.ClassMatching; | ||
| 47 | import cuchaz.enigma.convert.ClassNamer; | ||
| 48 | import cuchaz.enigma.convert.MappingsConverter; | ||
| 49 | import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener; | ||
| 50 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 51 | import cuchaz.enigma.mapping.Mappings; | ||
| 52 | import cuchaz.enigma.mapping.MappingsChecker; | ||
| 53 | import de.sciss.syntaxpane.DefaultSyntaxKit; | ||
| 54 | |||
| 55 | |||
| 56 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import java.awt.event.MouseAdapter; | ||
| 14 | import java.awt.event.MouseEvent; | ||
| 15 | import java.util.Collection; | ||
| 16 | import java.util.Collections; | ||
| 17 | import java.util.Comparator; | ||
| 18 | import java.util.Enumeration; | ||
| 19 | import java.util.List; | ||
| 20 | import java.util.Map; | ||
| 21 | |||
| 22 | import javax.swing.JTree; | ||
| 23 | import javax.swing.tree.DefaultMutableTreeNode; | ||
| 24 | import javax.swing.tree.DefaultTreeModel; | ||
| 25 | import javax.swing.tree.TreePath; | ||
| 26 | |||
| 27 | import com.google.common.collect.ArrayListMultimap; | ||
| 28 | import com.google.common.collect.Lists; | ||
| 29 | import com.google.common.collect.Maps; | ||
| 30 | import com.google.common.collect.Multimap; | ||
| 31 | |||
| 32 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 33 | |||
| 34 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import javax.swing.tree.DefaultMutableTreeNode; | ||
| 14 | |||
| 15 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 16 | |||
| 17 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import javax.swing.tree.DefaultMutableTreeNode; | ||
| 14 | |||
| 15 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import java.awt.Rectangle; | ||
| 14 | import java.awt.event.ActionEvent; | ||
| 15 | import java.awt.event.ActionListener; | ||
| 16 | |||
| 17 | import javax.swing.JEditorPane; | ||
| 18 | import javax.swing.SwingUtilities; | ||
| 19 | import javax.swing.Timer; | ||
| 20 | import javax.swing.event.CaretEvent; | ||
| 21 | import javax.swing.event.CaretListener; | ||
| 22 | import javax.swing.text.BadLocationException; | ||
| 23 | import javax.swing.text.Highlighter.HighlightPainter; | ||
| 24 | |||
| 25 | import com.strobel.decompiler.languages.java.ast.CompilationUnit; | ||
| 26 | |||
| 27 | import cuchaz.enigma.Deobfuscator; | ||
| 28 | import cuchaz.enigma.analysis.EntryReference; | ||
| 29 | import cuchaz.enigma.analysis.SourceIndex; | ||
| 30 | import cuchaz.enigma.analysis.Token; | ||
| 31 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 32 | import cuchaz.enigma.mapping.Entry; | ||
| 33 | import de.sciss.syntaxpane.DefaultSyntaxKit; | ||
| 34 | |||
| 35 | |||
| 36 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import java.awt.BorderLayout; | ||
| 14 | import java.awt.Container; | ||
| 15 | import java.awt.FlowLayout; | ||
| 16 | import java.awt.event.ActionEvent; | ||
| 17 | import java.awt.event.ActionListener; | ||
| 18 | import java.io.PrintWriter; | ||
| 19 | import java.io.StringWriter; | ||
| 20 | |||
| 21 | import javax.swing.BorderFactory; | ||
| 22 | import javax.swing.JButton; | ||
| 23 | import javax.swing.JFrame; | ||
| 24 | import javax.swing.JLabel; | ||
| 25 | import javax.swing.JPanel; | ||
| 26 | import javax.swing.JScrollPane; | ||
| 27 | import javax.swing.JTextArea; | ||
| 28 | import javax.swing.WindowConstants; | ||
| 29 | |||
| 30 | import cuchaz.enigma.Constants; | ||
| 31 | |||
| 32 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import java.awt.Color; | ||
| 14 | |||
| 15 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import java.awt.BorderLayout; | ||
| 14 | import java.awt.Color; | ||
| 15 | import java.awt.Container; | ||
| 16 | import java.awt.Dimension; | ||
| 17 | import java.awt.FlowLayout; | ||
| 18 | import java.awt.GridLayout; | ||
| 19 | import java.awt.event.ActionEvent; | ||
| 20 | import java.awt.event.ActionListener; | ||
| 21 | import java.awt.event.InputEvent; | ||
| 22 | import java.awt.event.KeyAdapter; | ||
| 23 | import java.awt.event.KeyEvent; | ||
| 24 | import java.awt.event.MouseAdapter; | ||
| 25 | import java.awt.event.MouseEvent; | ||
| 26 | import java.awt.event.WindowAdapter; | ||
| 27 | import java.awt.event.WindowEvent; | ||
| 28 | import java.io.File; | ||
| 29 | import java.io.IOException; | ||
| 30 | import java.lang.Thread.UncaughtExceptionHandler; | ||
| 31 | import java.util.Collection; | ||
| 32 | import java.util.Collections; | ||
| 33 | import java.util.List; | ||
| 34 | import java.util.Vector; | ||
| 35 | import java.util.jar.JarFile; | ||
| 36 | |||
| 37 | import javax.swing.BorderFactory; | ||
| 38 | import javax.swing.JEditorPane; | ||
| 39 | import javax.swing.JFileChooser; | ||
| 40 | import javax.swing.JFrame; | ||
| 41 | import javax.swing.JLabel; | ||
| 42 | import javax.swing.JList; | ||
| 43 | import javax.swing.JMenu; | ||
| 44 | import javax.swing.JMenuBar; | ||
| 45 | import javax.swing.JMenuItem; | ||
| 46 | import javax.swing.JOptionPane; | ||
| 47 | import javax.swing.JPanel; | ||
| 48 | import javax.swing.JPopupMenu; | ||
| 49 | import javax.swing.JScrollPane; | ||
| 50 | import javax.swing.JSplitPane; | ||
| 51 | import javax.swing.JTabbedPane; | ||
| 52 | import javax.swing.JTextField; | ||
| 53 | import javax.swing.JTree; | ||
| 54 | import javax.swing.KeyStroke; | ||
| 55 | import javax.swing.ListSelectionModel; | ||
| 56 | import javax.swing.WindowConstants; | ||
| 57 | import javax.swing.event.CaretEvent; | ||
| 58 | import javax.swing.event.CaretListener; | ||
| 59 | import javax.swing.text.BadLocationException; | ||
| 60 | import javax.swing.text.Highlighter; | ||
| 61 | import javax.swing.tree.DefaultTreeModel; | ||
| 62 | import javax.swing.tree.TreeNode; | ||
| 63 | import javax.swing.tree.TreePath; | ||
| 64 | |||
| 65 | import com.google.common.collect.Lists; | ||
| 66 | |||
| 67 | import cuchaz.enigma.Constants; | ||
| 68 | import cuchaz.enigma.ExceptionIgnorer; | ||
| 69 | import cuchaz.enigma.analysis.BehaviorReferenceTreeNode; | ||
| 70 | import cuchaz.enigma.analysis.ClassImplementationsTreeNode; | ||
| 71 | import cuchaz.enigma.analysis.ClassInheritanceTreeNode; | ||
| 72 | import cuchaz.enigma.analysis.EntryReference; | ||
| 73 | import cuchaz.enigma.analysis.FieldReferenceTreeNode; | ||
| 74 | import cuchaz.enigma.analysis.MethodImplementationsTreeNode; | ||
| 75 | import cuchaz.enigma.analysis.MethodInheritanceTreeNode; | ||
| 76 | import cuchaz.enigma.analysis.ReferenceTreeNode; | ||
| 77 | import cuchaz.enigma.analysis.Token; | ||
| 78 | import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener; | ||
| 79 | import cuchaz.enigma.mapping.ArgumentEntry; | ||
| 80 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 81 | import cuchaz.enigma.mapping.ConstructorEntry; | ||
| 82 | import cuchaz.enigma.mapping.Entry; | ||
| 83 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 84 | import cuchaz.enigma.mapping.IllegalNameException; | ||
| 85 | import cuchaz.enigma.mapping.MappingParseException; | ||
| 86 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 87 | import cuchaz.enigma.mapping.Signature; | ||
| 88 | import de.sciss.syntaxpane.DefaultSyntaxKit; | ||
| 89 | |||
| 90 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import java.io.File; | ||
| 14 | import java.io.FileReader; | ||
| 15 | import java.io.FileWriter; | ||
| 16 | import java.io.IOException; | ||
| 17 | import java.util.Collection; | ||
| 18 | import java.util.Deque; | ||
| 19 | import java.util.List; | ||
| 20 | import java.util.jar.JarFile; | ||
| 21 | |||
| 22 | import com.google.common.collect.Lists; | ||
| 23 | import com.google.common.collect.Queues; | ||
| 24 | import com.strobel.decompiler.languages.java.ast.CompilationUnit; | ||
| 25 | |||
| 26 | import cuchaz.enigma.Deobfuscator; | ||
| 27 | import cuchaz.enigma.Deobfuscator.ProgressListener; | ||
| 28 | import cuchaz.enigma.analysis.BehaviorReferenceTreeNode; | ||
| 29 | import cuchaz.enigma.analysis.ClassImplementationsTreeNode; | ||
| 30 | import cuchaz.enigma.analysis.ClassInheritanceTreeNode; | ||
| 31 | import cuchaz.enigma.analysis.EntryReference; | ||
| 32 | import cuchaz.enigma.analysis.FieldReferenceTreeNode; | ||
| 33 | import cuchaz.enigma.analysis.MethodImplementationsTreeNode; | ||
| 34 | import cuchaz.enigma.analysis.MethodInheritanceTreeNode; | ||
| 35 | import cuchaz.enigma.analysis.SourceIndex; | ||
| 36 | import cuchaz.enigma.analysis.Token; | ||
| 37 | import cuchaz.enigma.gui.ProgressDialog.ProgressRunnable; | ||
| 38 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 39 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 40 | import cuchaz.enigma.mapping.Entry; | ||
| 41 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 42 | import cuchaz.enigma.mapping.MappingParseException; | ||
| 43 | import cuchaz.enigma.mapping.MappingsReader; | ||
| 44 | import cuchaz.enigma.mapping.MappingsWriter; | ||
| 45 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 46 | import cuchaz.enigma.mapping.TranslationDirection; | ||
| 47 | |||
| 48 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import java.awt.Font; | ||
| 14 | import java.awt.event.ActionListener; | ||
| 15 | import java.awt.event.MouseEvent; | ||
| 16 | import java.util.Arrays; | ||
| 17 | |||
| 18 | import javax.swing.JButton; | ||
| 19 | import javax.swing.JComponent; | ||
| 20 | import javax.swing.JLabel; | ||
| 21 | import javax.swing.ToolTipManager; | ||
| 22 | |||
| 23 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import java.awt.BorderLayout; | ||
| 14 | import java.awt.Container; | ||
| 15 | import java.awt.Dimension; | ||
| 16 | import java.awt.FlowLayout; | ||
| 17 | import java.awt.event.ActionEvent; | ||
| 18 | import java.awt.event.ActionListener; | ||
| 19 | import java.awt.event.KeyAdapter; | ||
| 20 | import java.awt.event.KeyEvent; | ||
| 21 | import java.util.Collection; | ||
| 22 | import java.util.List; | ||
| 23 | import java.util.Map; | ||
| 24 | |||
| 25 | import javax.swing.BoxLayout; | ||
| 26 | import javax.swing.ButtonGroup; | ||
| 27 | import javax.swing.JButton; | ||
| 28 | import javax.swing.JFrame; | ||
| 29 | import javax.swing.JLabel; | ||
| 30 | import javax.swing.JPanel; | ||
| 31 | import javax.swing.JRadioButton; | ||
| 32 | import javax.swing.JScrollPane; | ||
| 33 | import javax.swing.JSplitPane; | ||
| 34 | import javax.swing.WindowConstants; | ||
| 35 | import javax.swing.text.Highlighter.HighlightPainter; | ||
| 36 | |||
| 37 | import com.google.common.collect.Lists; | ||
| 38 | import com.google.common.collect.Maps; | ||
| 39 | |||
| 40 | import cuchaz.enigma.Constants; | ||
| 41 | import cuchaz.enigma.Deobfuscator; | ||
| 42 | import cuchaz.enigma.analysis.EntryReference; | ||
| 43 | import cuchaz.enigma.analysis.SourceIndex; | ||
| 44 | import cuchaz.enigma.analysis.Token; | ||
| 45 | import cuchaz.enigma.convert.ClassMatches; | ||
| 46 | import cuchaz.enigma.convert.MemberMatches; | ||
| 47 | import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener; | ||
| 48 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 49 | import cuchaz.enigma.mapping.Entry; | ||
| 50 | import de.sciss.syntaxpane.DefaultSyntaxKit; | ||
| 51 | |||
| 52 | |||
| 53 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import java.awt.Color; | ||
| 14 | |||
| 15 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import java.awt.Color; | ||
| 14 | |||
| 15 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import java.awt.BorderLayout; | ||
| 14 | import java.awt.Container; | ||
| 15 | import java.awt.Dimension; | ||
| 16 | import java.awt.FlowLayout; | ||
| 17 | |||
| 18 | import javax.swing.BorderFactory; | ||
| 19 | import javax.swing.JFrame; | ||
| 20 | import javax.swing.JLabel; | ||
| 21 | import javax.swing.JPanel; | ||
| 22 | import javax.swing.JProgressBar; | ||
| 23 | import javax.swing.WindowConstants; | ||
| 24 | |||
| 25 | import cuchaz.enigma.Constants; | ||
| 26 | import cuchaz.enigma.Deobfuscator.ProgressListener; | ||
| 27 | |||
| 28 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import cuchaz.enigma.mapping.Entry; | ||
| 14 | |||
| 15 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 14 | |||
| 15 | |||
| 16 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import java.awt.BasicStroke; | ||
| 14 | import java.awt.Color; | ||
| 15 | import java.awt.Graphics; | ||
| 16 | import java.awt.Graphics2D; | ||
| 17 | import java.awt.Rectangle; | ||
| 18 | import java.awt.Shape; | ||
| 19 | |||
| 20 | import javax.swing.text.Highlighter; | ||
| 21 | import javax.swing.text.JTextComponent; | ||
| 22 | |||
| 23 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import java.awt.Component; | ||
| 14 | |||
| 15 | import javax.swing.DefaultListCellRenderer; | ||
| 16 | import javax.swing.JLabel; | ||
| 17 | import javax.swing.JList; | ||
| 18 | import javax.swing.ListCellRenderer; | ||
| 19 | |||
| 20 | import cuchaz.enigma.analysis.Token; | ||
| 21 | |||
| 22 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.io.Serializable; | ||
| 14 | |||
| 15 | import cuchaz.enigma.Util; | ||
| 16 | |||
| 17 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.io.Serializable; | ||
| 14 | |||
| 15 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.io.Serializable; | ||
| 14 | import java.util.List; | ||
| 15 | |||
| 16 | import com.google.common.collect.Lists; | ||
| 17 | |||
| 18 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.io.Serializable; | ||
| 14 | import java.util.ArrayList; | ||
| 15 | import java.util.Map; | ||
| 16 | |||
| 17 | import com.google.common.collect.Maps; | ||
| 18 | |||
| 19 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.io.Serializable; | ||
| 14 | |||
| 15 | import cuchaz.enigma.Util; | ||
| 16 | |||
| 17 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import javassist.CtBehavior; | ||
| 14 | import javassist.CtClass; | ||
| 15 | import javassist.CtConstructor; | ||
| 16 | import javassist.CtField; | ||
| 17 | import javassist.CtMethod; | ||
| 18 | import javassist.bytecode.Descriptor; | ||
| 19 | import javassist.expr.ConstructorCall; | ||
| 20 | import javassist.expr.FieldAccess; | ||
| 21 | import javassist.expr.MethodCall; | ||
| 22 | import javassist.expr.NewExpr; | ||
| 23 | |||
| 24 | import cuchaz.enigma.analysis.JarIndex; | ||
| 25 | |||
| 26 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.io.Serializable; | ||
| 14 | |||
| 15 | import cuchaz.enigma.Util; | ||
| 16 | |||
| 17 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.io.Serializable; | ||
| 14 | |||
| 15 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.io.Serializable; | ||
| 14 | import java.util.ArrayList; | ||
| 15 | import java.util.Collection; | ||
| 16 | import java.util.List; | ||
| 17 | import java.util.Map; | ||
| 18 | import java.util.Set; | ||
| 19 | |||
| 20 | import com.google.common.collect.Lists; | ||
| 21 | import com.google.common.collect.Maps; | ||
| 22 | import com.google.common.collect.Sets; | ||
| 23 | |||
| 24 | import cuchaz.enigma.analysis.TranslationIndex; | ||
| 25 | |||
| 26 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.util.Map; | ||
| 14 | |||
| 15 | import com.google.common.collect.Lists; | ||
| 16 | import com.google.common.collect.Maps; | ||
| 17 | |||
| 18 | import cuchaz.enigma.analysis.JarIndex; | ||
| 19 | import cuchaz.enigma.analysis.RelatedMethodChecker; | ||
| 20 | |||
| 21 | |||
| 22 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.io.BufferedReader; | ||
| 14 | import java.io.IOException; | ||
| 15 | import java.io.Reader; | ||
| 16 | import java.util.Deque; | ||
| 17 | |||
| 18 | import com.google.common.collect.Queues; | ||
| 19 | |||
| 20 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.io.IOException; | ||
| 14 | import java.io.ObjectOutputStream; | ||
| 15 | import java.io.OutputStream; | ||
| 16 | import java.util.List; | ||
| 17 | import java.util.Set; | ||
| 18 | import java.util.zip.GZIPOutputStream; | ||
| 19 | |||
| 20 | import cuchaz.enigma.analysis.JarIndex; | ||
| 21 | |||
| 22 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.io.IOException; | ||
| 14 | import java.io.PrintWriter; | ||
| 15 | import java.io.Writer; | ||
| 16 | import java.util.ArrayList; | ||
| 17 | import java.util.Collections; | ||
| 18 | import java.util.List; | ||
| 19 | |||
| 20 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | |||
| 14 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.io.Serializable; | ||
| 14 | |||
| 15 | import cuchaz.enigma.Util; | ||
| 16 | |||
| 17 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.io.Serializable; | ||
| 14 | import java.util.Map; | ||
| 15 | import java.util.Map.Entry; | ||
| 16 | |||
| 17 | import com.google.common.collect.Maps; | ||
| 18 | |||
| 19 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.util.Arrays; | ||
| 14 | import java.util.List; | ||
| 15 | import java.util.regex.Pattern; | ||
| 16 | |||
| 17 | import javassist.bytecode.Descriptor; | ||
| 18 | |||
| 19 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import com.strobel.assembler.metadata.FieldDefinition; | ||
| 14 | import com.strobel.assembler.metadata.MethodDefinition; | ||
| 15 | |||
| 16 | |||
| 17 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.io.Serializable; | ||
| 14 | import java.util.List; | ||
| 15 | |||
| 16 | import com.google.common.collect.Lists; | ||
| 17 | |||
| 18 | import cuchaz.enigma.Util; | ||
| 19 | |||
| 20 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.io.IOException; | ||
| 14 | import java.io.StringReader; | ||
| 15 | import java.util.List; | ||
| 16 | |||
| 17 | import com.google.common.collect.Lists; | ||
| 18 | |||
| 19 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.util.List; | ||
| 14 | import java.util.Map; | ||
| 15 | |||
| 16 | import com.google.common.collect.Lists; | ||
| 17 | import com.google.common.collect.Maps; | ||
| 18 | |||
| 19 | import cuchaz.enigma.analysis.TranslationIndex; | ||
| 20 | |||
| 21 | public 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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.io.Serializable; | ||
| 14 | import java.util.Map; | ||
| 15 | |||
| 16 | import com.google.common.collect.Maps; | ||
| 17 | |||
| 18 | public 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 | } | ||