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