summaryrefslogtreecommitdiff
path: root/src/main/java/cuchaz/enigma/Deobfuscator.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/cuchaz/enigma/Deobfuscator.java')
-rw-r--r--src/main/java/cuchaz/enigma/Deobfuscator.java287
1 files changed, 165 insertions, 122 deletions
diff --git a/src/main/java/cuchaz/enigma/Deobfuscator.java b/src/main/java/cuchaz/enigma/Deobfuscator.java
index 1e99af2..6ea1c40 100644
--- a/src/main/java/cuchaz/enigma/Deobfuscator.java
+++ b/src/main/java/cuchaz/enigma/Deobfuscator.java
@@ -11,7 +11,7 @@
11 11
12package cuchaz.enigma; 12package cuchaz.enigma;
13 13
14import com.google.common.base.Charsets; 14import com.google.common.base.Stopwatch;
15import com.google.common.collect.Lists; 15import com.google.common.collect.Lists;
16import com.google.common.collect.Maps; 16import com.google.common.collect.Maps;
17import com.google.common.collect.Sets; 17import com.google.common.collect.Sets;
@@ -25,60 +25,66 @@ import com.strobel.decompiler.languages.java.JavaOutputVisitor;
25import com.strobel.decompiler.languages.java.ast.AstBuilder; 25import com.strobel.decompiler.languages.java.ast.AstBuilder;
26import com.strobel.decompiler.languages.java.ast.CompilationUnit; 26import com.strobel.decompiler.languages.java.ast.CompilationUnit;
27import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor; 27import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor;
28import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform;
28import cuchaz.enigma.analysis.*; 29import cuchaz.enigma.analysis.*;
29import cuchaz.enigma.bytecode.ClassProtectifier; 30import cuchaz.enigma.bytecode.ClassProtectifier;
30import cuchaz.enigma.bytecode.ClassPublifier; 31import cuchaz.enigma.bytecode.ClassPublifier;
31import cuchaz.enigma.mapping.*; 32import cuchaz.enigma.mapping.*;
33import cuchaz.enigma.mapping.entry.*;
32import cuchaz.enigma.throwables.IllegalNameException; 34import cuchaz.enigma.throwables.IllegalNameException;
33import cuchaz.enigma.utils.Utils; 35import cuchaz.enigma.utils.Utils;
34import javassist.CtClass; 36import oml.ast.transformers.ObfuscatedEnumSwitchRewriterTransform;
35import javassist.bytecode.Descriptor; 37import org.objectweb.asm.ClassWriter;
38import org.objectweb.asm.Opcodes;
39import org.objectweb.asm.tree.ClassNode;
36 40
37import java.io.*; 41import java.io.*;
38import java.util.*; 42import java.util.*;
43import java.util.concurrent.atomic.AtomicInteger;
39import java.util.jar.JarEntry; 44import java.util.jar.JarEntry;
40import java.util.jar.JarFile; 45import java.util.jar.JarFile;
41import java.util.jar.JarOutputStream; 46import java.util.jar.JarOutputStream;
42 47
43public class Deobfuscator { 48public class Deobfuscator {
44 49
45 private final JarFile jar; 50 private final ReferencedEntryPool entryPool = new ReferencedEntryPool();
51 private final ParsedJar parsedJar;
46 private final DecompilerSettings settings; 52 private final DecompilerSettings settings;
47 private final JarIndex jarIndex; 53 private final JarIndex jarIndex;
48 private final MappingsRenamer renamer; 54 private final MappingsRenamer renamer;
49 private final Map<TranslationDirection, Translator> translatorCache; 55 private final Map<TranslationDirection, Translator> translatorCache;
50 private Mappings mappings; 56 private Mappings mappings;
51 57
52 public Deobfuscator(JarFile jar) { 58 public Deobfuscator(ParsedJar jar) {
53 this.jar = jar; 59 this.parsedJar = jar;
54 60
55 // build the jar index 61 // build the jar index
56 this.jarIndex = new JarIndex(); 62 this.jarIndex = new JarIndex(entryPool);
57 this.jarIndex.indexJar(this.jar, true); 63 this.jarIndex.indexJar(this.parsedJar, true);
58 64
59 // config the decompiler 65 // config the decompiler
60 this.settings = DecompilerSettings.javaDefaults(); 66 this.settings = DecompilerSettings.javaDefaults();
61 this.settings.setMergeVariables(Utils.getSystemPropertyAsBoolean("enigma.mergeVariables", true)); 67 this.settings.setMergeVariables(Utils.getSystemPropertyAsBoolean("enigma.mergeVariables", true));
62 this.settings.setForceExplicitImports(Utils.getSystemPropertyAsBoolean("enigma.forceExplicitImports", true)); 68 this.settings.setForceExplicitImports(Utils.getSystemPropertyAsBoolean("enigma.forceExplicitImports", true));
63 this.settings.setForceExplicitTypeArguments( 69 this.settings.setForceExplicitTypeArguments(
64 Utils.getSystemPropertyAsBoolean("enigma.forceExplicitTypeArguments", true)); 70 Utils.getSystemPropertyAsBoolean("enigma.forceExplicitTypeArguments", true));
65 // DEBUG 71 // DEBUG
66 this.settings.setShowDebugLineNumbers(Utils.getSystemPropertyAsBoolean("enigma.showDebugLineNumbers", false)); 72 this.settings.setShowDebugLineNumbers(Utils.getSystemPropertyAsBoolean("enigma.showDebugLineNumbers", false));
67 this.settings.setShowSyntheticMembers(Utils.getSystemPropertyAsBoolean("enigma.showSyntheticMembers", false)); 73 this.settings.setShowSyntheticMembers(Utils.getSystemPropertyAsBoolean("enigma.showSyntheticMembers", false));
68 74
69 // init defaults 75 // init defaults
70 this.translatorCache = Maps.newTreeMap(); 76 this.translatorCache = Maps.newTreeMap();
71 this.renamer = new MappingsRenamer(this.jarIndex, null); 77 this.renamer = new MappingsRenamer(this.jarIndex, null, this.entryPool);
72 // init mappings 78 // init mappings
73 setMappings(new Mappings()); 79 setMappings(new Mappings());
74 } 80 }
75 81
76 public JarFile getJar() { 82 public Deobfuscator(JarFile jar) throws IOException {
77 return this.jar; 83 this(new ParsedJar(jar));
78 } 84 }
79 85
80 public String getJarName() { 86 public ParsedJar getJar() {
81 return this.jar.getName(); 87 return this.parsedJar;
82 } 88 }
83 89
84 public JarIndex getJarIndex() { 90 public JarIndex getJarIndex() {
@@ -102,16 +108,16 @@ public class Deobfuscator {
102 MappingsChecker checker = new MappingsChecker(this.jarIndex); 108 MappingsChecker checker = new MappingsChecker(this.jarIndex);
103 checker.dropBrokenMappings(val); 109 checker.dropBrokenMappings(val);
104 if (warnAboutDrops) { 110 if (warnAboutDrops) {
105 for (java.util.Map.Entry<ClassEntry, ClassMapping> mapping : checker.getDroppedClassMappings().entrySet()) { 111 for (Map.Entry<ClassEntry, ClassMapping> mapping : checker.getDroppedClassMappings().entrySet()) {
106 System.out.println("WARNING: Couldn't find class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped."); 112 System.out.println("WARNING: Couldn't find class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped.");
107 } 113 }
108 for (java.util.Map.Entry<ClassEntry, ClassMapping> mapping : checker.getDroppedInnerClassMappings().entrySet()) { 114 for (Map.Entry<ClassEntry, ClassMapping> mapping : checker.getDroppedInnerClassMappings().entrySet()) {
109 System.out.println("WARNING: Couldn't find inner class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped."); 115 System.out.println("WARNING: Couldn't find inner class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped.");
110 } 116 }
111 for (java.util.Map.Entry<FieldEntry, FieldMapping> mapping : checker.getDroppedFieldMappings().entrySet()) { 117 for (Map.Entry<FieldEntry, FieldMapping> mapping : checker.getDroppedFieldMappings().entrySet()) {
112 System.out.println("WARNING: Couldn't find field entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped."); 118 System.out.println("WARNING: Couldn't find field entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped.");
113 } 119 }
114 for (java.util.Map.Entry<BehaviorEntry, MethodMapping> mapping : checker.getDroppedMethodMappings().entrySet()) { 120 for (Map.Entry<MethodEntry, MethodMapping> mapping : checker.getDroppedMethodMappings().entrySet()) {
115 System.out.println("WARNING: Couldn't find behavior entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped."); 121 System.out.println("WARNING: Couldn't find behavior entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped.");
116 } 122 }
117 } 123 }
@@ -123,7 +129,7 @@ public class Deobfuscator {
123 129
124 public Translator getTranslator(TranslationDirection direction) { 130 public Translator getTranslator(TranslationDirection direction) {
125 return this.translatorCache.computeIfAbsent(direction, 131 return this.translatorCache.computeIfAbsent(direction,
126 k -> this.mappings.getTranslator(direction, this.jarIndex.getTranslationIndex())); 132 k -> this.mappings.getTranslator(direction, this.jarIndex.getTranslationIndex()));
127 } 133 }
128 134
129 public void getSeparatedClasses(List<ClassEntry> obfClasses, List<ClassEntry> deobfClasses) { 135 public void getSeparatedClasses(List<ClassEntry> obfClasses, List<ClassEntry> deobfClasses) {
@@ -150,14 +156,19 @@ public class Deobfuscator {
150 156
151 public TranslatingTypeLoader createTypeLoader() { 157 public TranslatingTypeLoader createTypeLoader() {
152 return new TranslatingTypeLoader( 158 return new TranslatingTypeLoader(
153 this.jar, 159 this.parsedJar,
154 this.jarIndex, 160 this.jarIndex,
155 getTranslator(TranslationDirection.Obfuscating), 161 this.entryPool,
156 getTranslator(TranslationDirection.Deobfuscating) 162 getTranslator(TranslationDirection.OBFUSCATING),
163 getTranslator(TranslationDirection.DEOBFUSCATING)
157 ); 164 );
158 } 165 }
159 166
160 public CompilationUnit getSourceTree(String className) { 167 public CompilationUnit getSourceTree(String className) {
168 return getSourceTree(className, createTypeLoader());
169 }
170
171 public CompilationUnit getSourceTree(String className, ITranslatingTypeLoader loader) {
161 172
162 // we don't know if this class name is obfuscated or deobfuscated 173 // we don't know if this class name is obfuscated or deobfuscated
163 // we need to tell the decompiler the deobfuscated name so it doesn't get freaked out 174 // we need to tell the decompiler the deobfuscated name so it doesn't get freaked out
@@ -172,15 +183,14 @@ public class Deobfuscator {
172 deobfClassName = classMapping.getDeobfName(); 183 deobfClassName = classMapping.getDeobfName();
173 } 184 }
174 185
175 // set the type loader 186 // set the desc loader
176 TranslatingTypeLoader loader = createTypeLoader();
177 this.settings.setTypeLoader(loader); 187 this.settings.setTypeLoader(loader);
178 188
179 // see if procyon can find the type 189 // see if procyon can find the desc
180 TypeReference type = new MetadataSystem(loader).lookupType(deobfClassName); 190 TypeReference type = new MetadataSystem(loader).lookupType(deobfClassName);
181 if (type == null) { 191 if (type == null) {
182 throw new Error(String.format("Unable to find type: %s (deobf: %s)\nTried class names: %s", 192 throw new Error(String.format("Unable to find desc: %s (deobf: %s)\nTried class names: %s",
183 className, deobfClassName, loader.getClassNamesToTry(deobfClassName) 193 className, deobfClassName, loader.getClassNamesToTry(deobfClassName)
184 )); 194 ));
185 } 195 }
186 TypeDefinition resolvedType = type.resolve(); 196 TypeDefinition resolvedType = type.resolve();
@@ -192,6 +202,7 @@ public class Deobfuscator {
192 AstBuilder builder = new AstBuilder(context); 202 AstBuilder builder = new AstBuilder(context);
193 builder.addType(resolvedType); 203 builder.addType(resolvedType);
194 builder.runTransformations(null); 204 builder.runTransformations(null);
205 runCustomTransforms(builder, context);
195 return builder.getCompilationUnit(); 206 return builder.getCompilationUnit();
196 } 207 }
197 208
@@ -208,7 +219,7 @@ public class Deobfuscator {
208 } else { 219 } else {
209 index = new SourceIndex(source); 220 index = new SourceIndex(source);
210 } 221 }
211 sourceTree.acceptVisitor(new SourceIndexVisitor(), index); 222 sourceTree.acceptVisitor(new SourceIndexVisitor(entryPool), index);
212 223
213 // DEBUG 224 // DEBUG
214 // sourceTree.acceptVisitor( new TreeDumpVisitor( new File( "tree.txt" ) ), null ); 225 // sourceTree.acceptVisitor( new TreeDumpVisitor( new File( "tree.txt" ) ), null );
@@ -221,10 +232,10 @@ public class Deobfuscator {
221 Entry obfEntry = obfuscateEntry(deobfReference.entry); 232 Entry obfEntry = obfuscateEntry(deobfReference.entry);
222 233
223 // try to resolve the class 234 // try to resolve the class
224 ClassEntry resolvedObfClassEntry = this.jarIndex.getTranslationIndex().resolveEntryClass(obfEntry); 235 ClassEntry resolvedObfClassEntry = this.jarIndex.getTranslationIndex().resolveEntryOwner(obfEntry);
225 if (resolvedObfClassEntry != null && !resolvedObfClassEntry.equals(obfEntry.getClassEntry())) { 236 if (resolvedObfClassEntry != null && !resolvedObfClassEntry.equals(obfEntry.getOwnerClassEntry())) {
226 // change the class of the entry 237 // change the class of the entry
227 obfEntry = obfEntry.cloneToNewClass(resolvedObfClassEntry); 238 obfEntry = obfEntry.updateOwnership(resolvedObfClassEntry);
228 239
229 // save the new deobfuscated reference 240 // save the new deobfuscated reference
230 deobfReference.entry = deobfuscateEntry(obfEntry); 241 deobfReference.entry = deobfuscateEntry(obfEntry);
@@ -262,23 +273,29 @@ public class Deobfuscator {
262 progress.init(classEntries.size(), "Decompiling classes..."); 273 progress.init(classEntries.size(), "Decompiling classes...");
263 } 274 }
264 275
276 //create a common instance outside the loop as mappings shouldn't be changing while this is happening
277 //synchronized to make sure the parallelStream doesn't CME with the cache
278 ITranslatingTypeLoader typeLoader = new SynchronizedTypeLoader(createTypeLoader());
279
265 // DEOBFUSCATE ALL THE THINGS!! @_@ 280 // DEOBFUSCATE ALL THE THINGS!! @_@
266 int i = 0; 281 Stopwatch stopwatch = Stopwatch.createStarted();
267 for (ClassEntry obfClassEntry : classEntries) { 282 AtomicInteger count = new AtomicInteger();
283 classEntries.parallelStream().forEach(obfClassEntry -> {
268 ClassEntry deobfClassEntry = deobfuscateEntry(new ClassEntry(obfClassEntry)); 284 ClassEntry deobfClassEntry = deobfuscateEntry(new ClassEntry(obfClassEntry));
269 if (progress != null) { 285 if (progress != null) {
270 progress.onProgress(i++, deobfClassEntry.toString()); 286 progress.onProgress(count.getAndIncrement(), deobfClassEntry.toString());
271 } 287 }
272 288
273 try { 289 try {
274 // get the source 290 // get the source
275 String source = getSource(getSourceTree(obfClassEntry.getName())); 291 CompilationUnit sourceTree = getSourceTree(obfClassEntry.getName(), typeLoader);
276 292
277 // write the file 293 // write the file
278 File file = new File(dirOut, deobfClassEntry.getName().replace('.', '/') + ".java"); 294 File file = new File(dirOut, deobfClassEntry.getName().replace('.', '/') + ".java");
279 file.getParentFile().mkdirs(); 295 file.getParentFile().mkdirs();
280 try (OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(file), Charsets.UTF_8)) { 296 try (Writer writer = new BufferedWriter(new FileWriter(file))) {
281 out.write(source); 297 sourceTree.acceptVisitor(new InsertParenthesesVisitor(), null);
298 sourceTree.acceptVisitor(new JavaOutputVisitor(new PlainTextOutput(writer), settings), null);
282 } 299 }
283 } catch (Throwable t) { 300 } catch (Throwable t) {
284 // don't crash the whole world here, just log the error and keep going 301 // don't crash the whole world here, just log the error and keep going
@@ -286,9 +303,11 @@ public class Deobfuscator {
286 System.err.println("Unable to deobfuscate class " + deobfClassEntry + " (" + obfClassEntry + ")"); 303 System.err.println("Unable to deobfuscate class " + deobfClassEntry + " (" + obfClassEntry + ")");
287 t.printStackTrace(System.err); 304 t.printStackTrace(System.err);
288 } 305 }
289 } 306 });
307 stopwatch.stop();
308 System.out.println("writeSources Done in : " + stopwatch.toString());
290 if (progress != null) { 309 if (progress != null) {
291 progress.onProgress(i, "Done!"); 310 progress.onProgress(count.get(), "Done:");
292 } 311 }
293 } 312 }
294 313
@@ -305,18 +324,14 @@ public class Deobfuscator {
305 } 324 }
306 } 325 }
307 326
308 private boolean isBehaviorProvider(ClassEntry classObfEntry, BehaviorEntry behaviorEntry) { 327 public boolean isMethodProvider(ClassEntry classObfEntry, MethodEntry methodEntry) {
309 if (behaviorEntry instanceof MethodEntry) { 328 Set<ClassEntry> classEntries = new HashSet<>();
310 MethodEntry methodEntry = (MethodEntry) behaviorEntry; 329 addAllPotentialAncestors(classEntries, classObfEntry);
311
312 Set<ClassEntry> classEntries = new HashSet<>();
313 addAllPotentialAncestors(classEntries, classObfEntry);
314 330
315 for (ClassEntry parentEntry : classEntries) { 331 for (ClassEntry parentEntry : classEntries) {
316 MethodEntry ancestorMethodEntry = new MethodEntry(parentEntry, methodEntry.getName(), methodEntry.getSignature()); 332 MethodEntry ancestorMethodEntry = entryPool.getMethod(parentEntry, methodEntry.getName(), methodEntry.getDesc());
317 if (jarIndex.containsObfBehavior(ancestorMethodEntry)) { 333 if (jarIndex.containsObfMethod(ancestorMethodEntry)) {
318 return false; 334 return false;
319 }
320 } 335 }
321 } 336 }
322 337
@@ -332,9 +347,6 @@ public class Deobfuscator {
332 for (ClassMapping classMapping : Lists.newArrayList(getMappings().classes())) { 347 for (ClassMapping classMapping : Lists.newArrayList(getMappings().classes())) {
333 progress.onProgress(i++, classMapping.getDeobfName()); 348 progress.onProgress(i++, classMapping.getDeobfName());
334 rebuildMethodNames(classMapping, renameClassMap); 349 rebuildMethodNames(classMapping, renameClassMap);
335 for(ClassMapping innerClass : classMapping.innerClasses()){
336 rebuildMethodNames(innerClass, renameClassMap);
337 }
338 } 350 }
339 351
340 for (Map.Entry<ClassMapping, Map<Entry, String>> renameClassMapEntry : renameClassMap.entrySet()) { 352 for (Map.Entry<ClassMapping, Map<Entry, String>> renameClassMapEntry : renameClassMap.entrySet()) {
@@ -356,29 +368,29 @@ public class Deobfuscator {
356 368
357 try { 369 try {
358 rename(obfEntry, name); 370 rename(obfEntry, name);
359 } catch (IllegalNameException exception) 371 } catch (IllegalNameException exception) {
360 {
361 System.out.println("WARNING: " + exception.getMessage()); 372 System.out.println("WARNING: " + exception.getMessage());
362 } 373 }
363 } 374 }
364 } 375 }
365 } 376 }
366 377
367 private void rebuildMethodNames(ClassMapping classMapping, Map<ClassMapping, Map<Entry, String>> renameClassMap){ 378 private void rebuildMethodNames(ClassMapping classMapping, Map<ClassMapping, Map<Entry, String>> renameClassMap) {
368 Map<Entry, String> renameEntries = new HashMap<>(); 379 Map<Entry, String> renameEntries = new HashMap<>();
369 380
370 for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) { 381 for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) {
371 ClassEntry classObfEntry = classMapping.getObfEntry(); 382 ClassEntry classObfEntry = classMapping.getObfEntry();
372 BehaviorEntry obfEntry = methodMapping.getObfEntry(classObfEntry); 383 MethodEntry obfEntry = methodMapping.getObfEntry(classObfEntry);
373 384
374 if (isBehaviorProvider(classObfEntry, obfEntry)) { 385 if (isMethodProvider(classObfEntry, obfEntry)) {
375 if (hasDeobfuscatedName(obfEntry) && !(obfEntry instanceof ConstructorEntry) 386 if (hasDeobfuscatedName(obfEntry)
376 && !(methodMapping.getDeobfName().equals(methodMapping.getObfName()))) { 387 && !(methodMapping.getDeobfName().equals(methodMapping.getObfName()))) {
377 renameEntries.put(obfEntry, methodMapping.getDeobfName()); 388 renameEntries.put(obfEntry, methodMapping.getDeobfName());
378 } 389 }
379 390
380 for (ArgumentMapping argumentMapping : Lists.newArrayList(methodMapping.arguments())) { 391 ArrayList<LocalVariableMapping> arguments = Lists.newArrayList(methodMapping.arguments());
381 Entry argObfEntry = argumentMapping.getObfEntry(obfEntry); 392 for (LocalVariableMapping localVariableMapping : arguments) {
393 Entry argObfEntry = localVariableMapping.getObfEntry(obfEntry);
382 if (hasDeobfuscatedName(argObfEntry)) { 394 if (hasDeobfuscatedName(argObfEntry)) {
383 renameEntries.put(argObfEntry, deobfuscateEntry(argObfEntry).getName()); 395 renameEntries.put(argObfEntry, deobfuscateEntry(argObfEntry).getName());
384 } 396 }
@@ -386,49 +398,58 @@ public class Deobfuscator {
386 } 398 }
387 } 399 }
388 400
401 classMapping.markDirty();
389 renameClassMap.put(classMapping, renameEntries); 402 renameClassMap.put(classMapping, renameEntries);
403 for (ClassMapping innerClass : classMapping.innerClasses()) {
404 rebuildMethodNames(innerClass, renameClassMap);
405 }
390 } 406 }
391 407
392 408
393
394 public void writeJar(File out, ProgressListener progress) { 409 public void writeJar(File out, ProgressListener progress) {
395 transformJar(out, progress, createTypeLoader()::transformClass); 410 transformJar(out, progress, createTypeLoader()::transformInto);
396 } 411 }
397 412
398 public void protectifyJar(File out, ProgressListener progress) { 413 public void protectifyJar(File out, ProgressListener progress) {
399 transformJar(out, progress, ClassProtectifier::protectify); 414 transformJar(out, progress, (node, writer) -> {
415 node.accept(new ClassProtectifier(Opcodes.ASM5, writer));
416 return node.name;
417 });
400 } 418 }
401 419
402 public void publifyJar(File out, ProgressListener progress) { 420 public void publifyJar(File out, ProgressListener progress) {
403 transformJar(out, progress, ClassPublifier::publify); 421 transformJar(out, progress, (node, writer) -> {
422 node.accept(new ClassPublifier(Opcodes.ASM5, writer));
423 return node.name;
424 });
404 } 425 }
405 426
406 public void transformJar(File out, ProgressListener progress, ClassTransformer transformer) { 427 public void transformJar(File out, ProgressListener progress, ClassTransformer transformer) {
407 try (JarOutputStream outJar = new JarOutputStream(new FileOutputStream(out))) { 428 try (JarOutputStream outJar = new JarOutputStream(new FileOutputStream(out))) {
408 if (progress != null) { 429 if (progress != null) {
409 progress.init(JarClassIterator.getClassEntries(this.jar).size(), "Transforming classes..."); 430 progress.init(parsedJar.getClassCount(), "Transforming classes...");
410 } 431 }
411 432
412 int i = 0; 433 AtomicInteger i = new AtomicInteger();
413 for (CtClass c : JarClassIterator.classes(this.jar)) { 434 parsedJar.visit(node -> {
414 if (progress != null) { 435 if (progress != null) {
415 progress.onProgress(i++, c.getName()); 436 progress.onProgress(i.getAndIncrement(), node.name);
416 } 437 }
417 438
418 try { 439 try {
419 c = transformer.transform(c); 440 ClassWriter writer = new ClassWriter(0);
420 outJar.putNextEntry(new JarEntry(c.getName().replace('.', '/') + ".class")); 441 String transformedName = transformer.transform(node, writer);
421 outJar.write(c.toBytecode()); 442 outJar.putNextEntry(new JarEntry(transformedName.replace('.', '/') + ".class"));
443 outJar.write(writer.toByteArray());
422 outJar.closeEntry(); 444 outJar.closeEntry();
423 } catch (Throwable t) { 445 } catch (Throwable t) {
424 throw new Error("Unable to transform class " + c.getName(), t); 446 throw new Error("Unable to transform class " + node.name, t);
425 } 447 }
426 } 448 });
449
427 if (progress != null) { 450 if (progress != null) {
428 progress.onProgress(i, "Done!"); 451 progress.onProgress(i.get(), "Done!");
429 } 452 }
430
431 outJar.close();
432 } catch (IOException ex) { 453 } catch (IOException ex) {
433 throw new Error("Unable to write to Jar file!"); 454 throw new Error("Unable to write to Jar file!");
434 } 455 }
@@ -438,14 +459,22 @@ public class Deobfuscator {
438 if (deobfEntry == null) { 459 if (deobfEntry == null) {
439 return null; 460 return null;
440 } 461 }
441 return getTranslator(TranslationDirection.Obfuscating).translateEntry(deobfEntry); 462 T translatedEntry = getTranslator(TranslationDirection.OBFUSCATING).getTranslatedEntry(deobfEntry);
463 if (translatedEntry == null) {
464 return deobfEntry;
465 }
466 return translatedEntry;
442 } 467 }
443 468
444 public <T extends Entry> T deobfuscateEntry(T obfEntry) { 469 public <T extends Entry> T deobfuscateEntry(T obfEntry) {
445 if (obfEntry == null) { 470 if (obfEntry == null) {
446 return null; 471 return null;
447 } 472 }
448 return getTranslator(TranslationDirection.Deobfuscating).translateEntry(obfEntry); 473 T translatedEntry = getTranslator(TranslationDirection.DEOBFUSCATING).getTranslatedEntry(obfEntry);
474 if (translatedEntry == null) {
475 return obfEntry;
476 }
477 return translatedEntry;
449 } 478 }
450 479
451 public <E extends Entry, C extends Entry> EntryReference<E, C> obfuscateReference(EntryReference<E, C> deobfReference) { 480 public <E extends Entry, C extends Entry> EntryReference<E, C> obfuscateReference(EntryReference<E, C> deobfReference) {
@@ -473,7 +502,7 @@ public class Deobfuscator {
473 // HACKHACK: Object methods are not obfuscated identifiers 502 // HACKHACK: Object methods are not obfuscated identifiers
474 MethodEntry obfMethodEntry = (MethodEntry) obfEntry; 503 MethodEntry obfMethodEntry = (MethodEntry) obfEntry;
475 String name = obfMethodEntry.getName(); 504 String name = obfMethodEntry.getName();
476 String sig = obfMethodEntry.getSignature().toString(); 505 String sig = obfMethodEntry.getDesc().toString();
477 if (name.equals("clone") && sig.equals("()Ljava/lang/Object;")) { 506 if (name.equals("clone") && sig.equals("()Ljava/lang/Object;")) {
478 return false; 507 return false;
479 } else if (name.equals("equals") && sig.equals("(Ljava/lang/Object;)Z")) { 508 } else if (name.equals("equals") && sig.equals("(Ljava/lang/Object;)Z")) {
@@ -499,7 +528,7 @@ public class Deobfuscator {
499 } 528 }
500 529
501 // FIXME: HACK EVEN MORE HACK! 530 // FIXME: HACK EVEN MORE HACK!
502 if (hack && this.jarIndex.containsObfEntry(obfEntry.getClassEntry())) 531 if (hack && this.jarIndex.containsObfEntry(obfEntry.getOwnerClassEntry()))
503 return true; 532 return true;
504 } 533 }
505 534
@@ -515,27 +544,24 @@ public class Deobfuscator {
515 } 544 }
516 545
517 public boolean hasDeobfuscatedName(Entry obfEntry) { 546 public boolean hasDeobfuscatedName(Entry obfEntry) {
518 Translator translator = getTranslator(TranslationDirection.Deobfuscating); 547 Translator translator = getTranslator(TranslationDirection.DEOBFUSCATING);
519 if (obfEntry instanceof ClassEntry) { 548 if (obfEntry instanceof ClassEntry) {
520 ClassEntry obfClass = (ClassEntry) obfEntry; 549 ClassEntry obfClass = (ClassEntry) obfEntry;
521 List<ClassMapping> mappingChain = this.mappings.getClassMappingChain(obfClass); 550 List<ClassMapping> mappingChain = this.mappings.getClassMappingChain(obfClass);
522 ClassMapping classMapping = mappingChain.get(mappingChain.size() - 1); 551 ClassMapping classMapping = mappingChain.get(mappingChain.size() - 1);
523 return classMapping != null && classMapping.getDeobfName() != null; 552 return classMapping != null && classMapping.getDeobfName() != null;
524 } else if (obfEntry instanceof FieldEntry) { 553 } else if (obfEntry instanceof FieldEntry) {
525 return translator.translate((FieldEntry) obfEntry) != null; 554 return translator.hasFieldMapping((FieldEntry) obfEntry);
526 } else if (obfEntry instanceof MethodEntry) { 555 } else if (obfEntry instanceof MethodEntry) {
527 return translator.translate((MethodEntry) obfEntry) != null; 556 MethodEntry methodEntry = (MethodEntry) obfEntry;
528 } else if (obfEntry instanceof ConstructorEntry) { 557 if (methodEntry.isConstructor()) {
529 // constructors have no names 558 return false;
530 return false; 559 }
531 } else if (obfEntry instanceof ArgumentEntry) { 560 return translator.hasMethodMapping(methodEntry);
532 return translator.translate((ArgumentEntry) obfEntry) != null;
533 } else if (obfEntry instanceof LocalVariableEntry) { 561 } else if (obfEntry instanceof LocalVariableEntry) {
534 // TODO: Implement it 562 return translator.hasLocalVariableMapping((LocalVariableEntry) obfEntry);
535 //return translator.translate((LocalVariableEntry)obfEntry) != null;
536 return false;
537 } else { 563 } else {
538 throw new Error("Unknown entry type: " + obfEntry.getClass().getName()); 564 throw new Error("Unknown entry desc: " + obfEntry.getClass().getName());
539 } 565 }
540 } 566 }
541 567
@@ -547,19 +573,18 @@ public class Deobfuscator {
547 573
548 public void rename(Entry obfEntry, String newName, boolean clearCache) { 574 public void rename(Entry obfEntry, String newName, boolean clearCache) {
549 if (obfEntry instanceof ClassEntry) { 575 if (obfEntry instanceof ClassEntry) {
550 this.renamer.setClassName((ClassEntry) obfEntry, Descriptor.toJvmName(newName)); 576 this.renamer.setClassName((ClassEntry) obfEntry, newName);
551 } else if (obfEntry instanceof FieldEntry) { 577 } else if (obfEntry instanceof FieldEntry) {
552 this.renamer.setFieldName((FieldEntry) obfEntry, newName); 578 this.renamer.setFieldName((FieldEntry) obfEntry, newName);
553 } else if (obfEntry instanceof MethodEntry) { 579 } else if (obfEntry instanceof MethodEntry) {
580 if (((MethodEntry) obfEntry).isConstructor()) {
581 throw new IllegalArgumentException("Cannot rename constructors");
582 }
554 this.renamer.setMethodTreeName((MethodEntry) obfEntry, newName); 583 this.renamer.setMethodTreeName((MethodEntry) obfEntry, newName);
555 } else if (obfEntry instanceof ConstructorEntry) {
556 throw new IllegalArgumentException("Cannot rename constructors");
557 } else if (obfEntry instanceof ArgumentEntry) {
558 this.renamer.setArgumentTreeName((ArgumentEntry) obfEntry, newName);
559 } else if (obfEntry instanceof LocalVariableEntry) { 584 } else if (obfEntry instanceof LocalVariableEntry) {
560 // TODO: Implement it 585 this.renamer.setLocalVariableTreeName((LocalVariableEntry) obfEntry, newName);
561 } else { 586 } else {
562 throw new Error("Unknown entry type: " + obfEntry.getClass().getName()); 587 throw new Error("Unknown entry desc: " + obfEntry.getClass().getName());
563 } 588 }
564 589
565 // clear caches 590 // clear caches
@@ -573,13 +598,14 @@ public class Deobfuscator {
573 } else if (obfEntry instanceof FieldEntry) { 598 } else if (obfEntry instanceof FieldEntry) {
574 this.renamer.removeFieldMapping((FieldEntry) obfEntry); 599 this.renamer.removeFieldMapping((FieldEntry) obfEntry);
575 } else if (obfEntry instanceof MethodEntry) { 600 } else if (obfEntry instanceof MethodEntry) {
601 if (((MethodEntry) obfEntry).isConstructor()) {
602 throw new IllegalArgumentException("Cannot rename constructors");
603 }
576 this.renamer.removeMethodTreeMapping((MethodEntry) obfEntry); 604 this.renamer.removeMethodTreeMapping((MethodEntry) obfEntry);
577 } else if (obfEntry instanceof ConstructorEntry) { 605 } else if (obfEntry instanceof LocalVariableEntry) {
578 throw new IllegalArgumentException("Cannot rename constructors"); 606 this.renamer.removeLocalVariableMapping((LocalVariableEntry) obfEntry);
579 } else if (obfEntry instanceof ArgumentEntry) {
580 this.renamer.removeArgumentMapping((ArgumentEntry) obfEntry);
581 } else { 607 } else {
582 throw new Error("Unknown entry type: " + obfEntry); 608 throw new Error("Unknown entry desc: " + obfEntry);
583 } 609 }
584 610
585 // clear caches 611 // clear caches
@@ -592,15 +618,15 @@ public class Deobfuscator {
592 } else if (obfEntry instanceof FieldEntry) { 618 } else if (obfEntry instanceof FieldEntry) {
593 this.renamer.markFieldAsDeobfuscated((FieldEntry) obfEntry); 619 this.renamer.markFieldAsDeobfuscated((FieldEntry) obfEntry);
594 } else if (obfEntry instanceof MethodEntry) { 620 } else if (obfEntry instanceof MethodEntry) {
595 this.renamer.markMethodTreeAsDeobfuscated((MethodEntry) obfEntry); 621 MethodEntry methodEntry = (MethodEntry) obfEntry;
596 } else if (obfEntry instanceof ConstructorEntry) { 622 if (methodEntry.isConstructor()) {
597 throw new IllegalArgumentException("Cannot rename constructors"); 623 throw new IllegalArgumentException("Cannot rename constructors");
598 } else if (obfEntry instanceof ArgumentEntry) { 624 }
599 this.renamer.markArgumentAsDeobfuscated((ArgumentEntry) obfEntry); 625 this.renamer.markMethodTreeAsDeobfuscated(methodEntry);
600 } else if (obfEntry instanceof LocalVariableEntry) { 626 } else if (obfEntry instanceof LocalVariableEntry) {
601 // TODO: Implement it 627 this.renamer.markArgumentAsDeobfuscated((LocalVariableEntry) obfEntry);
602 } else { 628 } else {
603 throw new Error("Unknown entry type: " + obfEntry); 629 throw new Error("Unknown entry desc: " + obfEntry);
604 } 630 }
605 631
606 // clear caches 632 // clear caches
@@ -613,17 +639,33 @@ public class Deobfuscator {
613 this.renamer.setClassModifier((ClassEntry) obfEntry, modifierEntry); 639 this.renamer.setClassModifier((ClassEntry) obfEntry, modifierEntry);
614 else if (obfEntry instanceof FieldEntry) 640 else if (obfEntry instanceof FieldEntry)
615 this.renamer.setFieldModifier((FieldEntry) obfEntry, modifierEntry); 641 this.renamer.setFieldModifier((FieldEntry) obfEntry, modifierEntry);
616 else if (obfEntry instanceof BehaviorEntry) 642 else if (obfEntry instanceof MethodEntry)
617 this.renamer.setMethodModifier((BehaviorEntry) obfEntry, modifierEntry); 643 this.renamer.setMethodModifier((MethodEntry) obfEntry, modifierEntry);
618 else 644 else
619 throw new Error("Unknown entry type: " + obfEntry); 645 throw new Error("Unknown entry desc: " + obfEntry);
620 } 646 }
621 647
622 public Mappings.EntryModifier getModifier(Entry obEntry) { 648 public Mappings.EntryModifier getModifier(Entry obfEntry) {
623 Entry entry = obfuscateEntry(obEntry); 649 Entry entry = obfuscateEntry(obfEntry);
624 if (entry != null) 650 if (entry != null)
625 obEntry = entry; 651 obfEntry = entry;
626 return getTranslator(TranslationDirection.Deobfuscating).getModifier(obEntry); 652 if (obfEntry instanceof ClassEntry)
653 return this.renamer.getClassModifier((ClassEntry) obfEntry);
654 else if (obfEntry instanceof FieldEntry)
655 return this.renamer.getFieldModifier((FieldEntry) obfEntry);
656 else if (obfEntry instanceof MethodEntry)
657 return this.renamer.getMethodModfifier((MethodEntry) obfEntry);
658 else
659 throw new Error("Unknown entry desc: " + obfEntry);
660 }
661
662 public static void runCustomTransforms(AstBuilder builder, DecompilerContext context){
663 List<IAstTransform> transformers = Arrays.asList(
664 new ObfuscatedEnumSwitchRewriterTransform(context)
665 );
666 for (IAstTransform transform : transformers){
667 transform.run(builder.getCompilationUnit());
668 }
627 } 669 }
628 670
629 public interface ProgressListener { 671 public interface ProgressListener {
@@ -633,6 +675,7 @@ public class Deobfuscator {
633 } 675 }
634 676
635 public interface ClassTransformer { 677 public interface ClassTransformer {
636 CtClass transform(CtClass c) throws Exception; 678 String transform(ClassNode node, ClassWriter writer);
637 } 679 }
680
638} 681}