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