diff options
| author | 2017-03-08 08:17:04 +0100 | |
|---|---|---|
| committer | 2017-03-08 08:17:04 +0100 | |
| commit | 6e464ea251cab63c776ece0b2a356f1498ffa294 (patch) | |
| tree | 5ed30c03f5ac4cd2d6877874f5ede576049954f7 /src/main/java/cuchaz/enigma/convert/MappingsConverter.java | |
| parent | Drop unix case style and implement hashCode when equals is overrided (diff) | |
| download | enigma-fork-6e464ea251cab63c776ece0b2a356f1498ffa294.tar.gz enigma-fork-6e464ea251cab63c776ece0b2a356f1498ffa294.tar.xz enigma-fork-6e464ea251cab63c776ece0b2a356f1498ffa294.zip | |
Follow Fabric guidelines
Diffstat (limited to 'src/main/java/cuchaz/enigma/convert/MappingsConverter.java')
| -rw-r--r-- | src/main/java/cuchaz/enigma/convert/MappingsConverter.java | 1363 |
1 files changed, 679 insertions, 684 deletions
diff --git a/src/main/java/cuchaz/enigma/convert/MappingsConverter.java b/src/main/java/cuchaz/enigma/convert/MappingsConverter.java index a5ded67..fa3e936 100644 --- a/src/main/java/cuchaz/enigma/convert/MappingsConverter.java +++ b/src/main/java/cuchaz/enigma/convert/MappingsConverter.java | |||
| @@ -8,6 +8,7 @@ | |||
| 8 | * Contributors: | 8 | * Contributors: |
| 9 | * Jeff Martin - initial API and implementation | 9 | * Jeff Martin - initial API and implementation |
| 10 | ******************************************************************************/ | 10 | ******************************************************************************/ |
| 11 | |||
| 11 | package cuchaz.enigma.convert; | 12 | package cuchaz.enigma.convert; |
| 12 | 13 | ||
| 13 | import com.google.common.collect.*; | 14 | import com.google.common.collect.*; |
| @@ -29,688 +30,682 @@ import java.util.jar.JarFile; | |||
| 29 | 30 | ||
| 30 | public class MappingsConverter { | 31 | public class MappingsConverter { |
| 31 | 32 | ||
| 32 | public static ClassMatches computeClassMatches(JarFile sourceJar, JarFile destJar, Mappings mappings) { | 33 | public static ClassMatches computeClassMatches(JarFile sourceJar, JarFile destJar, Mappings mappings) { |
| 33 | 34 | ||
| 34 | // index jars | 35 | // index jars |
| 35 | System.out.println("Indexing source jar..."); | 36 | System.out.println("Indexing source jar..."); |
| 36 | JarIndex sourceIndex = new JarIndex(); | 37 | JarIndex sourceIndex = new JarIndex(); |
| 37 | sourceIndex.indexJar(sourceJar, false); | 38 | sourceIndex.indexJar(sourceJar, false); |
| 38 | System.out.println("Indexing dest jar..."); | 39 | System.out.println("Indexing dest jar..."); |
| 39 | JarIndex destIndex = new JarIndex(); | 40 | JarIndex destIndex = new JarIndex(); |
| 40 | destIndex.indexJar(destJar, false); | 41 | destIndex.indexJar(destJar, false); |
| 41 | 42 | ||
| 42 | // compute the matching | 43 | // compute the matching |
| 43 | ClassMatching matching = computeMatching(sourceJar, sourceIndex, destJar, destIndex, null); | 44 | ClassMatching matching = computeMatching(sourceJar, sourceIndex, destJar, destIndex, null); |
| 44 | return new ClassMatches(matching.matches()); | 45 | return new ClassMatches(matching.matches()); |
| 45 | } | 46 | } |
| 46 | 47 | ||
| 47 | public static ClassMatching computeMatching(JarFile sourceJar, JarIndex sourceIndex, JarFile destJar, JarIndex destIndex, BiMap<ClassEntry, ClassEntry> knownMatches) { | 48 | public static ClassMatching computeMatching(JarFile sourceJar, JarIndex sourceIndex, JarFile destJar, JarIndex destIndex, BiMap<ClassEntry, ClassEntry> knownMatches) { |
| 48 | 49 | ||
| 49 | System.out.println("Iteratively matching classes"); | 50 | System.out.println("Iteratively matching classes"); |
| 50 | 51 | ||
| 51 | ClassMatching lastMatching = null; | 52 | ClassMatching lastMatching = null; |
| 52 | int round = 0; | 53 | int round = 0; |
| 53 | SidedClassNamer sourceNamer = null; | 54 | SidedClassNamer sourceNamer = null; |
| 54 | SidedClassNamer destNamer = null; | 55 | SidedClassNamer destNamer = null; |
| 55 | for (boolean useReferences : Arrays.asList(false, true)) { | 56 | for (boolean useReferences : Arrays.asList(false, true)) { |
| 56 | 57 | ||
| 57 | int numUniqueMatchesLastTime = 0; | 58 | int numUniqueMatchesLastTime = 0; |
| 58 | if (lastMatching != null) { | 59 | if (lastMatching != null) { |
| 59 | numUniqueMatchesLastTime = lastMatching.uniqueMatches().size(); | 60 | numUniqueMatchesLastTime = lastMatching.uniqueMatches().size(); |
| 60 | } | 61 | } |
| 61 | 62 | ||
| 62 | while (true) { | 63 | while (true) { |
| 63 | 64 | ||
| 64 | System.out.println("Round " + (++round) + "..."); | 65 | System.out.println("Round " + (++round) + "..."); |
| 65 | 66 | ||
| 66 | // init the matching with identity settings | 67 | // init the matching with identity settings |
| 67 | ClassMatching matching = new ClassMatching( | 68 | ClassMatching matching = new ClassMatching( |
| 68 | new ClassIdentifier(sourceJar, sourceIndex, sourceNamer, useReferences), | 69 | new ClassIdentifier(sourceJar, sourceIndex, sourceNamer, useReferences), |
| 69 | new ClassIdentifier(destJar, destIndex, destNamer, useReferences) | 70 | new ClassIdentifier(destJar, destIndex, destNamer, useReferences) |
| 70 | ); | 71 | ); |
| 71 | 72 | ||
| 72 | if (knownMatches != null) { | 73 | if (knownMatches != null) { |
| 73 | matching.addKnownMatches(knownMatches); | 74 | matching.addKnownMatches(knownMatches); |
| 74 | } | 75 | } |
| 75 | 76 | ||
| 76 | if (lastMatching == null) { | 77 | if (lastMatching == null) { |
| 77 | // search all classes | 78 | // search all classes |
| 78 | matching.match(sourceIndex.getObfClassEntries(), destIndex.getObfClassEntries()); | 79 | matching.match(sourceIndex.getObfClassEntries(), destIndex.getObfClassEntries()); |
| 79 | } else { | 80 | } else { |
| 80 | // we already know about these matches from last time | 81 | // we already know about these matches from last time |
| 81 | matching.addKnownMatches(lastMatching.uniqueMatches()); | 82 | matching.addKnownMatches(lastMatching.uniqueMatches()); |
| 82 | 83 | ||
| 83 | // search unmatched and ambiguously-matched classes | 84 | // search unmatched and ambiguously-matched classes |
| 84 | matching.match(lastMatching.unmatchedSourceClasses(), lastMatching.unmatchedDestClasses()); | 85 | matching.match(lastMatching.unmatchedSourceClasses(), lastMatching.unmatchedDestClasses()); |
| 85 | for (ClassMatch match : lastMatching.ambiguousMatches()) { | 86 | for (ClassMatch match : lastMatching.ambiguousMatches()) { |
| 86 | matching.match(match.sourceClasses, match.destClasses); | 87 | matching.match(match.sourceClasses, match.destClasses); |
| 87 | } | 88 | } |
| 88 | } | 89 | } |
| 89 | System.out.println(matching); | 90 | System.out.println(matching); |
| 90 | BiMap<ClassEntry, ClassEntry> uniqueMatches = matching.uniqueMatches(); | 91 | BiMap<ClassEntry, ClassEntry> uniqueMatches = matching.uniqueMatches(); |
| 91 | 92 | ||
| 92 | // did we match anything new this time? | 93 | // did we match anything new this time? |
| 93 | if (uniqueMatches.size() > numUniqueMatchesLastTime) { | 94 | if (uniqueMatches.size() > numUniqueMatchesLastTime) { |
| 94 | numUniqueMatchesLastTime = uniqueMatches.size(); | 95 | numUniqueMatchesLastTime = uniqueMatches.size(); |
| 95 | lastMatching = matching; | 96 | lastMatching = matching; |
| 96 | } else { | 97 | } else { |
| 97 | break; | 98 | break; |
| 98 | } | 99 | } |
| 99 | 100 | ||
| 100 | // update the namers | 101 | // update the namers |
| 101 | ClassNamer namer = new ClassNamer(uniqueMatches); | 102 | ClassNamer namer = new ClassNamer(uniqueMatches); |
| 102 | sourceNamer = namer.getSourceNamer(); | 103 | sourceNamer = namer.getSourceNamer(); |
| 103 | destNamer = namer.getDestNamer(); | 104 | destNamer = namer.getDestNamer(); |
| 104 | } | 105 | } |
| 105 | } | 106 | } |
| 106 | 107 | ||
| 107 | return lastMatching; | 108 | return lastMatching; |
| 108 | } | 109 | } |
| 109 | 110 | ||
| 110 | public static Mappings newMappings(ClassMatches matches, Mappings oldMappings, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) | 111 | public static Mappings newMappings(ClassMatches matches, Mappings oldMappings, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) |
| 111 | throws MappingConflict { | 112 | throws MappingConflict { |
| 112 | // sort the unique matches by size of inner class chain | 113 | // sort the unique matches by size of inner class chain |
| 113 | Multimap<Integer, java.util.Map.Entry<ClassEntry, ClassEntry>> matchesByDestChainSize = HashMultimap.create(); | 114 | Multimap<Integer, java.util.Map.Entry<ClassEntry, ClassEntry>> matchesByDestChainSize = HashMultimap.create(); |
| 114 | for (java.util.Map.Entry<ClassEntry, ClassEntry> match : matches.getUniqueMatches().entrySet()) { | 115 | for (java.util.Map.Entry<ClassEntry, ClassEntry> match : matches.getUniqueMatches().entrySet()) { |
| 115 | int chainSize = destDeobfuscator.getJarIndex().getObfClassChain(match.getValue()).size(); | 116 | int chainSize = destDeobfuscator.getJarIndex().getObfClassChain(match.getValue()).size(); |
| 116 | matchesByDestChainSize.put(chainSize, match); | 117 | matchesByDestChainSize.put(chainSize, match); |
| 117 | } | 118 | } |
| 118 | 119 | ||
| 119 | // build the mappings (in order of small-to-large inner chains) | 120 | // build the mappings (in order of small-to-large inner chains) |
| 120 | Mappings newMappings = new Mappings(); | 121 | Mappings newMappings = new Mappings(); |
| 121 | List<Integer> chainSizes = Lists.newArrayList(matchesByDestChainSize.keySet()); | 122 | List<Integer> chainSizes = Lists.newArrayList(matchesByDestChainSize.keySet()); |
| 122 | Collections.sort(chainSizes); | 123 | Collections.sort(chainSizes); |
| 123 | for (int chainSize : chainSizes) { | 124 | for (int chainSize : chainSizes) { |
| 124 | for (java.util.Map.Entry<ClassEntry, ClassEntry> match : matchesByDestChainSize.get(chainSize)) { | 125 | for (java.util.Map.Entry<ClassEntry, ClassEntry> match : matchesByDestChainSize.get(chainSize)) { |
| 125 | // get class info | 126 | // get class info |
| 126 | ClassEntry obfSourceClassEntry = match.getKey(); | 127 | ClassEntry obfSourceClassEntry = match.getKey(); |
| 127 | ClassEntry obfDestClassEntry = match.getValue(); | 128 | ClassEntry obfDestClassEntry = match.getValue(); |
| 128 | List<ClassEntry> destClassChain = destDeobfuscator.getJarIndex().getObfClassChain(obfDestClassEntry); | 129 | List<ClassEntry> destClassChain = destDeobfuscator.getJarIndex().getObfClassChain(obfDestClassEntry); |
| 129 | 130 | ||
| 130 | ClassMapping sourceMapping; | 131 | ClassMapping sourceMapping; |
| 131 | if (obfSourceClassEntry.isInnerClass()) { | 132 | if (obfSourceClassEntry.isInnerClass()) { |
| 132 | List<ClassMapping> srcClassChain = sourceDeobfuscator.getMappings().getClassMappingChain(obfSourceClassEntry); | 133 | List<ClassMapping> srcClassChain = sourceDeobfuscator.getMappings().getClassMappingChain(obfSourceClassEntry); |
| 133 | sourceMapping = srcClassChain.get(srcClassChain.size() - 1); | 134 | sourceMapping = srcClassChain.get(srcClassChain.size() - 1); |
| 134 | } else { | 135 | } else { |
| 135 | sourceMapping = sourceDeobfuscator.getMappings().getClassByObf(obfSourceClassEntry); | 136 | sourceMapping = sourceDeobfuscator.getMappings().getClassByObf(obfSourceClassEntry); |
| 136 | } | 137 | } |
| 137 | 138 | ||
| 138 | if (sourceMapping == null) { | 139 | if (sourceMapping == null) { |
| 139 | // if this class was never deobfuscated, don't try to match it | 140 | // if this class was never deobfuscated, don't try to match it |
| 140 | continue; | 141 | continue; |
| 141 | } | 142 | } |
| 142 | 143 | ||
| 143 | // find out where to make the dest class mapping | 144 | // find out where to make the dest class mapping |
| 144 | if (destClassChain.size() == 1) { | 145 | if (destClassChain.size() == 1) { |
| 145 | // not an inner class, add directly to mappings | 146 | // not an inner class, add directly to mappings |
| 146 | newMappings.addClassMapping(migrateClassMapping(obfDestClassEntry, sourceMapping, matches, false)); | 147 | newMappings.addClassMapping(migrateClassMapping(obfDestClassEntry, sourceMapping, matches, false)); |
| 147 | } else { | 148 | } else { |
| 148 | // inner class, find the outer class mapping | 149 | // inner class, find the outer class mapping |
| 149 | ClassMapping destMapping = null; | 150 | ClassMapping destMapping = null; |
| 150 | for (int i = 0; i < destClassChain.size() - 1; i++) { | 151 | for (int i = 0; i < destClassChain.size() - 1; i++) { |
| 151 | ClassEntry destChainClassEntry = destClassChain.get(i); | 152 | ClassEntry destChainClassEntry = destClassChain.get(i); |
| 152 | if (destMapping == null) { | 153 | if (destMapping == null) { |
| 153 | destMapping = newMappings.getClassByObf(destChainClassEntry); | 154 | destMapping = newMappings.getClassByObf(destChainClassEntry); |
| 154 | if (destMapping == null) { | 155 | if (destMapping == null) { |
| 155 | destMapping = new ClassMapping(destChainClassEntry.getName()); | 156 | destMapping = new ClassMapping(destChainClassEntry.getName()); |
| 156 | newMappings.addClassMapping(destMapping); | 157 | newMappings.addClassMapping(destMapping); |
| 157 | } | 158 | } |
| 158 | } else { | 159 | } else { |
| 159 | destMapping = destMapping.getInnerClassByObfSimple(destChainClassEntry.getInnermostClassName()); | 160 | destMapping = destMapping.getInnerClassByObfSimple(destChainClassEntry.getInnermostClassName()); |
| 160 | if (destMapping == null) { | 161 | if (destMapping == null) { |
| 161 | destMapping = new ClassMapping(destChainClassEntry.getName()); | 162 | destMapping = new ClassMapping(destChainClassEntry.getName()); |
| 162 | destMapping.addInnerClassMapping(destMapping); | 163 | destMapping.addInnerClassMapping(destMapping); |
| 163 | } | 164 | } |
| 164 | } | 165 | } |
| 165 | } | 166 | } |
| 166 | destMapping.addInnerClassMapping(migrateClassMapping(obfDestClassEntry, sourceMapping, matches, true)); | 167 | destMapping.addInnerClassMapping(migrateClassMapping(obfDestClassEntry, sourceMapping, matches, true)); |
| 167 | } | 168 | } |
| 168 | } | 169 | } |
| 169 | } | 170 | } |
| 170 | return newMappings; | 171 | return newMappings; |
| 171 | } | 172 | } |
| 172 | 173 | ||
| 173 | private static ClassMapping migrateClassMapping(ClassEntry newObfClass, ClassMapping oldClassMapping, final ClassMatches matches, boolean useSimpleName) { | 174 | private static ClassMapping migrateClassMapping(ClassEntry newObfClass, ClassMapping oldClassMapping, final ClassMatches matches, boolean useSimpleName) { |
| 174 | 175 | ||
| 175 | ClassNameReplacer replacer = className -> | 176 | ClassNameReplacer replacer = className -> |
| 176 | { | 177 | { |
| 177 | ClassEntry newClassEntry = matches.getUniqueMatches().get(new ClassEntry(className)); | 178 | ClassEntry newClassEntry = matches.getUniqueMatches().get(new ClassEntry(className)); |
| 178 | if (newClassEntry != null) { | 179 | if (newClassEntry != null) { |
| 179 | return newClassEntry.getName(); | 180 | return newClassEntry.getName(); |
| 180 | } | 181 | } |
| 181 | return null; | 182 | return null; |
| 182 | }; | 183 | }; |
| 183 | 184 | ||
| 184 | ClassMapping newClassMapping; | 185 | ClassMapping newClassMapping; |
| 185 | String deobfName = oldClassMapping.getDeobfName(); | 186 | String deobfName = oldClassMapping.getDeobfName(); |
| 186 | if (deobfName != null) { | 187 | if (deobfName != null) { |
| 187 | if (useSimpleName) { | 188 | if (useSimpleName) { |
| 188 | deobfName = new ClassEntry(deobfName).getSimpleName(); | 189 | deobfName = new ClassEntry(deobfName).getSimpleName(); |
| 189 | } | 190 | } |
| 190 | newClassMapping = new ClassMapping(newObfClass.getName(), deobfName); | 191 | newClassMapping = new ClassMapping(newObfClass.getName(), deobfName); |
| 191 | } else { | 192 | } else { |
| 192 | newClassMapping = new ClassMapping(newObfClass.getName()); | 193 | newClassMapping = new ClassMapping(newObfClass.getName()); |
| 193 | } | 194 | } |
| 194 | 195 | ||
| 195 | // migrate fields | 196 | // migrate fields |
| 196 | for (FieldMapping oldFieldMapping : oldClassMapping.fields()) { | 197 | for (FieldMapping oldFieldMapping : oldClassMapping.fields()) { |
| 197 | if (canMigrate(oldFieldMapping.getObfType(), matches)) { | 198 | if (canMigrate(oldFieldMapping.getObfType(), matches)) { |
| 198 | newClassMapping.addFieldMapping(new FieldMapping(oldFieldMapping, replacer)); | 199 | newClassMapping.addFieldMapping(new FieldMapping(oldFieldMapping, replacer)); |
| 199 | } else { | 200 | } else { |
| 200 | System.out.println(String.format("Can't map field, dropping: %s.%s %s", | 201 | System.out.println(String.format("Can't map field, dropping: %s.%s %s", |
| 201 | oldClassMapping.getDeobfName(), | 202 | oldClassMapping.getDeobfName(), |
| 202 | oldFieldMapping.getDeobfName(), | 203 | oldFieldMapping.getDeobfName(), |
| 203 | oldFieldMapping.getObfType() | 204 | oldFieldMapping.getObfType() |
| 204 | )); | 205 | )); |
| 205 | } | 206 | } |
| 206 | } | 207 | } |
| 207 | 208 | ||
| 208 | // migrate methods | 209 | // migrate methods |
| 209 | for (MethodMapping oldMethodMapping : oldClassMapping.methods()) { | 210 | for (MethodMapping oldMethodMapping : oldClassMapping.methods()) { |
| 210 | if (canMigrate(oldMethodMapping.getObfSignature(), matches)) { | 211 | if (canMigrate(oldMethodMapping.getObfSignature(), matches)) { |
| 211 | newClassMapping.addMethodMapping(new MethodMapping(oldMethodMapping, replacer)); | 212 | newClassMapping.addMethodMapping(new MethodMapping(oldMethodMapping, replacer)); |
| 212 | } else { | 213 | } else { |
| 213 | System.out.println(String.format("Can't map method, dropping: %s.%s %s", | 214 | System.out.println(String.format("Can't map method, dropping: %s.%s %s", |
| 214 | oldClassMapping.getDeobfName(), | 215 | oldClassMapping.getDeobfName(), |
| 215 | oldMethodMapping.getDeobfName(), | 216 | oldMethodMapping.getDeobfName(), |
| 216 | oldMethodMapping.getObfSignature() | 217 | oldMethodMapping.getObfSignature() |
| 217 | )); | 218 | )); |
| 218 | } | 219 | } |
| 219 | } | 220 | } |
| 220 | 221 | ||
| 221 | return newClassMapping; | 222 | return newClassMapping; |
| 222 | } | 223 | } |
| 223 | 224 | ||
| 224 | private static boolean canMigrate(Signature oldObfSignature, ClassMatches classMatches) { | 225 | private static boolean canMigrate(Signature oldObfSignature, ClassMatches classMatches) { |
| 225 | for (Type oldObfType : oldObfSignature.types()) { | 226 | for (Type oldObfType : oldObfSignature.types()) { |
| 226 | if (!canMigrate(oldObfType, classMatches)) { | 227 | if (!canMigrate(oldObfType, classMatches)) { |
| 227 | return false; | 228 | return false; |
| 228 | } | 229 | } |
| 229 | } | 230 | } |
| 230 | return true; | 231 | return true; |
| 231 | } | 232 | } |
| 232 | 233 | ||
| 233 | private static boolean canMigrate(Type oldObfType, ClassMatches classMatches) { | 234 | private static boolean canMigrate(Type oldObfType, ClassMatches classMatches) { |
| 234 | 235 | ||
| 235 | // non classes can be migrated | 236 | // non classes can be migrated |
| 236 | if (!oldObfType.hasClass()) { | 237 | if (!oldObfType.hasClass()) { |
| 237 | return true; | 238 | return true; |
| 238 | } | 239 | } |
| 239 | 240 | ||
| 240 | // non obfuscated classes can be migrated | 241 | // non obfuscated classes can be migrated |
| 241 | ClassEntry classEntry = oldObfType.getClassEntry(); | 242 | ClassEntry classEntry = oldObfType.getClassEntry(); |
| 242 | if (classEntry.getPackageName() != null) { | 243 | if (classEntry.getPackageName() != null) { |
| 243 | return true; | 244 | return true; |
| 244 | } | 245 | } |
| 245 | 246 | ||
| 246 | // obfuscated classes with mappings can be migrated | 247 | // obfuscated classes with mappings can be migrated |
| 247 | return classMatches.getUniqueMatches().containsKey(classEntry); | 248 | return classMatches.getUniqueMatches().containsKey(classEntry); |
| 248 | } | 249 | } |
| 249 | 250 | ||
| 250 | public static void convertMappings(Mappings mappings, BiMap<ClassEntry, ClassEntry> changes) { | 251 | public static void convertMappings(Mappings mappings, BiMap<ClassEntry, ClassEntry> changes) { |
| 251 | 252 | ||
| 252 | // sort the changes so classes are renamed in the correct order | 253 | // sort the changes so classes are renamed in the correct order |
| 253 | // ie. if we have the mappings a->b, b->c, we have to apply b->c before a->b | 254 | // ie. if we have the mappings a->b, b->c, we have to apply b->c before a->b |
| 254 | LinkedHashMap<ClassEntry, ClassEntry> sortedChanges = Maps.newLinkedHashMap(); | 255 | LinkedHashMap<ClassEntry, ClassEntry> sortedChanges = Maps.newLinkedHashMap(); |
| 255 | int numChangesLeft = changes.size(); | 256 | int numChangesLeft = changes.size(); |
| 256 | while (!changes.isEmpty()) { | 257 | while (!changes.isEmpty()) { |
| 257 | Iterator<Map.Entry<ClassEntry, ClassEntry>> iter = changes.entrySet().iterator(); | 258 | Iterator<Map.Entry<ClassEntry, ClassEntry>> iter = changes.entrySet().iterator(); |
| 258 | while (iter.hasNext()) { | 259 | while (iter.hasNext()) { |
| 259 | Map.Entry<ClassEntry, ClassEntry> change = iter.next(); | 260 | Map.Entry<ClassEntry, ClassEntry> change = iter.next(); |
| 260 | if (changes.containsKey(change.getValue())) { | 261 | if (changes.containsKey(change.getValue())) { |
| 261 | sortedChanges.put(change.getKey(), change.getValue()); | 262 | sortedChanges.put(change.getKey(), change.getValue()); |
| 262 | iter.remove(); | 263 | iter.remove(); |
| 263 | } | 264 | } |
| 264 | } | 265 | } |
| 265 | 266 | ||
| 266 | // did we remove any changes? | 267 | // did we remove any changes? |
| 267 | if (numChangesLeft - changes.size() > 0) { | 268 | if (numChangesLeft - changes.size() > 0) { |
| 268 | // keep going | 269 | // keep going |
| 269 | numChangesLeft = changes.size(); | 270 | numChangesLeft = changes.size(); |
| 270 | } else { | 271 | } else { |
| 271 | // can't sort anymore. There must be a loop | 272 | // can't sort anymore. There must be a loop |
| 272 | break; | 273 | break; |
| 273 | } | 274 | } |
| 274 | } | 275 | } |
| 275 | if (!changes.isEmpty()) { | 276 | if (!changes.isEmpty()) { |
| 276 | throw new Error("Unable to sort class changes! There must be a cycle."); | 277 | throw new Error("Unable to sort class changes! There must be a cycle."); |
| 277 | } | 278 | } |
| 278 | 279 | ||
| 279 | // convert the mappings in the correct class order | 280 | // convert the mappings in the correct class order |
| 280 | for (Map.Entry<ClassEntry, ClassEntry> entry : sortedChanges.entrySet()) { | 281 | for (Map.Entry<ClassEntry, ClassEntry> entry : sortedChanges.entrySet()) { |
| 281 | mappings.renameObfClass(entry.getKey().getName(), entry.getValue().getName()); | 282 | mappings.renameObfClass(entry.getKey().getName(), entry.getValue().getName()); |
| 282 | } | 283 | } |
| 283 | } | 284 | } |
| 284 | 285 | ||
| 285 | public interface Doer<T extends Entry> { | 286 | public static Doer<FieldEntry> getFieldDoer() { |
| 286 | Collection<T> getDroppedEntries(MappingsChecker checker); | 287 | return new Doer<FieldEntry>() { |
| 287 | 288 | ||
| 288 | Collection<T> getObfEntries(JarIndex jarIndex); | 289 | @Override |
| 289 | 290 | public Collection<FieldEntry> getDroppedEntries(MappingsChecker checker) { | |
| 290 | Collection<? extends MemberMapping<T>> getMappings(ClassMapping destClassMapping); | 291 | return checker.getDroppedFieldMappings().keySet(); |
| 291 | 292 | } | |
| 292 | Set<T> filterEntries(Collection<T> obfEntries, T obfSourceEntry, ClassMatches classMatches); | 293 | |
| 293 | 294 | @Override | |
| 294 | void setUpdateObfMember(ClassMapping classMapping, MemberMapping<T> memberMapping, T newEntry); | 295 | public Collection<FieldEntry> getObfEntries(JarIndex jarIndex) { |
| 295 | 296 | return jarIndex.getObfFieldEntries(); | |
| 296 | boolean hasObfMember(ClassMapping classMapping, T obfEntry); | 297 | } |
| 297 | 298 | ||
| 298 | void removeMemberByObf(ClassMapping classMapping, T obfEntry); | 299 | @Override |
| 299 | } | 300 | public Collection<? extends MemberMapping<FieldEntry>> getMappings(ClassMapping destClassMapping) { |
| 300 | 301 | return (Collection<? extends MemberMapping<FieldEntry>>) destClassMapping.fields(); | |
| 301 | public static Doer<FieldEntry> getFieldDoer() { | 302 | } |
| 302 | return new Doer<FieldEntry>() { | 303 | |
| 303 | 304 | @Override | |
| 304 | @Override | 305 | public Set<FieldEntry> filterEntries(Collection<FieldEntry> obfDestFields, FieldEntry obfSourceField, ClassMatches classMatches) { |
| 305 | public Collection<FieldEntry> getDroppedEntries(MappingsChecker checker) { | 306 | Set<FieldEntry> out = Sets.newHashSet(); |
| 306 | return checker.getDroppedFieldMappings().keySet(); | 307 | for (FieldEntry obfDestField : obfDestFields) { |
| 307 | } | 308 | Type translatedDestType = translate(obfDestField.getType(), classMatches.getUniqueMatches().inverse()); |
| 308 | 309 | if (translatedDestType.equals(obfSourceField.getType())) { | |
| 309 | @Override | 310 | out.add(obfDestField); |
| 310 | public Collection<FieldEntry> getObfEntries(JarIndex jarIndex) { | 311 | } |
| 311 | return jarIndex.getObfFieldEntries(); | 312 | } |
| 312 | } | 313 | return out; |
| 313 | 314 | } | |
| 314 | @Override | 315 | |
| 315 | public Collection<? extends MemberMapping<FieldEntry>> getMappings(ClassMapping destClassMapping) { | 316 | @Override |
| 316 | return (Collection<? extends MemberMapping<FieldEntry>>) destClassMapping.fields(); | 317 | public void setUpdateObfMember(ClassMapping classMapping, MemberMapping<FieldEntry> memberMapping, FieldEntry newField) { |
| 317 | } | 318 | FieldMapping fieldMapping = (FieldMapping) memberMapping; |
| 318 | 319 | classMapping.setFieldObfNameAndType(fieldMapping.getObfName(), fieldMapping.getObfType(), newField.getName(), newField.getType()); | |
| 319 | @Override | 320 | } |
| 320 | public Set<FieldEntry> filterEntries(Collection<FieldEntry> obfDestFields, FieldEntry obfSourceField, ClassMatches classMatches) { | 321 | |
| 321 | Set<FieldEntry> out = Sets.newHashSet(); | 322 | @Override |
| 322 | for (FieldEntry obfDestField : obfDestFields) { | 323 | public boolean hasObfMember(ClassMapping classMapping, FieldEntry obfField) { |
| 323 | Type translatedDestType = translate(obfDestField.getType(), classMatches.getUniqueMatches().inverse()); | 324 | return classMapping.getFieldByObf(obfField.getName(), obfField.getType()) != null; |
| 324 | if (translatedDestType.equals(obfSourceField.getType())) { | 325 | } |
| 325 | out.add(obfDestField); | 326 | |
| 326 | } | 327 | @Override |
| 327 | } | 328 | public void removeMemberByObf(ClassMapping classMapping, FieldEntry obfField) { |
| 328 | return out; | 329 | classMapping.removeFieldMapping(classMapping.getFieldByObf(obfField.getName(), obfField.getType())); |
| 329 | } | 330 | } |
| 330 | 331 | }; | |
| 331 | @Override | 332 | } |
| 332 | public void setUpdateObfMember(ClassMapping classMapping, MemberMapping<FieldEntry> memberMapping, FieldEntry newField) { | 333 | |
| 333 | FieldMapping fieldMapping = (FieldMapping) memberMapping; | 334 | public static Doer<BehaviorEntry> getMethodDoer() { |
| 334 | classMapping.setFieldObfNameAndType(fieldMapping.getObfName(), fieldMapping.getObfType(), newField.getName(), newField.getType()); | 335 | return new Doer<BehaviorEntry>() { |
| 335 | } | 336 | |
| 336 | 337 | @Override | |
| 337 | @Override | 338 | public Collection<BehaviorEntry> getDroppedEntries(MappingsChecker checker) { |
| 338 | public boolean hasObfMember(ClassMapping classMapping, FieldEntry obfField) { | 339 | return checker.getDroppedMethodMappings().keySet(); |
| 339 | return classMapping.getFieldByObf(obfField.getName(), obfField.getType()) != null; | 340 | } |
| 340 | } | 341 | |
| 341 | 342 | @Override | |
| 342 | @Override | 343 | public Collection<BehaviorEntry> getObfEntries(JarIndex jarIndex) { |
| 343 | public void removeMemberByObf(ClassMapping classMapping, FieldEntry obfField) { | 344 | return jarIndex.getObfBehaviorEntries(); |
| 344 | classMapping.removeFieldMapping(classMapping.getFieldByObf(obfField.getName(), obfField.getType())); | 345 | } |
| 345 | } | 346 | |
| 346 | }; | 347 | @Override |
| 347 | } | 348 | public Collection<? extends MemberMapping<BehaviorEntry>> getMappings(ClassMapping destClassMapping) { |
| 348 | 349 | return (Collection<? extends MemberMapping<BehaviorEntry>>) destClassMapping.methods(); | |
| 349 | public static Doer<BehaviorEntry> getMethodDoer() { | 350 | } |
| 350 | return new Doer<BehaviorEntry>() { | 351 | |
| 351 | 352 | @Override | |
| 352 | @Override | 353 | public Set<BehaviorEntry> filterEntries(Collection<BehaviorEntry> obfDestFields, BehaviorEntry obfSourceField, ClassMatches classMatches) { |
| 353 | public Collection<BehaviorEntry> getDroppedEntries(MappingsChecker checker) { | 354 | Set<BehaviorEntry> out = Sets.newHashSet(); |
| 354 | return checker.getDroppedMethodMappings().keySet(); | 355 | for (BehaviorEntry obfDestField : obfDestFields) { |
| 355 | } | 356 | // Try to translate the signature |
| 356 | 357 | Signature translatedDestSignature = translate(obfDestField.getSignature(), classMatches.getUniqueMatches().inverse()); | |
| 357 | @Override | 358 | if (translatedDestSignature != null && obfSourceField.getSignature() != null && translatedDestSignature.equals(obfSourceField.getSignature())) |
| 358 | public Collection<BehaviorEntry> getObfEntries(JarIndex jarIndex) { | 359 | out.add(obfDestField); |
| 359 | return jarIndex.getObfBehaviorEntries(); | 360 | } |
| 360 | } | 361 | return out; |
| 361 | 362 | } | |
| 362 | @Override | 363 | |
| 363 | public Collection<? extends MemberMapping<BehaviorEntry>> getMappings(ClassMapping destClassMapping) { | 364 | @Override |
| 364 | return (Collection<? extends MemberMapping<BehaviorEntry>>) destClassMapping.methods(); | 365 | public void setUpdateObfMember(ClassMapping classMapping, MemberMapping<BehaviorEntry> memberMapping, BehaviorEntry newBehavior) { |
| 365 | } | 366 | MethodMapping methodMapping = (MethodMapping) memberMapping; |
| 366 | 367 | classMapping.setMethodObfNameAndSignature(methodMapping.getObfName(), methodMapping.getObfSignature(), newBehavior.getName(), newBehavior.getSignature()); | |
| 367 | @Override | 368 | } |
| 368 | public Set<BehaviorEntry> filterEntries(Collection<BehaviorEntry> obfDestFields, BehaviorEntry obfSourceField, ClassMatches classMatches) { | 369 | |
| 369 | Set<BehaviorEntry> out = Sets.newHashSet(); | 370 | @Override |
| 370 | for (BehaviorEntry obfDestField : obfDestFields) { | 371 | public boolean hasObfMember(ClassMapping classMapping, BehaviorEntry obfBehavior) { |
| 371 | // Try to translate the signature | 372 | return classMapping.getMethodByObf(obfBehavior.getName(), obfBehavior.getSignature()) != null; |
| 372 | Signature translatedDestSignature = translate(obfDestField.getSignature(), classMatches.getUniqueMatches().inverse()); | 373 | } |
| 373 | if (translatedDestSignature != null && obfSourceField.getSignature() != null && translatedDestSignature.equals(obfSourceField.getSignature())) | 374 | |
| 374 | out.add(obfDestField); | 375 | @Override |
| 375 | } | 376 | public void removeMemberByObf(ClassMapping classMapping, BehaviorEntry obfBehavior) { |
| 376 | return out; | 377 | classMapping.removeMethodMapping(classMapping.getMethodByObf(obfBehavior.getName(), obfBehavior.getSignature())); |
| 377 | } | 378 | } |
| 378 | 379 | }; | |
| 379 | @Override | 380 | } |
| 380 | public void setUpdateObfMember(ClassMapping classMapping, MemberMapping<BehaviorEntry> memberMapping, BehaviorEntry newBehavior) { | 381 | |
| 381 | MethodMapping methodMapping = (MethodMapping) memberMapping; | 382 | public static int compareMethodByteCode(CodeIterator sourceIt, CodeIterator destIt) { |
| 382 | classMapping.setMethodObfNameAndSignature(methodMapping.getObfName(), methodMapping.getObfSignature(), newBehavior.getName(), newBehavior.getSignature()); | 383 | int sourcePos = 0; |
| 383 | } | 384 | int destPos = 0; |
| 384 | 385 | while (sourceIt.hasNext() && destIt.hasNext()) { | |
| 385 | @Override | 386 | try { |
| 386 | public boolean hasObfMember(ClassMapping classMapping, BehaviorEntry obfBehavior) { | 387 | sourcePos = sourceIt.next(); |
| 387 | return classMapping.getMethodByObf(obfBehavior.getName(), obfBehavior.getSignature()) != null; | 388 | destPos = destIt.next(); |
| 388 | } | 389 | if (sourceIt.byteAt(sourcePos) != destIt.byteAt(destPos)) |
| 389 | 390 | return sourcePos; | |
| 390 | @Override | 391 | } catch (BadBytecode badBytecode) { |
| 391 | public void removeMemberByObf(ClassMapping classMapping, BehaviorEntry obfBehavior) { | 392 | // Ignore bad bytecode (it might be a little bit dangerous...) |
| 392 | classMapping.removeMethodMapping(classMapping.getMethodByObf(obfBehavior.getName(), obfBehavior.getSignature())); | 393 | } |
| 393 | } | 394 | } |
| 394 | }; | 395 | if (sourcePos < destPos) |
| 395 | } | 396 | return sourcePos; |
| 396 | 397 | else if (destPos < sourcePos) | |
| 397 | public static int compareMethodByteCode(CodeIterator sourceIt, CodeIterator destIt) | 398 | return destPos; |
| 398 | { | 399 | return sourcePos; |
| 399 | int sourcePos = 0; | 400 | } |
| 400 | int destPos = 0; | 401 | |
| 401 | while (sourceIt.hasNext() && destIt.hasNext()) | 402 | public static BehaviorEntry compareMethods(CtClass destCtClass, CtClass sourceCtClass, BehaviorEntry obfSourceEntry, |
| 402 | { | 403 | Set<BehaviorEntry> obfDestEntries) { |
| 403 | try | 404 | try { |
| 404 | { | 405 | // Get the source method with Javassist |
| 405 | sourcePos = sourceIt.next(); | 406 | CtMethod sourceCtClassMethod = sourceCtClass.getMethod(obfSourceEntry.getName(), obfSourceEntry.getSignature().toString()); |
| 406 | destPos = destIt.next(); | 407 | CodeAttribute sourceAttribute = sourceCtClassMethod.getMethodInfo().getCodeAttribute(); |
| 407 | if (sourceIt.byteAt(sourcePos) != destIt.byteAt(destPos)) | 408 | |
| 408 | return sourcePos; | 409 | // Empty method body, ignore! |
| 409 | } catch (BadBytecode badBytecode) | 410 | if (sourceAttribute == null) |
| 410 | { | 411 | return null; |
| 411 | // Ignore bad bytecode (it might be a little bit dangerous...) | 412 | for (BehaviorEntry desEntry : obfDestEntries) { |
| 412 | } | 413 | try { |
| 413 | } | 414 | CtMethod destCtClassMethod = destCtClass |
| 414 | if (sourcePos < destPos) | 415 | .getMethod(desEntry.getName(), desEntry.getSignature().toString()); |
| 415 | return sourcePos; | 416 | CodeAttribute destAttribute = destCtClassMethod.getMethodInfo().getCodeAttribute(); |
| 416 | else if (destPos < sourcePos) | 417 | |
| 417 | return destPos; | 418 | // Ignore empty body methods |
| 418 | return sourcePos; | 419 | if (destAttribute == null) |
| 419 | } | 420 | continue; |
| 420 | 421 | CodeIterator destIterator = destAttribute.iterator(); | |
| 421 | public static BehaviorEntry compareMethods(CtClass destCtClass, CtClass sourceCtClass, BehaviorEntry obfSourceEntry, | 422 | int maxPos = compareMethodByteCode(sourceAttribute.iterator(), destIterator); |
| 422 | Set<BehaviorEntry> obfDestEntries) | 423 | |
| 423 | { | 424 | // The bytecode is identical to the original method, assuming that the method is correct! |
| 424 | try | 425 | if (sourceAttribute.getCodeLength() == (maxPos + 1) && maxPos > 1) |
| 425 | { | 426 | return desEntry; |
| 426 | // Get the source method with Javassist | 427 | } catch (NotFoundException e) { |
| 427 | CtMethod sourceCtClassMethod = sourceCtClass.getMethod(obfSourceEntry.getName(), obfSourceEntry.getSignature().toString()); | 428 | e.printStackTrace(); |
| 428 | CodeAttribute sourceAttribute = sourceCtClassMethod.getMethodInfo().getCodeAttribute(); | 429 | } |
| 429 | 430 | } | |
| 430 | // Empty method body, ignore! | 431 | } catch (NotFoundException e) { |
| 431 | if (sourceAttribute == null) | 432 | e.printStackTrace(); |
| 432 | return null; | 433 | return null; |
| 433 | for (BehaviorEntry desEntry : obfDestEntries) | 434 | } |
| 434 | { | 435 | return null; |
| 435 | try | 436 | } |
| 436 | { | 437 | |
| 437 | CtMethod destCtClassMethod = destCtClass | 438 | public static MemberMatches<BehaviorEntry> computeMethodsMatches(Deobfuscator destDeobfuscator, |
| 438 | .getMethod(desEntry.getName(), desEntry.getSignature().toString()); | 439 | Mappings destMappings, |
| 439 | CodeAttribute destAttribute = destCtClassMethod.getMethodInfo().getCodeAttribute(); | 440 | Deobfuscator sourceDeobfuscator, |
| 440 | 441 | Mappings sourceMappings, | |
| 441 | // Ignore empty body methods | 442 | ClassMatches classMatches, |
| 442 | if (destAttribute == null) | 443 | Doer<BehaviorEntry> doer) { |
| 443 | continue; | 444 | |
| 444 | CodeIterator destIterator = destAttribute.iterator(); | 445 | MemberMatches<BehaviorEntry> memberMatches = new MemberMatches<>(); |
| 445 | int maxPos = compareMethodByteCode(sourceAttribute.iterator(), destIterator); | 446 | |
| 446 | 447 | // unmatched source fields are easy | |
| 447 | // The bytecode is identical to the original method, assuming that the method is correct! | 448 | MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex()); |
| 448 | if (sourceAttribute.getCodeLength() == (maxPos + 1) && maxPos > 1) | 449 | checker.dropBrokenMappings(destMappings); |
| 449 | return desEntry; | 450 | for (BehaviorEntry destObfEntry : doer.getDroppedEntries(checker)) { |
| 450 | } catch (NotFoundException e) | 451 | BehaviorEntry srcObfEntry = translate(destObfEntry, classMatches.getUniqueMatches().inverse()); |
| 451 | { | 452 | memberMatches.addUnmatchedSourceEntry(srcObfEntry); |
| 452 | e.printStackTrace(); | 453 | } |
| 453 | } | 454 | |
| 454 | } | 455 | // get matched fields (anything that's left after the checks/drops is matched( |
| 455 | } catch (NotFoundException e) | 456 | for (ClassMapping classMapping : destMappings.classes()) |
| 456 | { | 457 | collectMatchedFields(memberMatches, classMapping, classMatches, doer); |
| 457 | e.printStackTrace(); | 458 | |
| 458 | return null; | 459 | // get unmatched dest fields |
| 459 | } | 460 | doer.getObfEntries(destDeobfuscator.getJarIndex()).stream() |
| 460 | return null; | 461 | .filter(destEntry -> !memberMatches.isMatchedDestEntry(destEntry)) |
| 461 | } | 462 | .forEach(memberMatches::addUnmatchedDestEntry); |
| 462 | 463 | ||
| 463 | public static MemberMatches<BehaviorEntry> computeMethodsMatches(Deobfuscator destDeobfuscator, Mappings destMappings, Deobfuscator sourceDeobfuscator, Mappings sourceMappings, ClassMatches classMatches, Doer<BehaviorEntry> doer) { | 464 | // Apply mappings to deobfuscator |
| 464 | 465 | ||
| 465 | MemberMatches<BehaviorEntry> memberMatches = new MemberMatches<>(); | 466 | // Create type loader |
| 466 | 467 | TranslatingTypeLoader destTypeLoader = destDeobfuscator.createTypeLoader(); | |
| 467 | // unmatched source fields are easy | 468 | TranslatingTypeLoader sourceTypeLoader = sourceDeobfuscator.createTypeLoader(); |
| 468 | MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex()); | 469 | |
| 469 | checker.dropBrokenMappings(destMappings); | 470 | System.out.println("Automatching " + memberMatches.getUnmatchedSourceEntries().size() + " unmatched source entries..."); |
| 470 | for (BehaviorEntry destObfEntry : doer.getDroppedEntries(checker)) { | 471 | |
| 471 | BehaviorEntry srcObfEntry = translate(destObfEntry, classMatches.getUniqueMatches().inverse()); | 472 | // go through the unmatched source fields and try to pick out the easy matches |
| 472 | memberMatches.addUnmatchedSourceEntry(srcObfEntry); | 473 | for (ClassEntry obfSourceClass : Lists.newArrayList(memberMatches.getSourceClassesWithUnmatchedEntries())) { |
| 473 | } | 474 | for (BehaviorEntry obfSourceEntry : Lists.newArrayList(memberMatches.getUnmatchedSourceEntries(obfSourceClass))) { |
| 474 | 475 | ||
| 475 | // get matched fields (anything that's left after the checks/drops is matched( | 476 | // get the possible dest matches |
| 476 | for (ClassMapping classMapping : destMappings.classes()) | 477 | ClassEntry obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass); |
| 477 | collectMatchedFields(memberMatches, classMapping, classMatches, doer); | 478 | |
| 478 | 479 | // filter by type/signature | |
| 479 | // get unmatched dest fields | 480 | Set<BehaviorEntry> obfDestEntries = doer.filterEntries(memberMatches.getUnmatchedDestEntries(obfDestClass), obfSourceEntry, classMatches); |
| 480 | doer.getObfEntries(destDeobfuscator.getJarIndex()).stream() | 481 | |
| 481 | .filter(destEntry -> !memberMatches.isMatchedDestEntry(destEntry)) | 482 | if (obfDestEntries.size() == 1) { |
| 482 | .forEach(memberMatches::addUnmatchedDestEntry); | 483 | // make the easy match |
| 483 | 484 | memberMatches.makeMatch(obfSourceEntry, obfDestEntries.iterator().next()); | |
| 484 | // Apply mappings to deobfuscator | 485 | } else if (obfDestEntries.isEmpty()) { |
| 485 | 486 | // no match is possible =( | |
| 486 | // Create type loader | 487 | memberMatches.makeSourceUnmatchable(obfSourceEntry, null); |
| 487 | TranslatingTypeLoader destTypeLoader = destDeobfuscator.createTypeLoader(); | 488 | } else { |
| 488 | TranslatingTypeLoader sourceTypeLoader = sourceDeobfuscator.createTypeLoader(); | 489 | // Multiple matches! Scan methods instructions |
| 489 | 490 | CtClass destCtClass = destTypeLoader.loadClass(obfDestClass.getClassName()); | |
| 490 | System.out.println("Automatching " + memberMatches.getUnmatchedSourceEntries().size() + " unmatched source entries..."); | 491 | CtClass sourceCtClass = sourceTypeLoader.loadClass(obfSourceClass.getClassName()); |
| 491 | 492 | BehaviorEntry match = compareMethods(destCtClass, sourceCtClass, obfSourceEntry, obfDestEntries); | |
| 492 | // go through the unmatched source fields and try to pick out the easy matches | 493 | // the method match correctly, match it on the member mapping! |
| 493 | for (ClassEntry obfSourceClass : Lists.newArrayList(memberMatches.getSourceClassesWithUnmatchedEntries())) { | 494 | if (match != null) |
| 494 | for (BehaviorEntry obfSourceEntry : Lists.newArrayList(memberMatches.getUnmatchedSourceEntries(obfSourceClass))) { | 495 | memberMatches.makeMatch(obfSourceEntry, match); |
| 495 | 496 | } | |
| 496 | // get the possible dest matches | 497 | } |
| 497 | ClassEntry obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass); | 498 | } |
| 498 | 499 | ||
| 499 | // filter by type/signature | 500 | System.out.println(String.format("Ended up with %d ambiguous and %d unmatchable source entries", |
| 500 | Set<BehaviorEntry> obfDestEntries = doer.filterEntries(memberMatches.getUnmatchedDestEntries(obfDestClass), obfSourceEntry, classMatches); | 501 | memberMatches.getUnmatchedSourceEntries().size(), |
| 501 | 502 | memberMatches.getUnmatchableSourceEntries().size() | |
| 502 | if (obfDestEntries.size() == 1) { | 503 | )); |
| 503 | // make the easy match | 504 | |
| 504 | memberMatches.makeMatch(obfSourceEntry, obfDestEntries.iterator().next()); | 505 | return memberMatches; |
| 505 | } else if (obfDestEntries.isEmpty()) { | 506 | } |
| 506 | // no match is possible =( | 507 | |
| 507 | memberMatches.makeSourceUnmatchable(obfSourceEntry, null); | 508 | public static <T extends Entry> MemberMatches<T> computeMemberMatches(Deobfuscator destDeobfuscator, Mappings destMappings, ClassMatches classMatches, Doer<T> doer) { |
| 508 | } else | 509 | |
| 509 | { | 510 | MemberMatches<T> memberMatches = new MemberMatches<>(); |
| 510 | // Multiple matches! Scan methods instructions | 511 | |
| 511 | CtClass destCtClass = destTypeLoader.loadClass(obfDestClass.getClassName()); | 512 | // unmatched source fields are easy |
| 512 | CtClass sourceCtClass = sourceTypeLoader.loadClass(obfSourceClass.getClassName()); | 513 | MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex()); |
| 513 | BehaviorEntry match = compareMethods(destCtClass, sourceCtClass, obfSourceEntry, obfDestEntries); | 514 | checker.dropBrokenMappings(destMappings); |
| 514 | // the method match correctly, match it on the member mapping! | 515 | for (T destObfEntry : doer.getDroppedEntries(checker)) { |
| 515 | if (match != null) | 516 | T srcObfEntry = translate(destObfEntry, classMatches.getUniqueMatches().inverse()); |
| 516 | memberMatches.makeMatch(obfSourceEntry, match); | 517 | memberMatches.addUnmatchedSourceEntry(srcObfEntry); |
| 517 | } | 518 | } |
| 518 | } | 519 | |
| 519 | } | 520 | // get matched fields (anything that's left after the checks/drops is matched( |
| 520 | 521 | for (ClassMapping classMapping : destMappings.classes()) { | |
| 521 | System.out.println(String.format("Ended up with %d ambiguous and %d unmatchable source entries", | 522 | collectMatchedFields(memberMatches, classMapping, classMatches, doer); |
| 522 | memberMatches.getUnmatchedSourceEntries().size(), | 523 | } |
| 523 | memberMatches.getUnmatchableSourceEntries().size() | 524 | |
| 524 | )); | 525 | // get unmatched dest fields |
| 525 | 526 | for (T destEntry : doer.getObfEntries(destDeobfuscator.getJarIndex())) { | |
| 526 | return memberMatches; | 527 | if (!memberMatches.isMatchedDestEntry(destEntry)) { |
| 527 | } | 528 | memberMatches.addUnmatchedDestEntry(destEntry); |
| 528 | 529 | } | |
| 529 | public static <T extends Entry> MemberMatches<T> computeMemberMatches(Deobfuscator destDeobfuscator, Mappings destMappings, ClassMatches classMatches, Doer<T> doer) { | 530 | } |
| 530 | 531 | ||
| 531 | MemberMatches<T> memberMatches = new MemberMatches<>(); | 532 | System.out.println("Automatching " + memberMatches.getUnmatchedSourceEntries().size() + " unmatched source entries..."); |
| 532 | 533 | ||
| 533 | // unmatched source fields are easy | 534 | // go through the unmatched source fields and try to pick out the easy matches |
| 534 | MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex()); | 535 | for (ClassEntry obfSourceClass : Lists.newArrayList(memberMatches.getSourceClassesWithUnmatchedEntries())) { |
| 535 | checker.dropBrokenMappings(destMappings); | 536 | for (T obfSourceEntry : Lists.newArrayList(memberMatches.getUnmatchedSourceEntries(obfSourceClass))) { |
| 536 | for (T destObfEntry : doer.getDroppedEntries(checker)) { | 537 | |
| 537 | T srcObfEntry = translate(destObfEntry, classMatches.getUniqueMatches().inverse()); | 538 | // get the possible dest matches |
| 538 | memberMatches.addUnmatchedSourceEntry(srcObfEntry); | 539 | ClassEntry obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass); |
| 539 | } | 540 | |
| 540 | 541 | // filter by type/signature | |
| 541 | // get matched fields (anything that's left after the checks/drops is matched( | 542 | Set<T> obfDestEntries = doer.filterEntries(memberMatches.getUnmatchedDestEntries(obfDestClass), obfSourceEntry, classMatches); |
| 542 | for (ClassMapping classMapping : destMappings.classes()) { | 543 | |
| 543 | collectMatchedFields(memberMatches, classMapping, classMatches, doer); | 544 | if (obfDestEntries.size() == 1) { |
| 544 | } | 545 | // make the easy match |
| 545 | 546 | memberMatches.makeMatch(obfSourceEntry, obfDestEntries.iterator().next()); | |
| 546 | // get unmatched dest fields | 547 | } else if (obfDestEntries.isEmpty()) { |
| 547 | for (T destEntry : doer.getObfEntries(destDeobfuscator.getJarIndex())) { | 548 | // no match is possible =( |
| 548 | if (!memberMatches.isMatchedDestEntry(destEntry)) { | 549 | memberMatches.makeSourceUnmatchable(obfSourceEntry, null); |
| 549 | memberMatches.addUnmatchedDestEntry(destEntry); | 550 | } |
| 550 | } | 551 | } |
| 551 | } | 552 | } |
| 552 | 553 | ||
| 553 | System.out.println("Automatching " + memberMatches.getUnmatchedSourceEntries().size() + " unmatched source entries..."); | 554 | System.out.println(String.format("Ended up with %d ambiguous and %d unmatchable source entries", |
| 554 | 555 | memberMatches.getUnmatchedSourceEntries().size(), | |
| 555 | // go through the unmatched source fields and try to pick out the easy matches | 556 | memberMatches.getUnmatchableSourceEntries().size() |
| 556 | for (ClassEntry obfSourceClass : Lists.newArrayList(memberMatches.getSourceClassesWithUnmatchedEntries())) { | 557 | )); |
| 557 | for (T obfSourceEntry : Lists.newArrayList(memberMatches.getUnmatchedSourceEntries(obfSourceClass))) { | 558 | |
| 558 | 559 | return memberMatches; | |
| 559 | // get the possible dest matches | 560 | } |
| 560 | ClassEntry obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass); | 561 | |
| 561 | 562 | private static <T extends Entry> void collectMatchedFields(MemberMatches<T> memberMatches, ClassMapping destClassMapping, ClassMatches classMatches, Doer<T> doer) { | |
| 562 | // filter by type/signature | 563 | |
| 563 | Set<T> obfDestEntries = doer.filterEntries(memberMatches.getUnmatchedDestEntries(obfDestClass), obfSourceEntry, classMatches); | 564 | // get the fields for this class |
| 564 | 565 | for (MemberMapping<T> destEntryMapping : doer.getMappings(destClassMapping)) { | |
| 565 | if (obfDestEntries.size() == 1) { | 566 | T destObfField = destEntryMapping.getObfEntry(destClassMapping.getObfEntry()); |
| 566 | // make the easy match | 567 | T srcObfField = translate(destObfField, classMatches.getUniqueMatches().inverse()); |
| 567 | memberMatches.makeMatch(obfSourceEntry, obfDestEntries.iterator().next()); | 568 | memberMatches.addMatch(srcObfField, destObfField); |
| 568 | } else if (obfDestEntries.isEmpty()) { | 569 | } |
| 569 | // no match is possible =( | 570 | |
| 570 | memberMatches.makeSourceUnmatchable(obfSourceEntry, null); | 571 | // recurse |
| 571 | } | 572 | for (ClassMapping destInnerClassMapping : destClassMapping.innerClasses()) { |
| 572 | } | 573 | collectMatchedFields(memberMatches, destInnerClassMapping, classMatches, doer); |
| 573 | } | 574 | } |
| 574 | 575 | } | |
| 575 | System.out.println(String.format("Ended up with %d ambiguous and %d unmatchable source entries", | 576 | |
| 576 | memberMatches.getUnmatchedSourceEntries().size(), | 577 | @SuppressWarnings("unchecked") |
| 577 | memberMatches.getUnmatchableSourceEntries().size() | 578 | private static <T extends Entry> T translate(T in, BiMap<ClassEntry, ClassEntry> map) { |
| 578 | )); | 579 | if (in instanceof FieldEntry) { |
| 579 | 580 | return (T) new FieldEntry( | |
| 580 | return memberMatches; | 581 | map.get(in.getClassEntry()), |
| 581 | } | 582 | in.getName(), |
| 582 | 583 | translate(((FieldEntry) in).getType(), map) | |
| 583 | private static <T extends Entry> void collectMatchedFields(MemberMatches<T> memberMatches, ClassMapping destClassMapping, ClassMatches classMatches, Doer<T> doer) { | 584 | ); |
| 584 | 585 | } else if (in instanceof MethodEntry) { | |
| 585 | // get the fields for this class | 586 | return (T) new MethodEntry( |
| 586 | for (MemberMapping<T> destEntryMapping : doer.getMappings(destClassMapping)) { | 587 | map.get(in.getClassEntry()), |
| 587 | T destObfField = destEntryMapping.getObfEntry(destClassMapping.getObfEntry()); | 588 | in.getName(), |
| 588 | T srcObfField = translate(destObfField, classMatches.getUniqueMatches().inverse()); | 589 | translate(((MethodEntry) in).getSignature(), map) |
| 589 | memberMatches.addMatch(srcObfField, destObfField); | 590 | ); |
| 590 | } | 591 | } else if (in instanceof ConstructorEntry) { |
| 591 | 592 | return (T) new ConstructorEntry( | |
| 592 | // recurse | 593 | map.get(in.getClassEntry()), |
| 593 | for (ClassMapping destInnerClassMapping : destClassMapping.innerClasses()) { | 594 | translate(((ConstructorEntry) in).getSignature(), map) |
| 594 | collectMatchedFields(memberMatches, destInnerClassMapping, classMatches, doer); | 595 | ); |
| 595 | } | 596 | } |
| 596 | } | 597 | throw new Error("Unhandled entry type: " + in.getClass()); |
| 597 | 598 | } | |
| 598 | @SuppressWarnings("unchecked") | 599 | |
| 599 | private static <T extends Entry> T translate(T in, BiMap<ClassEntry, ClassEntry> map) { | 600 | private static Type translate(Type type, final BiMap<ClassEntry, ClassEntry> map) { |
| 600 | if (in instanceof FieldEntry) { | 601 | return new Type(type, inClassName -> |
| 601 | return (T) new FieldEntry( | 602 | { |
| 602 | map.get(in.getClassEntry()), | 603 | ClassEntry outClassEntry = map.get(new ClassEntry(inClassName)); |
| 603 | in.getName(), | 604 | if (outClassEntry == null) { |
| 604 | translate(((FieldEntry) in).getType(), map) | 605 | return null; |
| 605 | ); | 606 | } |
| 606 | } else if (in instanceof MethodEntry) { | 607 | return outClassEntry.getName(); |
| 607 | return (T) new MethodEntry( | 608 | }); |
| 608 | map.get(in.getClassEntry()), | 609 | } |
| 609 | in.getName(), | 610 | |
| 610 | translate(((MethodEntry) in).getSignature(), map) | 611 | private static Signature translate(Signature signature, final BiMap<ClassEntry, ClassEntry> map) { |
| 611 | ); | 612 | if (signature == null) { |
| 612 | } else if (in instanceof ConstructorEntry) { | 613 | return null; |
| 613 | return (T) new ConstructorEntry( | 614 | } |
| 614 | map.get(in.getClassEntry()), | 615 | return new Signature(signature, inClassName -> |
| 615 | translate(((ConstructorEntry) in).getSignature(), map) | 616 | { |
| 616 | ); | 617 | ClassEntry outClassEntry = map.get(new ClassEntry(inClassName)); |
| 617 | } | 618 | if (outClassEntry == null) { |
| 618 | throw new Error("Unhandled entry type: " + in.getClass()); | 619 | return null; |
| 619 | } | 620 | } |
| 620 | 621 | return outClassEntry.getName(); | |
| 621 | private static Type translate(Type type, final BiMap<ClassEntry, ClassEntry> map) { | 622 | }); |
| 622 | return new Type(type, inClassName -> | 623 | } |
| 623 | { | 624 | |
| 624 | ClassEntry outClassEntry = map.get(new ClassEntry(inClassName)); | 625 | public static <T extends Entry> void applyMemberMatches(Mappings mappings, ClassMatches classMatches, MemberMatches<T> memberMatches, Doer<T> doer) { |
| 625 | if (outClassEntry == null) { | 626 | for (ClassMapping classMapping : mappings.classes()) { |
| 626 | return null; | 627 | applyMemberMatches(classMapping, classMatches, memberMatches, doer); |
| 627 | } | 628 | } |
| 628 | return outClassEntry.getName(); | 629 | } |
| 629 | }); | 630 | |
| 630 | } | 631 | private static <T extends Entry> void applyMemberMatches(ClassMapping classMapping, ClassMatches classMatches, MemberMatches<T> memberMatches, Doer<T> doer) { |
| 631 | 632 | ||
| 632 | private static Signature translate(Signature signature, final BiMap<ClassEntry, ClassEntry> map) { | 633 | // get the classes |
| 633 | if (signature == null) { | 634 | ClassEntry obfDestClass = new ClassEntry(classMapping.getObfFullName()); |
| 634 | return null; | 635 | |
| 635 | } | 636 | // make a map of all the renames we need to make |
| 636 | return new Signature(signature, inClassName -> | 637 | Map<T, T> renames = Maps.newHashMap(); |
| 637 | { | 638 | for (MemberMapping<T> memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) { |
| 638 | ClassEntry outClassEntry = map.get(new ClassEntry(inClassName)); | 639 | T obfOldDestEntry = memberMapping.getObfEntry(obfDestClass); |
| 639 | if (outClassEntry == null) { | 640 | T obfSourceEntry = getSourceEntryFromDestMapping(memberMapping, obfDestClass, classMatches); |
| 640 | return null; | 641 | |
| 641 | } | 642 | // but drop the unmatchable things |
| 642 | return outClassEntry.getName(); | 643 | if (memberMatches.isUnmatchableSourceEntry(obfSourceEntry)) { |
| 643 | }); | 644 | doer.removeMemberByObf(classMapping, obfOldDestEntry); |
| 644 | } | 645 | continue; |
| 645 | 646 | } | |
| 646 | public static <T extends Entry> void applyMemberMatches(Mappings mappings, ClassMatches classMatches, MemberMatches<T> memberMatches, Doer<T> doer) { | 647 | |
| 647 | for (ClassMapping classMapping : mappings.classes()) { | 648 | T obfNewDestEntry = memberMatches.matches().get(obfSourceEntry); |
| 648 | applyMemberMatches(classMapping, classMatches, memberMatches, doer); | 649 | if (obfNewDestEntry != null && !obfOldDestEntry.getName().equals(obfNewDestEntry.getName())) { |
| 649 | } | 650 | renames.put(obfOldDestEntry, obfNewDestEntry); |
| 650 | } | 651 | } |
| 651 | 652 | } | |
| 652 | private static <T extends Entry> void applyMemberMatches(ClassMapping classMapping, ClassMatches classMatches, MemberMatches<T> memberMatches, Doer<T> doer) { | 653 | |
| 653 | 654 | if (!renames.isEmpty()) { | |
| 654 | // get the classes | 655 | |
| 655 | ClassEntry obfDestClass = new ClassEntry(classMapping.getObfFullName()); | 656 | // apply to this class (should never need more than n passes) |
| 656 | 657 | int numRenamesAppliedThisRound; | |
| 657 | // make a map of all the renames we need to make | 658 | do { |
| 658 | Map<T, T> renames = Maps.newHashMap(); | 659 | numRenamesAppliedThisRound = 0; |
| 659 | for (MemberMapping<T> memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) { | 660 | |
| 660 | T obfOldDestEntry = memberMapping.getObfEntry(obfDestClass); | 661 | for (MemberMapping<T> memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) { |
| 661 | T obfSourceEntry = getSourceEntryFromDestMapping(memberMapping, obfDestClass, classMatches); | 662 | T obfOldDestEntry = memberMapping.getObfEntry(obfDestClass); |
| 662 | 663 | T obfNewDestEntry = renames.get(obfOldDestEntry); | |
| 663 | // but drop the unmatchable things | 664 | if (obfNewDestEntry != null) { |
| 664 | if (memberMatches.isUnmatchableSourceEntry(obfSourceEntry)) { | 665 | // make sure this rename won't cause a collision |
| 665 | doer.removeMemberByObf(classMapping, obfOldDestEntry); | 666 | // otherwise, save it for the next round and try again next time |
| 666 | continue; | 667 | if (!doer.hasObfMember(classMapping, obfNewDestEntry)) { |
| 667 | } | 668 | doer.setUpdateObfMember(classMapping, memberMapping, obfNewDestEntry); |
| 668 | 669 | renames.remove(obfOldDestEntry); | |
| 669 | T obfNewDestEntry = memberMatches.matches().get(obfSourceEntry); | 670 | numRenamesAppliedThisRound++; |
| 670 | if (obfNewDestEntry != null && !obfOldDestEntry.getName().equals(obfNewDestEntry.getName())) { | 671 | } |
| 671 | renames.put(obfOldDestEntry, obfNewDestEntry); | 672 | } |
| 672 | } | 673 | } |
| 673 | } | 674 | } while (numRenamesAppliedThisRound > 0); |
| 674 | 675 | ||
| 675 | if (!renames.isEmpty()) { | 676 | if (!renames.isEmpty()) { |
| 676 | 677 | System.err.println(String.format("WARNING: Couldn't apply all the renames for class %s. %d renames left.", | |
| 677 | // apply to this class (should never need more than n passes) | 678 | classMapping.getObfFullName(), renames.size() |
| 678 | int numRenamesAppliedThisRound; | 679 | )); |
| 679 | do { | 680 | for (Map.Entry<T, T> entry : renames.entrySet()) { |
| 680 | numRenamesAppliedThisRound = 0; | 681 | System.err.println(String.format("\t%s -> %s", entry.getKey().getName(), entry.getValue().getName())); |
| 681 | 682 | } | |
| 682 | for (MemberMapping<T> memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) { | 683 | } |
| 683 | T obfOldDestEntry = memberMapping.getObfEntry(obfDestClass); | 684 | } |
| 684 | T obfNewDestEntry = renames.get(obfOldDestEntry); | 685 | |
| 685 | if (obfNewDestEntry != null) { | 686 | // recurse |
| 686 | // make sure this rename won't cause a collision | 687 | for (ClassMapping innerClassMapping : classMapping.innerClasses()) { |
| 687 | // otherwise, save it for the next round and try again next time | 688 | applyMemberMatches(innerClassMapping, classMatches, memberMatches, doer); |
| 688 | if (!doer.hasObfMember(classMapping, obfNewDestEntry)) { | 689 | } |
| 689 | doer.setUpdateObfMember(classMapping, memberMapping, obfNewDestEntry); | 690 | } |
| 690 | renames.remove(obfOldDestEntry); | 691 | |
| 691 | numRenamesAppliedThisRound++; | 692 | private static <T extends Entry> T getSourceEntryFromDestMapping(MemberMapping<T> destMemberMapping, ClassEntry obfDestClass, ClassMatches classMatches) { |
| 692 | } | 693 | return translate(destMemberMapping.getObfEntry(obfDestClass), classMatches.getUniqueMatches().inverse()); |
| 693 | } | 694 | } |
| 694 | } | 695 | |
| 695 | } while (numRenamesAppliedThisRound > 0); | 696 | public interface Doer<T extends Entry> { |
| 696 | 697 | Collection<T> getDroppedEntries(MappingsChecker checker); | |
| 697 | if (!renames.isEmpty()) { | 698 | |
| 698 | System.err.println(String.format("WARNING: Couldn't apply all the renames for class %s. %d renames left.", | 699 | Collection<T> getObfEntries(JarIndex jarIndex); |
| 699 | classMapping.getObfFullName(), renames.size() | 700 | |
| 700 | )); | 701 | Collection<? extends MemberMapping<T>> getMappings(ClassMapping destClassMapping); |
| 701 | for (Map.Entry<T, T> entry : renames.entrySet()) { | 702 | |
| 702 | System.err.println(String.format("\t%s -> %s", entry.getKey().getName(), entry.getValue().getName())); | 703 | Set<T> filterEntries(Collection<T> obfEntries, T obfSourceEntry, ClassMatches classMatches); |
| 703 | } | 704 | |
| 704 | } | 705 | void setUpdateObfMember(ClassMapping classMapping, MemberMapping<T> memberMapping, T newEntry); |
| 705 | } | 706 | |
| 706 | 707 | boolean hasObfMember(ClassMapping classMapping, T obfEntry); | |
| 707 | // recurse | 708 | |
| 708 | for (ClassMapping innerClassMapping : classMapping.innerClasses()) { | 709 | void removeMemberByObf(ClassMapping classMapping, T obfEntry); |
| 709 | applyMemberMatches(innerClassMapping, classMatches, memberMatches, doer); | 710 | } |
| 710 | } | ||
| 711 | } | ||
| 712 | |||
| 713 | private static <T extends Entry> T getSourceEntryFromDestMapping(MemberMapping<T> destMemberMapping, ClassEntry obfDestClass, ClassMatches classMatches) { | ||
| 714 | return translate(destMemberMapping.getObfEntry(obfDestClass), classMatches.getUniqueMatches().inverse()); | ||
| 715 | } | ||
| 716 | } | 711 | } |