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