diff options
Diffstat (limited to 'src/main/java/cuchaz/enigma/convert/MappingsConverter.java')
| -rw-r--r-- | src/main/java/cuchaz/enigma/convert/MappingsConverter.java | 583 |
1 files changed, 583 insertions, 0 deletions
diff --git a/src/main/java/cuchaz/enigma/convert/MappingsConverter.java b/src/main/java/cuchaz/enigma/convert/MappingsConverter.java new file mode 100644 index 0000000..61b0e7e --- /dev/null +++ b/src/main/java/cuchaz/enigma/convert/MappingsConverter.java | |||
| @@ -0,0 +1,583 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.convert; | ||
| 12 | |||
| 13 | import com.google.common.collect.*; | ||
| 14 | |||
| 15 | import java.util.*; | ||
| 16 | import java.util.jar.JarFile; | ||
| 17 | |||
| 18 | import cuchaz.enigma.Constants; | ||
| 19 | import cuchaz.enigma.Deobfuscator; | ||
| 20 | import cuchaz.enigma.analysis.JarIndex; | ||
| 21 | import cuchaz.enigma.convert.ClassNamer.SidedClassNamer; | ||
| 22 | import cuchaz.enigma.mapping.*; | ||
| 23 | import cuchaz.enigma.throwables.MappingConflict; | ||
| 24 | |||
| 25 | public class MappingsConverter { | ||
| 26 | |||
| 27 | public static ClassMatches computeClassMatches(JarFile sourceJar, JarFile destJar, Mappings mappings) { | ||
| 28 | |||
| 29 | // index jars | ||
| 30 | System.out.println("Indexing source jar..."); | ||
| 31 | JarIndex sourceIndex = new JarIndex(); | ||
| 32 | sourceIndex.indexJar(sourceJar, false); | ||
| 33 | System.out.println("Indexing dest jar..."); | ||
| 34 | JarIndex destIndex = new JarIndex(); | ||
| 35 | destIndex.indexJar(destJar, false); | ||
| 36 | |||
| 37 | // compute the matching | ||
| 38 | ClassMatching matching = computeMatching(sourceJar, sourceIndex, destJar, destIndex, null); | ||
| 39 | return new ClassMatches(matching.matches()); | ||
| 40 | } | ||
| 41 | |||
| 42 | public static ClassMatching computeMatching(JarFile sourceJar, JarIndex sourceIndex, JarFile destJar, JarIndex destIndex, BiMap<ClassEntry, ClassEntry> knownMatches) { | ||
| 43 | |||
| 44 | System.out.println("Iteratively matching classes"); | ||
| 45 | |||
| 46 | ClassMatching lastMatching = null; | ||
| 47 | int round = 0; | ||
| 48 | SidedClassNamer sourceNamer = null; | ||
| 49 | SidedClassNamer destNamer = null; | ||
| 50 | for (boolean useReferences : Arrays.asList(false, true)) { | ||
| 51 | |||
| 52 | int numUniqueMatchesLastTime = 0; | ||
| 53 | if (lastMatching != null) { | ||
| 54 | numUniqueMatchesLastTime = lastMatching.uniqueMatches().size(); | ||
| 55 | } | ||
| 56 | |||
| 57 | while (true) { | ||
| 58 | |||
| 59 | System.out.println("Round " + (++round) + "..."); | ||
| 60 | |||
| 61 | // init the matching with identity settings | ||
| 62 | ClassMatching matching = new ClassMatching( | ||
| 63 | new ClassIdentifier(sourceJar, sourceIndex, sourceNamer, useReferences), | ||
| 64 | new ClassIdentifier(destJar, destIndex, destNamer, useReferences) | ||
| 65 | ); | ||
| 66 | |||
| 67 | if (knownMatches != null) { | ||
| 68 | matching.addKnownMatches(knownMatches); | ||
| 69 | } | ||
| 70 | |||
| 71 | if (lastMatching == null) { | ||
| 72 | // search all classes | ||
| 73 | matching.match(sourceIndex.getObfClassEntries(), destIndex.getObfClassEntries()); | ||
| 74 | } else { | ||
| 75 | // we already know about these matches from last time | ||
| 76 | matching.addKnownMatches(lastMatching.uniqueMatches()); | ||
| 77 | |||
| 78 | // search unmatched and ambiguously-matched classes | ||
| 79 | matching.match(lastMatching.unmatchedSourceClasses(), lastMatching.unmatchedDestClasses()); | ||
| 80 | for (ClassMatch match : lastMatching.ambiguousMatches()) { | ||
| 81 | matching.match(match.sourceClasses, match.destClasses); | ||
| 82 | } | ||
| 83 | } | ||
| 84 | System.out.println(matching); | ||
| 85 | BiMap<ClassEntry, ClassEntry> uniqueMatches = matching.uniqueMatches(); | ||
| 86 | |||
| 87 | // did we match anything new this time? | ||
| 88 | if (uniqueMatches.size() > numUniqueMatchesLastTime) { | ||
| 89 | numUniqueMatchesLastTime = uniqueMatches.size(); | ||
| 90 | lastMatching = matching; | ||
| 91 | } else { | ||
| 92 | break; | ||
| 93 | } | ||
| 94 | |||
| 95 | // update the namers | ||
| 96 | ClassNamer namer = new ClassNamer(uniqueMatches); | ||
| 97 | sourceNamer = namer.getSourceNamer(); | ||
| 98 | destNamer = namer.getDestNamer(); | ||
| 99 | } | ||
| 100 | } | ||
| 101 | |||
| 102 | return lastMatching; | ||
| 103 | } | ||
| 104 | |||
| 105 | public static Mappings newMappings(ClassMatches matches, Mappings oldMappings, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) | ||
| 106 | throws MappingConflict { | ||
| 107 | // sort the unique matches by size of inner class chain | ||
| 108 | Multimap<Integer, java.util.Map.Entry<ClassEntry, ClassEntry>> matchesByDestChainSize = HashMultimap.create(); | ||
| 109 | for (java.util.Map.Entry<ClassEntry, ClassEntry> match : matches.getUniqueMatches().entrySet()) { | ||
| 110 | int chainSize = destDeobfuscator.getJarIndex().getObfClassChain(match.getValue()).size(); | ||
| 111 | matchesByDestChainSize.put(chainSize, match); | ||
| 112 | } | ||
| 113 | |||
| 114 | // build the mappings (in order of small-to-large inner chains) | ||
| 115 | Mappings newMappings = new Mappings(); | ||
| 116 | List<Integer> chainSizes = Lists.newArrayList(matchesByDestChainSize.keySet()); | ||
| 117 | Collections.sort(chainSizes); | ||
| 118 | for (int chainSize : chainSizes) { | ||
| 119 | for (java.util.Map.Entry<ClassEntry, ClassEntry> match : matchesByDestChainSize.get(chainSize)) { | ||
| 120 | |||
| 121 | // get class info | ||
| 122 | ClassEntry obfSourceClassEntry = match.getKey(); | ||
| 123 | ClassEntry obfDestClassEntry = match.getValue(); | ||
| 124 | List<ClassEntry> destClassChain = destDeobfuscator.getJarIndex().getObfClassChain(obfDestClassEntry); | ||
| 125 | |||
| 126 | ClassMapping sourceMapping = sourceDeobfuscator.getMappings().getClassByObf(obfSourceClassEntry); | ||
| 127 | if (sourceMapping == null) { | ||
| 128 | // if this class was never deobfuscated, don't try to match it | ||
| 129 | continue; | ||
| 130 | } | ||
| 131 | |||
| 132 | // find out where to make the dest class mapping | ||
| 133 | if (destClassChain.size() == 1) { | ||
| 134 | // not an inner class, add directly to mappings | ||
| 135 | newMappings.addClassMapping(migrateClassMapping(obfDestClassEntry, sourceMapping, matches, false)); | ||
| 136 | } else { | ||
| 137 | // inner class, find the outer class mapping | ||
| 138 | ClassMapping destMapping = null; | ||
| 139 | for (int i = 0; i < destClassChain.size() - 1; i++) { | ||
| 140 | ClassEntry destChainClassEntry = destClassChain.get(i); | ||
| 141 | if (destMapping == null) { | ||
| 142 | destMapping = newMappings.getClassByObf(destChainClassEntry); | ||
| 143 | if (destMapping == null) { | ||
| 144 | destMapping = new ClassMapping(destChainClassEntry.getName()); | ||
| 145 | newMappings.addClassMapping(destMapping); | ||
| 146 | } | ||
| 147 | } else { | ||
| 148 | destMapping = destMapping.getInnerClassByObfSimple(destChainClassEntry.getInnermostClassName()); | ||
| 149 | if (destMapping == null) { | ||
| 150 | destMapping = new ClassMapping(destChainClassEntry.getName()); | ||
| 151 | destMapping.addInnerClassMapping(destMapping); | ||
| 152 | } | ||
| 153 | } | ||
| 154 | } | ||
| 155 | destMapping.addInnerClassMapping(migrateClassMapping(obfDestClassEntry, sourceMapping, matches, true)); | ||
| 156 | } | ||
| 157 | } | ||
| 158 | } | ||
| 159 | return newMappings; | ||
| 160 | } | ||
| 161 | |||
| 162 | private static ClassMapping migrateClassMapping(ClassEntry newObfClass, ClassMapping oldClassMapping, final ClassMatches matches, boolean useSimpleName) { | ||
| 163 | |||
| 164 | ClassNameReplacer replacer = new ClassNameReplacer() { | ||
| 165 | @Override | ||
| 166 | public String replace(String className) { | ||
| 167 | ClassEntry newClassEntry = matches.getUniqueMatches().get(new ClassEntry(className)); | ||
| 168 | if (newClassEntry != null) { | ||
| 169 | return newClassEntry.getName(); | ||
| 170 | } | ||
| 171 | return null; | ||
| 172 | } | ||
| 173 | }; | ||
| 174 | |||
| 175 | ClassMapping newClassMapping; | ||
| 176 | String deobfName = oldClassMapping.getDeobfName(); | ||
| 177 | if (deobfName != null) { | ||
| 178 | if (useSimpleName) { | ||
| 179 | deobfName = new ClassEntry(deobfName).getSimpleName(); | ||
| 180 | } | ||
| 181 | newClassMapping = new ClassMapping(newObfClass.getName(), deobfName); | ||
| 182 | } else { | ||
| 183 | newClassMapping = new ClassMapping(newObfClass.getName()); | ||
| 184 | } | ||
| 185 | |||
| 186 | // migrate fields | ||
| 187 | for (FieldMapping oldFieldMapping : oldClassMapping.fields()) { | ||
| 188 | if (canMigrate(oldFieldMapping.getObfType(), matches)) { | ||
| 189 | newClassMapping.addFieldMapping(new FieldMapping(oldFieldMapping, replacer)); | ||
| 190 | } else { | ||
| 191 | System.out.println(String.format("Can't map field, dropping: %s.%s %s", | ||
| 192 | oldClassMapping.getDeobfName(), | ||
| 193 | oldFieldMapping.getDeobfName(), | ||
| 194 | oldFieldMapping.getObfType() | ||
| 195 | )); | ||
| 196 | } | ||
| 197 | } | ||
| 198 | |||
| 199 | // migrate methods | ||
| 200 | for (MethodMapping oldMethodMapping : oldClassMapping.methods()) { | ||
| 201 | if (canMigrate(oldMethodMapping.getObfSignature(), matches)) { | ||
| 202 | newClassMapping.addMethodMapping(new MethodMapping(oldMethodMapping, replacer)); | ||
| 203 | } else { | ||
| 204 | System.out.println(String.format("Can't map method, dropping: %s.%s %s", | ||
| 205 | oldClassMapping.getDeobfName(), | ||
| 206 | oldMethodMapping.getDeobfName(), | ||
| 207 | oldMethodMapping.getObfSignature() | ||
| 208 | )); | ||
| 209 | } | ||
| 210 | } | ||
| 211 | |||
| 212 | return newClassMapping; | ||
| 213 | } | ||
| 214 | |||
| 215 | private static boolean canMigrate(Signature oldObfSignature, ClassMatches classMatches) { | ||
| 216 | for (Type oldObfType : oldObfSignature.types()) { | ||
| 217 | if (!canMigrate(oldObfType, classMatches)) { | ||
| 218 | return false; | ||
| 219 | } | ||
| 220 | } | ||
| 221 | return true; | ||
| 222 | } | ||
| 223 | |||
| 224 | private static boolean canMigrate(Type oldObfType, ClassMatches classMatches) { | ||
| 225 | |||
| 226 | // non classes can be migrated | ||
| 227 | if (!oldObfType.hasClass()) { | ||
| 228 | return true; | ||
| 229 | } | ||
| 230 | |||
| 231 | // non obfuscated classes can be migrated | ||
| 232 | ClassEntry classEntry = oldObfType.getClassEntry(); | ||
| 233 | if (!classEntry.getPackageName().equals(Constants.NONE_PACKAGE)) { | ||
| 234 | return true; | ||
| 235 | } | ||
| 236 | |||
| 237 | // obfuscated classes with mappings can be migrated | ||
| 238 | return classMatches.getUniqueMatches().containsKey(classEntry); | ||
| 239 | } | ||
| 240 | |||
| 241 | public static void convertMappings(Mappings mappings, BiMap<ClassEntry, ClassEntry> changes) { | ||
| 242 | |||
| 243 | // sort the changes so classes are renamed in the correct order | ||
| 244 | // ie. if we have the mappings a->b, b->c, we have to apply b->c before a->b | ||
| 245 | LinkedHashMap<ClassEntry, ClassEntry> sortedChanges = Maps.newLinkedHashMap(); | ||
| 246 | int numChangesLeft = changes.size(); | ||
| 247 | while (!changes.isEmpty()) { | ||
| 248 | Iterator<Map.Entry<ClassEntry, ClassEntry>> iter = changes.entrySet().iterator(); | ||
| 249 | while (iter.hasNext()) { | ||
| 250 | Map.Entry<ClassEntry, ClassEntry> change = iter.next(); | ||
| 251 | if (changes.containsKey(change.getValue())) { | ||
| 252 | sortedChanges.put(change.getKey(), change.getValue()); | ||
| 253 | iter.remove(); | ||
| 254 | } | ||
| 255 | } | ||
| 256 | |||
| 257 | // did we remove any changes? | ||
| 258 | if (numChangesLeft - changes.size() > 0) { | ||
| 259 | // keep going | ||
| 260 | numChangesLeft = changes.size(); | ||
| 261 | } else { | ||
| 262 | // can't sort anymore. There must be a loop | ||
| 263 | break; | ||
| 264 | } | ||
| 265 | } | ||
| 266 | if (!changes.isEmpty()) { | ||
| 267 | throw new Error("Unable to sort class changes! There must be a cycle."); | ||
| 268 | } | ||
| 269 | |||
| 270 | // convert the mappings in the correct class order | ||
| 271 | for (Map.Entry<ClassEntry, ClassEntry> entry : sortedChanges.entrySet()) { | ||
| 272 | mappings.renameObfClass(entry.getKey().getName(), entry.getValue().getName()); | ||
| 273 | } | ||
| 274 | } | ||
| 275 | |||
| 276 | public interface Doer<T extends Entry> { | ||
| 277 | Collection<T> getDroppedEntries(MappingsChecker checker); | ||
| 278 | |||
| 279 | Collection<T> getObfEntries(JarIndex jarIndex); | ||
| 280 | |||
| 281 | Collection<? extends MemberMapping<T>> getMappings(ClassMapping destClassMapping); | ||
| 282 | |||
| 283 | Set<T> filterEntries(Collection<T> obfEntries, T obfSourceEntry, ClassMatches classMatches); | ||
| 284 | |||
| 285 | void setUpdateObfMember(ClassMapping classMapping, MemberMapping<T> memberMapping, T newEntry); | ||
| 286 | |||
| 287 | boolean hasObfMember(ClassMapping classMapping, T obfEntry); | ||
| 288 | |||
| 289 | void removeMemberByObf(ClassMapping classMapping, T obfEntry); | ||
| 290 | } | ||
| 291 | |||
| 292 | public static Doer<FieldEntry> getFieldDoer() { | ||
| 293 | return new Doer<FieldEntry>() { | ||
| 294 | |||
| 295 | @Override | ||
| 296 | public Collection<FieldEntry> getDroppedEntries(MappingsChecker checker) { | ||
| 297 | return checker.getDroppedFieldMappings().keySet(); | ||
| 298 | } | ||
| 299 | |||
| 300 | @Override | ||
| 301 | public Collection<FieldEntry> getObfEntries(JarIndex jarIndex) { | ||
| 302 | return jarIndex.getObfFieldEntries(); | ||
| 303 | } | ||
| 304 | |||
| 305 | @Override | ||
| 306 | public Collection<? extends MemberMapping<FieldEntry>> getMappings(ClassMapping destClassMapping) { | ||
| 307 | return (Collection<? extends MemberMapping<FieldEntry>>) destClassMapping.fields(); | ||
| 308 | } | ||
| 309 | |||
| 310 | @Override | ||
| 311 | public Set<FieldEntry> filterEntries(Collection<FieldEntry> obfDestFields, FieldEntry obfSourceField, ClassMatches classMatches) { | ||
| 312 | Set<FieldEntry> out = Sets.newHashSet(); | ||
| 313 | for (FieldEntry obfDestField : obfDestFields) { | ||
| 314 | Type translatedDestType = translate(obfDestField.getType(), classMatches.getUniqueMatches().inverse()); | ||
| 315 | if (translatedDestType.equals(obfSourceField.getType())) { | ||
| 316 | out.add(obfDestField); | ||
| 317 | } | ||
| 318 | } | ||
| 319 | return out; | ||
| 320 | } | ||
| 321 | |||
| 322 | @Override | ||
| 323 | public void setUpdateObfMember(ClassMapping classMapping, MemberMapping<FieldEntry> memberMapping, FieldEntry newField) { | ||
| 324 | FieldMapping fieldMapping = (FieldMapping) memberMapping; | ||
| 325 | classMapping.setFieldObfNameAndType(fieldMapping.getObfName(), fieldMapping.getObfType(), newField.getName(), newField.getType()); | ||
| 326 | } | ||
| 327 | |||
| 328 | @Override | ||
| 329 | public boolean hasObfMember(ClassMapping classMapping, FieldEntry obfField) { | ||
| 330 | return classMapping.getFieldByObf(obfField.getName(), obfField.getType()) != null; | ||
| 331 | } | ||
| 332 | |||
| 333 | @Override | ||
| 334 | public void removeMemberByObf(ClassMapping classMapping, FieldEntry obfField) { | ||
| 335 | classMapping.removeFieldMapping(classMapping.getFieldByObf(obfField.getName(), obfField.getType())); | ||
| 336 | } | ||
| 337 | }; | ||
| 338 | } | ||
| 339 | |||
| 340 | public static Doer<BehaviorEntry> getMethodDoer() { | ||
| 341 | return new Doer<BehaviorEntry>() { | ||
| 342 | |||
| 343 | @Override | ||
| 344 | public Collection<BehaviorEntry> getDroppedEntries(MappingsChecker checker) { | ||
| 345 | return checker.getDroppedMethodMappings().keySet(); | ||
| 346 | } | ||
| 347 | |||
| 348 | @Override | ||
| 349 | public Collection<BehaviorEntry> getObfEntries(JarIndex jarIndex) { | ||
| 350 | return jarIndex.getObfBehaviorEntries(); | ||
| 351 | } | ||
| 352 | |||
| 353 | @Override | ||
| 354 | public Collection<? extends MemberMapping<BehaviorEntry>> getMappings(ClassMapping destClassMapping) { | ||
| 355 | return (Collection<? extends MemberMapping<BehaviorEntry>>) destClassMapping.methods(); | ||
| 356 | } | ||
| 357 | |||
| 358 | @Override | ||
| 359 | public Set<BehaviorEntry> filterEntries(Collection<BehaviorEntry> obfDestFields, BehaviorEntry obfSourceField, ClassMatches classMatches) { | ||
| 360 | Set<BehaviorEntry> out = Sets.newHashSet(); | ||
| 361 | for (BehaviorEntry obfDestField : obfDestFields) { | ||
| 362 | Signature translatedDestSignature = translate(obfDestField.getSignature(), classMatches.getUniqueMatches().inverse()); | ||
| 363 | if (translatedDestSignature == null && obfSourceField.getSignature() == null) { | ||
| 364 | out.add(obfDestField); | ||
| 365 | } else if (translatedDestSignature == null || obfSourceField.getSignature() == null) { | ||
| 366 | // skip it | ||
| 367 | } else if (translatedDestSignature.equals(obfSourceField.getSignature())) { | ||
| 368 | out.add(obfDestField); | ||
| 369 | } | ||
| 370 | } | ||
| 371 | return out; | ||
| 372 | } | ||
| 373 | |||
| 374 | @Override | ||
| 375 | public void setUpdateObfMember(ClassMapping classMapping, MemberMapping<BehaviorEntry> memberMapping, BehaviorEntry newBehavior) { | ||
| 376 | MethodMapping methodMapping = (MethodMapping) memberMapping; | ||
| 377 | classMapping.setMethodObfNameAndSignature(methodMapping.getObfName(), methodMapping.getObfSignature(), newBehavior.getName(), newBehavior.getSignature()); | ||
| 378 | } | ||
| 379 | |||
| 380 | @Override | ||
| 381 | public boolean hasObfMember(ClassMapping classMapping, BehaviorEntry obfBehavior) { | ||
| 382 | return classMapping.getMethodByObf(obfBehavior.getName(), obfBehavior.getSignature()) != null; | ||
| 383 | } | ||
| 384 | |||
| 385 | @Override | ||
| 386 | public void removeMemberByObf(ClassMapping classMapping, BehaviorEntry obfBehavior) { | ||
| 387 | classMapping.removeMethodMapping(classMapping.getMethodByObf(obfBehavior.getName(), obfBehavior.getSignature())); | ||
| 388 | } | ||
| 389 | }; | ||
| 390 | } | ||
| 391 | |||
| 392 | public static <T extends Entry> MemberMatches<T> computeMemberMatches(Deobfuscator destDeobfuscator, Mappings destMappings, ClassMatches classMatches, Doer<T> doer) { | ||
| 393 | |||
| 394 | MemberMatches<T> memberMatches = new MemberMatches<T>(); | ||
| 395 | |||
| 396 | // unmatched source fields are easy | ||
| 397 | MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex()); | ||
| 398 | checker.dropBrokenMappings(destMappings); | ||
| 399 | for (T destObfEntry : doer.getDroppedEntries(checker)) { | ||
| 400 | T srcObfEntry = translate(destObfEntry, classMatches.getUniqueMatches().inverse()); | ||
| 401 | memberMatches.addUnmatchedSourceEntry(srcObfEntry); | ||
| 402 | } | ||
| 403 | |||
| 404 | // get matched fields (anything that's left after the checks/drops is matched( | ||
| 405 | for (ClassMapping classMapping : destMappings.classes()) { | ||
| 406 | collectMatchedFields(memberMatches, classMapping, classMatches, doer); | ||
| 407 | } | ||
| 408 | |||
| 409 | // get unmatched dest fields | ||
| 410 | for (T destEntry : doer.getObfEntries(destDeobfuscator.getJarIndex())) { | ||
| 411 | if (!memberMatches.isMatchedDestEntry(destEntry)) { | ||
| 412 | memberMatches.addUnmatchedDestEntry(destEntry); | ||
| 413 | } | ||
| 414 | } | ||
| 415 | |||
| 416 | System.out.println("Automatching " + memberMatches.getUnmatchedSourceEntries().size() + " unmatched source entries..."); | ||
| 417 | |||
| 418 | // go through the unmatched source fields and try to pick out the easy matches | ||
| 419 | for (ClassEntry obfSourceClass : Lists.newArrayList(memberMatches.getSourceClassesWithUnmatchedEntries())) { | ||
| 420 | for (T obfSourceEntry : Lists.newArrayList(memberMatches.getUnmatchedSourceEntries(obfSourceClass))) { | ||
| 421 | |||
| 422 | // get the possible dest matches | ||
| 423 | ClassEntry obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass); | ||
| 424 | |||
| 425 | // filter by type/signature | ||
| 426 | Set<T> obfDestEntries = doer.filterEntries(memberMatches.getUnmatchedDestEntries(obfDestClass), obfSourceEntry, classMatches); | ||
| 427 | |||
| 428 | if (obfDestEntries.size() == 1) { | ||
| 429 | // make the easy match | ||
| 430 | memberMatches.makeMatch(obfSourceEntry, obfDestEntries.iterator().next()); | ||
| 431 | } else if (obfDestEntries.isEmpty()) { | ||
| 432 | // no match is possible =( | ||
| 433 | memberMatches.makeSourceUnmatchable(obfSourceEntry); | ||
| 434 | } | ||
| 435 | } | ||
| 436 | } | ||
| 437 | |||
| 438 | System.out.println(String.format("Ended up with %d ambiguous and %d unmatchable source entries", | ||
| 439 | memberMatches.getUnmatchedSourceEntries().size(), | ||
| 440 | memberMatches.getUnmatchableSourceEntries().size() | ||
| 441 | )); | ||
| 442 | |||
| 443 | return memberMatches; | ||
| 444 | } | ||
| 445 | |||
| 446 | private static <T extends Entry> void collectMatchedFields(MemberMatches<T> memberMatches, ClassMapping destClassMapping, ClassMatches classMatches, Doer<T> doer) { | ||
| 447 | |||
| 448 | // get the fields for this class | ||
| 449 | for (MemberMapping<T> destEntryMapping : doer.getMappings(destClassMapping)) { | ||
| 450 | T destObfField = destEntryMapping.getObfEntry(destClassMapping.getObfEntry()); | ||
| 451 | T srcObfField = translate(destObfField, classMatches.getUniqueMatches().inverse()); | ||
| 452 | memberMatches.addMatch(srcObfField, destObfField); | ||
| 453 | } | ||
| 454 | |||
| 455 | // recurse | ||
| 456 | for (ClassMapping destInnerClassMapping : destClassMapping.innerClasses()) { | ||
| 457 | collectMatchedFields(memberMatches, destInnerClassMapping, classMatches, doer); | ||
| 458 | } | ||
| 459 | } | ||
| 460 | |||
| 461 | @SuppressWarnings("unchecked") | ||
| 462 | private static <T extends Entry> T translate(T in, BiMap<ClassEntry, ClassEntry> map) { | ||
| 463 | if (in instanceof FieldEntry) { | ||
| 464 | return (T) new FieldEntry( | ||
| 465 | map.get(in.getClassEntry()), | ||
| 466 | in.getName(), | ||
| 467 | translate(((FieldEntry) in).getType(), map) | ||
| 468 | ); | ||
| 469 | } else if (in instanceof MethodEntry) { | ||
| 470 | return (T) new MethodEntry( | ||
| 471 | map.get(in.getClassEntry()), | ||
| 472 | in.getName(), | ||
| 473 | translate(((MethodEntry) in).getSignature(), map) | ||
| 474 | ); | ||
| 475 | } else if (in instanceof ConstructorEntry) { | ||
| 476 | return (T) new ConstructorEntry( | ||
| 477 | map.get(in.getClassEntry()), | ||
| 478 | translate(((ConstructorEntry) in).getSignature(), map) | ||
| 479 | ); | ||
| 480 | } | ||
| 481 | throw new Error("Unhandled entry type: " + in.getClass()); | ||
| 482 | } | ||
| 483 | |||
| 484 | private static Type translate(Type type, final BiMap<ClassEntry, ClassEntry> map) { | ||
| 485 | return new Type(type, new ClassNameReplacer() { | ||
| 486 | @Override | ||
| 487 | public String replace(String inClassName) { | ||
| 488 | ClassEntry outClassEntry = map.get(new ClassEntry(inClassName)); | ||
| 489 | if (outClassEntry == null) { | ||
| 490 | return null; | ||
| 491 | } | ||
| 492 | return outClassEntry.getName(); | ||
| 493 | } | ||
| 494 | }); | ||
| 495 | } | ||
| 496 | |||
| 497 | private static Signature translate(Signature signature, final BiMap<ClassEntry, ClassEntry> map) { | ||
| 498 | if (signature == null) { | ||
| 499 | return null; | ||
| 500 | } | ||
| 501 | return new Signature(signature, new ClassNameReplacer() { | ||
| 502 | @Override | ||
| 503 | public String replace(String inClassName) { | ||
| 504 | ClassEntry outClassEntry = map.get(new ClassEntry(inClassName)); | ||
| 505 | if (outClassEntry == null) { | ||
| 506 | return null; | ||
| 507 | } | ||
| 508 | return outClassEntry.getName(); | ||
| 509 | } | ||
| 510 | }); | ||
| 511 | } | ||
| 512 | |||
| 513 | public static <T extends Entry> void applyMemberMatches(Mappings mappings, ClassMatches classMatches, MemberMatches<T> memberMatches, Doer<T> doer) { | ||
| 514 | for (ClassMapping classMapping : mappings.classes()) { | ||
| 515 | applyMemberMatches(classMapping, classMatches, memberMatches, doer); | ||
| 516 | } | ||
| 517 | } | ||
| 518 | |||
| 519 | private static <T extends Entry> void applyMemberMatches(ClassMapping classMapping, ClassMatches classMatches, MemberMatches<T> memberMatches, Doer<T> doer) { | ||
| 520 | |||
| 521 | // get the classes | ||
| 522 | ClassEntry obfDestClass = new ClassEntry(classMapping.getObfFullName()); | ||
| 523 | |||
| 524 | // make a map of all the renames we need to make | ||
| 525 | Map<T, T> renames = Maps.newHashMap(); | ||
| 526 | for (MemberMapping<T> memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) { | ||
| 527 | T obfOldDestEntry = memberMapping.getObfEntry(obfDestClass); | ||
| 528 | T obfSourceEntry = getSourceEntryFromDestMapping(memberMapping, obfDestClass, classMatches); | ||
| 529 | |||
| 530 | // but drop the unmatchable things | ||
| 531 | if (memberMatches.isUnmatchableSourceEntry(obfSourceEntry)) { | ||
| 532 | doer.removeMemberByObf(classMapping, obfOldDestEntry); | ||
| 533 | continue; | ||
| 534 | } | ||
| 535 | |||
| 536 | T obfNewDestEntry = memberMatches.matches().get(obfSourceEntry); | ||
| 537 | if (obfNewDestEntry != null && !obfOldDestEntry.getName().equals(obfNewDestEntry.getName())) { | ||
| 538 | renames.put(obfOldDestEntry, obfNewDestEntry); | ||
| 539 | } | ||
| 540 | } | ||
| 541 | |||
| 542 | if (!renames.isEmpty()) { | ||
| 543 | |||
| 544 | // apply to this class (should never need more than n passes) | ||
| 545 | int numRenamesAppliedThisRound; | ||
| 546 | do { | ||
| 547 | numRenamesAppliedThisRound = 0; | ||
| 548 | |||
| 549 | for (MemberMapping<T> memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) { | ||
| 550 | T obfOldDestEntry = memberMapping.getObfEntry(obfDestClass); | ||
| 551 | T obfNewDestEntry = renames.get(obfOldDestEntry); | ||
| 552 | if (obfNewDestEntry != null) { | ||
| 553 | // make sure this rename won't cause a collision | ||
| 554 | // otherwise, save it for the next round and try again next time | ||
| 555 | if (!doer.hasObfMember(classMapping, obfNewDestEntry)) { | ||
| 556 | doer.setUpdateObfMember(classMapping, memberMapping, obfNewDestEntry); | ||
| 557 | renames.remove(obfOldDestEntry); | ||
| 558 | numRenamesAppliedThisRound++; | ||
| 559 | } | ||
| 560 | } | ||
| 561 | } | ||
| 562 | } while (numRenamesAppliedThisRound > 0); | ||
| 563 | |||
| 564 | if (!renames.isEmpty()) { | ||
| 565 | System.err.println(String.format("WARNING: Couldn't apply all the renames for class %s. %d renames left.", | ||
| 566 | classMapping.getObfFullName(), renames.size() | ||
| 567 | )); | ||
| 568 | for (Map.Entry<T, T> entry : renames.entrySet()) { | ||
| 569 | System.err.println(String.format("\t%s -> %s", entry.getKey().getName(), entry.getValue().getName())); | ||
| 570 | } | ||
| 571 | } | ||
| 572 | } | ||
| 573 | |||
| 574 | // recurse | ||
| 575 | for (ClassMapping innerClassMapping : classMapping.innerClasses()) { | ||
| 576 | applyMemberMatches(innerClassMapping, classMatches, memberMatches, doer); | ||
| 577 | } | ||
| 578 | } | ||
| 579 | |||
| 580 | private static <T extends Entry> T getSourceEntryFromDestMapping(MemberMapping<T> destMemberMapping, ClassEntry obfDestClass, ClassMatches classMatches) { | ||
| 581 | return translate(destMemberMapping.getObfEntry(obfDestClass), classMatches.getUniqueMatches().inverse()); | ||
| 582 | } | ||
| 583 | } | ||