diff options
Diffstat (limited to 'src/main/java/cuchaz/enigma/Deobfuscator.java')
| -rw-r--r-- | src/main/java/cuchaz/enigma/Deobfuscator.java | 483 |
1 files changed, 96 insertions, 387 deletions
diff --git a/src/main/java/cuchaz/enigma/Deobfuscator.java b/src/main/java/cuchaz/enigma/Deobfuscator.java index 4a945cd..076c546 100644 --- a/src/main/java/cuchaz/enigma/Deobfuscator.java +++ b/src/main/java/cuchaz/enigma/Deobfuscator.java | |||
| @@ -13,8 +13,6 @@ package cuchaz.enigma; | |||
| 13 | 13 | ||
| 14 | import com.google.common.base.Stopwatch; | 14 | import com.google.common.base.Stopwatch; |
| 15 | import com.google.common.collect.Lists; | 15 | import com.google.common.collect.Lists; |
| 16 | import com.google.common.collect.Maps; | ||
| 17 | import com.google.common.collect.Sets; | ||
| 18 | import com.strobel.assembler.metadata.ITypeLoader; | 16 | import com.strobel.assembler.metadata.ITypeLoader; |
| 19 | import com.strobel.assembler.metadata.MetadataSystem; | 17 | import com.strobel.assembler.metadata.MetadataSystem; |
| 20 | import com.strobel.assembler.metadata.TypeDefinition; | 18 | import com.strobel.assembler.metadata.TypeDefinition; |
| @@ -28,16 +26,17 @@ import com.strobel.decompiler.languages.java.ast.CompilationUnit; | |||
| 28 | import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor; | 26 | import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor; |
| 29 | import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; | 27 | import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; |
| 30 | import cuchaz.enigma.analysis.*; | 28 | import cuchaz.enigma.analysis.*; |
| 29 | import cuchaz.enigma.analysis.index.JarIndex; | ||
| 31 | import cuchaz.enigma.api.EnigmaPlugin; | 30 | import cuchaz.enigma.api.EnigmaPlugin; |
| 32 | import cuchaz.enigma.mapping.*; | 31 | import cuchaz.enigma.translation.mapping.*; |
| 33 | import cuchaz.enigma.mapping.entry.*; | 32 | import cuchaz.enigma.translation.mapping.tree.DeltaTrackingTree; |
| 34 | import cuchaz.enigma.throwables.IllegalNameException; | 33 | import cuchaz.enigma.translation.mapping.tree.EntryTree; |
| 34 | import cuchaz.enigma.translation.representation.ReferencedEntryPool; | ||
| 35 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 36 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 37 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 35 | import cuchaz.enigma.utils.Utils; | 38 | import cuchaz.enigma.utils.Utils; |
| 36 | import oml.ast.transformers.InvalidIdentifierFix; | 39 | import oml.ast.transformers.*; |
| 37 | import oml.ast.transformers.Java8Generics; | ||
| 38 | import oml.ast.transformers.ObfuscatedEnumSwitchRewriterTransform; | ||
| 39 | import oml.ast.transformers.RemoveObjectCasts; | ||
| 40 | import oml.ast.transformers.VarargsFixer; | ||
| 41 | import org.objectweb.asm.ClassWriter; | 40 | import org.objectweb.asm.ClassWriter; |
| 42 | import org.objectweb.asm.tree.ClassNode; | 41 | import org.objectweb.asm.tree.ClassNode; |
| 43 | 42 | ||
| @@ -49,6 +48,7 @@ import java.util.function.Consumer; | |||
| 49 | import java.util.jar.JarEntry; | 48 | import java.util.jar.JarEntry; |
| 50 | import java.util.jar.JarFile; | 49 | import java.util.jar.JarFile; |
| 51 | import java.util.jar.JarOutputStream; | 50 | import java.util.jar.JarOutputStream; |
| 51 | import java.util.stream.Collectors; | ||
| 52 | 52 | ||
| 53 | public class Deobfuscator { | 53 | public class Deobfuscator { |
| 54 | 54 | ||
| @@ -57,24 +57,24 @@ public class Deobfuscator { | |||
| 57 | private final ParsedJar parsedJar; | 57 | private final ParsedJar parsedJar; |
| 58 | private final DecompilerSettings settings; | 58 | private final DecompilerSettings settings; |
| 59 | private final JarIndex jarIndex; | 59 | private final JarIndex jarIndex; |
| 60 | private final MappingsRenamer renamer; | 60 | private final IndexTreeBuilder indexTreeBuilder; |
| 61 | private final Map<TranslationDirection, Translator> translatorCache; | 61 | private EntryRemapper mapper; |
| 62 | private Mappings mappings; | ||
| 63 | 62 | ||
| 64 | public Deobfuscator(ParsedJar jar, Consumer<String> listener) { | 63 | public Deobfuscator(ParsedJar jar, Consumer<String> listener) { |
| 65 | this.parsedJar = jar; | 64 | this.parsedJar = jar; |
| 66 | 65 | ||
| 67 | // build the jar index | 66 | // build the jar index |
| 68 | listener.accept("Indexing JAR..."); | 67 | this.jarIndex = JarIndex.empty(); |
| 69 | this.jarIndex = new JarIndex(entryPool); | 68 | this.jarIndex.indexJar(this.parsedJar, listener); |
| 70 | this.jarIndex.indexJar(this.parsedJar, true); | ||
| 71 | 69 | ||
| 72 | listener.accept("Initializing plugins..."); | 70 | listener.accept("Initializing plugins..."); |
| 73 | for (EnigmaPlugin plugin : getPlugins()) { | 71 | for (EnigmaPlugin plugin : getPlugins()) { |
| 74 | plugin.onClassesLoaded(parsedJar.getClassDataMap(), parsedJar::getClassNode); | 72 | plugin.onClassesLoaded(parsedJar.getClassDataMap(), parsedJar::getClassNode); |
| 75 | } | 73 | } |
| 76 | 74 | ||
| 77 | listener.accept("Preparing..."); | 75 | this.indexTreeBuilder = new IndexTreeBuilder(jarIndex); |
| 76 | |||
| 77 | listener.accept("Preparing..."); | ||
| 78 | // config the decompiler | 78 | // config the decompiler |
| 79 | this.settings = DecompilerSettings.javaDefaults(); | 79 | this.settings = DecompilerSettings.javaDefaults(); |
| 80 | this.settings.setMergeVariables(Utils.getSystemPropertyAsBoolean("enigma.mergeVariables", true)); | 80 | this.settings.setMergeVariables(Utils.getSystemPropertyAsBoolean("enigma.mergeVariables", true)); |
| @@ -85,11 +85,8 @@ public class Deobfuscator { | |||
| 85 | this.settings.setShowDebugLineNumbers(Utils.getSystemPropertyAsBoolean("enigma.showDebugLineNumbers", false)); | 85 | this.settings.setShowDebugLineNumbers(Utils.getSystemPropertyAsBoolean("enigma.showDebugLineNumbers", false)); |
| 86 | this.settings.setShowSyntheticMembers(Utils.getSystemPropertyAsBoolean("enigma.showSyntheticMembers", false)); | 86 | this.settings.setShowSyntheticMembers(Utils.getSystemPropertyAsBoolean("enigma.showSyntheticMembers", false)); |
| 87 | 87 | ||
| 88 | // init defaults | ||
| 89 | this.translatorCache = Maps.newTreeMap(); | ||
| 90 | this.renamer = new MappingsRenamer(this.jarIndex, null, this.entryPool); | ||
| 91 | // init mappings | 88 | // init mappings |
| 92 | setMappings(new Mappings()); | 89 | mapper = new EntryRemapper(jarIndex); |
| 93 | } | 90 | } |
| 94 | 91 | ||
| 95 | public Deobfuscator(JarFile jar, Consumer<String> listener) throws IOException { | 92 | public Deobfuscator(JarFile jar, Consumer<String> listener) throws IOException { |
| @@ -97,12 +94,14 @@ public class Deobfuscator { | |||
| 97 | } | 94 | } |
| 98 | 95 | ||
| 99 | public Deobfuscator(ParsedJar jar) throws IOException { | 96 | public Deobfuscator(ParsedJar jar) throws IOException { |
| 100 | this(jar, (msg) -> {}); | 97 | this(jar, (msg) -> { |
| 101 | } | 98 | }); |
| 99 | } | ||
| 102 | 100 | ||
| 103 | public Deobfuscator(JarFile jar) throws IOException { | 101 | public Deobfuscator(JarFile jar) throws IOException { |
| 104 | this(jar, (msg) -> {}); | 102 | this(jar, (msg) -> { |
| 105 | } | 103 | }); |
| 104 | } | ||
| 106 | 105 | ||
| 107 | public ServiceLoader<EnigmaPlugin> getPlugins() { | 106 | public ServiceLoader<EnigmaPlugin> getPlugins() { |
| 108 | return plugins; | 107 | return plugins; |
| @@ -116,56 +115,50 @@ public class Deobfuscator { | |||
| 116 | return this.jarIndex; | 115 | return this.jarIndex; |
| 117 | } | 116 | } |
| 118 | 117 | ||
| 119 | public Mappings getMappings() { | 118 | public IndexTreeBuilder getIndexTreeBuilder() { |
| 120 | return this.mappings; | 119 | return indexTreeBuilder; |
| 121 | } | 120 | } |
| 122 | 121 | ||
| 123 | public void setMappings(Mappings val) { | 122 | public EntryRemapper getMapper() { |
| 124 | setMappings(val, true); | 123 | return this.mapper; |
| 125 | } | 124 | } |
| 126 | 125 | ||
| 127 | public void setMappings(Mappings val, boolean warnAboutDrops) { | 126 | public void setMappings(EntryTree<EntryMapping> mappings) { |
| 128 | if (val == null) { | 127 | if (mappings != null) { |
| 129 | val = new Mappings(); | 128 | Collection<Entry<?>> dropped = dropMappings(mappings); |
| 130 | } | 129 | mapper = new EntryRemapper(jarIndex, mappings); |
| 131 | 130 | ||
| 132 | // drop mappings that don't match the jar | 131 | DeltaTrackingTree<EntryMapping> deobfToObf = mapper.getDeobfToObf(); |
| 133 | MappingsChecker checker = new MappingsChecker(this.jarIndex); | 132 | for (Entry<?> entry : dropped) { |
| 134 | checker.dropBrokenMappings(val); | 133 | deobfToObf.trackDeletion(entry); |
| 135 | if (warnAboutDrops) { | ||
| 136 | for (Map.Entry<ClassEntry, ClassMapping> mapping : checker.getDroppedClassMappings().entrySet()) { | ||
| 137 | System.out.println("WARNING: Couldn't find class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped."); | ||
| 138 | } | ||
| 139 | for (Map.Entry<ClassEntry, ClassMapping> mapping : checker.getDroppedInnerClassMappings().entrySet()) { | ||
| 140 | System.out.println("WARNING: Couldn't find inner class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped."); | ||
| 141 | } | ||
| 142 | for (Map.Entry<FieldEntry, FieldMapping> mapping : checker.getDroppedFieldMappings().entrySet()) { | ||
| 143 | System.out.println("WARNING: Couldn't find field entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped."); | ||
| 144 | } | ||
| 145 | for (Map.Entry<MethodEntry, MethodMapping> mapping : checker.getDroppedMethodMappings().entrySet()) { | ||
| 146 | System.out.println("WARNING: Couldn't find behavior entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped."); | ||
| 147 | } | 134 | } |
| 135 | } else { | ||
| 136 | mapper = new EntryRemapper(jarIndex); | ||
| 148 | } | 137 | } |
| 149 | |||
| 150 | this.mappings = val; | ||
| 151 | this.renamer.setMappings(mappings); | ||
| 152 | this.translatorCache.clear(); | ||
| 153 | } | 138 | } |
| 154 | 139 | ||
| 155 | public Translator getTranslator(TranslationDirection direction) { | 140 | private Collection<Entry<?>> dropMappings(EntryTree<EntryMapping> mappings) { |
| 156 | return this.translatorCache.computeIfAbsent(direction, | 141 | // drop mappings that don't match the jar |
| 157 | k -> this.mappings.getTranslator(direction, this.jarIndex.getTranslationIndex())); | 142 | MappingsChecker checker = new MappingsChecker(jarIndex, mappings); |
| 143 | MappingsChecker.Dropped dropped = checker.dropBrokenMappings(); | ||
| 144 | |||
| 145 | Map<Entry<?>, String> droppedMappings = dropped.getDroppedMappings(); | ||
| 146 | for (Map.Entry<Entry<?>, String> mapping : droppedMappings.entrySet()) { | ||
| 147 | System.out.println("WARNING: Couldn't find " + mapping.getKey() + " (" + mapping.getValue() + ") in jar. Mapping was dropped."); | ||
| 148 | } | ||
| 149 | |||
| 150 | return droppedMappings.keySet(); | ||
| 158 | } | 151 | } |
| 159 | 152 | ||
| 160 | public void getSeparatedClasses(List<ClassEntry> obfClasses, List<ClassEntry> deobfClasses) { | 153 | public void getSeparatedClasses(List<ClassEntry> obfClasses, List<ClassEntry> deobfClasses) { |
| 161 | for (ClassEntry obfClassEntry : this.jarIndex.getObfClassEntries()) { | 154 | for (ClassEntry obfClassEntry : this.jarIndex.getEntryIndex().getClasses()) { |
| 162 | // skip inner classes | 155 | // skip inner classes |
| 163 | if (obfClassEntry.isInnerClass()) { | 156 | if (obfClassEntry.isInnerClass()) { |
| 164 | continue; | 157 | continue; |
| 165 | } | 158 | } |
| 166 | 159 | ||
| 167 | // separate the classes | 160 | // separate the classes |
| 168 | ClassEntry deobfClassEntry = deobfuscateEntry(obfClassEntry); | 161 | ClassEntry deobfClassEntry = mapper.deobfuscate(obfClassEntry); |
| 169 | if (!deobfClassEntry.equals(obfClassEntry)) { | 162 | if (!deobfClassEntry.equals(obfClassEntry)) { |
| 170 | // if the class has a mapping, clearly it's deobfuscated | 163 | // if the class has a mapping, clearly it's deobfuscated |
| 171 | deobfClasses.add(deobfClassEntry); | 164 | deobfClasses.add(deobfClassEntry); |
| @@ -184,8 +177,8 @@ public class Deobfuscator { | |||
| 184 | this.parsedJar, | 177 | this.parsedJar, |
| 185 | this.jarIndex, | 178 | this.jarIndex, |
| 186 | this.entryPool, | 179 | this.entryPool, |
| 187 | getTranslator(TranslationDirection.OBFUSCATING), | 180 | this.mapper.getObfuscator(), |
| 188 | getTranslator(TranslationDirection.DEOBFUSCATING) | 181 | this.mapper.getDeobfuscator() |
| 189 | ); | 182 | ); |
| 190 | } | 183 | } |
| 191 | 184 | ||
| @@ -203,14 +196,7 @@ public class Deobfuscator { | |||
| 203 | // we need to tell the decompiler the deobfuscated name so it doesn't get freaked out | 196 | // we need to tell the decompiler the deobfuscated name so it doesn't get freaked out |
| 204 | // the decompiler only sees classes after deobfuscation, so we need to load it by the deobfuscated name if there is one | 197 | // the decompiler only sees classes after deobfuscation, so we need to load it by the deobfuscated name if there is one |
| 205 | 198 | ||
| 206 | // first, assume class name is deobf | 199 | String deobfClassName = mapper.deobfuscate(new ClassEntry(className)).getFullName(); |
| 207 | String deobfClassName = className; | ||
| 208 | |||
| 209 | // if it wasn't actually deobf, then we can find a mapping for it and get the deobf name | ||
| 210 | ClassMapping classMapping = this.mappings.getClassByObf(className); | ||
| 211 | if (classMapping != null && classMapping.getDeobfName() != null) { | ||
| 212 | deobfClassName = classMapping.getDeobfName(); | ||
| 213 | } | ||
| 214 | 200 | ||
| 215 | // set the desc loader | 201 | // set the desc loader |
| 216 | this.settings.setTypeLoader(loader); | 202 | this.settings.setTypeLoader(loader); |
| @@ -236,43 +222,23 @@ public class Deobfuscator { | |||
| 236 | } | 222 | } |
| 237 | 223 | ||
| 238 | public SourceIndex getSourceIndex(CompilationUnit sourceTree, String source) { | 224 | public SourceIndex getSourceIndex(CompilationUnit sourceTree, String source) { |
| 239 | return getSourceIndex(sourceTree, source, null); | 225 | return getSourceIndex(sourceTree, source, true); |
| 240 | } | 226 | } |
| 241 | 227 | ||
| 242 | public SourceIndex getSourceIndex(CompilationUnit sourceTree, String source, Boolean ignoreBadTokens) { | 228 | public SourceIndex getSourceIndex(CompilationUnit sourceTree, String source, boolean ignoreBadTokens) { |
| 243 | 229 | ||
| 244 | // build the source index | 230 | // build the source index |
| 245 | SourceIndex index; | 231 | SourceIndex index = new SourceIndex(source, ignoreBadTokens); |
| 246 | if (ignoreBadTokens != null) { | ||
| 247 | index = new SourceIndex(source, ignoreBadTokens); | ||
| 248 | } else { | ||
| 249 | index = new SourceIndex(source); | ||
| 250 | } | ||
| 251 | sourceTree.acceptVisitor(new SourceIndexVisitor(entryPool), index); | 232 | sourceTree.acceptVisitor(new SourceIndexVisitor(entryPool), index); |
| 252 | 233 | ||
| 253 | // DEBUG | 234 | EntryResolver resolver = mapper.getDeobfResolver(); |
| 254 | // sourceTree.acceptVisitor( new TreeDumpVisitor( new File( "tree.txt" ) ), null ); | ||
| 255 | |||
| 256 | // resolve all the classes in the source references | ||
| 257 | for (Token token : index.referenceTokens()) { | ||
| 258 | EntryReference<Entry, Entry> deobfReference = index.getDeobfReference(token); | ||
| 259 | |||
| 260 | // get the obfuscated entry | ||
| 261 | Entry obfEntry = obfuscateEntry(deobfReference.entry); | ||
| 262 | 235 | ||
| 263 | // try to resolve the class | 236 | Collection<Token> tokens = Lists.newArrayList(index.referenceTokens()); |
| 264 | ClassEntry resolvedObfClassEntry = this.jarIndex.getTranslationIndex().resolveEntryOwner(obfEntry); | ||
| 265 | if (resolvedObfClassEntry != null && !resolvedObfClassEntry.equals(obfEntry.getOwnerClassEntry())) { | ||
| 266 | // change the class of the entry | ||
| 267 | obfEntry = obfEntry.updateOwnership(resolvedObfClassEntry); | ||
| 268 | |||
| 269 | // save the new deobfuscated reference | ||
| 270 | deobfReference.entry = deobfuscateEntry(obfEntry); | ||
| 271 | index.replaceDeobfReference(token, deobfReference); | ||
| 272 | } | ||
| 273 | 237 | ||
| 274 | // DEBUG | 238 | // resolve all the classes in the source references |
| 275 | // System.out.println( token + " -> " + reference + " -> " + index.getReferenceToken( reference ) ); | 239 | for (Token token : tokens) { |
| 240 | EntryReference<Entry<?>, Entry<?>> deobfReference = index.getDeobfReference(token); | ||
| 241 | index.replaceDeobfReference(token, resolver.resolveFirstReference(deobfReference, ResolutionStrategy.RESOLVE_CLOSEST)); | ||
| 276 | } | 242 | } |
| 277 | 243 | ||
| 278 | return index; | 244 | return index; |
| @@ -288,15 +254,9 @@ public class Deobfuscator { | |||
| 288 | 254 | ||
| 289 | public void writeSources(File dirOut, ProgressListener progress) { | 255 | public void writeSources(File dirOut, ProgressListener progress) { |
| 290 | // get the classes to decompile | 256 | // get the classes to decompile |
| 291 | Set<ClassEntry> classEntries = Sets.newHashSet(); | 257 | Set<ClassEntry> classEntries = jarIndex.getEntryIndex().getClasses().stream() |
| 292 | for (ClassEntry obfClassEntry : this.jarIndex.getObfClassEntries()) { | 258 | .filter(classEntry -> !classEntry.isInnerClass()) |
| 293 | // skip inner classes | 259 | .collect(Collectors.toSet()); |
| 294 | if (obfClassEntry.isInnerClass()) { | ||
| 295 | continue; | ||
| 296 | } | ||
| 297 | |||
| 298 | classEntries.add(obfClassEntry); | ||
| 299 | } | ||
| 300 | 260 | ||
| 301 | if (progress != null) { | 261 | if (progress != null) { |
| 302 | progress.init(classEntries.size(), "Decompiling classes..."); | 262 | progress.init(classEntries.size(), "Decompiling classes..."); |
| @@ -313,9 +273,9 @@ public class Deobfuscator { | |||
| 313 | Stopwatch stopwatch = Stopwatch.createStarted(); | 273 | Stopwatch stopwatch = Stopwatch.createStarted(); |
| 314 | AtomicInteger count = new AtomicInteger(); | 274 | AtomicInteger count = new AtomicInteger(); |
| 315 | classEntries.parallelStream().forEach(obfClassEntry -> { | 275 | classEntries.parallelStream().forEach(obfClassEntry -> { |
| 316 | ClassEntry deobfClassEntry = deobfuscateEntry(new ClassEntry(obfClassEntry)); | 276 | ClassEntry deobfClassEntry = mapper.deobfuscate(obfClassEntry); |
| 317 | if (progress != null) { | 277 | if (progress != null) { |
| 318 | progress.onProgress(count.getAndIncrement(), deobfClassEntry.toString()); | 278 | progress.step(count.getAndIncrement(), deobfClassEntry.toString()); |
| 319 | } | 279 | } |
| 320 | 280 | ||
| 321 | try { | 281 | try { |
| @@ -332,131 +292,17 @@ public class Deobfuscator { | |||
| 332 | } catch (Throwable t) { | 292 | } catch (Throwable t) { |
| 333 | // don't crash the whole world here, just log the error and keep going | 293 | // don't crash the whole world here, just log the error and keep going |
| 334 | // TODO: set up logback via log4j | 294 | // TODO: set up logback via log4j |
| 335 | System.err.println("Unable to deobfuscate class " + deobfClassEntry + " (" + obfClassEntry + ")"); | 295 | System.err.println("Unable to decompile class " + deobfClassEntry + " (" + obfClassEntry + ")"); |
| 336 | t.printStackTrace(System.err); | 296 | t.printStackTrace(System.err); |
| 337 | } | 297 | } |
| 338 | }); | 298 | }); |
| 339 | stopwatch.stop(); | 299 | stopwatch.stop(); |
| 340 | System.out.println("writeSources Done in : " + stopwatch.toString()); | 300 | System.out.println("writeSources Done in : " + stopwatch.toString()); |
| 341 | if (progress != null) { | 301 | if (progress != null) { |
| 342 | progress.onProgress(count.get(), "Done:"); | 302 | progress.step(count.get(), "Done:"); |
| 343 | } | ||
| 344 | } | ||
| 345 | |||
| 346 | private void addAllPotentialAncestors(Set<ClassEntry> classEntries, ClassEntry classObfEntry) { | ||
| 347 | for (ClassEntry interfaceEntry : jarIndex.getTranslationIndex().getInterfaces(classObfEntry)) { | ||
| 348 | if (classEntries.add(interfaceEntry)) { | ||
| 349 | addAllPotentialAncestors(classEntries, interfaceEntry); | ||
| 350 | } | ||
| 351 | } | ||
| 352 | |||
| 353 | ClassEntry superClassEntry = jarIndex.getTranslationIndex().getSuperclass(classObfEntry); | ||
| 354 | if (superClassEntry != null && classEntries.add(superClassEntry)) { | ||
| 355 | addAllPotentialAncestors(classEntries, superClassEntry); | ||
| 356 | } | ||
| 357 | } | ||
| 358 | |||
| 359 | public boolean isMethodProvider(MethodEntry methodEntry) { | ||
| 360 | Set<ClassEntry> classEntries = new HashSet<>(); | ||
| 361 | addAllPotentialAncestors(classEntries, methodEntry.getOwnerClassEntry()); | ||
| 362 | |||
| 363 | for (ClassEntry parentEntry : classEntries) { | ||
| 364 | MethodEntry ancestorMethodEntry = entryPool.getMethod(parentEntry, methodEntry.getName(), methodEntry.getDesc()); | ||
| 365 | if (jarIndex.containsObfMethod(ancestorMethodEntry)) { | ||
| 366 | return false; | ||
| 367 | } | ||
| 368 | } | ||
| 369 | |||
| 370 | return true; | ||
| 371 | } | ||
| 372 | |||
| 373 | @Deprecated | ||
| 374 | public boolean isMethodProvider(ClassEntry classObfEntry, MethodEntry methodEntry) { | ||
| 375 | Set<ClassEntry> classEntries = new HashSet<>(); | ||
| 376 | addAllPotentialAncestors(classEntries, classObfEntry); | ||
| 377 | |||
| 378 | for (ClassEntry parentEntry : classEntries) { | ||
| 379 | MethodEntry ancestorMethodEntry = entryPool.getMethod(parentEntry, methodEntry.getName(), methodEntry.getDesc()); | ||
| 380 | if (jarIndex.containsObfMethod(ancestorMethodEntry)) { | ||
| 381 | return false; | ||
| 382 | } | ||
| 383 | } | ||
| 384 | |||
| 385 | return true; | ||
| 386 | } | ||
| 387 | |||
| 388 | public void rebuildMethodNames(ProgressListener progress) { | ||
| 389 | final AtomicInteger i = new AtomicInteger(); | ||
| 390 | Map<ClassMapping, Map<Entry, String>> renameClassMap = new ConcurrentHashMap<>(); | ||
| 391 | |||
| 392 | progress.init(getMappings().classes().size() * 3, "Rebuilding method names"); | ||
| 393 | |||
| 394 | Lists.newArrayList(getMappings().classes()).parallelStream().forEach(classMapping -> { | ||
| 395 | progress.onProgress(i.getAndIncrement(), classMapping.getDeobfName()); | ||
| 396 | rebuildMethodNames(classMapping, renameClassMap); | ||
| 397 | }); | ||
| 398 | |||
| 399 | |||
| 400 | renameClassMap.entrySet().stream().forEach(renameClassMapEntry -> { | ||
| 401 | progress.onProgress(i.getAndIncrement(), renameClassMapEntry.getKey().getDeobfName()); | ||
| 402 | for (Map.Entry<Entry, String> entry : renameClassMapEntry.getValue().entrySet()) { | ||
| 403 | Entry obfEntry = entry.getKey(); | ||
| 404 | |||
| 405 | removeMapping(obfEntry, false); | ||
| 406 | } | ||
| 407 | }); | ||
| 408 | |||
| 409 | translatorCache.clear(); | ||
| 410 | |||
| 411 | renameClassMap.entrySet().stream().forEach(renameClassMapEntry -> { | ||
| 412 | progress.onProgress(i.getAndIncrement(), renameClassMapEntry.getKey().getDeobfName()); | ||
| 413 | |||
| 414 | for (Map.Entry<Entry, String> entry : renameClassMapEntry.getValue().entrySet()) { | ||
| 415 | Entry obfEntry = entry.getKey(); | ||
| 416 | String name = entry.getValue(); | ||
| 417 | |||
| 418 | if (name != null) { | ||
| 419 | try { | ||
| 420 | rename(obfEntry, name); | ||
| 421 | } catch (IllegalNameException exception) { | ||
| 422 | System.out.println("WARNING: " + exception.getMessage()); | ||
| 423 | } | ||
| 424 | } | ||
| 425 | } | ||
| 426 | }); | ||
| 427 | } | ||
| 428 | |||
| 429 | private void rebuildMethodNames(ClassMapping classMapping, Map<ClassMapping, Map<Entry, String>> renameClassMap) { | ||
| 430 | Map<Entry, String> renameEntries = new HashMap<>(); | ||
| 431 | |||
| 432 | for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) { | ||
| 433 | ClassEntry classObfEntry = classMapping.getObfEntry(); | ||
| 434 | MethodEntry obfEntry = methodMapping.getObfEntry(classObfEntry); | ||
| 435 | boolean isProvider = isMethodProvider(obfEntry); | ||
| 436 | |||
| 437 | if (hasDeobfuscatedName(obfEntry) | ||
| 438 | && !(methodMapping.getDeobfName().equals(methodMapping.getObfName()))) { | ||
| 439 | renameEntries.put(obfEntry, isProvider ? methodMapping.getDeobfName() : null); | ||
| 440 | } | ||
| 441 | |||
| 442 | if (isProvider) { | ||
| 443 | for (LocalVariableMapping localVariableMapping : methodMapping.arguments()) { | ||
| 444 | Entry argObfEntry = localVariableMapping.getObfEntry(obfEntry); | ||
| 445 | if (hasDeobfuscatedName(argObfEntry)) { | ||
| 446 | renameEntries.put(argObfEntry, deobfuscateEntry(argObfEntry).getName()); | ||
| 447 | } | ||
| 448 | } | ||
| 449 | } | ||
| 450 | } | ||
| 451 | |||
| 452 | classMapping.markDirty(); | ||
| 453 | renameClassMap.put(classMapping, renameEntries); | ||
| 454 | for (ClassMapping innerClass : classMapping.innerClasses()) { | ||
| 455 | rebuildMethodNames(innerClass, renameClassMap); | ||
| 456 | } | 303 | } |
| 457 | } | 304 | } |
| 458 | 305 | ||
| 459 | |||
| 460 | public void writeJar(File out, ProgressListener progress) { | 306 | public void writeJar(File out, ProgressListener progress) { |
| 461 | transformJar(out, progress, createTypeLoader()::transformInto); | 307 | transformJar(out, progress, createTypeLoader()::transformInto); |
| 462 | } | 308 | } |
| @@ -470,7 +316,7 @@ public class Deobfuscator { | |||
| 470 | AtomicInteger i = new AtomicInteger(); | 316 | AtomicInteger i = new AtomicInteger(); |
| 471 | parsedJar.visitNode(node -> { | 317 | parsedJar.visitNode(node -> { |
| 472 | if (progress != null) { | 318 | if (progress != null) { |
| 473 | progress.onProgress(i.getAndIncrement(), node.name); | 319 | progress.step(i.getAndIncrement(), node.name); |
| 474 | } | 320 | } |
| 475 | 321 | ||
| 476 | try { | 322 | try { |
| @@ -485,50 +331,31 @@ public class Deobfuscator { | |||
| 485 | }); | 331 | }); |
| 486 | 332 | ||
| 487 | if (progress != null) { | 333 | if (progress != null) { |
| 488 | progress.onProgress(i.get(), "Done!"); | 334 | progress.step(i.get(), "Done!"); |
| 489 | } | 335 | } |
| 490 | } catch (IOException ex) { | 336 | } catch (IOException ex) { |
| 491 | throw new Error("Unable to write to Jar file!"); | 337 | throw new Error("Unable to write to Jar file!"); |
| 492 | } | 338 | } |
| 493 | } | 339 | } |
| 494 | 340 | ||
| 495 | public <T extends Entry> T obfuscateEntry(T deobfEntry) { | 341 | public AccessModifier getModifier(Entry<?> entry) { |
| 496 | if (deobfEntry == null) { | 342 | EntryMapping mapping = mapper.getDeobfMapping(entry); |
| 497 | return null; | 343 | if (mapping == null) { |
| 498 | } | 344 | return AccessModifier.UNCHANGED; |
| 499 | T translatedEntry = getTranslator(TranslationDirection.OBFUSCATING).getTranslatedEntry(deobfEntry); | ||
| 500 | if (translatedEntry == null) { | ||
| 501 | return deobfEntry; | ||
| 502 | } | ||
| 503 | return translatedEntry; | ||
| 504 | } | ||
| 505 | |||
| 506 | public <T extends Entry> T deobfuscateEntry(T obfEntry) { | ||
| 507 | if (obfEntry == null) { | ||
| 508 | return null; | ||
| 509 | } | ||
| 510 | T translatedEntry = getTranslator(TranslationDirection.DEOBFUSCATING).getTranslatedEntry(obfEntry); | ||
| 511 | if (translatedEntry == null) { | ||
| 512 | return obfEntry; | ||
| 513 | } | ||
| 514 | return translatedEntry; | ||
| 515 | } | ||
| 516 | |||
| 517 | public <E extends Entry, C extends Entry> EntryReference<E, C> obfuscateReference(EntryReference<E, C> deobfReference) { | ||
| 518 | if (deobfReference == null) { | ||
| 519 | return null; | ||
| 520 | } | 345 | } |
| 521 | return new EntryReference<>(obfuscateEntry(deobfReference.entry), obfuscateEntry(deobfReference.context), deobfReference); | 346 | return mapping.getAccessModifier(); |
| 522 | } | 347 | } |
| 523 | 348 | ||
| 524 | public <E extends Entry, C extends Entry> EntryReference<E, C> deobfuscateReference(EntryReference<E, C> obfReference) { | 349 | public void changeModifier(Entry<?> entry, AccessModifier modifier) { |
| 525 | if (obfReference == null) { | 350 | EntryMapping mapping = mapper.getDeobfMapping(entry); |
| 526 | return null; | 351 | if (mapping != null) { |
| 352 | mapper.mapFromObf(entry, new EntryMapping(mapping.getTargetName(), modifier)); | ||
| 353 | } else { | ||
| 354 | mapper.mapFromObf(entry, new EntryMapping(entry.getName(), modifier)); | ||
| 527 | } | 355 | } |
| 528 | return new EntryReference<>(deobfuscateEntry(obfReference.entry), deobfuscateEntry(obfReference.context), obfReference); | ||
| 529 | } | 356 | } |
| 530 | 357 | ||
| 531 | public boolean isObfuscatedIdentifier(Entry obfEntry) { | 358 | public boolean isObfuscatedIdentifier(Entry<?> obfEntry) { |
| 532 | if (obfEntry instanceof MethodEntry) { | 359 | if (obfEntry instanceof MethodEntry) { |
| 533 | // HACKHACK: Object methods are not obfuscated identifiers | 360 | // HACKHACK: Object methods are not obfuscated identifiers |
| 534 | MethodEntry obfMethodEntry = (MethodEntry) obfEntry; | 361 | MethodEntry obfMethodEntry = (MethodEntry) obfEntry; |
| @@ -559,142 +386,30 @@ public class Deobfuscator { | |||
| 559 | } | 386 | } |
| 560 | } | 387 | } |
| 561 | 388 | ||
| 562 | return this.jarIndex.containsObfEntry(obfEntry); | 389 | return this.jarIndex.getEntryIndex().hasEntry(obfEntry); |
| 563 | } | 390 | } |
| 564 | 391 | ||
| 565 | public boolean isRenameable(EntryReference<Entry, Entry> obfReference) { | 392 | public boolean isRenameable(EntryReference<Entry<?>, Entry<?>> obfReference) { |
| 566 | return obfReference.isNamed() && isObfuscatedIdentifier(obfReference.getNameableEntry()); | 393 | return obfReference.isNamed() && isObfuscatedIdentifier(obfReference.getNameableEntry()); |
| 567 | } | 394 | } |
| 568 | 395 | ||
| 569 | public boolean hasDeobfuscatedName(Entry obfEntry) { | 396 | public boolean hasDeobfuscatedName(Entry<?> obfEntry) { |
| 570 | Translator translator = getTranslator(TranslationDirection.DEOBFUSCATING); | 397 | return mapper.hasDeobfMapping(obfEntry); |
| 571 | if (obfEntry instanceof ClassEntry) { | ||
| 572 | ClassEntry obfClass = (ClassEntry) obfEntry; | ||
| 573 | List<ClassMapping> mappingChain = this.mappings.getClassMappingChain(obfClass); | ||
| 574 | ClassMapping classMapping = mappingChain.get(mappingChain.size() - 1); | ||
| 575 | return classMapping != null && classMapping.getDeobfName() != null; | ||
| 576 | } else if (obfEntry instanceof FieldEntry) { | ||
| 577 | return translator.hasFieldMapping((FieldEntry) obfEntry); | ||
| 578 | } else if (obfEntry instanceof MethodEntry) { | ||
| 579 | MethodEntry methodEntry = (MethodEntry) obfEntry; | ||
| 580 | if (methodEntry.isConstructor()) { | ||
| 581 | return false; | ||
| 582 | } | ||
| 583 | return translator.hasMethodMapping(methodEntry); | ||
| 584 | } else if (obfEntry instanceof LocalVariableEntry) { | ||
| 585 | return translator.hasLocalVariableMapping((LocalVariableEntry) obfEntry); | ||
| 586 | } else { | ||
| 587 | throw new Error("Unknown entry desc: " + obfEntry.getClass().getName()); | ||
| 588 | } | ||
| 589 | } | 398 | } |
| 590 | 399 | ||
| 591 | public void rename(Entry obfEntry, String newName) { | 400 | public void rename(Entry<?> obfEntry, String newName) { |
| 592 | rename(obfEntry, newName, true); | 401 | mapper.mapFromObf(obfEntry, new EntryMapping(newName)); |
| 593 | } | 402 | } |
| 594 | 403 | ||
| 595 | // NOTE: these methods are a bit messy... oh well | 404 | public void removeMapping(Entry<?> obfEntry) { |
| 596 | 405 | mapper.removeByObf(obfEntry); | |
| 597 | public void rename(Entry obfEntry, String newName, boolean clearCache) { | ||
| 598 | if (obfEntry instanceof ClassEntry) { | ||
| 599 | this.renamer.setClassName((ClassEntry) obfEntry, newName); | ||
| 600 | } else if (obfEntry instanceof FieldEntry) { | ||
| 601 | this.renamer.setFieldName((FieldEntry) obfEntry, newName); | ||
| 602 | } else if (obfEntry instanceof MethodEntry) { | ||
| 603 | if (((MethodEntry) obfEntry).isConstructor()) { | ||
| 604 | throw new IllegalArgumentException("Cannot rename constructors"); | ||
| 605 | } | ||
| 606 | |||
| 607 | this.renamer.setMethodTreeName((MethodEntry) obfEntry, newName); | ||
| 608 | } else if (obfEntry instanceof LocalVariableEntry) { | ||
| 609 | // TODO: Discern between arguments (propagate) and local vars (don't) | ||
| 610 | this.renamer.setLocalVariableTreeName((LocalVariableEntry) obfEntry, newName); | ||
| 611 | } else { | ||
| 612 | throw new Error("Unknown entry desc: " + obfEntry.getClass().getName()); | ||
| 613 | } | ||
| 614 | |||
| 615 | // clear caches | ||
| 616 | if (clearCache) | ||
| 617 | this.translatorCache.clear(); | ||
| 618 | } | 406 | } |
| 619 | 407 | ||
| 620 | public void removeMapping(Entry obfEntry) { | 408 | public void markAsDeobfuscated(Entry<?> obfEntry) { |
| 621 | removeMapping(obfEntry, true); | 409 | mapper.mapFromObf(obfEntry, new EntryMapping(mapper.deobfuscate(obfEntry).getName())); |
| 622 | } | 410 | } |
| 623 | 411 | ||
| 624 | public void removeMapping(Entry obfEntry, boolean clearCache) { | 412 | public static void runCustomTransforms(AstBuilder builder, DecompilerContext context) { |
| 625 | if (obfEntry instanceof ClassEntry) { | ||
| 626 | this.renamer.removeClassMapping((ClassEntry) obfEntry); | ||
| 627 | } else if (obfEntry instanceof FieldEntry) { | ||
| 628 | this.renamer.removeFieldMapping((FieldEntry) obfEntry); | ||
| 629 | } else if (obfEntry instanceof MethodEntry) { | ||
| 630 | if (((MethodEntry) obfEntry).isConstructor()) { | ||
| 631 | throw new IllegalArgumentException("Cannot rename constructors"); | ||
| 632 | } | ||
| 633 | this.renamer.removeMethodTreeMapping((MethodEntry) obfEntry); | ||
| 634 | } else if (obfEntry instanceof LocalVariableEntry) { | ||
| 635 | this.renamer.removeLocalVariableMapping((LocalVariableEntry) obfEntry); | ||
| 636 | } else { | ||
| 637 | throw new Error("Unknown entry desc: " + obfEntry); | ||
| 638 | } | ||
| 639 | |||
| 640 | // clear caches | ||
| 641 | if (clearCache) | ||
| 642 | this.translatorCache.clear(); | ||
| 643 | } | ||
| 644 | |||
| 645 | public void markAsDeobfuscated(Entry obfEntry) { | ||
| 646 | markAsDeobfuscated(obfEntry, true); | ||
| 647 | } | ||
| 648 | |||
| 649 | public void markAsDeobfuscated(Entry obfEntry, boolean clearCache) { | ||
| 650 | if (obfEntry instanceof ClassEntry) { | ||
| 651 | this.renamer.markClassAsDeobfuscated((ClassEntry) obfEntry); | ||
| 652 | } else if (obfEntry instanceof FieldEntry) { | ||
| 653 | this.renamer.markFieldAsDeobfuscated((FieldEntry) obfEntry); | ||
| 654 | } else if (obfEntry instanceof MethodEntry) { | ||
| 655 | MethodEntry methodEntry = (MethodEntry) obfEntry; | ||
| 656 | if (methodEntry.isConstructor()) { | ||
| 657 | throw new IllegalArgumentException("Cannot rename constructors"); | ||
| 658 | } | ||
| 659 | this.renamer.markMethodTreeAsDeobfuscated(methodEntry); | ||
| 660 | } else if (obfEntry instanceof LocalVariableEntry) { | ||
| 661 | this.renamer.markArgumentAsDeobfuscated((LocalVariableEntry) obfEntry); | ||
| 662 | } else { | ||
| 663 | throw new Error("Unknown entry desc: " + obfEntry); | ||
| 664 | } | ||
| 665 | |||
| 666 | // clear caches | ||
| 667 | if (clearCache) | ||
| 668 | this.translatorCache.clear(); | ||
| 669 | } | ||
| 670 | |||
| 671 | public void changeModifier(Entry entry, Mappings.EntryModifier modifierEntry) { | ||
| 672 | Entry obfEntry = obfuscateEntry(entry); | ||
| 673 | if (obfEntry instanceof ClassEntry) | ||
| 674 | this.renamer.setClassModifier((ClassEntry) obfEntry, modifierEntry); | ||
| 675 | else if (obfEntry instanceof FieldEntry) | ||
| 676 | this.renamer.setFieldModifier((FieldEntry) obfEntry, modifierEntry); | ||
| 677 | else if (obfEntry instanceof MethodEntry) | ||
| 678 | this.renamer.setMethodModifier((MethodEntry) obfEntry, modifierEntry); | ||
| 679 | else | ||
| 680 | throw new Error("Unknown entry desc: " + obfEntry); | ||
| 681 | } | ||
| 682 | |||
| 683 | public Mappings.EntryModifier getModifier(Entry obfEntry) { | ||
| 684 | Entry entry = obfuscateEntry(obfEntry); | ||
| 685 | if (entry != null) | ||
| 686 | obfEntry = entry; | ||
| 687 | if (obfEntry instanceof ClassEntry) | ||
| 688 | return this.renamer.getClassModifier((ClassEntry) obfEntry); | ||
| 689 | else if (obfEntry instanceof FieldEntry) | ||
| 690 | return this.renamer.getFieldModifier((FieldEntry) obfEntry); | ||
| 691 | else if (obfEntry instanceof MethodEntry) | ||
| 692 | return this.renamer.getMethodModfifier((MethodEntry) obfEntry); | ||
| 693 | else | ||
| 694 | throw new Error("Unknown entry desc: " + obfEntry); | ||
| 695 | } | ||
| 696 | |||
| 697 | public static void runCustomTransforms(AstBuilder builder, DecompilerContext context){ | ||
| 698 | List<IAstTransform> transformers = Arrays.asList( | 413 | List<IAstTransform> transformers = Arrays.asList( |
| 699 | new ObfuscatedEnumSwitchRewriterTransform(context), | 414 | new ObfuscatedEnumSwitchRewriterTransform(context), |
| 700 | new VarargsFixer(context), | 415 | new VarargsFixer(context), |
| @@ -707,12 +422,6 @@ public class Deobfuscator { | |||
| 707 | } | 422 | } |
| 708 | } | 423 | } |
| 709 | 424 | ||
| 710 | public interface ProgressListener { | ||
| 711 | void init(int totalWork, String title); | ||
| 712 | |||
| 713 | void onProgress(int numDone, String message); | ||
| 714 | } | ||
| 715 | |||
| 716 | public interface ClassTransformer { | 425 | public interface ClassTransformer { |
| 717 | String transform(ClassNode node, ClassWriter writer); | 426 | String transform(ClassNode node, ClassWriter writer); |
| 718 | } | 427 | } |