diff options
| author | 2018-07-17 19:14:08 +0200 | |
|---|---|---|
| committer | 2018-07-17 19:14:08 +0200 | |
| commit | a88175ffc95792b88a8724f66db6dda2b8cc32ee (patch) | |
| tree | 65895bbc6cf1766f4ca01e1257619ab1993e71dc /src/main/java/cuchaz/enigma/Deobfuscator.java | |
| parent | Merge pull request #3 from thiakil/src-jar (diff) | |
| download | enigma-fork-a88175ffc95792b88a8724f66db6dda2b8cc32ee.tar.gz enigma-fork-a88175ffc95792b88a8724f66db6dda2b8cc32ee.tar.xz enigma-fork-a88175ffc95792b88a8724f66db6dda2b8cc32ee.zip | |
ASM Based Class Translator (#1)
* Initial port to ASM
* Package updates
* Annotation + inner class translation
* Fix inner class mapping
* More bytecode translation
* Signature refactoring
* Fix highlighting of mapped names
* Fix parameter name offset
* Fix anonymous class generation
* Fix issues with inner class signature transformation
* Fix bridged method detection
* Fix compile issues
* Resolve all failed tests
* Apply deobfuscated name to transformed classes
* Fix class signatures not being translated
* Fix frame array type translation
* Fix frame array type translation
* Fix array translation in method calls
* Fix method reference and bridge detection
* Fix handling of null deobf mappings
* Parameter translation in interfaces
* Fix enum parameter index offset
* Fix parsed local variable indexing
* Fix stackoverflow on rebuilding method names
* Ignore invalid decompiled variable indices
* basic source jar
* Output directly to file on source export
* Make decompile parallel
* fix incorrect super calls
* Use previous save state to delete old mapping files
* Fix old mappings not properly being removed
* Fix old mappings not properly being removed
* make isMethodProvider public
(cherry picked from commit ebad6a9)
* speed up Deobfuscator's getSources by using a single TranslatingTypeloader and caching the ClassLoaderTypeloader
* ignore .idea project folders
* move SynchronizedTypeLoader to a non-inner
* fix signature remap of inners for now
* index & resolve method/field references for usages view
* Allow reader/writer subclasses to provide the underlying file operations
* fix giving obf classes a name not removing them from the panel
* buffer the ParsedJar class entry inputstream, allow use with a jarinputstream
* make CachingClasspathTypeLoader public
* make CachingClasspathTypeLoader public
* support enum switches with obfuscated SwitchMaps
Diffstat (limited to 'src/main/java/cuchaz/enigma/Deobfuscator.java')
| -rw-r--r-- | src/main/java/cuchaz/enigma/Deobfuscator.java | 287 |
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 | ||
| 12 | package cuchaz.enigma; | 12 | package cuchaz.enigma; |
| 13 | 13 | ||
| 14 | import com.google.common.base.Charsets; | 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; | 16 | import com.google.common.collect.Maps; |
| 17 | import com.google.common.collect.Sets; | 17 | import com.google.common.collect.Sets; |
| @@ -25,60 +25,66 @@ import com.strobel.decompiler.languages.java.JavaOutputVisitor; | |||
| 25 | import com.strobel.decompiler.languages.java.ast.AstBuilder; | 25 | import com.strobel.decompiler.languages.java.ast.AstBuilder; |
| 26 | import com.strobel.decompiler.languages.java.ast.CompilationUnit; | 26 | import com.strobel.decompiler.languages.java.ast.CompilationUnit; |
| 27 | import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor; | 27 | import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor; |
| 28 | import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; | ||
| 28 | import cuchaz.enigma.analysis.*; | 29 | import cuchaz.enigma.analysis.*; |
| 29 | import cuchaz.enigma.bytecode.ClassProtectifier; | 30 | import cuchaz.enigma.bytecode.ClassProtectifier; |
| 30 | import cuchaz.enigma.bytecode.ClassPublifier; | 31 | import cuchaz.enigma.bytecode.ClassPublifier; |
| 31 | import cuchaz.enigma.mapping.*; | 32 | import cuchaz.enigma.mapping.*; |
| 33 | import cuchaz.enigma.mapping.entry.*; | ||
| 32 | import cuchaz.enigma.throwables.IllegalNameException; | 34 | import cuchaz.enigma.throwables.IllegalNameException; |
| 33 | import cuchaz.enigma.utils.Utils; | 35 | import cuchaz.enigma.utils.Utils; |
| 34 | import javassist.CtClass; | 36 | import oml.ast.transformers.ObfuscatedEnumSwitchRewriterTransform; |
| 35 | import javassist.bytecode.Descriptor; | 37 | import org.objectweb.asm.ClassWriter; |
| 38 | import org.objectweb.asm.Opcodes; | ||
| 39 | import org.objectweb.asm.tree.ClassNode; | ||
| 36 | 40 | ||
| 37 | import java.io.*; | 41 | import java.io.*; |
| 38 | import java.util.*; | 42 | import java.util.*; |
| 43 | import java.util.concurrent.atomic.AtomicInteger; | ||
| 39 | import java.util.jar.JarEntry; | 44 | import java.util.jar.JarEntry; |
| 40 | import java.util.jar.JarFile; | 45 | import java.util.jar.JarFile; |
| 41 | import java.util.jar.JarOutputStream; | 46 | import java.util.jar.JarOutputStream; |
| 42 | 47 | ||
| 43 | public class Deobfuscator { | 48 | public 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 | } |