diff options
| author | 2017-05-16 00:24:29 +0200 | |
|---|---|---|
| committer | 2017-05-16 00:24:29 +0200 | |
| commit | b280104d2f926ab74772cef2bf1602663cefa312 (patch) | |
| tree | d130c86a30ec5df37b3a9c4bab576e971ae2e664 /src/main/java/cuchaz/enigma/convert/MappingsConverter.java | |
| parent | Add offset for Enum constructor arguments (Fix #58) (diff) | |
| download | enigma-fork-b280104d2f926ab74772cef2bf1602663cefa312.tar.gz enigma-fork-b280104d2f926ab74772cef2bf1602663cefa312.tar.xz enigma-fork-b280104d2f926ab74772cef2bf1602663cefa312.zip | |
Remove the converter + some reorganization
Diffstat (limited to 'src/main/java/cuchaz/enigma/convert/MappingsConverter.java')
| -rw-r--r-- | src/main/java/cuchaz/enigma/convert/MappingsConverter.java | 711 |
1 files changed, 0 insertions, 711 deletions
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 fa3e936..0000000 --- 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 | } | ||