From a88175ffc95792b88a8724f66db6dda2b8cc32ee Mon Sep 17 00:00:00 2001 From: gegy1000 Date: Tue, 17 Jul 2018 19:14:08 +0200 Subject: 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 --- .../cuchaz/enigma/CachingClasspathTypeLoader.java | 20 + src/main/java/cuchaz/enigma/CachingTypeLoader.java | 38 ++ src/main/java/cuchaz/enigma/CommandMain.java | 2 +- src/main/java/cuchaz/enigma/Deobfuscator.java | 287 ++++++---- .../java/cuchaz/enigma/ITranslatingTypeLoader.java | 19 + .../java/cuchaz/enigma/SynchronizedTypeLoader.java | 39 ++ .../java/cuchaz/enigma/TranslatingTypeLoader.java | 187 ++---- src/main/java/cuchaz/enigma/analysis/Access.java | 11 +- .../enigma/analysis/BehaviorReferenceTreeNode.java | 93 --- .../java/cuchaz/enigma/analysis/BridgeMarker.java | 38 -- .../analysis/ClassImplementationsTreeNode.java | 12 +- .../enigma/analysis/ClassInheritanceTreeNode.java | 16 +- .../cuchaz/enigma/analysis/EntryReference.java | 18 +- .../java/cuchaz/enigma/analysis/EntryRenamer.java | 63 +- .../enigma/analysis/FieldReferenceTreeNode.java | 29 +- .../cuchaz/enigma/analysis/IndexClassVisitor.java | 38 ++ .../enigma/analysis/IndexInnerClassVisitor.java | 23 + .../enigma/analysis/IndexReferenceVisitor.java | 77 +++ .../cuchaz/enigma/analysis/JarClassIterator.java | 123 ---- src/main/java/cuchaz/enigma/analysis/JarIndex.java | 635 ++++++--------------- .../analysis/MethodImplementationsTreeNode.java | 13 +- .../enigma/analysis/MethodInheritanceTreeNode.java | 16 +- .../enigma/analysis/MethodReferenceTreeNode.java | 94 +++ .../java/cuchaz/enigma/analysis/ParsedJar.java | 95 +++ .../cuchaz/enigma/analysis/ReferenceTreeNode.java | 2 +- .../java/cuchaz/enigma/analysis/SourceIndex.java | 2 +- .../analysis/SourceIndexBehaviorVisitor.java | 204 ------- .../enigma/analysis/SourceIndexClassVisitor.java | 44 +- .../enigma/analysis/SourceIndexMethodVisitor.java | 224 ++++++++ .../cuchaz/enigma/analysis/SourceIndexVisitor.java | 14 +- .../cuchaz/enigma/analysis/TranslationIndex.java | 122 ++-- .../java/cuchaz/enigma/bytecode/AccessFlags.java | 80 +++ .../cuchaz/enigma/bytecode/ClassProtectifier.java | 54 +- .../cuchaz/enigma/bytecode/ClassPublifier.java | 58 +- .../java/cuchaz/enigma/bytecode/ClassRenamer.java | 539 ----------------- .../cuchaz/enigma/bytecode/ConstPoolEditor.java | 264 --------- src/main/java/cuchaz/enigma/bytecode/InfoType.java | 266 --------- .../enigma/bytecode/MethodParametersAttribute.java | 85 --- .../bytecode/accessors/ClassInfoAccessor.java | 56 -- .../bytecode/accessors/ConstInfoAccessor.java | 124 ---- .../accessors/InvokeDynamicInfoAccessor.java | 75 --- .../bytecode/accessors/MemberRefInfoAccessor.java | 75 --- .../accessors/MethodHandleInfoAccessor.java | 75 --- .../bytecode/accessors/MethodTypeInfoAccessor.java | 57 -- .../accessors/NameAndTypeInfoAccessor.java | 75 --- .../bytecode/accessors/StringInfoAccessor.java | 56 -- .../bytecode/accessors/Utf8InfoAccessor.java | 29 - .../bytecode/translators/ClassTranslator.java | 161 ------ .../bytecode/translators/InnerClassWriter.java | 144 ----- .../translators/LocalVariableTranslator.java | 142 ----- .../translators/MethodParameterTranslator.java | 62 -- .../translators/TranslationAnnotationVisitor.java | 43 ++ .../translators/TranslationClassVisitor.java | 113 ++++ .../translators/TranslationFieldVisitor.java | 33 ++ .../translators/TranslationMethodVisitor.java | 191 +++++++ .../translators/TranslationSignatureVisitor.java | 129 +++++ src/main/java/cuchaz/enigma/gui/ClassSelector.java | 8 +- src/main/java/cuchaz/enigma/gui/CodeReader.java | 6 +- src/main/java/cuchaz/enigma/gui/Gui.java | 67 +-- src/main/java/cuchaz/enigma/gui/GuiController.java | 24 +- src/main/java/cuchaz/enigma/gui/GuiTricks.java | 5 +- .../java/cuchaz/enigma/gui/ScoredClassEntry.java | 2 +- .../enigma/gui/node/ClassSelectorClassNode.java | 2 +- .../enigma/gui/node/ClassSelectorPackageNode.java | 4 +- .../java/cuchaz/enigma/gui/panels/PanelObf.java | 2 +- .../java/cuchaz/enigma/mapping/ArgumentEntry.java | 110 ---- .../cuchaz/enigma/mapping/ArgumentMapping.java | 50 -- .../java/cuchaz/enigma/mapping/BehaviorEntry.java | 16 - .../java/cuchaz/enigma/mapping/ClassEntry.java | 167 ------ .../java/cuchaz/enigma/mapping/ClassMapping.java | 210 ++++--- .../cuchaz/enigma/mapping/ClassNameReplacer.java | 16 - .../cuchaz/enigma/mapping/ConstructorEntry.java | 105 ---- .../enigma/mapping/DirectionalTranslator.java | 370 ++++++++++++ src/main/java/cuchaz/enigma/mapping/Entry.java | 22 - .../java/cuchaz/enigma/mapping/EntryFactory.java | 132 ----- .../java/cuchaz/enigma/mapping/FieldEntry.java | 87 --- .../java/cuchaz/enigma/mapping/FieldMapping.java | 38 +- .../cuchaz/enigma/mapping/LocalVariableEntry.java | 102 ---- .../enigma/mapping/LocalVariableMapping.java | 53 ++ src/main/java/cuchaz/enigma/mapping/Mappings.java | 58 +- .../cuchaz/enigma/mapping/MappingsChecker.java | 14 +- .../enigma/mapping/MappingsEnigmaReader.java | 167 +++--- .../enigma/mapping/MappingsEnigmaWriter.java | 124 ++-- .../cuchaz/enigma/mapping/MappingsRenamer.java | 164 +++--- .../cuchaz/enigma/mapping/MappingsSRGWriter.java | 7 +- .../cuchaz/enigma/mapping/MappingsTinyReader.java | 7 +- .../java/cuchaz/enigma/mapping/MemberMapping.java | 3 + .../cuchaz/enigma/mapping/MethodDescriptor.java | 114 ++++ .../java/cuchaz/enigma/mapping/MethodEntry.java | 90 --- .../java/cuchaz/enigma/mapping/MethodMapping.java | 139 +++-- .../java/cuchaz/enigma/mapping/NameValidator.java | 16 +- .../cuchaz/enigma/mapping/ProcyonEntryFactory.java | 67 --- src/main/java/cuchaz/enigma/mapping/Signature.java | 128 ++--- .../enigma/mapping/TranslationDirection.java | 10 +- .../java/cuchaz/enigma/mapping/Translator.java | 365 +++--------- src/main/java/cuchaz/enigma/mapping/Type.java | 235 -------- .../java/cuchaz/enigma/mapping/TypeDescriptor.java | 244 ++++++++ .../cuchaz/enigma/mapping/entry/ClassDefEntry.java | 37 ++ .../cuchaz/enigma/mapping/entry/ClassEntry.java | 175 ++++++ .../java/cuchaz/enigma/mapping/entry/Entry.java | 22 + .../cuchaz/enigma/mapping/entry/EntryFactory.java | 49 ++ .../cuchaz/enigma/mapping/entry/FieldDefEntry.java | 43 ++ .../cuchaz/enigma/mapping/entry/FieldEntry.java | 77 +++ .../mapping/entry/LocalVariableDefEntry.java | 57 ++ .../enigma/mapping/entry/LocalVariableEntry.java | 82 +++ .../enigma/mapping/entry/MethodDefEntry.java | 54 ++ .../cuchaz/enigma/mapping/entry/MethodEntry.java | 80 +++ .../enigma/mapping/entry/ProcyonEntryFactory.java | 48 ++ .../enigma/mapping/entry/ReferencedEntryPool.java | 53 ++ .../enigma/throwables/MappingParseException.java | 7 + .../ObfuscatedEnumSwitchRewriterTransform.java | 414 ++++++++++++++ src/test/java/cuchaz/enigma/TestDeobfed.java | 8 +- src/test/java/cuchaz/enigma/TestDeobfuscator.java | 2 +- src/test/java/cuchaz/enigma/TestEntryFactory.java | 31 +- src/test/java/cuchaz/enigma/TestInnerClasses.java | 49 +- .../enigma/TestJarIndexConstructorReferences.java | 80 ++- .../cuchaz/enigma/TestJarIndexInheritanceTree.java | 111 ++-- .../java/cuchaz/enigma/TestJarIndexLoneClass.java | 38 +- .../java/cuchaz/enigma/TestMethodDescriptor.java | 247 ++++++++ src/test/java/cuchaz/enigma/TestSignature.java | 270 --------- src/test/java/cuchaz/enigma/TestSourceIndex.java | 2 +- .../java/cuchaz/enigma/TestTokensConstructors.java | 90 ++- src/test/java/cuchaz/enigma/TestTranslator.java | 2 +- src/test/java/cuchaz/enigma/TestType.java | 243 -------- .../java/cuchaz/enigma/TestTypeDescriptor.java | 243 ++++++++ src/test/java/cuchaz/enigma/TokenChecker.java | 2 +- 126 files changed, 5216 insertions(+), 6534 deletions(-) create mode 100644 src/main/java/cuchaz/enigma/CachingClasspathTypeLoader.java create mode 100644 src/main/java/cuchaz/enigma/CachingTypeLoader.java create mode 100644 src/main/java/cuchaz/enigma/ITranslatingTypeLoader.java create mode 100644 src/main/java/cuchaz/enigma/SynchronizedTypeLoader.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/BridgeMarker.java create mode 100644 src/main/java/cuchaz/enigma/analysis/IndexClassVisitor.java create mode 100644 src/main/java/cuchaz/enigma/analysis/IndexInnerClassVisitor.java create mode 100644 src/main/java/cuchaz/enigma/analysis/IndexReferenceVisitor.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/JarClassIterator.java create mode 100644 src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java create mode 100644 src/main/java/cuchaz/enigma/analysis/ParsedJar.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java create mode 100644 src/main/java/cuchaz/enigma/analysis/SourceIndexMethodVisitor.java create mode 100644 src/main/java/cuchaz/enigma/bytecode/AccessFlags.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/ClassRenamer.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/ConstPoolEditor.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/InfoType.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/MethodParametersAttribute.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/translators/ClassTranslator.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/translators/InnerClassWriter.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableTranslator.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/translators/MethodParameterTranslator.java create mode 100644 src/main/java/cuchaz/enigma/bytecode/translators/TranslationAnnotationVisitor.java create mode 100644 src/main/java/cuchaz/enigma/bytecode/translators/TranslationClassVisitor.java create mode 100644 src/main/java/cuchaz/enigma/bytecode/translators/TranslationFieldVisitor.java create mode 100644 src/main/java/cuchaz/enigma/bytecode/translators/TranslationMethodVisitor.java create mode 100644 src/main/java/cuchaz/enigma/bytecode/translators/TranslationSignatureVisitor.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/ArgumentEntry.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/ArgumentMapping.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/BehaviorEntry.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/ClassEntry.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/ClassNameReplacer.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/ConstructorEntry.java create mode 100644 src/main/java/cuchaz/enigma/mapping/DirectionalTranslator.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/Entry.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/EntryFactory.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/FieldEntry.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/LocalVariableEntry.java create mode 100644 src/main/java/cuchaz/enigma/mapping/LocalVariableMapping.java create mode 100644 src/main/java/cuchaz/enigma/mapping/MethodDescriptor.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/MethodEntry.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/ProcyonEntryFactory.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/Type.java create mode 100644 src/main/java/cuchaz/enigma/mapping/TypeDescriptor.java create mode 100644 src/main/java/cuchaz/enigma/mapping/entry/ClassDefEntry.java create mode 100644 src/main/java/cuchaz/enigma/mapping/entry/ClassEntry.java create mode 100644 src/main/java/cuchaz/enigma/mapping/entry/Entry.java create mode 100644 src/main/java/cuchaz/enigma/mapping/entry/EntryFactory.java create mode 100644 src/main/java/cuchaz/enigma/mapping/entry/FieldDefEntry.java create mode 100644 src/main/java/cuchaz/enigma/mapping/entry/FieldEntry.java create mode 100644 src/main/java/cuchaz/enigma/mapping/entry/LocalVariableDefEntry.java create mode 100644 src/main/java/cuchaz/enigma/mapping/entry/LocalVariableEntry.java create mode 100644 src/main/java/cuchaz/enigma/mapping/entry/MethodDefEntry.java create mode 100644 src/main/java/cuchaz/enigma/mapping/entry/MethodEntry.java create mode 100644 src/main/java/cuchaz/enigma/mapping/entry/ProcyonEntryFactory.java create mode 100644 src/main/java/cuchaz/enigma/mapping/entry/ReferencedEntryPool.java create mode 100644 src/main/java/oml/ast/transformers/ObfuscatedEnumSwitchRewriterTransform.java create mode 100644 src/test/java/cuchaz/enigma/TestMethodDescriptor.java delete mode 100644 src/test/java/cuchaz/enigma/TestSignature.java delete mode 100644 src/test/java/cuchaz/enigma/TestType.java create mode 100644 src/test/java/cuchaz/enigma/TestTypeDescriptor.java (limited to 'src') diff --git a/src/main/java/cuchaz/enigma/CachingClasspathTypeLoader.java b/src/main/java/cuchaz/enigma/CachingClasspathTypeLoader.java new file mode 100644 index 00000000..58682e23 --- /dev/null +++ b/src/main/java/cuchaz/enigma/CachingClasspathTypeLoader.java @@ -0,0 +1,20 @@ +package cuchaz.enigma; + +import com.strobel.assembler.metadata.Buffer; +import com.strobel.assembler.metadata.ClasspathTypeLoader; +import com.strobel.assembler.metadata.ITypeLoader; + +/** + * Caching version of {@link ClasspathTypeLoader} + */ +public class CachingClasspathTypeLoader extends CachingTypeLoader { + private final ITypeLoader classpathLoader = new ClasspathTypeLoader(); + + protected byte[] doLoad(String className) { + Buffer parentBuf = new Buffer(); + if (classpathLoader.tryLoadType(className, parentBuf)) { + return parentBuf.array(); + } + return EMPTY_ARRAY;//need to return *something* as null means no store + } +} diff --git a/src/main/java/cuchaz/enigma/CachingTypeLoader.java b/src/main/java/cuchaz/enigma/CachingTypeLoader.java new file mode 100644 index 00000000..22c31c63 --- /dev/null +++ b/src/main/java/cuchaz/enigma/CachingTypeLoader.java @@ -0,0 +1,38 @@ +package cuchaz.enigma; + +import com.google.common.collect.Maps; +import com.strobel.assembler.metadata.Buffer; +import com.strobel.assembler.metadata.ITypeLoader; + +import java.util.Map; + +/** + * Common cache functions + */ +public abstract class CachingTypeLoader implements ITypeLoader { + protected static final byte[] EMPTY_ARRAY = {}; + + private final Map cache = Maps.newHashMap(); + + protected abstract byte[] doLoad(String className); + + @Override + public boolean tryLoadType(String className, Buffer out) { + + // check the cache + byte[] data = this.cache.computeIfAbsent(className, this::doLoad); + + if (data == EMPTY_ARRAY) { + return false; + } + + out.reset(data.length); + System.arraycopy(data, 0, out.array(), out.position(), data.length); + out.position(0); + return true; + } + + public void clearCache() { + this.cache.clear(); + } +} diff --git a/src/main/java/cuchaz/enigma/CommandMain.java b/src/main/java/cuchaz/enigma/CommandMain.java index f546eb18..59eb1b66 100644 --- a/src/main/java/cuchaz/enigma/CommandMain.java +++ b/src/main/java/cuchaz/enigma/CommandMain.java @@ -99,7 +99,7 @@ public class CommandMain { private static void convertMappings(String[] args) throws Exception { File fileMappings = getReadableFile(getArg(args, 1, "enigma mapping", true)); File result = getWritableFile(getArg(args, 2, "enigma mapping", true)); - String name = getArg(args, 3, "format type", true); + String name = getArg(args, 3, "format desc", true); Mappings.FormatType formatType; try { formatType = Mappings.FormatType.valueOf(name.toUpperCase()); diff --git a/src/main/java/cuchaz/enigma/Deobfuscator.java b/src/main/java/cuchaz/enigma/Deobfuscator.java index 1e99af2f..6ea1c40b 100644 --- a/src/main/java/cuchaz/enigma/Deobfuscator.java +++ b/src/main/java/cuchaz/enigma/Deobfuscator.java @@ -11,7 +11,7 @@ package cuchaz.enigma; -import com.google.common.base.Charsets; +import com.google.common.base.Stopwatch; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; @@ -25,60 +25,66 @@ import com.strobel.decompiler.languages.java.JavaOutputVisitor; import com.strobel.decompiler.languages.java.ast.AstBuilder; import com.strobel.decompiler.languages.java.ast.CompilationUnit; import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor; +import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; import cuchaz.enigma.analysis.*; import cuchaz.enigma.bytecode.ClassProtectifier; import cuchaz.enigma.bytecode.ClassPublifier; import cuchaz.enigma.mapping.*; +import cuchaz.enigma.mapping.entry.*; import cuchaz.enigma.throwables.IllegalNameException; import cuchaz.enigma.utils.Utils; -import javassist.CtClass; -import javassist.bytecode.Descriptor; +import oml.ast.transformers.ObfuscatedEnumSwitchRewriterTransform; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.ClassNode; import java.io.*; import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; public class Deobfuscator { - private final JarFile jar; + private final ReferencedEntryPool entryPool = new ReferencedEntryPool(); + private final ParsedJar parsedJar; private final DecompilerSettings settings; private final JarIndex jarIndex; private final MappingsRenamer renamer; private final Map translatorCache; private Mappings mappings; - public Deobfuscator(JarFile jar) { - this.jar = jar; + public Deobfuscator(ParsedJar jar) { + this.parsedJar = jar; // build the jar index - this.jarIndex = new JarIndex(); - this.jarIndex.indexJar(this.jar, true); + this.jarIndex = new JarIndex(entryPool); + this.jarIndex.indexJar(this.parsedJar, true); // config the decompiler this.settings = DecompilerSettings.javaDefaults(); this.settings.setMergeVariables(Utils.getSystemPropertyAsBoolean("enigma.mergeVariables", true)); this.settings.setForceExplicitImports(Utils.getSystemPropertyAsBoolean("enigma.forceExplicitImports", true)); this.settings.setForceExplicitTypeArguments( - Utils.getSystemPropertyAsBoolean("enigma.forceExplicitTypeArguments", true)); + Utils.getSystemPropertyAsBoolean("enigma.forceExplicitTypeArguments", true)); // DEBUG this.settings.setShowDebugLineNumbers(Utils.getSystemPropertyAsBoolean("enigma.showDebugLineNumbers", false)); this.settings.setShowSyntheticMembers(Utils.getSystemPropertyAsBoolean("enigma.showSyntheticMembers", false)); // init defaults this.translatorCache = Maps.newTreeMap(); - this.renamer = new MappingsRenamer(this.jarIndex, null); + this.renamer = new MappingsRenamer(this.jarIndex, null, this.entryPool); // init mappings setMappings(new Mappings()); } - public JarFile getJar() { - return this.jar; + public Deobfuscator(JarFile jar) throws IOException { + this(new ParsedJar(jar)); } - public String getJarName() { - return this.jar.getName(); + public ParsedJar getJar() { + return this.parsedJar; } public JarIndex getJarIndex() { @@ -102,16 +108,16 @@ public class Deobfuscator { MappingsChecker checker = new MappingsChecker(this.jarIndex); checker.dropBrokenMappings(val); if (warnAboutDrops) { - for (java.util.Map.Entry mapping : checker.getDroppedClassMappings().entrySet()) { + for (Map.Entry mapping : checker.getDroppedClassMappings().entrySet()) { System.out.println("WARNING: Couldn't find class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped."); } - for (java.util.Map.Entry mapping : checker.getDroppedInnerClassMappings().entrySet()) { + for (Map.Entry mapping : checker.getDroppedInnerClassMappings().entrySet()) { System.out.println("WARNING: Couldn't find inner class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped."); } - for (java.util.Map.Entry mapping : checker.getDroppedFieldMappings().entrySet()) { + for (Map.Entry mapping : checker.getDroppedFieldMappings().entrySet()) { System.out.println("WARNING: Couldn't find field entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped."); } - for (java.util.Map.Entry mapping : checker.getDroppedMethodMappings().entrySet()) { + for (Map.Entry mapping : checker.getDroppedMethodMappings().entrySet()) { System.out.println("WARNING: Couldn't find behavior entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped."); } } @@ -123,7 +129,7 @@ public class Deobfuscator { public Translator getTranslator(TranslationDirection direction) { return this.translatorCache.computeIfAbsent(direction, - k -> this.mappings.getTranslator(direction, this.jarIndex.getTranslationIndex())); + k -> this.mappings.getTranslator(direction, this.jarIndex.getTranslationIndex())); } public void getSeparatedClasses(List obfClasses, List deobfClasses) { @@ -150,14 +156,19 @@ public class Deobfuscator { public TranslatingTypeLoader createTypeLoader() { return new TranslatingTypeLoader( - this.jar, - this.jarIndex, - getTranslator(TranslationDirection.Obfuscating), - getTranslator(TranslationDirection.Deobfuscating) + this.parsedJar, + this.jarIndex, + this.entryPool, + getTranslator(TranslationDirection.OBFUSCATING), + getTranslator(TranslationDirection.DEOBFUSCATING) ); } public CompilationUnit getSourceTree(String className) { + return getSourceTree(className, createTypeLoader()); + } + + public CompilationUnit getSourceTree(String className, ITranslatingTypeLoader loader) { // we don't know if this class name is obfuscated or deobfuscated // we need to tell the decompiler the deobfuscated name so it doesn't get freaked out @@ -172,15 +183,14 @@ public class Deobfuscator { deobfClassName = classMapping.getDeobfName(); } - // set the type loader - TranslatingTypeLoader loader = createTypeLoader(); + // set the desc loader this.settings.setTypeLoader(loader); - // see if procyon can find the type + // see if procyon can find the desc TypeReference type = new MetadataSystem(loader).lookupType(deobfClassName); if (type == null) { - throw new Error(String.format("Unable to find type: %s (deobf: %s)\nTried class names: %s", - className, deobfClassName, loader.getClassNamesToTry(deobfClassName) + throw new Error(String.format("Unable to find desc: %s (deobf: %s)\nTried class names: %s", + className, deobfClassName, loader.getClassNamesToTry(deobfClassName) )); } TypeDefinition resolvedType = type.resolve(); @@ -192,6 +202,7 @@ public class Deobfuscator { AstBuilder builder = new AstBuilder(context); builder.addType(resolvedType); builder.runTransformations(null); + runCustomTransforms(builder, context); return builder.getCompilationUnit(); } @@ -208,7 +219,7 @@ public class Deobfuscator { } else { index = new SourceIndex(source); } - sourceTree.acceptVisitor(new SourceIndexVisitor(), index); + sourceTree.acceptVisitor(new SourceIndexVisitor(entryPool), index); // DEBUG // sourceTree.acceptVisitor( new TreeDumpVisitor( new File( "tree.txt" ) ), null ); @@ -221,10 +232,10 @@ public class Deobfuscator { Entry obfEntry = obfuscateEntry(deobfReference.entry); // try to resolve the class - ClassEntry resolvedObfClassEntry = this.jarIndex.getTranslationIndex().resolveEntryClass(obfEntry); - if (resolvedObfClassEntry != null && !resolvedObfClassEntry.equals(obfEntry.getClassEntry())) { + ClassEntry resolvedObfClassEntry = this.jarIndex.getTranslationIndex().resolveEntryOwner(obfEntry); + if (resolvedObfClassEntry != null && !resolvedObfClassEntry.equals(obfEntry.getOwnerClassEntry())) { // change the class of the entry - obfEntry = obfEntry.cloneToNewClass(resolvedObfClassEntry); + obfEntry = obfEntry.updateOwnership(resolvedObfClassEntry); // save the new deobfuscated reference deobfReference.entry = deobfuscateEntry(obfEntry); @@ -262,23 +273,29 @@ public class Deobfuscator { progress.init(classEntries.size(), "Decompiling classes..."); } + //create a common instance outside the loop as mappings shouldn't be changing while this is happening + //synchronized to make sure the parallelStream doesn't CME with the cache + ITranslatingTypeLoader typeLoader = new SynchronizedTypeLoader(createTypeLoader()); + // DEOBFUSCATE ALL THE THINGS!! @_@ - int i = 0; - for (ClassEntry obfClassEntry : classEntries) { + Stopwatch stopwatch = Stopwatch.createStarted(); + AtomicInteger count = new AtomicInteger(); + classEntries.parallelStream().forEach(obfClassEntry -> { ClassEntry deobfClassEntry = deobfuscateEntry(new ClassEntry(obfClassEntry)); if (progress != null) { - progress.onProgress(i++, deobfClassEntry.toString()); + progress.onProgress(count.getAndIncrement(), deobfClassEntry.toString()); } try { // get the source - String source = getSource(getSourceTree(obfClassEntry.getName())); + CompilationUnit sourceTree = getSourceTree(obfClassEntry.getName(), typeLoader); // write the file File file = new File(dirOut, deobfClassEntry.getName().replace('.', '/') + ".java"); file.getParentFile().mkdirs(); - try (OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(file), Charsets.UTF_8)) { - out.write(source); + try (Writer writer = new BufferedWriter(new FileWriter(file))) { + sourceTree.acceptVisitor(new InsertParenthesesVisitor(), null); + sourceTree.acceptVisitor(new JavaOutputVisitor(new PlainTextOutput(writer), settings), null); } } catch (Throwable t) { // don't crash the whole world here, just log the error and keep going @@ -286,9 +303,11 @@ public class Deobfuscator { System.err.println("Unable to deobfuscate class " + deobfClassEntry + " (" + obfClassEntry + ")"); t.printStackTrace(System.err); } - } + }); + stopwatch.stop(); + System.out.println("writeSources Done in : " + stopwatch.toString()); if (progress != null) { - progress.onProgress(i, "Done!"); + progress.onProgress(count.get(), "Done:"); } } @@ -305,18 +324,14 @@ public class Deobfuscator { } } - private boolean isBehaviorProvider(ClassEntry classObfEntry, BehaviorEntry behaviorEntry) { - if (behaviorEntry instanceof MethodEntry) { - MethodEntry methodEntry = (MethodEntry) behaviorEntry; - - Set classEntries = new HashSet<>(); - addAllPotentialAncestors(classEntries, classObfEntry); + public boolean isMethodProvider(ClassEntry classObfEntry, MethodEntry methodEntry) { + Set classEntries = new HashSet<>(); + addAllPotentialAncestors(classEntries, classObfEntry); - for (ClassEntry parentEntry : classEntries) { - MethodEntry ancestorMethodEntry = new MethodEntry(parentEntry, methodEntry.getName(), methodEntry.getSignature()); - if (jarIndex.containsObfBehavior(ancestorMethodEntry)) { - return false; - } + for (ClassEntry parentEntry : classEntries) { + MethodEntry ancestorMethodEntry = entryPool.getMethod(parentEntry, methodEntry.getName(), methodEntry.getDesc()); + if (jarIndex.containsObfMethod(ancestorMethodEntry)) { + return false; } } @@ -332,9 +347,6 @@ public class Deobfuscator { for (ClassMapping classMapping : Lists.newArrayList(getMappings().classes())) { progress.onProgress(i++, classMapping.getDeobfName()); rebuildMethodNames(classMapping, renameClassMap); - for(ClassMapping innerClass : classMapping.innerClasses()){ - rebuildMethodNames(innerClass, renameClassMap); - } } for (Map.Entry> renameClassMapEntry : renameClassMap.entrySet()) { @@ -356,29 +368,29 @@ public class Deobfuscator { try { rename(obfEntry, name); - } catch (IllegalNameException exception) - { + } catch (IllegalNameException exception) { System.out.println("WARNING: " + exception.getMessage()); } } } } - private void rebuildMethodNames(ClassMapping classMapping, Map> renameClassMap){ + private void rebuildMethodNames(ClassMapping classMapping, Map> renameClassMap) { Map renameEntries = new HashMap<>(); for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) { ClassEntry classObfEntry = classMapping.getObfEntry(); - BehaviorEntry obfEntry = methodMapping.getObfEntry(classObfEntry); + MethodEntry obfEntry = methodMapping.getObfEntry(classObfEntry); - if (isBehaviorProvider(classObfEntry, obfEntry)) { - if (hasDeobfuscatedName(obfEntry) && !(obfEntry instanceof ConstructorEntry) - && !(methodMapping.getDeobfName().equals(methodMapping.getObfName()))) { + if (isMethodProvider(classObfEntry, obfEntry)) { + if (hasDeobfuscatedName(obfEntry) + && !(methodMapping.getDeobfName().equals(methodMapping.getObfName()))) { renameEntries.put(obfEntry, methodMapping.getDeobfName()); } - for (ArgumentMapping argumentMapping : Lists.newArrayList(methodMapping.arguments())) { - Entry argObfEntry = argumentMapping.getObfEntry(obfEntry); + ArrayList arguments = Lists.newArrayList(methodMapping.arguments()); + for (LocalVariableMapping localVariableMapping : arguments) { + Entry argObfEntry = localVariableMapping.getObfEntry(obfEntry); if (hasDeobfuscatedName(argObfEntry)) { renameEntries.put(argObfEntry, deobfuscateEntry(argObfEntry).getName()); } @@ -386,49 +398,58 @@ public class Deobfuscator { } } + classMapping.markDirty(); renameClassMap.put(classMapping, renameEntries); + for (ClassMapping innerClass : classMapping.innerClasses()) { + rebuildMethodNames(innerClass, renameClassMap); + } } - public void writeJar(File out, ProgressListener progress) { - transformJar(out, progress, createTypeLoader()::transformClass); + transformJar(out, progress, createTypeLoader()::transformInto); } public void protectifyJar(File out, ProgressListener progress) { - transformJar(out, progress, ClassProtectifier::protectify); + transformJar(out, progress, (node, writer) -> { + node.accept(new ClassProtectifier(Opcodes.ASM5, writer)); + return node.name; + }); } public void publifyJar(File out, ProgressListener progress) { - transformJar(out, progress, ClassPublifier::publify); + transformJar(out, progress, (node, writer) -> { + node.accept(new ClassPublifier(Opcodes.ASM5, writer)); + return node.name; + }); } public void transformJar(File out, ProgressListener progress, ClassTransformer transformer) { try (JarOutputStream outJar = new JarOutputStream(new FileOutputStream(out))) { if (progress != null) { - progress.init(JarClassIterator.getClassEntries(this.jar).size(), "Transforming classes..."); + progress.init(parsedJar.getClassCount(), "Transforming classes..."); } - int i = 0; - for (CtClass c : JarClassIterator.classes(this.jar)) { + AtomicInteger i = new AtomicInteger(); + parsedJar.visit(node -> { if (progress != null) { - progress.onProgress(i++, c.getName()); + progress.onProgress(i.getAndIncrement(), node.name); } try { - c = transformer.transform(c); - outJar.putNextEntry(new JarEntry(c.getName().replace('.', '/') + ".class")); - outJar.write(c.toBytecode()); + ClassWriter writer = new ClassWriter(0); + String transformedName = transformer.transform(node, writer); + outJar.putNextEntry(new JarEntry(transformedName.replace('.', '/') + ".class")); + outJar.write(writer.toByteArray()); outJar.closeEntry(); } catch (Throwable t) { - throw new Error("Unable to transform class " + c.getName(), t); + throw new Error("Unable to transform class " + node.name, t); } - } + }); + if (progress != null) { - progress.onProgress(i, "Done!"); + progress.onProgress(i.get(), "Done!"); } - - outJar.close(); } catch (IOException ex) { throw new Error("Unable to write to Jar file!"); } @@ -438,14 +459,22 @@ public class Deobfuscator { if (deobfEntry == null) { return null; } - return getTranslator(TranslationDirection.Obfuscating).translateEntry(deobfEntry); + T translatedEntry = getTranslator(TranslationDirection.OBFUSCATING).getTranslatedEntry(deobfEntry); + if (translatedEntry == null) { + return deobfEntry; + } + return translatedEntry; } public T deobfuscateEntry(T obfEntry) { if (obfEntry == null) { return null; } - return getTranslator(TranslationDirection.Deobfuscating).translateEntry(obfEntry); + T translatedEntry = getTranslator(TranslationDirection.DEOBFUSCATING).getTranslatedEntry(obfEntry); + if (translatedEntry == null) { + return obfEntry; + } + return translatedEntry; } public EntryReference obfuscateReference(EntryReference deobfReference) { @@ -473,7 +502,7 @@ public class Deobfuscator { // HACKHACK: Object methods are not obfuscated identifiers MethodEntry obfMethodEntry = (MethodEntry) obfEntry; String name = obfMethodEntry.getName(); - String sig = obfMethodEntry.getSignature().toString(); + String sig = obfMethodEntry.getDesc().toString(); if (name.equals("clone") && sig.equals("()Ljava/lang/Object;")) { return false; } else if (name.equals("equals") && sig.equals("(Ljava/lang/Object;)Z")) { @@ -499,7 +528,7 @@ public class Deobfuscator { } // FIXME: HACK EVEN MORE HACK! - if (hack && this.jarIndex.containsObfEntry(obfEntry.getClassEntry())) + if (hack && this.jarIndex.containsObfEntry(obfEntry.getOwnerClassEntry())) return true; } @@ -515,27 +544,24 @@ public class Deobfuscator { } public boolean hasDeobfuscatedName(Entry obfEntry) { - Translator translator = getTranslator(TranslationDirection.Deobfuscating); + Translator translator = getTranslator(TranslationDirection.DEOBFUSCATING); if (obfEntry instanceof ClassEntry) { ClassEntry obfClass = (ClassEntry) obfEntry; List mappingChain = this.mappings.getClassMappingChain(obfClass); ClassMapping classMapping = mappingChain.get(mappingChain.size() - 1); return classMapping != null && classMapping.getDeobfName() != null; } else if (obfEntry instanceof FieldEntry) { - return translator.translate((FieldEntry) obfEntry) != null; + return translator.hasFieldMapping((FieldEntry) obfEntry); } else if (obfEntry instanceof MethodEntry) { - return translator.translate((MethodEntry) obfEntry) != null; - } else if (obfEntry instanceof ConstructorEntry) { - // constructors have no names - return false; - } else if (obfEntry instanceof ArgumentEntry) { - return translator.translate((ArgumentEntry) obfEntry) != null; + MethodEntry methodEntry = (MethodEntry) obfEntry; + if (methodEntry.isConstructor()) { + return false; + } + return translator.hasMethodMapping(methodEntry); } else if (obfEntry instanceof LocalVariableEntry) { - // TODO: Implement it - //return translator.translate((LocalVariableEntry)obfEntry) != null; - return false; + return translator.hasLocalVariableMapping((LocalVariableEntry) obfEntry); } else { - throw new Error("Unknown entry type: " + obfEntry.getClass().getName()); + throw new Error("Unknown entry desc: " + obfEntry.getClass().getName()); } } @@ -547,19 +573,18 @@ public class Deobfuscator { public void rename(Entry obfEntry, String newName, boolean clearCache) { if (obfEntry instanceof ClassEntry) { - this.renamer.setClassName((ClassEntry) obfEntry, Descriptor.toJvmName(newName)); + this.renamer.setClassName((ClassEntry) obfEntry, newName); } else if (obfEntry instanceof FieldEntry) { this.renamer.setFieldName((FieldEntry) obfEntry, newName); } else if (obfEntry instanceof MethodEntry) { + if (((MethodEntry) obfEntry).isConstructor()) { + throw new IllegalArgumentException("Cannot rename constructors"); + } this.renamer.setMethodTreeName((MethodEntry) obfEntry, newName); - } else if (obfEntry instanceof ConstructorEntry) { - throw new IllegalArgumentException("Cannot rename constructors"); - } else if (obfEntry instanceof ArgumentEntry) { - this.renamer.setArgumentTreeName((ArgumentEntry) obfEntry, newName); } else if (obfEntry instanceof LocalVariableEntry) { - // TODO: Implement it + this.renamer.setLocalVariableTreeName((LocalVariableEntry) obfEntry, newName); } else { - throw new Error("Unknown entry type: " + obfEntry.getClass().getName()); + throw new Error("Unknown entry desc: " + obfEntry.getClass().getName()); } // clear caches @@ -573,13 +598,14 @@ public class Deobfuscator { } else if (obfEntry instanceof FieldEntry) { this.renamer.removeFieldMapping((FieldEntry) obfEntry); } else if (obfEntry instanceof MethodEntry) { + if (((MethodEntry) obfEntry).isConstructor()) { + throw new IllegalArgumentException("Cannot rename constructors"); + } this.renamer.removeMethodTreeMapping((MethodEntry) obfEntry); - } else if (obfEntry instanceof ConstructorEntry) { - throw new IllegalArgumentException("Cannot rename constructors"); - } else if (obfEntry instanceof ArgumentEntry) { - this.renamer.removeArgumentMapping((ArgumentEntry) obfEntry); + } else if (obfEntry instanceof LocalVariableEntry) { + this.renamer.removeLocalVariableMapping((LocalVariableEntry) obfEntry); } else { - throw new Error("Unknown entry type: " + obfEntry); + throw new Error("Unknown entry desc: " + obfEntry); } // clear caches @@ -592,15 +618,15 @@ public class Deobfuscator { } else if (obfEntry instanceof FieldEntry) { this.renamer.markFieldAsDeobfuscated((FieldEntry) obfEntry); } else if (obfEntry instanceof MethodEntry) { - this.renamer.markMethodTreeAsDeobfuscated((MethodEntry) obfEntry); - } else if (obfEntry instanceof ConstructorEntry) { - throw new IllegalArgumentException("Cannot rename constructors"); - } else if (obfEntry instanceof ArgumentEntry) { - this.renamer.markArgumentAsDeobfuscated((ArgumentEntry) obfEntry); + MethodEntry methodEntry = (MethodEntry) obfEntry; + if (methodEntry.isConstructor()) { + throw new IllegalArgumentException("Cannot rename constructors"); + } + this.renamer.markMethodTreeAsDeobfuscated(methodEntry); } else if (obfEntry instanceof LocalVariableEntry) { - // TODO: Implement it + this.renamer.markArgumentAsDeobfuscated((LocalVariableEntry) obfEntry); } else { - throw new Error("Unknown entry type: " + obfEntry); + throw new Error("Unknown entry desc: " + obfEntry); } // clear caches @@ -613,17 +639,33 @@ public class Deobfuscator { this.renamer.setClassModifier((ClassEntry) obfEntry, modifierEntry); else if (obfEntry instanceof FieldEntry) this.renamer.setFieldModifier((FieldEntry) obfEntry, modifierEntry); - else if (obfEntry instanceof BehaviorEntry) - this.renamer.setMethodModifier((BehaviorEntry) obfEntry, modifierEntry); + else if (obfEntry instanceof MethodEntry) + this.renamer.setMethodModifier((MethodEntry) obfEntry, modifierEntry); else - throw new Error("Unknown entry type: " + obfEntry); + throw new Error("Unknown entry desc: " + obfEntry); } - public Mappings.EntryModifier getModifier(Entry obEntry) { - Entry entry = obfuscateEntry(obEntry); + public Mappings.EntryModifier getModifier(Entry obfEntry) { + Entry entry = obfuscateEntry(obfEntry); if (entry != null) - obEntry = entry; - return getTranslator(TranslationDirection.Deobfuscating).getModifier(obEntry); + obfEntry = entry; + if (obfEntry instanceof ClassEntry) + return this.renamer.getClassModifier((ClassEntry) obfEntry); + else if (obfEntry instanceof FieldEntry) + return this.renamer.getFieldModifier((FieldEntry) obfEntry); + else if (obfEntry instanceof MethodEntry) + return this.renamer.getMethodModfifier((MethodEntry) obfEntry); + else + throw new Error("Unknown entry desc: " + obfEntry); + } + + public static void runCustomTransforms(AstBuilder builder, DecompilerContext context){ + List transformers = Arrays.asList( + new ObfuscatedEnumSwitchRewriterTransform(context) + ); + for (IAstTransform transform : transformers){ + transform.run(builder.getCompilationUnit()); + } } public interface ProgressListener { @@ -633,6 +675,7 @@ public class Deobfuscator { } public interface ClassTransformer { - CtClass transform(CtClass c) throws Exception; + String transform(ClassNode node, ClassWriter writer); } + } diff --git a/src/main/java/cuchaz/enigma/ITranslatingTypeLoader.java b/src/main/java/cuchaz/enigma/ITranslatingTypeLoader.java new file mode 100644 index 00000000..547ed0b2 --- /dev/null +++ b/src/main/java/cuchaz/enigma/ITranslatingTypeLoader.java @@ -0,0 +1,19 @@ +package cuchaz.enigma; + +import com.strobel.assembler.metadata.ITypeLoader; +import cuchaz.enigma.mapping.entry.ClassEntry; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.tree.ClassNode; + +import java.util.List; + +/** + * For delegation of TranslatingTypeLoader without needing the subclass the whole thing + */ +public interface ITranslatingTypeLoader extends ITypeLoader { + List getClassNamesToTry(String className); + + List getClassNamesToTry(ClassEntry obfClassEntry); + + String transformInto(ClassNode node, ClassWriter writer); +} diff --git a/src/main/java/cuchaz/enigma/SynchronizedTypeLoader.java b/src/main/java/cuchaz/enigma/SynchronizedTypeLoader.java new file mode 100644 index 00000000..f4a7fe09 --- /dev/null +++ b/src/main/java/cuchaz/enigma/SynchronizedTypeLoader.java @@ -0,0 +1,39 @@ +package cuchaz.enigma; + +import com.strobel.assembler.metadata.Buffer; +import cuchaz.enigma.mapping.entry.ClassEntry; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.tree.ClassNode; + +import java.util.List; + +/** + * Typeloader with synchronized tryLoadType method + */ +public class SynchronizedTypeLoader implements ITranslatingTypeLoader { + private final TranslatingTypeLoader delegate; + + public SynchronizedTypeLoader(TranslatingTypeLoader delegate) { + this.delegate = delegate; + } + + @Override + public List getClassNamesToTry(String className) { + return delegate.getClassNamesToTry(className); + } + + @Override + public List getClassNamesToTry(ClassEntry obfClassEntry) { + return delegate.getClassNamesToTry(obfClassEntry); + } + + @Override + public String transformInto(ClassNode node, ClassWriter writer) { + return delegate.transformInto(node, writer); + } + + @Override + public synchronized boolean tryLoadType(String internalName, Buffer buffer) { + return delegate.tryLoadType(internalName, buffer); + } +} diff --git a/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java b/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java index 2a2041a0..eb780ee9 100644 --- a/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java +++ b/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java @@ -12,106 +12,63 @@ package cuchaz.enigma; import com.google.common.collect.Lists; -import com.google.common.collect.Maps; import com.strobel.assembler.metadata.Buffer; -import com.strobel.assembler.metadata.ClasspathTypeLoader; import com.strobel.assembler.metadata.ITypeLoader; -import cuchaz.enigma.analysis.BridgeMarker; import cuchaz.enigma.analysis.JarIndex; -import cuchaz.enigma.bytecode.translators.ClassTranslator; -import cuchaz.enigma.bytecode.translators.InnerClassWriter; -import cuchaz.enigma.bytecode.translators.LocalVariableTranslator; -import cuchaz.enigma.bytecode.translators.MethodParameterTranslator; -import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.analysis.ParsedJar; +import cuchaz.enigma.bytecode.translators.TranslationClassVisitor; +import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.mapping.entry.ReferencedEntryPool; import cuchaz.enigma.mapping.Translator; -import javassist.*; -import javassist.bytecode.Descriptor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.ClassNode; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; import java.util.List; -import java.util.Map; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -public class TranslatingTypeLoader implements ITypeLoader { +public class TranslatingTypeLoader extends CachingTypeLoader implements ITranslatingTypeLoader { + //Store one instance as the classpath shouldnt change during load + private static final ITypeLoader defaultTypeLoader = new CachingClasspathTypeLoader(); - private JarFile jar; - private JarIndex jarIndex; - private Translator obfuscatingTranslator; - private Translator deobfuscatingTranslator; - private Map cache; - private ClasspathTypeLoader defaultTypeLoader; + private final ParsedJar jar; + private final JarIndex jarIndex; + private final ReferencedEntryPool entryPool; + private final Translator obfuscatingTranslator; + private final Translator deobfuscatingTranslator; - public TranslatingTypeLoader(JarFile jar, JarIndex jarIndex, Translator obfuscatingTranslator, Translator deobfuscatingTranslator) { + public TranslatingTypeLoader(ParsedJar jar, JarIndex jarIndex, ReferencedEntryPool entryPool, Translator obfuscatingTranslator, Translator deobfuscatingTranslator) { this.jar = jar; this.jarIndex = jarIndex; + this.entryPool = entryPool; this.obfuscatingTranslator = obfuscatingTranslator; this.deobfuscatingTranslator = deobfuscatingTranslator; - this.cache = Maps.newHashMap(); - this.defaultTypeLoader = new ClasspathTypeLoader(); - - } - - public void clearCache() { - this.cache.clear(); - } - - @Override - public boolean tryLoadType(String className, Buffer out) { - - // check the cache - byte[] data; - if (this.cache.containsKey(className)) { - data = this.cache.get(className); - } else { - data = loadType(className); - this.cache.put(className, data); - } - - if (data == null) { - // chain to default type loader - return this.defaultTypeLoader.tryLoadType(className, out); - } - - // send the class to the decompiler - out.reset(data.length); - System.arraycopy(data, 0, out.array(), out.position(), data.length); - out.position(0); - return true; } - public CtClass loadClass(String deobfClassName) { - - byte[] data = loadType(deobfClassName); + protected byte[] doLoad(String className){ + byte[] data = loadType(className); if (data == null) { - return null; - } - - // return a javassist handle for the class - String javaClassFileName = Descriptor.toJavaName(deobfClassName); - ClassPool classPool = new ClassPool(); - classPool.insertClassPath(new ByteArrayClassPath(javaClassFileName, data)); - try { - return classPool.get(javaClassFileName); - } catch (NotFoundException ex) { - throw new Error(ex); + // chain to default desc loader + Buffer parentBuf = new Buffer(); + if (defaultTypeLoader.tryLoadType(className, parentBuf)){ + return parentBuf.array(); + } + return EMPTY_ARRAY;//need to return *something* as null means no store } + return data; } private byte[] loadType(String className) { // NOTE: don't know if class name is obf or deobf ClassEntry classEntry = new ClassEntry(className); - ClassEntry obfClassEntry = this.obfuscatingTranslator.translateEntry(classEntry); + ClassEntry obfClassEntry = this.obfuscatingTranslator.getTranslatedClass(classEntry); // is this an inner class referenced directly? (ie trying to load b instead of a$b) if (!obfClassEntry.isInnerClass()) { List classChain = this.jarIndex.getObfClassChain(obfClassEntry); if (classChain.size() > 1) { System.err.println(String.format("WARNING: no class %s after inner class reconstruction. Try %s", - className, obfClassEntry.buildClassEntry(classChain) + className, obfClassEntry.buildClassEntry(classChain) )); return null; } @@ -126,56 +83,26 @@ public class TranslatingTypeLoader implements ITypeLoader { //System.out.println(String.format("Looking for %s (obf: %s)", classEntry.getName(), obfClassEntry.getName())); // find the class in the jar - String classInJarName = findClassInJar(obfClassEntry); - if (classInJarName == null) { + ClassNode node = findClassInJar(obfClassEntry); + if (node == null) { // couldn't find it return null; } - try { - // read the class file into a buffer - ByteArrayOutputStream data = new ByteArrayOutputStream(); - byte[] buf = new byte[1024 * 1024]; // 1 KiB - InputStream in = this.jar.getInputStream(this.jar.getJarEntry(classInJarName + ".class")); - while (true) { - int bytesRead = in.read(buf); - if (bytesRead <= 0) { - break; - } - data.write(buf, 0, bytesRead); - } - data.close(); - in.close(); - buf = data.toByteArray(); - - // load the javassist handle to the raw class - ClassPool classPool = new ClassPool(); - String classInJarJavaName = Descriptor.toJavaName(classInJarName); - classPool.insertClassPath(new ByteArrayClassPath(classInJarJavaName, buf)); - CtClass c = classPool.get(classInJarJavaName); - - c = transformClass(c); - - // sanity checking - assertClassName(c, classEntry); + ClassWriter writer = new ClassWriter(0); + transformInto(node, writer); - // DEBUG - //Util.writeClass( c ); - - // we have a transformed class! - return c.toBytecode(); - } catch (IOException | NotFoundException | CannotCompileException ex) { - throw new Error(ex); - } + // we have a transformed class! + return writer.toByteArray(); } - private String findClassInJar(ClassEntry obfClassEntry) { + private ClassNode findClassInJar(ClassEntry obfClassEntry) { // try to find the class in the jar for (String className : getClassNamesToTry(obfClassEntry)) { - JarEntry jarEntry = this.jar.getJarEntry(className + ".class"); - if (jarEntry != null) { - return className; + ClassNode node = this.jar.getClassNode(className); + if (node != null) { + return node; } } @@ -183,10 +110,12 @@ public class TranslatingTypeLoader implements ITypeLoader { return null; } + @Override public List getClassNamesToTry(String className) { - return getClassNamesToTry(this.obfuscatingTranslator.translateEntry(new ClassEntry(className))); + return getClassNamesToTry(this.obfuscatingTranslator.getTranslatedClass(new ClassEntry(className))); } + @Override public List getClassNamesToTry(ClassEntry obfClassEntry) { List classNamesToTry = Lists.newArrayList(); classNamesToTry.add(obfClassEntry.getName()); @@ -197,36 +126,10 @@ public class TranslatingTypeLoader implements ITypeLoader { return classNamesToTry; } - public CtClass transformClass(CtClass c) - throws IOException, NotFoundException, CannotCompileException { - - // reconstruct inner classes - InnerClassWriter.write(jarIndex, c); - - // re-get the javassist handle since we changed class names - ClassEntry obfClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); - String javaClassReconstructedName = Descriptor.toJavaName(obfClassEntry.getName()); - ClassPool classPool = new ClassPool(); - classPool.insertClassPath(new ByteArrayClassPath(javaClassReconstructedName, c.toBytecode())); - c = classPool.get(javaClassReconstructedName); - - // check that the file is correct after inner class reconstruction (ie cause Javassist to fail fast if something is wrong) - assertClassName(c, obfClassEntry); - - // do all kinds of deobfuscating transformations on the class - BridgeMarker.markBridges(this.jarIndex, c); - MethodParameterTranslator.translate(this.deobfuscatingTranslator, c); - LocalVariableTranslator.translate(this.deobfuscatingTranslator, c); - ClassTranslator.translate(this.deobfuscatingTranslator, c); - - return c; + @Override + public String transformInto(ClassNode node, ClassWriter writer) { + node.accept(new TranslationClassVisitor(deobfuscatingTranslator, jarIndex, entryPool, Opcodes.ASM5, writer)); + return deobfuscatingTranslator.getTranslatedClass(new ClassEntry(node.name)).getName(); } - private void assertClassName(CtClass c, ClassEntry obfClassEntry) { - String name1 = Descriptor.toJvmName(c.getName()); - assert (name1.equals(obfClassEntry.getName())) : String.format("Looking for %s, instead found %s", obfClassEntry.getName(), name1); - - String name2 = Descriptor.toJvmName(c.getClassFile().getName()); - assert (name2.equals(obfClassEntry.getName())) : String.format("Looking for %s, instead found %s", obfClassEntry.getName(), name2); - } } diff --git a/src/main/java/cuchaz/enigma/analysis/Access.java b/src/main/java/cuchaz/enigma/analysis/Access.java index 547d85ef..81814183 100644 --- a/src/main/java/cuchaz/enigma/analysis/Access.java +++ b/src/main/java/cuchaz/enigma/analysis/Access.java @@ -11,8 +11,7 @@ package cuchaz.enigma.analysis; -import javassist.CtBehavior; -import javassist.CtField; +import cuchaz.enigma.bytecode.AccessFlags; import java.lang.reflect.Modifier; @@ -20,12 +19,8 @@ public enum Access { PUBLIC, PROTECTED, PACKAGE, PRIVATE; - public static Access get(CtBehavior behavior) { - return get(behavior.getModifiers()); - } - - public static Access get(CtField field) { - return get(field.getModifiers()); + public static Access get(AccessFlags flags) { + return get(flags.getFlags()); } public static Access get(int modifiers) { diff --git a/src/main/java/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java deleted file mode 100644 index 6556b2cf..00000000 --- a/src/main/java/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java +++ /dev/null @@ -1,93 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.analysis; - -import com.google.common.collect.Sets; -import cuchaz.enigma.mapping.BehaviorEntry; -import cuchaz.enigma.mapping.Entry; -import cuchaz.enigma.mapping.Translator; - -import javax.swing.tree.DefaultMutableTreeNode; -import javax.swing.tree.TreeNode; -import java.util.Set; - -public class BehaviorReferenceTreeNode extends DefaultMutableTreeNode - implements ReferenceTreeNode { - - private Translator deobfuscatingTranslator; - private BehaviorEntry entry; - private EntryReference reference; - private Access access; - - public BehaviorReferenceTreeNode(Translator deobfuscatingTranslator, BehaviorEntry entry) { - this.deobfuscatingTranslator = deobfuscatingTranslator; - this.entry = entry; - this.reference = null; - } - - public BehaviorReferenceTreeNode(Translator deobfuscatingTranslator, - EntryReference reference, Access access) { - this.deobfuscatingTranslator = deobfuscatingTranslator; - this.entry = reference.entry; - this.reference = reference; - this.access = access; - } - - @Override - public BehaviorEntry getEntry() { - return this.entry; - } - - @Override - public EntryReference getReference() { - return this.reference; - } - - @Override - public String toString() { - if (this.reference != null) { - return String.format("%s (%s)", this.deobfuscatingTranslator.translateEntry(this.reference.context), - this.access); - } - return this.deobfuscatingTranslator.translateEntry(this.entry).toString(); - } - - public void load(JarIndex index, boolean recurse) { - // get all the child nodes - for (EntryReference reference : index.getBehaviorReferences(this.entry)) { - add(new BehaviorReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccess(this.entry))); - } - - if (recurse && this.children != null) { - for (Object child : this.children) { - if (child instanceof BehaviorReferenceTreeNode) { - BehaviorReferenceTreeNode node = (BehaviorReferenceTreeNode) child; - - // don't recurse into ancestor - Set ancestors = Sets.newHashSet(); - TreeNode n = node; - while (n.getParent() != null) { - n = n.getParent(); - if (n instanceof BehaviorReferenceTreeNode) { - ancestors.add(((BehaviorReferenceTreeNode) n).getEntry()); - } - } - if (ancestors.contains(node.getEntry())) { - continue; - } - - node.load(index, true); - } - } - } - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java b/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java deleted file mode 100644 index a2f1f909..00000000 --- a/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java +++ /dev/null @@ -1,38 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.analysis; - -import cuchaz.enigma.mapping.EntryFactory; -import cuchaz.enigma.mapping.MethodEntry; -import javassist.CtClass; -import javassist.CtMethod; -import javassist.bytecode.AccessFlag; - -public class BridgeMarker { - - public static void markBridges(JarIndex jarIndex, CtClass c) { - - for (CtMethod method : c.getDeclaredMethods()) { - MethodEntry methodEntry = EntryFactory.getMethodEntry(method); - - // is this a bridge method? - MethodEntry bridgedMethodEntry = jarIndex.getBridgedMethod(methodEntry); - if (bridgedMethodEntry != null) { - - // it's a bridge method! add the bridge flag - int flags = method.getMethodInfo().getAccessFlags(); - flags |= AccessFlag.BRIDGE; - method.getMethodInfo().setAccessFlags(flags); - } - } - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java b/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java index f2fb2f8d..e876bb07 100644 --- a/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java +++ b/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java @@ -12,8 +12,8 @@ package cuchaz.enigma.analysis; import com.google.common.collect.Lists; -import cuchaz.enigma.mapping.ClassEntry; -import cuchaz.enigma.mapping.MethodEntry; +import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.mapping.entry.MethodEntry; import cuchaz.enigma.mapping.Translator; import javax.swing.tree.DefaultMutableTreeNode; @@ -21,8 +21,8 @@ import java.util.List; public class ClassImplementationsTreeNode extends DefaultMutableTreeNode { - private Translator deobfuscatingTranslator; - private ClassEntry entry; + private final Translator deobfuscatingTranslator; + private final ClassEntry entry; public ClassImplementationsTreeNode(Translator deobfuscatingTranslator, ClassEntry entry) { this.deobfuscatingTranslator = deobfuscatingTranslator; @@ -31,7 +31,7 @@ public class ClassImplementationsTreeNode extends DefaultMutableTreeNode { public static ClassImplementationsTreeNode findNode(ClassImplementationsTreeNode node, MethodEntry entry) { // is this the node? - if (node.entry.equals(entry.getClassEntry())) { + if (node.entry.equals(entry.getOwnerClassEntry())) { return node; } @@ -50,7 +50,7 @@ public class ClassImplementationsTreeNode extends DefaultMutableTreeNode { } public String getDeobfClassName() { - return this.deobfuscatingTranslator.translateClass(this.entry.getClassName()); + return this.deobfuscatingTranslator.getTranslatedClass(entry).getClassName(); } @Override diff --git a/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java index 24e7cb0b..b8ee17da 100644 --- a/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java +++ b/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java @@ -12,7 +12,7 @@ package cuchaz.enigma.analysis; import com.google.common.collect.Lists; -import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.entry.ClassEntry; import cuchaz.enigma.mapping.Translator; import javax.swing.tree.DefaultMutableTreeNode; @@ -20,12 +20,12 @@ import java.util.List; public class ClassInheritanceTreeNode extends DefaultMutableTreeNode { - private Translator deobfuscatingTranslator; - private String obfClassName; + private final Translator deobfuscatingTranslator; + private final ClassEntry obfClassEntry; public ClassInheritanceTreeNode(Translator deobfuscatingTranslator, String obfClassName) { this.deobfuscatingTranslator = deobfuscatingTranslator; - this.obfClassName = obfClassName; + this.obfClassEntry = new ClassEntry(obfClassName); } public static ClassInheritanceTreeNode findNode(ClassInheritanceTreeNode node, ClassEntry entry) { @@ -45,11 +45,11 @@ public class ClassInheritanceTreeNode extends DefaultMutableTreeNode { } public String getObfClassName() { - return this.obfClassName; + return this.obfClassEntry.getClassName(); } public String getDeobfClassName() { - return this.deobfuscatingTranslator.translateClass(this.obfClassName); + return this.deobfuscatingTranslator.getTranslatedClass(this.obfClassEntry).getClassName(); } @Override @@ -58,13 +58,13 @@ public class ClassInheritanceTreeNode extends DefaultMutableTreeNode { if (deobfClassName != null) { return deobfClassName; } - return this.obfClassName; + return this.obfClassEntry.getName(); } public void load(TranslationIndex ancestries, boolean recurse) { // get all the child nodes List nodes = Lists.newArrayList(); - for (ClassEntry subclassEntry : ancestries.getSubclass(new ClassEntry(this.obfClassName))) { + for (ClassEntry subclassEntry : ancestries.getSubclass(this.obfClassEntry)) { nodes.add(new ClassInheritanceTreeNode(this.deobfuscatingTranslator, subclassEntry.getName())); } diff --git a/src/main/java/cuchaz/enigma/analysis/EntryReference.java b/src/main/java/cuchaz/enigma/analysis/EntryReference.java index 3761fca8..101729d8 100644 --- a/src/main/java/cuchaz/enigma/analysis/EntryReference.java +++ b/src/main/java/cuchaz/enigma/analysis/EntryReference.java @@ -11,9 +11,9 @@ package cuchaz.enigma.analysis; -import cuchaz.enigma.mapping.ClassEntry; -import cuchaz.enigma.mapping.ConstructorEntry; -import cuchaz.enigma.mapping.Entry; +import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.mapping.entry.Entry; +import cuchaz.enigma.mapping.entry.MethodEntry; import cuchaz.enigma.utils.Utils; import java.util.Arrays; @@ -21,7 +21,7 @@ import java.util.List; public class EntryReference { - private static final List ConstructorNonNames = Arrays.asList("this", "super", "static"); + private static final List CONSTRUCTOR_NON_NAMES = Arrays.asList("this", "super", "static"); public E entry; public C context; @@ -40,7 +40,7 @@ public class EntryReference { this.context = context; this.sourceName = sourceName != null && !sourceName.isEmpty(); - if (entry instanceof ConstructorEntry && ConstructorNonNames.contains(sourceName)) { + if (entry instanceof MethodEntry && ((MethodEntry) entry).isConstructor() && CONSTRUCTOR_NON_NAMES.contains(sourceName)) { this.sourceName = false; } } @@ -53,9 +53,9 @@ public class EntryReference { public ClassEntry getLocationClassEntry() { if (context != null) { - return context.getClassEntry(); + return context.getOwnerClassEntry(); } - return entry.getClassEntry(); + return entry.getOwnerClassEntry(); } public boolean isNamed() { @@ -63,9 +63,9 @@ public class EntryReference { } public Entry getNameableEntry() { - if (entry instanceof ConstructorEntry) { + if (entry instanceof MethodEntry && ((MethodEntry) entry).isConstructor()) { // renaming a constructor really means renaming the class - return entry.getClassEntry(); + return entry.getOwnerClassEntry(); } return entry; } diff --git a/src/main/java/cuchaz/enigma/analysis/EntryRenamer.java b/src/main/java/cuchaz/enigma/analysis/EntryRenamer.java index 75806c33..9be8378e 100644 --- a/src/main/java/cuchaz/enigma/analysis/EntryRenamer.java +++ b/src/main/java/cuchaz/enigma/analysis/EntryRenamer.java @@ -15,6 +15,7 @@ import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; import cuchaz.enigma.mapping.*; +import cuchaz.enigma.mapping.entry.*; import java.util.AbstractMap; import java.util.List; @@ -87,18 +88,18 @@ public class EntryRenamer { MethodEntry newMethodEntry = renames.get(methodEntry); if (newMethodEntry != null) { return (T) new MethodEntry( - methodEntry.getClassEntry(), - newMethodEntry.getName(), - methodEntry.getSignature() + methodEntry.getOwnerClassEntry(), + newMethodEntry.getName(), + methodEntry.getDesc() ); } return thing; - } else if (thing instanceof ArgumentEntry) { - ArgumentEntry argumentEntry = (ArgumentEntry) thing; - return (T) new ArgumentEntry( - renameMethodsInThing(renames, argumentEntry.getBehaviorEntry()), - argumentEntry.getIndex(), - argumentEntry.getName() + } else if (thing instanceof LocalVariableEntry) { + LocalVariableEntry variableEntry = (LocalVariableEntry) thing; + return (T) new LocalVariableEntry( + renameMethodsInThing(renames, variableEntry.getOwnerEntry()), + variableEntry.getIndex(), + variableEntry.getName() ); } else if (thing instanceof EntryReference) { EntryReference reference = (EntryReference) thing; @@ -119,27 +120,45 @@ public class EntryRenamer { } else if (thing instanceof ClassEntry) { ClassEntry classEntry = (ClassEntry) thing; return (T) new ClassEntry(renameClassesInThing(renames, classEntry.getClassName())); - } else if (thing instanceof FieldEntry) { - FieldEntry fieldEntry = (FieldEntry) thing; - return (T) new FieldEntry(renameClassesInThing(renames, fieldEntry.getClassEntry()), fieldEntry.getName(), renameClassesInThing(renames, fieldEntry.getType())); - } else if (thing instanceof ConstructorEntry) { - ConstructorEntry constructorEntry = (ConstructorEntry) thing; - return (T) new ConstructorEntry(renameClassesInThing(renames, constructorEntry.getClassEntry()), renameClassesInThing(renames, constructorEntry.getSignature())); + } else if (thing instanceof FieldDefEntry) { + FieldDefEntry fieldEntry = (FieldDefEntry) thing; + return (T) new FieldDefEntry( + renameClassesInThing(renames, fieldEntry.getOwnerClassEntry()), + fieldEntry.getName(), + renameClassesInThing(renames, fieldEntry.getDesc()), + renameClassesInThing(renames, fieldEntry.getSignature()), + fieldEntry.getAccess() + ); + } else if (thing instanceof MethodDefEntry) { + MethodDefEntry methodEntry = (MethodDefEntry) thing; + return (T) new MethodDefEntry( + renameClassesInThing(renames, methodEntry.getOwnerClassEntry()), + methodEntry.getName(), + renameClassesInThing(renames, methodEntry.getDesc()), + renameClassesInThing(renames, methodEntry.getSignature()), + methodEntry.getAccess() + ); } else if (thing instanceof MethodEntry) { MethodEntry methodEntry = (MethodEntry) thing; - return (T) new MethodEntry(renameClassesInThing(renames, methodEntry.getClassEntry()), methodEntry.getName(), renameClassesInThing(renames, methodEntry.getSignature())); - } else if (thing instanceof ArgumentEntry) { - ArgumentEntry argumentEntry = (ArgumentEntry) thing; - return (T) new ArgumentEntry(renameClassesInThing(renames, argumentEntry.getBehaviorEntry()), argumentEntry.getIndex(), argumentEntry.getName()); + return (T) new MethodEntry( + renameClassesInThing(renames, methodEntry.getOwnerClassEntry()), + methodEntry.getName(), + renameClassesInThing(renames, methodEntry.getDesc()) + ); + } else if (thing instanceof LocalVariableEntry) { + LocalVariableEntry argumentEntry = (LocalVariableEntry) thing; + return (T) new LocalVariableEntry(renameClassesInThing(renames, argumentEntry.getOwnerEntry()), argumentEntry.getIndex(), argumentEntry.getName()); } else if (thing instanceof EntryReference) { EntryReference reference = (EntryReference) thing; reference.entry = renameClassesInThing(renames, reference.entry); reference.context = renameClassesInThing(renames, reference.context); return thing; + } else if (thing instanceof MethodDescriptor) { + return (T) ((MethodDescriptor) thing).remap(className -> renameClassesInThing(renames, className)); + } else if (thing instanceof TypeDescriptor) { + return (T) ((TypeDescriptor) thing).remap(className -> renameClassesInThing(renames, className)); } else if (thing instanceof Signature) { - return (T) new Signature((Signature) thing, className -> renameClassesInThing(renames, className)); - } else if (thing instanceof Type) { - return (T) new Type((Type) thing, className -> renameClassesInThing(renames, className)); + return (T) ((Signature) thing).remap(className -> renameClassesInThing(renames, className)); } return thing; diff --git a/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java index 34d2eff1..f63b779a 100644 --- a/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java +++ b/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java @@ -11,17 +11,18 @@ package cuchaz.enigma.analysis; -import cuchaz.enigma.mapping.BehaviorEntry; -import cuchaz.enigma.mapping.FieldEntry; -import cuchaz.enigma.mapping.Translator; +import cuchaz.enigma.mapping.*; +import cuchaz.enigma.mapping.entry.FieldEntry; +import cuchaz.enigma.mapping.entry.MethodDefEntry; +import cuchaz.enigma.mapping.entry.MethodEntry; import javax.swing.tree.DefaultMutableTreeNode; -public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode { +public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode { private Translator deobfuscatingTranslator; private FieldEntry entry; - private EntryReference reference; + private EntryReference reference; private Access access; public FieldReferenceTreeNode(Translator deobfuscatingTranslator, FieldEntry entry) { @@ -30,7 +31,7 @@ public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements Re this.reference = null; } - private FieldReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference reference, Access access) { + private FieldReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference reference, Access access) { this.deobfuscatingTranslator = deobfuscatingTranslator; this.entry = reference.entry; this.reference = reference; @@ -43,34 +44,34 @@ public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements Re } @Override - public EntryReference getReference() { + public EntryReference getReference() { return this.reference; } @Override public String toString() { if (this.reference != null) { - return String.format("%s (%s)", this.deobfuscatingTranslator.translateEntry(this.reference.context), this.access); + return String.format("%s (%s)", this.deobfuscatingTranslator.getTranslatedMethodDef(this.reference.context), this.access); } - return this.deobfuscatingTranslator.translateEntry(this.entry).toString(); + return deobfuscatingTranslator.getTranslatedField(entry).getName(); } public void load(JarIndex index, boolean recurse) { // get all the child nodes if (this.reference == null) { - for (EntryReference reference : index.getFieldReferences(this.entry)) { + for (EntryReference reference : index.getFieldReferences(this.entry)) { add(new FieldReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccess(this.entry))); } } else { - for (EntryReference reference : index.getBehaviorReferences(this.reference.context)) { - add(new BehaviorReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccess(this.reference.context))); + for (EntryReference reference : index.getMethodsReferencing(this.reference.context)) { + add(new MethodReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccess(this.reference.context))); } } if (recurse && children != null) { for (Object node : children) { - if (node instanceof BehaviorReferenceTreeNode) { - ((BehaviorReferenceTreeNode) node).load(index, true); + if (node instanceof MethodReferenceTreeNode) { + ((MethodReferenceTreeNode) node).load(index, true); } else if (node instanceof FieldReferenceTreeNode) { ((FieldReferenceTreeNode) node).load(index, true); } diff --git a/src/main/java/cuchaz/enigma/analysis/IndexClassVisitor.java b/src/main/java/cuchaz/enigma/analysis/IndexClassVisitor.java new file mode 100644 index 00000000..69fe54fc --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/IndexClassVisitor.java @@ -0,0 +1,38 @@ +package cuchaz.enigma.analysis; + +import cuchaz.enigma.mapping.entry.ClassDefEntry; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; + +public class IndexClassVisitor extends ClassVisitor { + private final JarIndex index; + private ClassDefEntry classEntry; + + public IndexClassVisitor(JarIndex index, int api) { + super(api); + this.index = index; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + this.classEntry = this.index.indexClass(access, name, signature, superName, interfaces); + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { + if (this.classEntry != null) { + this.index.indexField(this.classEntry, access, name, desc, signature); + } + return super.visitField(access, name, desc, signature, value); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + if (this.classEntry != null) { + this.index.indexMethod(this.classEntry, access, name, desc, signature); + } + return super.visitMethod(access, name, desc, signature, exceptions); + } +} diff --git a/src/main/java/cuchaz/enigma/analysis/IndexInnerClassVisitor.java b/src/main/java/cuchaz/enigma/analysis/IndexInnerClassVisitor.java new file mode 100644 index 00000000..04742278 --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/IndexInnerClassVisitor.java @@ -0,0 +1,23 @@ +package cuchaz.enigma.analysis; + +import cuchaz.enigma.mapping.entry.ClassEntry; +import org.objectweb.asm.ClassVisitor; + +public class IndexInnerClassVisitor extends ClassVisitor { + private final JarIndex index; + + public IndexInnerClassVisitor(JarIndex index, int api) { + super(api); + this.index = index; + } + + @Override + public void visitInnerClass(String name, String outerName, String innerName, int access) { + ClassEntry entry = new ClassEntry(name); + // Ignore anonymous classes + if (innerName != null && outerName != null) { + ClassEntry outerEntry = new ClassEntry(outerName); + index.indexInnerClass(entry, outerEntry); + } + } +} diff --git a/src/main/java/cuchaz/enigma/analysis/IndexReferenceVisitor.java b/src/main/java/cuchaz/enigma/analysis/IndexReferenceVisitor.java new file mode 100644 index 00000000..f37f1e90 --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/IndexReferenceVisitor.java @@ -0,0 +1,77 @@ +package cuchaz.enigma.analysis; + +import cuchaz.enigma.bytecode.AccessFlags; +import cuchaz.enigma.mapping.MethodDescriptor; +import cuchaz.enigma.mapping.Signature; +import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.mapping.entry.MethodDefEntry; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Handle; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +public class IndexReferenceVisitor extends ClassVisitor { + private final JarIndex index; + private ClassEntry classEntry; + + public IndexReferenceVisitor(JarIndex index, int api) { + super(api); + this.index = index; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + this.classEntry = new ClassEntry(name); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + MethodDefEntry entry = new MethodDefEntry(classEntry, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access)); + return new Method(this.index, entry, this.api); + } + + private class Method extends MethodVisitor { + private final JarIndex index; + private final MethodDefEntry callerEntry; + + public Method(JarIndex index, MethodDefEntry callerEntry, int api) { + super(api); + this.index = index; + this.callerEntry = callerEntry; + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + this.index.indexFieldAccess(callerEntry, owner, name, desc); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + this.index.indexMethodCall(callerEntry, owner, name, desc); + } + + @Override + public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { + for (Object bsmArg : bsmArgs){ + if (bsmArg instanceof Handle){ + Handle handle = (Handle)bsmArg; + switch (handle.getTag()){ + case Opcodes.H_GETFIELD: + case Opcodes.H_GETSTATIC: + case Opcodes.H_PUTFIELD: + case Opcodes.H_PUTSTATIC: + this.index.indexFieldAccess(callerEntry, handle.getOwner(), handle.getName(), handle.getDesc()); + break; + case Opcodes.H_INVOKEINTERFACE: + case Opcodes.H_INVOKESPECIAL: + case Opcodes.H_INVOKESTATIC: + case Opcodes.H_INVOKEVIRTUAL: + case Opcodes.H_NEWINVOKESPECIAL: + this.index.indexMethodCall(callerEntry, handle.getOwner(), handle.getName(), handle.getDesc()); + break; + } + } + } + } + } +} diff --git a/src/main/java/cuchaz/enigma/analysis/JarClassIterator.java b/src/main/java/cuchaz/enigma/analysis/JarClassIterator.java deleted file mode 100644 index 87d3797d..00000000 --- a/src/main/java/cuchaz/enigma/analysis/JarClassIterator.java +++ /dev/null @@ -1,123 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.analysis; - -import com.google.common.collect.Lists; -import cuchaz.enigma.Constants; -import cuchaz.enigma.mapping.ClassEntry; -import javassist.ByteArrayClassPath; -import javassist.ClassPool; -import javassist.CtClass; -import javassist.NotFoundException; -import javassist.bytecode.Descriptor; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Enumeration; -import java.util.Iterator; -import java.util.List; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; - -public class JarClassIterator implements Iterator { - - private JarFile jar; - private Iterator iter; - - public JarClassIterator(JarFile jar) { - this.jar = jar; - - // get the jar entries that correspond to classes - List classEntries = Lists.newArrayList(); - Enumeration entries = this.jar.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - - // is this a class file? - if (entry.getName().endsWith(".class")) { - classEntries.add(entry); - } - } - this.iter = classEntries.iterator(); - } - - public static List getClassEntries(JarFile jar) { - List classEntries = Lists.newArrayList(); - Enumeration entries = jar.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - - // is this a class file? - if (!entry.isDirectory() && entry.getName().endsWith(".class")) { - classEntries.add(getClassEntry(entry)); - } - } - return classEntries; - } - - public static Iterable classes(final JarFile jar) { - return () -> new JarClassIterator(jar); - } - - private static CtClass getClass(JarFile jar, JarEntry entry) throws IOException, NotFoundException { - // read the class into a buffer - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - byte[] buf = new byte[Constants.KiB]; - int totalNumBytesRead = 0; - InputStream in = jar.getInputStream(entry); - while (in.available() > 0) { - int numBytesRead = in.read(buf); - if (numBytesRead < 0) { - break; - } - bos.write(buf, 0, numBytesRead); - - // sanity checking - totalNumBytesRead += numBytesRead; - if (totalNumBytesRead > Constants.MiB) { - throw new Error("Class file " + entry.getName() + " larger than 1 MiB! Something is wrong!"); - } - } - - // get a javassist handle for the class - String className = Descriptor.toJavaName(getClassEntry(entry).getName()); - ClassPool classPool = new ClassPool(); - classPool.appendSystemPath(); - classPool.insertClassPath(new ByteArrayClassPath(className, bos.toByteArray())); - return classPool.get(className); - } - - private static ClassEntry getClassEntry(JarEntry entry) { - return new ClassEntry(entry.getName().substring(0, entry.getName().length() - ".class".length())); - } - - @Override - public boolean hasNext() { - return this.iter.hasNext(); - } - - @Override - public CtClass next() { - JarEntry entry = this.iter.next(); - try { - return getClass(this.jar, entry); - } catch (IOException | NotFoundException ex) { - throw new Error("Unable to load class: " + entry.getName()); - } - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/JarIndex.java b/src/main/java/cuchaz/enigma/analysis/JarIndex.java index d0d0f2c5..5917a32f 100644 --- a/src/main/java/cuchaz/enigma/analysis/JarIndex.java +++ b/src/main/java/cuchaz/enigma/analysis/JarIndex.java @@ -12,113 +12,73 @@ package cuchaz.enigma.analysis; import com.google.common.collect.*; +import cuchaz.enigma.bytecode.AccessFlags; import cuchaz.enigma.mapping.*; -import cuchaz.enigma.mapping.Translator; -import javassist.*; -import javassist.bytecode.*; -import javassist.expr.*; +import cuchaz.enigma.mapping.entry.*; +import org.objectweb.asm.Opcodes; -import java.lang.reflect.Modifier; import java.util.*; -import java.util.jar.JarFile; public class JarIndex { + private final ReferencedEntryPool entryPool; + private Set obfClassEntries; private TranslationIndex translationIndex; private Map access; - private Multimap fields; - private Multimap behaviors; - private Multimap methodImplementations; - private Multimap> behaviorReferences; - private Multimap> fieldReferences; + private Multimap fields; + private Multimap methods; + private Multimap methodImplementations; + private Multimap> methodsReferencing; + private Multimap methodReferences; + private Multimap> fieldReferences; private Multimap innerClassesByOuter; private Map outerClassesByInner; - private Map anonymousClasses; private Map bridgedMethods; private Set syntheticMethods; - public JarIndex() { + public JarIndex(ReferencedEntryPool entryPool) { + this.entryPool = entryPool; this.obfClassEntries = Sets.newHashSet(); - this.translationIndex = new TranslationIndex(); + this.translationIndex = new TranslationIndex(entryPool); this.access = Maps.newHashMap(); this.fields = HashMultimap.create(); - this.behaviors = HashMultimap.create(); + this.methods = HashMultimap.create(); this.methodImplementations = HashMultimap.create(); - this.behaviorReferences = HashMultimap.create(); + this.methodsReferencing = HashMultimap.create(); + this.methodReferences = HashMultimap.create(); this.fieldReferences = HashMultimap.create(); this.innerClassesByOuter = HashMultimap.create(); this.outerClassesByInner = Maps.newHashMap(); - this.anonymousClasses = Maps.newHashMap(); this.bridgedMethods = Maps.newHashMap(); this.syntheticMethods = Sets.newHashSet(); } - public void indexJar(JarFile jar, boolean buildInnerClasses) { + public void indexJar(ParsedJar jar, boolean buildInnerClasses) { // step 1: read the class names - this.obfClassEntries.addAll(JarClassIterator.getClassEntries(jar)); - - // step 2: index field/method/constructor access - for (CtClass c : JarClassIterator.classes(jar)) { - for (CtField field : c.getDeclaredFields()) { - FieldEntry fieldEntry = EntryFactory.getFieldEntry(field); - this.access.put(fieldEntry, Access.get(field)); - this.fields.put(fieldEntry.getClassEntry(), fieldEntry); - } - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); - this.access.put(behaviorEntry, Access.get(behavior)); - this.behaviors.put(behaviorEntry.getClassEntry(), behaviorEntry); - } - } + obfClassEntries.addAll(jar.getClassEntries()); - // step 3: index extends, implements, fields, and methods - for (CtClass c : JarClassIterator.classes(jar)) { - this.translationIndex.indexClass(c); - String className = Descriptor.toJvmName(c.getName()); - for (String interfaceName : c.getClassFile().getInterfaces()) { - className = Descriptor.toJvmName(className); - interfaceName = Descriptor.toJvmName(interfaceName); - if (className.equals(interfaceName)) { - throw new IllegalArgumentException("Class cannot be its own interface! " + className); - } - } - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - indexBehavior(behavior); - } - } + // step 2: index classes, fields, methods, interfaces + jar.visit(node -> node.accept(new IndexClassVisitor(this, Opcodes.ASM5))); - // step 4: index field, method, constructor references - for (CtClass c : JarClassIterator.classes(jar)) { - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - indexBehaviorReferences(behavior); + // step 3: index field, method, constructor references + jar.visit(node -> node.accept(new IndexReferenceVisitor(this, Opcodes.ASM5))); + + // step 4: index access and bridged methods + for (MethodDefEntry methodEntry : methods.values()) { + // look for access and bridged methods + MethodEntry accessedMethod = findAccessMethod(methodEntry); + if (accessedMethod != null) { + if (isBridgedMethod(accessedMethod, methodEntry)) { + this.bridgedMethods.put(methodEntry, accessedMethod); + } } } if (buildInnerClasses) { - // step 5: index inner classes and anonymous classes - for (CtClass c : JarClassIterator.classes(jar)) { - ClassEntry innerClassEntry = EntryFactory.getClassEntry(c); - ClassEntry outerClassEntry = findOuterClass(c); - if (outerClassEntry != null) { - this.innerClassesByOuter.put(outerClassEntry, innerClassEntry); - boolean innerWasAdded = this.outerClassesByInner.put(innerClassEntry, outerClassEntry) == null; - assert (innerWasAdded); - - BehaviorEntry enclosingBehavior = isAnonymousClass(c, outerClassEntry); - if (enclosingBehavior != null) { - this.anonymousClasses.put(innerClassEntry, enclosingBehavior); - - // DEBUG - //System.out.println("ANONYMOUS: " + outerClassEntry.getName() + "$" + innerClassEntry.getSimpleName()); - }/* else { - // DEBUG - //System.out.println("INNER: " + outerClassEntry.getName() + "$" + innerClassEntry.getSimpleName()); - }*/ - } - } + jar.visit(node -> node.accept(new IndexInnerClassVisitor(this, Opcodes.ASM5))); // step 6: update other indices with inner class info Map renames = Maps.newHashMap(); @@ -133,385 +93,138 @@ public class JarIndex { EntryRenamer.renameClassesInSet(renames, this.obfClassEntries); this.translationIndex.renameClasses(renames); EntryRenamer.renameClassesInMultimap(renames, this.methodImplementations); - EntryRenamer.renameClassesInMultimap(renames, this.behaviorReferences); + EntryRenamer.renameClassesInMultimap(renames, this.methodsReferencing); + EntryRenamer.renameClassesInMultimap(renames, this.methodReferences); EntryRenamer.renameClassesInMultimap(renames, this.fieldReferences); EntryRenamer.renameClassesInMap(renames, this.access); } } - private void indexBehavior(CtBehavior behavior) { - // get the behavior entry - final BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); - if (behaviorEntry instanceof MethodEntry) { - MethodEntry methodEntry = (MethodEntry) behaviorEntry; - - // is synthetic - if ((behavior.getModifiers() & AccessFlag.SYNTHETIC) != 0) { - syntheticMethods.add(methodEntry); + protected ClassDefEntry indexClass(int access, String name, String signature, String superName, String[] interfaces) { + for (String interfaceName : interfaces) { + if (name.equals(interfaceName)) { + throw new IllegalArgumentException("Class cannot be its own interface! " + name); } - - // index implementation - this.methodImplementations.put(behaviorEntry.getClassName(), methodEntry); - - // look for bridge and bridged methods - CtMethod bridgedMethod = getBridgedMethod((CtMethod) behavior); - if (bridgedMethod != null) { - this.bridgedMethods.put(methodEntry, EntryFactory.getMethodEntry(bridgedMethod)); - } - } - // looks like we don't care about constructors here - } - - private void indexBehaviorReferences(CtBehavior behavior) { - // index method calls - final BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); - try { - behavior.instrument(new ExprEditor() { - @Override - public void edit(MethodCall call) { - MethodEntry calledMethodEntry = EntryFactory.getMethodEntry(call); - ClassEntry resolvedClassEntry = translationIndex.resolveEntryClass(calledMethodEntry); - if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledMethodEntry.getClassEntry())) { - calledMethodEntry = new MethodEntry( - resolvedClassEntry, - calledMethodEntry.getName(), - calledMethodEntry.getSignature() - ); - } - EntryReference reference = new EntryReference<>( - calledMethodEntry, - call.getMethodName(), - behaviorEntry - ); - behaviorReferences.put(calledMethodEntry, reference); - } - - @Override - public void edit(FieldAccess call) { - FieldEntry calledFieldEntry = EntryFactory.getFieldEntry(call); - ClassEntry resolvedClassEntry = translationIndex.resolveEntryClass(calledFieldEntry); - if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledFieldEntry.getClassEntry())) { - calledFieldEntry = new FieldEntry(calledFieldEntry, resolvedClassEntry); - } - EntryReference reference = new EntryReference<>( - calledFieldEntry, - call.getFieldName(), - behaviorEntry - ); - fieldReferences.put(calledFieldEntry, reference); - } - - @Override - public void edit(ConstructorCall call) { - ConstructorEntry calledConstructorEntry = EntryFactory.getConstructorEntry(call); - EntryReference reference = new EntryReference<>( - calledConstructorEntry, - call.getMethodName(), - behaviorEntry - ); - behaviorReferences.put(calledConstructorEntry, reference); - } - - @Override - public void edit(NewExpr call) { - ConstructorEntry calledConstructorEntry = EntryFactory.getConstructorEntry(call); - EntryReference reference = new EntryReference<>( - calledConstructorEntry, - call.getClassName(), - behaviorEntry - ); - behaviorReferences.put(calledConstructorEntry, reference); - } - }); - } catch (CannotCompileException ex) { - throw new Error(ex); } + return this.translationIndex.indexClass(access, name, signature, superName, interfaces); } - private CtMethod getBridgedMethod(CtMethod method) { - - // bridge methods just call another method, cast it to the return type, and return the result - // let's see if we can detect this scenario - - // skip non-synthetic methods - if ((method.getModifiers() & AccessFlag.SYNTHETIC) == 0) { - return null; - } + protected void indexField(ClassDefEntry owner, int access, String name, String desc, String signature) { + FieldDefEntry fieldEntry = new FieldDefEntry(owner, name, new TypeDescriptor(desc), Signature.createTypedSignature(signature), new AccessFlags(access)); + this.translationIndex.indexField(fieldEntry); + this.access.put(fieldEntry, Access.get(access)); + this.fields.put(fieldEntry.getOwnerClassEntry(), fieldEntry); + } - // get all the called methods - final List methodCalls = Lists.newArrayList(); - try { - method.instrument(new ExprEditor() { - @Override - public void edit(MethodCall call) { - methodCalls.add(call); - } - }); - } catch (CannotCompileException ex) { - // this is stupid... we're not even compiling anything - throw new Error(ex); - } + protected void indexMethod(ClassDefEntry owner, int access, String name, String desc, String signature) { + MethodDefEntry methodEntry = new MethodDefEntry(owner, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access)); + this.translationIndex.indexMethod(methodEntry); + this.access.put(methodEntry, Access.get(access)); + this.methods.put(methodEntry.getOwnerClassEntry(), methodEntry); - // is there just one? - if (methodCalls.size() != 1) { - return null; + if (new AccessFlags(access).isSynthetic()) { + syntheticMethods.add(methodEntry); } - MethodCall call = methodCalls.get(0); - try { - // we have a bridge method! - return call.getMethod(); - } catch (NotFoundException ex) { - // can't find the type? not a bridge method - return null; + // we don't care about constructors here + if (!methodEntry.isConstructor()) { + // index implementation + this.methodImplementations.put(methodEntry.getClassName(), methodEntry); } } - private ClassEntry findOuterClass(CtClass c) { - - ClassEntry classEntry = EntryFactory.getClassEntry(c); - - // does this class already have an outer class? - if (classEntry.isInnerClass()) { - return classEntry.getOuterClassEntry(); + protected void indexMethodCall(MethodDefEntry callerEntry, String owner, String name, String desc) { + MethodEntry referencedMethod = new MethodEntry(entryPool.getClass(owner), name, new MethodDescriptor(desc)); + ClassEntry resolvedClassEntry = translationIndex.resolveEntryOwner(referencedMethod); + if (resolvedClassEntry != null && !resolvedClassEntry.equals(referencedMethod.getOwnerClassEntry())) { + referencedMethod = referencedMethod.updateOwnership(resolvedClassEntry); } - - // inner classes: - // have constructors that can (illegally) set synthetic fields - // the outer class is the only class that calls constructors - - // use the synthetic fields to find the synthetic constructors - for (CtConstructor constructor : c.getDeclaredConstructors()) { - Set syntheticFieldTypes = Sets.newHashSet(); - if (!isIllegalConstructor(syntheticFieldTypes, constructor)) { - continue; - } - - ConstructorEntry constructorEntry = EntryFactory.getConstructorEntry(constructor); - - // gather the classes from the illegally-set synthetic fields - Set illegallySetClasses = Sets.newHashSet(); - for (String type : syntheticFieldTypes) { - if (type.startsWith("L")) { - ClassEntry outerClassEntry = new ClassEntry(type.substring(1, type.length() - 1)); - if (isSaneOuterClass(outerClassEntry, classEntry)) { - illegallySetClasses.add(outerClassEntry); - } - } - } - - // who calls this constructor? - Set callerClasses = Sets.newHashSet(); - for (EntryReference reference : getBehaviorReferences(constructorEntry)) { - - // make sure it's not a call to super - if (reference.entry instanceof ConstructorEntry && reference.context instanceof ConstructorEntry) { - - // is the entry a superclass of the context? - ClassEntry calledClassEntry = reference.entry.getClassEntry(); - ClassEntry superclassEntry = this.translationIndex.getSuperclass(reference.context.getClassEntry()); - if (superclassEntry != null && superclassEntry.equals(calledClassEntry)) { - // it's a super call, skip - continue; - } - } - - if (isSaneOuterClass(reference.context.getClassEntry(), classEntry)) { - callerClasses.add(reference.context.getClassEntry()); - } - } - - // do we have an answer yet? - if (callerClasses.isEmpty()) { - if (illegallySetClasses.size() == 1) { - return illegallySetClasses.iterator().next(); - } else { - System.out.println(String.format("WARNING: Unable to find outer class for %s. No caller and no illegally set field classes.", classEntry)); - } - } else { - if (callerClasses.size() == 1) { - return callerClasses.iterator().next(); - } else { - // multiple callers, do the illegally set classes narrow it down? - Set intersection = Sets.newHashSet(callerClasses); - intersection.retainAll(illegallySetClasses); - if (intersection.size() == 1) { - return intersection.iterator().next(); - } else { - System.out.println(String.format("WARNING: Unable to choose outer class for %s among options: %s", classEntry, callerClasses)); - } - } - } - } - - return null; + methodsReferencing.put(referencedMethod, new EntryReference<>(referencedMethod, referencedMethod.getName(), callerEntry)); + methodReferences.put(callerEntry, referencedMethod); } - private boolean isSaneOuterClass(ClassEntry outerClassEntry, ClassEntry innerClassEntry) { - - // clearly this would be silly - if (outerClassEntry.equals(innerClassEntry)) { - return false; + protected void indexFieldAccess(MethodDefEntry callerEntry, String owner, String name, String desc) { + FieldEntry referencedField = new FieldEntry(entryPool.getClass(owner), name, new TypeDescriptor(desc)); + ClassEntry resolvedClassEntry = translationIndex.resolveEntryOwner(referencedField); + if (resolvedClassEntry != null && !resolvedClassEntry.equals(referencedField.getOwnerClassEntry())) { + referencedField = referencedField.updateOwnership(resolvedClassEntry); } - - // is the outer class in the jar? - return this.obfClassEntries.contains(outerClassEntry); - + fieldReferences.put(referencedField, new EntryReference<>(referencedField, referencedField.getName(), callerEntry)); } - @SuppressWarnings("unchecked") - private boolean isIllegalConstructor(Set syntheticFieldTypes, CtConstructor constructor) { + public void indexInnerClass(ClassEntry innerEntry, ClassEntry outerEntry) { + this.innerClassesByOuter.put(outerEntry, innerEntry); + this.outerClassesByInner.putIfAbsent(innerEntry, outerEntry); + } - // illegal constructors only set synthetic member fields, then call super() - String className = constructor.getDeclaringClass().getName(); + private MethodEntry findAccessMethod(MethodDefEntry method) { - // collect all the field accesses, constructor calls, and method calls - final List illegalFieldWrites = Lists.newArrayList(); - final List constructorCalls = Lists.newArrayList(); - try { - constructor.instrument(new ExprEditor() { - @Override - public void edit(FieldAccess fieldAccess) { - if (fieldAccess.isWriter() && constructorCalls.isEmpty()) { - illegalFieldWrites.add(fieldAccess); - } - } + // we want to find all compiler-added methods that directly call another with no processing - @Override - public void edit(ConstructorCall constructorCall) { - constructorCalls.add(constructorCall); - } - }); - } catch (CannotCompileException ex) { - // we're not compiling anything... this is stupid - throw new Error(ex); - } - - // are there any illegal field writes? - if (illegalFieldWrites.isEmpty()) { - return false; + // skip non-synthetic methods + if (!method.getAccess().isSynthetic()) { + return null; } - // are all the writes to synthetic fields? - for (FieldAccess fieldWrite : illegalFieldWrites) { - - // all illegal writes have to be to the local class - if (!fieldWrite.getClassName().equals(className)) { - System.err.println(String.format("WARNING: illegal write to non-member field %s.%s", fieldWrite.getClassName(), fieldWrite.getFieldName())); - return false; - } - - // find the field - FieldInfo fieldInfo = null; - for (FieldInfo info : (List) constructor.getDeclaringClass().getClassFile().getFields()) { - if (info.getName().equals(fieldWrite.getFieldName()) && info.getDescriptor().equals(fieldWrite.getSignature())) { - fieldInfo = info; - break; - } - } - if (fieldInfo == null) { - // field is in a superclass or something, can't be a local synthetic member - return false; - } + // get all the methods that we call + final Collection referencedMethods = methodReferences.get(method); - // is this field synthetic? - boolean isSynthetic = (fieldInfo.getAccessFlags() & AccessFlag.SYNTHETIC) != 0; - if (isSynthetic) { - syntheticFieldTypes.add(fieldInfo.getDescriptor()); - } else { - System.err.println(String.format("WARNING: illegal write to non synthetic field %s %s.%s", fieldInfo.getDescriptor(), className, fieldInfo.getName())); - return false; - } + // is there just one? + if (referencedMethods.size() != 1) { + return null; } - // we passed all the tests! - return true; + return referencedMethods.stream().findFirst().orElse(null); } - private BehaviorEntry isAnonymousClass(CtClass c, ClassEntry outerClassEntry) { - - // is this class already marked anonymous? - EnclosingMethodAttribute enclosingMethodAttribute = (EnclosingMethodAttribute) c.getClassFile().getAttribute(EnclosingMethodAttribute.tag); - if (enclosingMethodAttribute != null) { - if (enclosingMethodAttribute.methodIndex() > 0) { - return EntryFactory.getBehaviorEntry( - Descriptor.toJvmName(enclosingMethodAttribute.className()), - enclosingMethodAttribute.methodName(), - enclosingMethodAttribute.methodDescriptor() - ); - } else { - // an attribute but no method? assume not anonymous - return null; - } - } - - // if there's an inner class attribute, but not an enclosing method attribute, then it's not anonymous - InnerClassesAttribute innerClassesAttribute = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag); - if (innerClassesAttribute != null) { - return null; + private boolean isBridgedMethod(MethodEntry called, MethodEntry access) { + // Bridged methods will always have the same name as the method they are calling + // They will also have the same amount of parameters (though equal descriptors cannot be guaranteed) + if (!called.getName().equals(access.getName()) || called.getDesc().getArgumentDescs().size() != access.getDesc().getArgumentDescs().size()) { + return false; } - ClassEntry innerClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); - - // anonymous classes: - // can't be abstract - // have only one constructor - // it's called exactly once by the outer class - // the type the instance is assigned to can't be this type - - // is abstract? - if (Modifier.isAbstract(c.getModifiers())) { - return null; + TypeDescriptor accessReturn = access.getDesc().getReturnDesc(); + TypeDescriptor calledReturn = called.getDesc().getReturnDesc(); + if (calledReturn.isVoid() || calledReturn.isPrimitive() || accessReturn.isVoid() || accessReturn.isPrimitive()) { + return false; } - // is there exactly one constructor? - if (c.getDeclaredConstructors().length != 1) { - return null; + // Bridged methods will never have the same type as what they are calling + if (accessReturn.equals(calledReturn)) { + return false; } - CtConstructor constructor = c.getDeclaredConstructors()[0]; - // is this constructor called exactly once? - ConstructorEntry constructorEntry = EntryFactory.getConstructorEntry(constructor); - Collection> references = getBehaviorReferences(constructorEntry); - if (references.size() != 1) { - return null; - } + String accessType = accessReturn.toString(); - // does the caller use this type? - BehaviorEntry caller = references.iterator().next().context; - for (FieldEntry fieldEntry : getReferencedFields(caller)) { - if (fieldEntry.getType().hasClass() && fieldEntry.getType().getClassEntry().equals(innerClassEntry)) { - // caller references this type, so it can't be anonymous - return null; - } - } - for (BehaviorEntry behaviorEntry : getReferencedBehaviors(caller)) { - if (behaviorEntry.getSignature().hasClass(innerClassEntry)) { - return null; - } + // If we're casting down from generic type to type-erased Object we're a bridge method + if (accessType.equals("Ljava/lang/Object;")) { + return true; } - return caller; + // Now we need to detect cases where we are being casted down to a higher type bound + List calledAncestry = translationIndex.getAncestry(calledReturn.getTypeEntry()); + return calledAncestry.contains(accessReturn.getTypeEntry()); } public Set getObfClassEntries() { return this.obfClassEntries; } - public Collection getObfFieldEntries() { + public Collection getObfFieldEntries() { return this.fields.values(); } - public Collection getObfFieldEntries(ClassEntry classEntry) { + public Collection getObfFieldEntries(ClassEntry classEntry) { return this.fields.get(classEntry); } - public Collection getObfBehaviorEntries() { - return this.behaviors.values(); + public Collection getObfBehaviorEntries() { + return this.methods.values(); } - public Collection getObfBehaviorEntries(ClassEntry classEntry) { - return this.behaviors.get(classEntry); + public Collection getObfBehaviorEntries(ClassEntry classEntry) { + return this.methods.get(classEntry); } public TranslationIndex getTranslationIndex() { @@ -533,8 +246,8 @@ public class JarIndex { } } ClassInheritanceTreeNode rootNode = new ClassInheritanceTreeNode( - deobfuscatingTranslator, - ancestry.get(ancestry.size() - 1) + deobfuscatingTranslator, + ancestry.get(ancestry.size() - 1) ); // expand all children recursively @@ -557,28 +270,20 @@ public class JarIndex { public MethodInheritanceTreeNode getMethodInheritance(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) { // travel to the ancestor implementation - ClassEntry baseImplementationClassEntry = obfMethodEntry.getClassEntry(); - for (ClassEntry ancestorClassEntry : this.translationIndex.getAncestry(obfMethodEntry.getClassEntry())) { - MethodEntry ancestorMethodEntry = new MethodEntry( - new ClassEntry(ancestorClassEntry), - obfMethodEntry.getName(), - obfMethodEntry.getSignature() - ); - if (containsObfBehavior(ancestorMethodEntry)) { + ClassEntry baseImplementationClassEntry = obfMethodEntry.getOwnerClassEntry(); + for (ClassEntry ancestorClassEntry : this.translationIndex.getAncestry(obfMethodEntry.getOwnerClassEntry())) { + MethodEntry ancestorMethodEntry = entryPool.getMethod(ancestorClassEntry, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString()); + if (ancestorMethodEntry != null && containsObfMethod(ancestorMethodEntry)) { baseImplementationClassEntry = ancestorClassEntry; } } // make a root node at the base - MethodEntry methodEntry = new MethodEntry( - baseImplementationClassEntry, - obfMethodEntry.getName(), - obfMethodEntry.getSignature() - ); + MethodEntry methodEntry = entryPool.getMethod(baseImplementationClassEntry, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString()); MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode( - deobfuscatingTranslator, - methodEntry, - containsObfBehavior(methodEntry) + deobfuscatingTranslator, + methodEntry, + containsObfMethod(methodEntry) ); // expand the full tree @@ -599,12 +304,8 @@ public class JarIndex { for (ClassEntry interfaceEntry : getInterfaces(obfMethodEntry.getClassName())) { // is this method defined in this interface? - MethodEntry methodInterface = new MethodEntry( - interfaceEntry, - obfMethodEntry.getName(), - obfMethodEntry.getSignature() - ); - if (containsObfBehavior(methodInterface)) { + MethodEntry methodInterface = entryPool.getMethod(interfaceEntry, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString()); + if (methodInterface != null && containsObfMethod(methodInterface)) { interfaceMethodEntries.add(methodInterface); } } @@ -623,27 +324,30 @@ public class JarIndex { public Set getRelatedMethodImplementations(MethodEntry obfMethodEntry) { Set methodEntries = Sets.newHashSet(); - getRelatedMethodImplementations(methodEntries, getMethodInheritance(new Translator(), obfMethodEntry)); + getRelatedMethodImplementations(methodEntries, getMethodInheritance(new DirectionalTranslator(entryPool), obfMethodEntry)); return methodEntries; } private void getRelatedMethodImplementations(Set methodEntries, MethodInheritanceTreeNode node) { MethodEntry methodEntry = node.getMethodEntry(); + if (methodEntries.contains(methodEntry)) { + return; + } - if (containsObfBehavior(methodEntry)) { + if (containsObfMethod(methodEntry)) { // collect the entry methodEntries.add(methodEntry); } - // look at bridged methods! - MethodEntry bridgedEntry = getBridgedMethod(methodEntry); - while (bridgedEntry != null) { - methodEntries.addAll(getRelatedMethodImplementations(bridgedEntry)); - bridgedEntry = getBridgedMethod(bridgedEntry); + // look at bridge methods! + MethodEntry bridgedMethod = getBridgedMethod(methodEntry); + while (bridgedMethod != null) { + methodEntries.addAll(getRelatedMethodImplementations(bridgedMethod)); + bridgedMethod = getBridgedMethod(bridgedMethod); } // look at interface methods too - for (MethodImplementationsTreeNode implementationsNode : getMethodImplementations(new Translator(), methodEntry)) { + for (MethodImplementationsTreeNode implementationsNode : getMethodImplementations(new DirectionalTranslator(entryPool), methodEntry)) { getRelatedMethodImplementations(methodEntries, implementationsNode); } @@ -655,16 +359,16 @@ public class JarIndex { private void getRelatedMethodImplementations(Set methodEntries, MethodImplementationsTreeNode node) { MethodEntry methodEntry = node.getMethodEntry(); - if (containsObfBehavior(methodEntry)) { + if (containsObfMethod(methodEntry)) { // collect the entry methodEntries.add(methodEntry); } - // look at bridged methods! - MethodEntry bridgedEntry = getBridgedMethod(methodEntry); - while (bridgedEntry != null) { - methodEntries.addAll(getRelatedMethodImplementations(bridgedEntry)); - bridgedEntry = getBridgedMethod(bridgedEntry); + // look at bridge methods! + MethodEntry bridgedMethod = getBridgedMethod(methodEntry); + while (bridgedMethod != null) { + methodEntries.addAll(getRelatedMethodImplementations(bridgedMethod)); + bridgedMethod = getBridgedMethod(bridgedMethod); } // recurse @@ -673,34 +377,27 @@ public class JarIndex { } } - public Collection> getFieldReferences(FieldEntry fieldEntry) { + public Collection> getFieldReferences(FieldEntry fieldEntry) { return this.fieldReferences.get(fieldEntry); } - public Collection getReferencedFields(BehaviorEntry behaviorEntry) { + public Collection getReferencedFields(MethodDefEntry methodEntry) { // linear search is fast enough for now Set fieldEntries = Sets.newHashSet(); - for (EntryReference reference : this.fieldReferences.values()) { - if (reference.context == behaviorEntry) { + for (EntryReference reference : this.fieldReferences.values()) { + if (reference.context == methodEntry) { fieldEntries.add(reference.entry); } } return fieldEntries; } - public Collection> getBehaviorReferences(BehaviorEntry behaviorEntry) { - return this.behaviorReferences.get(behaviorEntry); + public Collection> getMethodsReferencing(MethodEntry methodEntry) { + return this.methodsReferencing.get(methodEntry); } - public Collection getReferencedBehaviors(BehaviorEntry behaviorEntry) { - // linear search is fast enough for now - Set behaviorEntries = Sets.newHashSet(); - for (EntryReference reference : this.behaviorReferences.values()) { - if (reference.context == behaviorEntry) { - behaviorEntries.add(reference.entry); - } - } - return behaviorEntries; + public Collection getReferencedMethods(MethodDefEntry methodEntry) { + return this.methodReferences.get(methodEntry); } public Collection getInnerClasses(ClassEntry obfOuterClassEntry) { @@ -711,22 +408,13 @@ public class JarIndex { return this.outerClassesByInner.get(obfInnerClassEntry); } - public boolean isAnonymousClass(ClassEntry obfInnerClassEntry) { - return this.anonymousClasses.containsKey(obfInnerClassEntry); - } - public boolean isSyntheticMethod(MethodEntry methodEntry) { return this.syntheticMethods.contains(methodEntry); } - public BehaviorEntry getAnonymousClassCaller(ClassEntry obfInnerClassName) { - return this.anonymousClasses.get(obfInnerClassName); - } - public Set getInterfaces(String className) { - ClassEntry classEntry = new ClassEntry(className); - Set interfaces = new HashSet<>(); - interfaces.addAll(this.translationIndex.getInterfaces(classEntry)); + ClassEntry classEntry = entryPool.getClass(className); + Set interfaces = new HashSet<>(this.translationIndex.getInterfaces(classEntry)); for (ClassEntry ancestor : this.translationIndex.getAncestry(classEntry)) { interfaces.addAll(this.translationIndex.getInterfaces(ancestor)); } @@ -754,7 +442,7 @@ public class JarIndex { } public boolean isInterface(String className) { - return this.translationIndex.isInterface(new ClassEntry(className)); + return this.translationIndex.isInterface(entryPool.getClass(className)); } public boolean containsObfClass(ClassEntry obfClassEntry) { @@ -765,8 +453,8 @@ public class JarIndex { return this.access.containsKey(obfFieldEntry); } - public boolean containsObfBehavior(BehaviorEntry obfBehaviorEntry) { - return this.access.containsKey(obfBehaviorEntry); + public boolean containsObfMethod(MethodEntry obfMethodEntry) { + return this.access.containsKey(obfMethodEntry); } public boolean containsEntryWithSameName(Entry entry) { @@ -776,15 +464,13 @@ public class JarIndex { return false; } - public boolean containsObfArgument(ArgumentEntry obfArgumentEntry) { + public boolean containsObfVariable(LocalVariableEntry obfVariableEntry) { // check the behavior - if (!containsObfBehavior(obfArgumentEntry.getBehaviorEntry())) { + if (!containsObfMethod(obfVariableEntry.getOwnerEntry())) { return false; } - // check the argument - return obfArgumentEntry.getIndex() < obfArgumentEntry.getBehaviorEntry().getSignature().getArgumentTypes().size(); - + return true; } public boolean containsObfEntry(Entry obfEntry) { @@ -792,15 +478,12 @@ public class JarIndex { return containsObfClass((ClassEntry) obfEntry); } else if (obfEntry instanceof FieldEntry) { return containsObfField((FieldEntry) obfEntry); - } else if (obfEntry instanceof BehaviorEntry) { - return containsObfBehavior((BehaviorEntry) obfEntry); - } else if (obfEntry instanceof ArgumentEntry) { - return containsObfArgument((ArgumentEntry) obfEntry); + } else if (obfEntry instanceof MethodEntry) { + return containsObfMethod((MethodEntry) obfEntry); } else if (obfEntry instanceof LocalVariableEntry) { - // TODO: Implement it - return false; + return containsObfVariable((LocalVariableEntry) obfEntry); } else { - throw new Error("Entry type not supported: " + obfEntry.getClass().getName()); + throw new Error("Entry desc not supported: " + obfEntry.getClass().getName()); } } diff --git a/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java b/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java index bacb1aac..723fffed 100644 --- a/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java +++ b/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java @@ -12,8 +12,8 @@ package cuchaz.enigma.analysis; import com.google.common.collect.Lists; -import cuchaz.enigma.mapping.ClassEntry; -import cuchaz.enigma.mapping.MethodEntry; +import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.mapping.entry.MethodEntry; import cuchaz.enigma.mapping.Translator; import javax.swing.tree.DefaultMutableTreeNode; @@ -54,11 +54,11 @@ public class MethodImplementationsTreeNode extends DefaultMutableTreeNode { } public String getDeobfClassName() { - return this.deobfuscatingTranslator.translateClass(this.entry.getClassName()); + return this.deobfuscatingTranslator.getTranslatedClass(this.entry.getOwnerClassEntry()).getClassName(); } public String getDeobfMethodName() { - return this.deobfuscatingTranslator.translate(this.entry); + return this.deobfuscatingTranslator.getTranslatedMethod(this.entry).getName(); } @Override @@ -80,9 +80,8 @@ public class MethodImplementationsTreeNode extends DefaultMutableTreeNode { // get all method implementations List nodes = Lists.newArrayList(); for (String implementingClassName : index.getImplementingClasses(this.entry.getClassName())) { - MethodEntry methodEntry = new MethodEntry(new ClassEntry(implementingClassName), this.entry.getName(), this.entry.getSignature() - ); - if (index.containsObfBehavior(methodEntry)) { + MethodEntry methodEntry = new MethodEntry(new ClassEntry(implementingClassName), this.entry.getName(), this.entry.getDesc()); + if (index.containsObfMethod(methodEntry)) { nodes.add(new MethodImplementationsTreeNode(this.deobfuscatingTranslator, methodEntry)); } } diff --git a/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java index 4f84dd09..904e5945 100644 --- a/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java +++ b/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java @@ -12,8 +12,8 @@ package cuchaz.enigma.analysis; import com.google.common.collect.Lists; -import cuchaz.enigma.mapping.ClassEntry; -import cuchaz.enigma.mapping.MethodEntry; +import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.mapping.entry.MethodEntry; import cuchaz.enigma.mapping.Translator; import javax.swing.tree.DefaultMutableTreeNode; @@ -52,11 +52,11 @@ public class MethodInheritanceTreeNode extends DefaultMutableTreeNode { } public String getDeobfClassName() { - return this.deobfuscatingTranslator.translateClass(this.entry.getClassName()); + return this.deobfuscatingTranslator.getTranslatedClass(this.entry.getOwnerClassEntry()).getName(); } public String getDeobfMethodName() { - return this.deobfuscatingTranslator.translate(this.entry); + return this.deobfuscatingTranslator.getTranslatedMethod(this.entry).getName(); } public boolean isImplemented() { @@ -84,11 +84,9 @@ public class MethodInheritanceTreeNode extends DefaultMutableTreeNode { public void load(JarIndex index, boolean recurse) { // get all the child nodes List nodes = Lists.newArrayList(); - for (ClassEntry subclassEntry : index.getTranslationIndex().getSubclass(this.entry.getClassEntry())) { - MethodEntry methodEntry = new MethodEntry(subclassEntry, this.entry.getName(), this.entry.getSignature() - ); - nodes.add(new MethodInheritanceTreeNode(this.deobfuscatingTranslator, methodEntry, index.containsObfBehavior(methodEntry) - )); + for (ClassEntry subclassEntry : index.getTranslationIndex().getSubclass(this.entry.getOwnerClassEntry())) { + MethodEntry methodEntry = new MethodEntry(subclassEntry, this.entry.getName(), this.entry.getDesc()); + nodes.add(new MethodInheritanceTreeNode(this.deobfuscatingTranslator, methodEntry, index.containsObfMethod(methodEntry))); } // add them to this node diff --git a/src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java new file mode 100644 index 00000000..76c73c15 --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java @@ -0,0 +1,94 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.analysis; + +import com.google.common.collect.Sets; +import cuchaz.enigma.mapping.*; +import cuchaz.enigma.mapping.entry.Entry; +import cuchaz.enigma.mapping.entry.MethodDefEntry; +import cuchaz.enigma.mapping.entry.MethodEntry; + +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.TreeNode; +import java.util.Set; + +public class MethodReferenceTreeNode extends DefaultMutableTreeNode + implements ReferenceTreeNode { + + private Translator deobfuscatingTranslator; + private MethodEntry entry; + private EntryReference reference; + private Access access; + + public MethodReferenceTreeNode(Translator deobfuscatingTranslator, MethodEntry entry) { + this.deobfuscatingTranslator = deobfuscatingTranslator; + this.entry = entry; + this.reference = null; + } + + public MethodReferenceTreeNode(Translator deobfuscatingTranslator, + EntryReference reference, Access access) { + this.deobfuscatingTranslator = deobfuscatingTranslator; + this.entry = reference.entry; + this.reference = reference; + this.access = access; + } + + @Override + public MethodEntry getEntry() { + return this.entry; + } + + @Override + public EntryReference getReference() { + return this.reference; + } + + @Override + public String toString() { + if (this.reference != null) { + return String.format("%s (%s)", this.deobfuscatingTranslator.getTranslatedMethodDef(this.reference.context), + this.access); + } + return this.deobfuscatingTranslator.getTranslatedMethod(this.entry).getName(); + } + + public void load(JarIndex index, boolean recurse) { + // get all the child nodes + for (EntryReference reference : index.getMethodsReferencing(this.entry)) { + add(new MethodReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccess(this.entry))); + } + + if (recurse && this.children != null) { + for (Object child : this.children) { + if (child instanceof MethodReferenceTreeNode) { + MethodReferenceTreeNode node = (MethodReferenceTreeNode) child; + + // don't recurse into ancestor + Set ancestors = Sets.newHashSet(); + TreeNode n = node; + while (n.getParent() != null) { + n = n.getParent(); + if (n instanceof MethodReferenceTreeNode) { + ancestors.add(((MethodReferenceTreeNode) n).getEntry()); + } + } + if (ancestors.contains(node.getEntry())) { + continue; + } + + node.load(index, true); + } + } + } + } +} diff --git a/src/main/java/cuchaz/enigma/analysis/ParsedJar.java b/src/main/java/cuchaz/enigma/analysis/ParsedJar.java new file mode 100644 index 00000000..55f2141b --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/ParsedJar.java @@ -0,0 +1,95 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.analysis; + +import cuchaz.enigma.mapping.entry.ClassEntry; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.tree.ClassNode; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.*; +import java.util.function.Consumer; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarInputStream; + +public class ParsedJar { + private final Map nodes = new LinkedHashMap<>(); + + public ParsedJar(JarFile jar) throws IOException { + try { + // get the jar entries that correspond to classes + Enumeration entries = jar.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + // is this a class file? + if (entry.getName().endsWith(".class")) { + try (InputStream input = new BufferedInputStream(jar.getInputStream(entry))) { + // read the ClassNode from the jar + ClassReader reader = new ClassReader(input); + ClassNode node = new ClassNode(); + reader.accept(node, 0); + String path = entry.getName().substring(0, entry.getName().length() - ".class".length()); + nodes.put(path, node); + } + } + } + } finally { + jar.close(); + } + } + + public ParsedJar(JarInputStream jar) throws IOException { + try { + // get the jar entries that correspond to classes + JarEntry entry; + while ((entry = jar.getNextJarEntry()) != null) { + // is this a class file? + if (entry.getName().endsWith(".class")) { + // read the ClassNode from the jar + ClassReader reader = new ClassReader(jar); + ClassNode node = new ClassNode(); + reader.accept(node, 0); + String path = entry.getName().substring(0, entry.getName().length() - ".class".length()); + nodes.put(path, node); + jar.closeEntry(); + } + } + } finally { + jar.close(); + } + } + + public void visit(Consumer visitor) { + for (ClassNode node : nodes.values()) { + visitor.accept(node); + } + } + + public int getClassCount() { + return nodes.size(); + } + + public List getClassEntries() { + List entries = new ArrayList<>(nodes.size()); + for (ClassNode node : nodes.values()) { + entries.add(new ClassEntry(node.name)); + } + return entries; + } + + public ClassNode getClassNode(String name) { + return nodes.get(name); + } +} diff --git a/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java index 04693637..3950d165 100644 --- a/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java +++ b/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java @@ -11,7 +11,7 @@ package cuchaz.enigma.analysis; -import cuchaz.enigma.mapping.Entry; +import cuchaz.enigma.mapping.entry.Entry; public interface ReferenceTreeNode { E getEntry(); diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndex.java b/src/main/java/cuchaz/enigma/analysis/SourceIndex.java index 19250c8d..14b2e768 100644 --- a/src/main/java/cuchaz/enigma/analysis/SourceIndex.java +++ b/src/main/java/cuchaz/enigma/analysis/SourceIndex.java @@ -18,7 +18,7 @@ import com.google.common.collect.Multimap; import com.strobel.decompiler.languages.Region; import com.strobel.decompiler.languages.java.ast.AstNode; import com.strobel.decompiler.languages.java.ast.Identifier; -import cuchaz.enigma.mapping.Entry; +import cuchaz.enigma.mapping.entry.Entry; import java.util.Collection; import java.util.List; diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java b/src/main/java/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java deleted file mode 100644 index 1b619164..00000000 --- a/src/main/java/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java +++ /dev/null @@ -1,204 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.analysis; - -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Multimap; -import com.strobel.assembler.metadata.MemberReference; -import com.strobel.assembler.metadata.MethodReference; -import com.strobel.assembler.metadata.ParameterDefinition; -import com.strobel.assembler.metadata.TypeReference; -import com.strobel.decompiler.languages.TextLocation; -import com.strobel.decompiler.languages.java.ast.*; -import cuchaz.enigma.mapping.*; -import javassist.bytecode.Descriptor; - -import java.util.HashMap; -import java.util.Map; - -public class SourceIndexBehaviorVisitor extends SourceIndexVisitor { - private BehaviorEntry behaviorEntry; - - // TODO: Really fix Procyon index problem with inner classes - private int argumentPosition; - private int localsPosition; - private Multimap unmatchedIdentifier = HashMultimap.create(); - private Map identifierEntryCache = new HashMap<>(); - - public SourceIndexBehaviorVisitor(BehaviorEntry behaviorEntry, boolean isEnum) { - this.behaviorEntry = behaviorEntry; - this.argumentPosition = isEnum ? 2 : 0; - this.localsPosition = 0; - } - - @Override - public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) { - MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); - - // get the behavior entry - ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); - BehaviorEntry behaviorEntry = null; - if (ref instanceof MethodReference) { - MethodReference methodRef = (MethodReference) ref; - if (methodRef.isConstructor()) { - behaviorEntry = new ConstructorEntry(classEntry, new Signature(ref.getErasedSignature())); - } else if (methodRef.isTypeInitializer()) { - behaviorEntry = new ConstructorEntry(classEntry); - } else { - behaviorEntry = new MethodEntry(classEntry, ref.getName(), new Signature(ref.getErasedSignature())); - } - } - if (behaviorEntry != null) { - // get the node for the token - AstNode tokenNode = null; - if (node.getTarget() instanceof MemberReferenceExpression) { - tokenNode = ((MemberReferenceExpression) node.getTarget()).getMemberNameToken(); - } else if (node.getTarget() instanceof SuperReferenceExpression) { - tokenNode = node.getTarget(); - } else if (node.getTarget() instanceof ThisReferenceExpression) { - tokenNode = node.getTarget(); - } - if (tokenNode != null) { - index.addReference(tokenNode, behaviorEntry, this.behaviorEntry); - } - } - - // Check for identifier - node.getArguments().stream().filter(expression -> expression instanceof IdentifierExpression) - .forEach(expression -> this.checkIdentifier((IdentifierExpression) expression, index)); - return recurse(node, index); - } - - @Override - public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) { - MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); - if (ref != null) { - // make sure this is actually a field - if (ref.getErasedSignature().indexOf('(') >= 0) { - throw new Error("Expected a field here! got " + ref); - } - - ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); - FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new Type(ref.getErasedSignature())); - index.addReference(node.getMemberNameToken(), fieldEntry, this.behaviorEntry); - } - - return recurse(node, index); - } - - @Override - public Void visitSimpleType(SimpleType node, SourceIndex index) { - TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE); - if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) { - ClassEntry classEntry = new ClassEntry(ref.getInternalName()); - index.addReference(node.getIdentifierToken(), classEntry, this.behaviorEntry); - } - - return recurse(node, index); - } - - @Override - public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) { - ParameterDefinition def = node.getUserData(Keys.PARAMETER_DEFINITION); - if (def.getMethod() instanceof MemberReference && def.getMethod() instanceof MethodReference) { - ArgumentEntry argumentEntry = new ArgumentEntry(ProcyonEntryFactory.getBehaviorEntry((MethodReference) def.getMethod()), - argumentPosition++, node.getName()); - Identifier identifier = node.getNameToken(); - // cache the argument entry and the identifier - identifierEntryCache.put(identifier.getName(), argumentEntry); - index.addDeclaration(identifier, argumentEntry); - } - - return recurse(node, index); - } - - @Override - public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) { - MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); - if (ref != null) { - ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); - FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new Type(ref.getErasedSignature())); - index.addReference(node.getIdentifierToken(), fieldEntry, this.behaviorEntry); - } else - this.checkIdentifier(node, index); - return recurse(node, index); - } - - private void checkIdentifier(IdentifierExpression node, SourceIndex index) { - if (identifierEntryCache.containsKey(node.getIdentifier())) // If it's in the argument cache, create a token! - index.addDeclaration(node.getIdentifierToken(), identifierEntryCache.get(node.getIdentifier())); - else - unmatchedIdentifier.put(node.getIdentifier(), node.getIdentifierToken()); // Not matched actually, put it! - } - - private void addDeclarationToUnmatched(String key, SourceIndex index) { - Entry entry = identifierEntryCache.get(key); - - // This cannot happened in theory - if (entry == null) - return; - for (Identifier identifier : unmatchedIdentifier.get(key)) - index.addDeclaration(identifier, entry); - unmatchedIdentifier.removeAll(key); - } - - @Override - public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) { - MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); - if (ref != null) { - ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); - ConstructorEntry constructorEntry = new ConstructorEntry(classEntry, new Signature(ref.getErasedSignature())); - if (node.getType() instanceof SimpleType) { - SimpleType simpleTypeNode = (SimpleType) node.getType(); - index.addReference(simpleTypeNode.getIdentifierToken(), constructorEntry, this.behaviorEntry); - } - } - - return recurse(node, index); - } - - @Override - public Void visitForEachStatement(ForEachStatement node, SourceIndex index) { - if (node.getVariableType() instanceof SimpleType) { - SimpleType type = (SimpleType) node.getVariableType(); - TypeReference typeReference = type.getUserData(Keys.TYPE_REFERENCE); - Identifier identifier = node.getVariableNameToken(); - String signature = Descriptor.of(typeReference.getErasedDescription()); - LocalVariableEntry localVariableEntry = new LocalVariableEntry(behaviorEntry, argumentPosition + localsPosition++, identifier.getName(), new Type(signature)); - identifierEntryCache.put(identifier.getName(), localVariableEntry); - addDeclarationToUnmatched(identifier.getName(), index); - index.addDeclaration(identifier, localVariableEntry); - } - return recurse(node, index); - } - - @Override - public Void visitVariableDeclaration(VariableDeclarationStatement node, SourceIndex index) { - AstNodeCollection variables = node.getVariables(); - - // Single assignation - if (variables.size() == 1) { - VariableInitializer initializer = variables.firstOrNullObject(); - if (initializer != null && node.getType() instanceof SimpleType) { - SimpleType type = (SimpleType) node.getType(); - TypeReference typeReference = type.getUserData(Keys.TYPE_REFERENCE); - String signature = Descriptor.of(typeReference.getErasedDescription()); - Identifier identifier = initializer.getNameToken(); - LocalVariableEntry localVariableEntry = new LocalVariableEntry(behaviorEntry, argumentPosition + localsPosition++, initializer.getName(), new Type(signature)); - identifierEntryCache.put(identifier.getName(), localVariableEntry); - addDeclarationToUnmatched(identifier.getName(), index); - index.addDeclaration(identifier, localVariableEntry); - } - } - return recurse(node, index); - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java b/src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java index b13415da..dd5bcef0 100644 --- a/src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java +++ b/src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java @@ -17,14 +17,21 @@ import com.strobel.assembler.metadata.TypeDefinition; import com.strobel.assembler.metadata.TypeReference; import com.strobel.decompiler.languages.TextLocation; import com.strobel.decompiler.languages.java.ast.*; -import cuchaz.enigma.mapping.*; +import cuchaz.enigma.bytecode.AccessFlags; +import cuchaz.enigma.mapping.Signature; +import cuchaz.enigma.mapping.entry.*; public class SourceIndexClassVisitor extends SourceIndexVisitor { + private final ReferencedEntryPool entryPool; + private final ProcyonEntryFactory entryFactory; - private ClassEntry classEntry; + private ClassDefEntry classEntry; private boolean isEnum; - public SourceIndexClassVisitor(ClassEntry classEntry) { + public SourceIndexClassVisitor(ReferencedEntryPool entryPool, ClassDefEntry classEntry) { + super(entryPool); + this.entryPool = entryPool; + this.entryFactory = new ProcyonEntryFactory(entryPool); this.classEntry = classEntry; } @@ -32,11 +39,11 @@ public class SourceIndexClassVisitor extends SourceIndexVisitor { public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) { // is this this class, or a subtype? TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION); - ClassEntry classEntry = new ClassEntry(def.getInternalName()); + ClassDefEntry classEntry = new ClassDefEntry(def.getInternalName(), Signature.createSignature(def.getSignature()), new AccessFlags(def.getModifiers())); if (!classEntry.equals(this.classEntry)) { - // it's a sub-type, recurse + // it's a subtype, recurse index.addDeclaration(node.getNameToken(), classEntry); - return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index); + return node.acceptVisitor(new SourceIndexClassVisitor(entryPool, classEntry), index); } return recurse(node, index); @@ -56,31 +63,28 @@ public class SourceIndexClassVisitor extends SourceIndexVisitor { @Override public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) { MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION); - BehaviorEntry behaviorEntry = ProcyonEntryFactory.getBehaviorEntry(def); + MethodDefEntry methodEntry = entryFactory.getMethodDefEntry(def); AstNode tokenNode = node.getNameToken(); - if (behaviorEntry instanceof ConstructorEntry) { - ConstructorEntry constructorEntry = (ConstructorEntry) behaviorEntry; - if (constructorEntry.isStatic()) { - // for static initializers, check elsewhere for the token node - tokenNode = node.getModifiers().firstOrNullObject(); - } + if (methodEntry.isConstructor() && methodEntry.getName().equals("")) { + // for static initializers, check elsewhere for the token node + tokenNode = node.getModifiers().firstOrNullObject(); } - index.addDeclaration(tokenNode, behaviorEntry); - return node.acceptVisitor(new SourceIndexBehaviorVisitor(behaviorEntry, false), index); + index.addDeclaration(tokenNode, methodEntry); + return node.acceptVisitor(new SourceIndexMethodVisitor(entryPool, classEntry, methodEntry), index); } @Override public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) { MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION); - ConstructorEntry constructorEntry = ProcyonEntryFactory.getConstructorEntry(def); - index.addDeclaration(node.getNameToken(), constructorEntry); - return node.acceptVisitor(new SourceIndexBehaviorVisitor(constructorEntry, isEnum), index); + MethodDefEntry methodEntry = entryFactory.getMethodDefEntry(def); + index.addDeclaration(node.getNameToken(), methodEntry); + return node.acceptVisitor(new SourceIndexMethodVisitor(entryPool, classEntry, methodEntry), index); } @Override public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) { FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); - FieldEntry fieldEntry = ProcyonEntryFactory.getFieldEntry(def); + FieldDefEntry fieldEntry = entryFactory.getFieldDefEntry(def); assert (node.getVariables().size() == 1); VariableInitializer variable = node.getVariables().firstOrNullObject(); index.addDeclaration(variable.getNameToken(), fieldEntry); @@ -92,7 +96,7 @@ public class SourceIndexClassVisitor extends SourceIndexVisitor { public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) { // treat enum declarations as field declarations FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); - FieldEntry fieldEntry = ProcyonEntryFactory.getFieldEntry(def); + FieldDefEntry fieldEntry = entryFactory.getFieldDefEntry(def); index.addDeclaration(node.getNameToken(), fieldEntry); this.isEnum = true; return recurse(node, index); diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndexMethodVisitor.java b/src/main/java/cuchaz/enigma/analysis/SourceIndexMethodVisitor.java new file mode 100644 index 00000000..83fe296c --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/SourceIndexMethodVisitor.java @@ -0,0 +1,224 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.analysis; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.strobel.assembler.metadata.*; +import com.strobel.decompiler.ast.Variable; +import com.strobel.decompiler.languages.TextLocation; +import com.strobel.decompiler.languages.java.ast.*; +import cuchaz.enigma.mapping.TypeDescriptor; +import cuchaz.enigma.mapping.entry.*; + +import java.lang.Error; +import java.util.HashMap; +import java.util.Map; + +public class SourceIndexMethodVisitor extends SourceIndexVisitor { + private final ReferencedEntryPool entryPool; + private final ProcyonEntryFactory entryFactory; + + private final ClassDefEntry ownerEntry; + private final MethodDefEntry methodEntry; + + private Multimap unmatchedIdentifier = HashMultimap.create(); + private Map identifierEntryCache = new HashMap<>(); + + public SourceIndexMethodVisitor(ReferencedEntryPool entryPool, ClassDefEntry ownerEntry, MethodDefEntry methodEntry) { + super(entryPool); + this.entryPool = entryPool; + this.entryFactory = new ProcyonEntryFactory(entryPool); + this.ownerEntry = ownerEntry; + this.methodEntry = methodEntry; + } + + @Override + public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) { + MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); + + // get the behavior entry + ClassEntry classEntry = entryPool.getClass(ref.getDeclaringType().getInternalName()); + MethodEntry methodEntry = null; + if (ref instanceof MethodReference) { + methodEntry = entryPool.getMethod(classEntry, ref.getName(), ref.getErasedSignature()); + } + if (methodEntry != null) { + // get the node for the token + AstNode tokenNode = null; + if (node.getTarget() instanceof MemberReferenceExpression) { + tokenNode = ((MemberReferenceExpression) node.getTarget()).getMemberNameToken(); + } else if (node.getTarget() instanceof SuperReferenceExpression) { + tokenNode = node.getTarget(); + } else if (node.getTarget() instanceof ThisReferenceExpression) { + tokenNode = node.getTarget(); + } + if (tokenNode != null) { + index.addReference(tokenNode, methodEntry, this.methodEntry); + } + } + + // Check for identifier + node.getArguments().stream().filter(expression -> expression instanceof IdentifierExpression) + .forEach(expression -> this.checkIdentifier((IdentifierExpression) expression, index)); + return recurse(node, index); + } + + @Override + public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) { + MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); + if (ref != null) { + // make sure this is actually a field + String erasedSignature = ref.getErasedSignature(); + if (erasedSignature.indexOf('(') >= 0) { + throw new Error("Expected a field here! got " + ref); + } + + ClassEntry classEntry = entryPool.getClass(ref.getDeclaringType().getInternalName()); + FieldEntry fieldEntry = entryPool.getField(classEntry, ref.getName(), new TypeDescriptor(erasedSignature)); + if (fieldEntry == null) { + throw new Error("Failed to find field " + ref.getName() + " on " + classEntry.getName()); + } + index.addReference(node.getMemberNameToken(), fieldEntry, this.methodEntry); + } + + return recurse(node, index); + } + + @Override + public Void visitSimpleType(SimpleType node, SourceIndex index) { + TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE); + if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) { + ClassEntry classEntry = entryPool.getClass(ref.getInternalName()); + index.addReference(node.getIdentifierToken(), classEntry, this.methodEntry); + } + + return recurse(node, index); + } + + @Override + public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) { + ParameterDefinition def = node.getUserData(Keys.PARAMETER_DEFINITION); + + int variableOffset = this.methodEntry.getVariableOffset(ownerEntry); + int parameterIndex = def.getSlot() - variableOffset; + + if (parameterIndex >= 0) { + LocalVariableEntry localVariableEntry = new LocalVariableEntry(methodEntry, parameterIndex, node.getName()); + Identifier identifier = node.getNameToken(); + // cache the argument entry and the identifier + identifierEntryCache.put(identifier.getName(), localVariableEntry); + index.addDeclaration(identifier, localVariableEntry); + } + + return recurse(node, index); + } + + @Override + public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) { + MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); + if (ref != null) { + ClassEntry classEntry = entryPool.getClass(ref.getDeclaringType().getInternalName()); + FieldEntry fieldEntry = entryPool.getField(classEntry, ref.getName(), new TypeDescriptor(ref.getErasedSignature())); + if (fieldEntry == null) { + throw new Error("Failed to find field " + ref.getName() + " on " + classEntry.getName()); + } + index.addReference(node.getIdentifierToken(), fieldEntry, this.methodEntry); + } else + this.checkIdentifier(node, index); + return recurse(node, index); + } + + private void checkIdentifier(IdentifierExpression node, SourceIndex index) { + if (identifierEntryCache.containsKey(node.getIdentifier())) // If it's in the argument cache, create a token! + index.addDeclaration(node.getIdentifierToken(), identifierEntryCache.get(node.getIdentifier())); + else + unmatchedIdentifier.put(node.getIdentifier(), node.getIdentifierToken()); // Not matched actually, put it! + } + + private void addDeclarationToUnmatched(String key, SourceIndex index) { + Entry entry = identifierEntryCache.get(key); + + // This cannot happened in theory + if (entry == null) + return; + for (Identifier identifier : unmatchedIdentifier.get(key)) + index.addDeclaration(identifier, entry); + unmatchedIdentifier.removeAll(key); + } + + @Override + public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) { + MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); + if (ref != null) { + ClassEntry classEntry = entryPool.getClass(ref.getDeclaringType().getInternalName()); + MethodEntry constructorEntry = entryPool.getMethod(classEntry, "", ref.getErasedSignature()); + if (node.getType() instanceof SimpleType) { + SimpleType simpleTypeNode = (SimpleType) node.getType(); + index.addReference(simpleTypeNode.getIdentifierToken(), constructorEntry, this.methodEntry); + } + } + + return recurse(node, index); + } + + @Override + public Void visitVariableDeclaration(VariableDeclarationStatement node, SourceIndex index) { + AstNodeCollection variables = node.getVariables(); + + // Single assignation + if (variables.size() == 1) { + VariableInitializer initializer = variables.firstOrNullObject(); + if (initializer != null && node.getType() instanceof SimpleType) { + Identifier identifier = initializer.getNameToken(); + Variable variable = initializer.getUserData(Keys.VARIABLE); + if (variable != null) { + VariableDefinition originalVariable = variable.getOriginalVariable(); + if (originalVariable != null) { + int variableOffset = methodEntry.getVariableOffset(ownerEntry); + int variableIndex = originalVariable.getSlot() - variableOffset; + if (variableIndex >= 0) { + LocalVariableEntry localVariableEntry = new LocalVariableEntry(methodEntry, variableIndex, initializer.getName()); + identifierEntryCache.put(identifier.getName(), localVariableEntry); + addDeclarationToUnmatched(identifier.getName(), index); + index.addDeclaration(identifier, localVariableEntry); + } + } + } + } + } + return recurse(node, index); + } + + @Override + public Void visitMethodGroupExpression(MethodGroupExpression node, SourceIndex index) { + MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); + + if (ref instanceof MethodReference) { + // get the behavior entry + ClassEntry classEntry = entryPool.getClass(ref.getDeclaringType().getInternalName()); + MethodEntry methodEntry = null; + + methodEntry = entryPool.getMethod(classEntry, ref.getName(), ref.getErasedSignature()); + // get the node for the token + AstNode tokenNode = node.getMethodNameToken(); + if (tokenNode == null || (tokenNode.getRegion().getBeginLine() == 0 || tokenNode.getRegion().getEndLine() == 0)){ + tokenNode = node.getTarget(); + } + if (tokenNode != null) { + index.addReference(tokenNode, methodEntry, this.methodEntry); + } + } + + return recurse(node, index); + } +} diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java b/src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java index a94a55b7..e588d24b 100644 --- a/src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java +++ b/src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java @@ -14,17 +14,25 @@ package cuchaz.enigma.analysis; import com.strobel.assembler.metadata.TypeDefinition; import com.strobel.decompiler.languages.java.ast.*; import com.strobel.decompiler.patterns.Pattern; -import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.bytecode.AccessFlags; +import cuchaz.enigma.mapping.Signature; +import cuchaz.enigma.mapping.entry.ClassDefEntry; +import cuchaz.enigma.mapping.entry.ReferencedEntryPool; public class SourceIndexVisitor implements IAstVisitor { + private final ReferencedEntryPool entryPool; + + public SourceIndexVisitor(ReferencedEntryPool entryPool) { + this.entryPool = entryPool; + } @Override public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) { TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION); - ClassEntry classEntry = new ClassEntry(def.getInternalName()); + ClassDefEntry classEntry = new ClassDefEntry(def.getInternalName(), Signature.createSignature(def.getSignature()), new AccessFlags(def.getModifiers())); index.addDeclaration(node.getNameToken(), classEntry); - return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index); + return node.acceptVisitor(new SourceIndexClassVisitor(entryPool, classEntry), index); } protected Void recurse(AstNode node, SourceIndex index) { diff --git a/src/main/java/cuchaz/enigma/analysis/TranslationIndex.java b/src/main/java/cuchaz/enigma/analysis/TranslationIndex.java index 26be05b4..b2ddc5fa 100644 --- a/src/main/java/cuchaz/enigma/analysis/TranslationIndex.java +++ b/src/main/java/cuchaz/enigma/analysis/TranslationIndex.java @@ -15,11 +15,9 @@ import com.google.common.collect.HashMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; +import cuchaz.enigma.bytecode.AccessFlags; import cuchaz.enigma.mapping.*; -import javassist.CtBehavior; -import javassist.CtClass; -import javassist.CtField; -import javassist.bytecode.Descriptor; +import cuchaz.enigma.mapping.entry.*; import java.util.Collection; import java.util.List; @@ -28,96 +26,91 @@ import java.util.Set; public class TranslationIndex { + private final ReferencedEntryPool entryPool; private Map superclasses; - private Multimap fieldEntries; - private Multimap behaviorEntries; + private Multimap fieldEntries; + private Multimap methodEntries; private Multimap interfaces; - public TranslationIndex() { + public TranslationIndex(ReferencedEntryPool entryPool) { + this.entryPool = entryPool; this.superclasses = Maps.newHashMap(); this.fieldEntries = HashMultimap.create(); - this.behaviorEntries = HashMultimap.create(); + this.methodEntries = HashMultimap.create(); this.interfaces = HashMultimap.create(); } public TranslationIndex(TranslationIndex other, Translator translator) { + this.entryPool = other.entryPool; + // translate the superclasses this.superclasses = Maps.newHashMap(); for (Map.Entry mapEntry : other.superclasses.entrySet()) { - this.superclasses.put(translator.translateEntry(mapEntry.getKey()), translator.translateEntry(mapEntry.getValue())); + this.superclasses.put(translator.getTranslatedClass(mapEntry.getKey()), translator.getTranslatedClass(mapEntry.getValue())); } // translate the interfaces this.interfaces = HashMultimap.create(); for (Map.Entry mapEntry : other.interfaces.entries()) { this.interfaces.put( - translator.translateEntry(mapEntry.getKey()), - translator.translateEntry(mapEntry.getValue()) + translator.getTranslatedClass(mapEntry.getKey()), + translator.getTranslatedClass(mapEntry.getValue()) ); } // translate the fields this.fieldEntries = HashMultimap.create(); - for (Map.Entry mapEntry : other.fieldEntries.entries()) { + for (Map.Entry mapEntry : other.fieldEntries.entries()) { this.fieldEntries.put( - translator.translateEntry(mapEntry.getKey()), - translator.translateEntry(mapEntry.getValue()) + translator.getTranslatedClass(mapEntry.getKey()), + translator.getTranslatedFieldDef(mapEntry.getValue()) ); } - this.behaviorEntries = HashMultimap.create(); - for (Map.Entry mapEntry : other.behaviorEntries.entries()) { - this.behaviorEntries.put( - translator.translateEntry(mapEntry.getKey()), - translator.translateEntry(mapEntry.getValue()) + this.methodEntries = HashMultimap.create(); + for (Map.Entry mapEntry : other.methodEntries.entries()) { + this.methodEntries.put( + translator.getTranslatedClass(mapEntry.getKey()), + translator.getTranslatedMethodDef(mapEntry.getValue()) ); } } - public void indexClass(CtClass c) { - indexClass(c, true); - } - - public void indexClass(CtClass c, boolean indexMembers) { - ClassEntry classEntry = EntryFactory.getClassEntry(c); + protected ClassDefEntry indexClass(int access, String name, String signature, String superName, String[] interfaces) { + ClassDefEntry classEntry = new ClassDefEntry(name, Signature.createSignature(signature), new AccessFlags(access)); if (isJre(classEntry)) { - return; + return null; } // add the superclass - ClassEntry superclassEntry = EntryFactory.getSuperclassEntry(c); + ClassEntry superclassEntry = entryPool.getClass(superName); if (superclassEntry != null) { this.superclasses.put(classEntry, superclassEntry); } // add the interfaces - for (String interfaceClassName : c.getClassFile().getInterfaces()) { - ClassEntry interfaceClassEntry = new ClassEntry(Descriptor.toJvmName(interfaceClassName)); + for (String interfaceClassName : interfaces) { + ClassEntry interfaceClassEntry = entryPool.getClass(interfaceClassName); if (!isJre(interfaceClassEntry)) { - this.interfaces.put(classEntry, interfaceClassEntry); } } - if (indexMembers) { - // add fields - for (CtField field : c.getDeclaredFields()) { - FieldEntry fieldEntry = EntryFactory.getFieldEntry(field); - this.fieldEntries.put(fieldEntry.getClassEntry(), fieldEntry); - } + return classEntry; + } - // add behaviors - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); - this.behaviorEntries.put(behaviorEntry.getClassEntry(), behaviorEntry); - } - } + protected void indexField(FieldDefEntry fieldEntry) { + this.fieldEntries.put(fieldEntry.getOwnerClassEntry(), fieldEntry); + } + + protected void indexMethod(MethodDefEntry methodEntry) { + this.methodEntries.put(methodEntry.getOwnerClassEntry(), methodEntry); } public void renameClasses(Map renames) { EntryRenamer.renameClassesInMap(renames, this.superclasses); EntryRenamer.renameClassesInMultimap(renames, this.fieldEntries); - EntryRenamer.renameClassesInMultimap(renames, this.behaviorEntries); + EntryRenamer.renameClassesInMultimap(renames, this.methodEntries); } public ClassEntry getSuperclass(ClassEntry classEntry) { @@ -175,31 +168,32 @@ public class TranslationIndex { } public boolean entryExists(Entry entry) { + if (entry == null) { + return false; + } if (entry instanceof FieldEntry) { return fieldExists((FieldEntry) entry); - } else if (entry instanceof BehaviorEntry) { - return behaviorExists((BehaviorEntry) entry); - } else if (entry instanceof ArgumentEntry) { - return behaviorExists(((ArgumentEntry) entry).getBehaviorEntry()); + } else if (entry instanceof MethodEntry) { + return methodExists((MethodEntry) entry); } else if (entry instanceof LocalVariableEntry) { - return behaviorExists(((LocalVariableEntry) entry).getBehaviorEntry()); + return methodExists(((LocalVariableEntry) entry).getOwnerEntry()); } throw new IllegalArgumentException("Cannot check existence for " + entry.getClass()); } public boolean fieldExists(FieldEntry fieldEntry) { - return this.fieldEntries.containsEntry(fieldEntry.getClassEntry(), fieldEntry); + return this.fieldEntries.containsEntry(fieldEntry.getOwnerClassEntry(), fieldEntry); } - public boolean behaviorExists(BehaviorEntry behaviorEntry) { - return this.behaviorEntries.containsEntry(behaviorEntry.getClassEntry(), behaviorEntry); + public boolean methodExists(MethodEntry methodEntry) { + return this.methodEntries.containsEntry(methodEntry.getOwnerClassEntry(), methodEntry); } - public ClassEntry resolveEntryClass(Entry entry) { - return resolveEntryClass(entry, false); + public ClassEntry resolveEntryOwner(Entry entry) { + return resolveEntryOwner(entry, false); } - public ClassEntry resolveEntryClass(Entry entry, boolean checkSuperclassBeforeChild) { + public ClassEntry resolveEntryOwner(Entry entry, boolean checkSuperclassBeforeChild) { if (entry instanceof ClassEntry) { return (ClassEntry) entry; } @@ -227,12 +221,12 @@ public class TranslationIndex { Entry originalEntry = entry; // Get all possible superclasses and reverse the list - List superclasses = Lists.reverse(getAncestry(originalEntry.getClassEntry())); + List superclasses = Lists.reverse(getAncestry(originalEntry.getOwnerClassEntry())); boolean existInEntry = false; for (ClassEntry classEntry : superclasses) { - entry = entry.cloneToNewClass(classEntry); + entry = entry.updateOwnership(classEntry); existInEntry = entryExists(entry); // Check for possible entry in interfaces of superclasses @@ -245,9 +239,9 @@ public class TranslationIndex { // Doesn't exists in superclasses? check the child or return null if (!existInEntry) - return !entryExists(originalEntry) ? null : originalEntry.getClassEntry(); + return !entryExists(originalEntry) ? null : originalEntry.getOwnerClassEntry(); - return entry.getClassEntry(); + return entry.getOwnerClassEntry(); } public ClassEntry resolveSuperclass(Entry entry) { @@ -256,7 +250,7 @@ public class TranslationIndex { while (!entryExists(entry)) { // is there a parent class? - ClassEntry superclassEntry = getSuperclass(entry.getClassEntry()); + ClassEntry superclassEntry = getSuperclass(entry.getOwnerClassEntry()); if (superclassEntry == null) { // this is probably a method from a class in a library // we can't trace the implementation up any higher unless we index the library @@ -264,23 +258,23 @@ public class TranslationIndex { } // move up to the parent class - entry = entry.cloneToNewClass(superclassEntry); + entry = entry.updateOwnership(superclassEntry); } - return entry.getClassEntry(); + return entry.getOwnerClassEntry(); } public ClassEntry resolveInterface(Entry entry) { // the interfaces for any class is a forest // so let's look at all the trees - for (ClassEntry interfaceEntry : this.interfaces.get(entry.getClassEntry())) { + for (ClassEntry interfaceEntry : this.interfaces.get(entry.getOwnerClassEntry())) { Collection subInterface = this.interfaces.get(interfaceEntry); if (subInterface != null && !subInterface.isEmpty()) { - ClassEntry result = resolveInterface(entry.cloneToNewClass(interfaceEntry)); + ClassEntry result = resolveInterface(entry.updateOwnership(interfaceEntry)); if (result != null) return result; } - ClassEntry resolvedClassEntry = resolveSuperclass(entry.cloneToNewClass(interfaceEntry)); + ClassEntry resolvedClassEntry = resolveSuperclass(entry.updateOwnership(interfaceEntry)); if (resolvedClassEntry != null) { return resolvedClassEntry; } diff --git a/src/main/java/cuchaz/enigma/bytecode/AccessFlags.java b/src/main/java/cuchaz/enigma/bytecode/AccessFlags.java new file mode 100644 index 00000000..21b24897 --- /dev/null +++ b/src/main/java/cuchaz/enigma/bytecode/AccessFlags.java @@ -0,0 +1,80 @@ +package cuchaz.enigma.bytecode; + +import org.objectweb.asm.Opcodes; + +import java.lang.reflect.Modifier; + +public class AccessFlags { + private int flags; + + public AccessFlags(int flags) { + this.flags = flags; + } + + public boolean isPrivate() { + return Modifier.isPrivate(this.flags); + } + + public boolean isProtected() { + return Modifier.isProtected(this.flags); + } + + public boolean isPublic() { + return Modifier.isPublic(this.flags); + } + + public boolean isSynthetic() { + return (this.flags & Opcodes.ACC_SYNTHETIC) != 0; + } + + public boolean isStatic() { + return Modifier.isStatic(this.flags); + } + + public boolean isEnum() { + return (flags & Opcodes.ACC_ENUM) != 0; + } + + public AccessFlags setPrivate() { + this.setVisibility(Opcodes.ACC_PRIVATE); + return this; + } + + public AccessFlags setProtected() { + this.setVisibility(Opcodes.ACC_PROTECTED); + return this; + } + + public AccessFlags setPublic() { + this.setVisibility(Opcodes.ACC_PUBLIC); + return this; + } + + public AccessFlags setBridged() { + this.setVisibility(Opcodes.ACC_BRIDGE); + return this; + } + + public void setVisibility(int visibility) { + this.resetVisibility(); + this.flags |= visibility; + } + + private void resetVisibility() { + this.flags &= ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED | Opcodes.ACC_PUBLIC); + } + + public int getFlags() { + return this.flags; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof AccessFlags && ((AccessFlags) obj).flags == flags; + } + + @Override + public int hashCode() { + return flags; + } +} diff --git a/src/main/java/cuchaz/enigma/bytecode/ClassProtectifier.java b/src/main/java/cuchaz/enigma/bytecode/ClassProtectifier.java index 6ec576e0..9ed6db9f 100644 --- a/src/main/java/cuchaz/enigma/bytecode/ClassProtectifier.java +++ b/src/main/java/cuchaz/enigma/bytecode/ClassProtectifier.java @@ -11,41 +11,39 @@ package cuchaz.enigma.bytecode; -import javassist.CtBehavior; -import javassist.CtClass; -import javassist.CtField; -import javassist.bytecode.AccessFlag; -import javassist.bytecode.InnerClassesAttribute; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; -public class ClassProtectifier { +public class ClassProtectifier extends ClassVisitor { - public static CtClass protectify(CtClass c) { - - // protectify all the fields - for (CtField field : c.getDeclaredFields()) { - field.setModifiers(protectify(field.getModifiers())); - } + public ClassProtectifier(int api, ClassVisitor cv) { + super(api, cv); + } - // protectify all the methods and constructors - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - behavior.setModifiers(protectify(behavior.getModifiers())); - } + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + access = protectify(access); + return super.visitMethod(access, name, desc, signature, exceptions); + } - // protectify all the inner classes - InnerClassesAttribute attr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag); - if (attr != null) { - for (int i = 0; i < attr.tableLength(); i++) { - attr.setAccessFlags(i, protectify(attr.accessFlags(i))); - } - } + @Override + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { + access = protectify(access); + return super.visitField(access, name, desc, signature, value); + } - return c; + @Override + public void visitInnerClass(String name, String outerName, String innerName, int access) { + access = protectify(access); + super.visitInnerClass(name, outerName, innerName, access); } - private static int protectify(int flags) { - if (AccessFlag.isPrivate(flags)) { - flags = AccessFlag.setProtected(flags); + private static int protectify(int access) { + AccessFlags accessFlags = new AccessFlags(access); + if (accessFlags.isPrivate()) { + accessFlags.setProtected(); } - return flags; + return accessFlags.getFlags(); } } diff --git a/src/main/java/cuchaz/enigma/bytecode/ClassPublifier.java b/src/main/java/cuchaz/enigma/bytecode/ClassPublifier.java index d627fe91..64de788f 100644 --- a/src/main/java/cuchaz/enigma/bytecode/ClassPublifier.java +++ b/src/main/java/cuchaz/enigma/bytecode/ClassPublifier.java @@ -11,41 +11,45 @@ package cuchaz.enigma.bytecode; -import javassist.CtBehavior; -import javassist.CtClass; -import javassist.CtField; -import javassist.bytecode.AccessFlag; -import javassist.bytecode.InnerClassesAttribute; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; -public class ClassPublifier { +public class ClassPublifier extends ClassVisitor { - public static CtClass publify(CtClass c) { + public ClassPublifier(int api, ClassVisitor cv) { + super(api, cv); + } - // publify all the fields - for (CtField field : c.getDeclaredFields()) { - field.setModifiers(publify(field.getModifiers())); - } + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + access = publify(access); + super.visit(version, access, name, signature, superName, interfaces); + } - // publify all the methods and constructors - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - behavior.setModifiers(publify(behavior.getModifiers())); - } + @Override + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { + access = publify(access); + return super.visitField(access, name, desc, signature, value); + } - // publify all the inner classes - InnerClassesAttribute attr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag); - if (attr != null) { - for (int i = 0; i < attr.tableLength(); i++) { - attr.setAccessFlags(i, publify(attr.accessFlags(i))); - } - } + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + access = publify(access); + return super.visitMethod(access, name, desc, signature, exceptions); + } - return c; + @Override + public void visitInnerClass(String name, String outerName, String innerName, int access) { + access = publify(access); + super.visitInnerClass(name, outerName, innerName, access); } - private static int publify(int flags) { - if (!AccessFlag.isPublic(flags)) { - flags = AccessFlag.setPublic(flags); + private static int publify(int access) { + AccessFlags accessFlags = new AccessFlags(access); + if (!accessFlags.isPublic()) { + accessFlags.setPublic(); } - return flags; + return accessFlags.getFlags(); } } diff --git a/src/main/java/cuchaz/enigma/bytecode/ClassRenamer.java b/src/main/java/cuchaz/enigma/bytecode/ClassRenamer.java deleted file mode 100644 index 62a838d1..00000000 --- a/src/main/java/cuchaz/enigma/bytecode/ClassRenamer.java +++ /dev/null @@ -1,539 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.bytecode; - -import cuchaz.enigma.mapping.ClassEntry; -import cuchaz.enigma.mapping.ClassNameReplacer; -import cuchaz.enigma.mapping.Mappings; -import cuchaz.enigma.mapping.Translator; -import javassist.CtBehavior; -import javassist.CtClass; -import javassist.CtField; -import javassist.Modifier; -import javassist.bytecode.*; -import javassist.bytecode.SignatureAttribute.*; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class ClassRenamer { - - public static void applyModifier(Object obj, Mappings.EntryModifier modifier) { - int mod = -1; - if (obj instanceof CtField) - mod = ((CtField) obj).getModifiers(); - else if (obj instanceof CtBehavior) - mod = ((CtBehavior) obj).getModifiers(); - else if (obj instanceof CtClass) - mod = ((CtClass) obj).getModifiers(); - - if (mod != -1) { - switch (modifier) { - case PRIVATE: - mod = Modifier.setPrivate(mod); - break; - case PROTECTED: - mod = Modifier.setProtected(mod); - break; - case PUBLIC: - mod = Modifier.setPublic(mod); - break; - default: - break; - } - if (obj instanceof CtField) - ((CtField) obj).setModifiers(mod); - else if (obj instanceof CtBehavior) - ((CtBehavior) obj).setModifiers(mod); - else - ((CtClass) obj).setModifiers(mod); - } - } - - public static void renameClasses(CtClass c, final Translator translator) { - renameClasses(c, className -> { - ClassEntry entry = translator.translateEntry(new ClassEntry(className)); - if (entry != null) { - return entry.getName(); - } - return null; - }); - } - - public static void moveAllClassesOutOfDefaultPackage(CtClass c, final String newPackageName) { - renameClasses(c, className -> { - ClassEntry entry = new ClassEntry(className); - if (entry.isInDefaultPackage()) { - return newPackageName + "/" + entry.getName(); - } - return null; - }); - } - - public static void moveAllClassesIntoDefaultPackage(CtClass c, final String oldPackageName) { - renameClasses(c, className -> { - ClassEntry entry = new ClassEntry(className); - if (entry.getPackageName().equals(oldPackageName)) { - return entry.getSimpleName(); - } - return null; - }); - } - - @SuppressWarnings("unchecked") - public static void renameClasses(CtClass c, ClassNameReplacer replacer) { - - // sadly, we can't use CtClass.renameClass() because SignatureAttribute.renameClass() is extremely buggy =( - - ReplacerClassMap map = new ReplacerClassMap(replacer); - ClassFile classFile = c.getClassFile(); - - // rename the constant pool (covers ClassInfo, MethodTypeInfo, and NameAndTypeInfo) - ConstPool constPool = c.getClassFile().getConstPool(); - constPool.renameClass(map); - - // rename class attributes - renameAttributes(classFile.getAttributes(), map, SignatureType.Class); - - // rename methods - for (MethodInfo methodInfo : (List) classFile.getMethods()) { - methodInfo.setDescriptor(Descriptor.rename(methodInfo.getDescriptor(), map)); - renameAttributes(methodInfo.getAttributes(), map, SignatureType.Method); - } - - // rename fields - for (FieldInfo fieldInfo : (List) classFile.getFields()) { - fieldInfo.setDescriptor(Descriptor.rename(fieldInfo.getDescriptor(), map)); - renameAttributes(fieldInfo.getAttributes(), map, SignatureType.Field); - } - - // rename the class name itself last - // NOTE: don't use the map here, because setName() calls the buggy SignatureAttribute.renameClass() - // we only want to replace exactly this class name - String newName = renameClassName(c.getName(), map); - if (newName != null) { - c.setName(newName); - } - - // replace simple names in the InnerClasses attribute too - InnerClassesAttribute attr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag); - if (attr != null) { - for (int i = 0; i < attr.tableLength(); i++) { - - String innerName = attr.innerClass(i); - // get the inner class full name (which has already been translated) - ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(innerName)); - - if (attr.innerNameIndex(i) != 0) { - // update the inner name - attr.setInnerNameIndex(i, constPool.addUtf8Info(classEntry.getInnermostClassName())); - } - - /* DEBUG - System.out.println(String.format("\tDEOBF: %s-> ATTR: %s,%s,%s", classEntry, attr.outerClass(i), attr.innerClass(i), attr.innerName(i))); - */ - } - } - } - - @SuppressWarnings("unchecked") - private static void renameAttributes(List attributes, ReplacerClassMap map, SignatureType type) { - try { - - // make the rename class method accessible - Method renameClassMethod = AttributeInfo.class.getDeclaredMethod("renameClass", Map.class); - renameClassMethod.setAccessible(true); - - for (AttributeInfo attribute : attributes) { - if (attribute instanceof SignatureAttribute) { - // this has to be handled specially because SignatureAttribute.renameClass() is buggy as hell - SignatureAttribute signatureAttribute = (SignatureAttribute) attribute; - String newSignature = type.rename(signatureAttribute.getSignature(), map); - if (newSignature != null) { - signatureAttribute.setSignature(newSignature); - } - } else if (attribute instanceof CodeAttribute) { - // code attributes have signature attributes too (indirectly) - CodeAttribute codeAttribute = (CodeAttribute) attribute; - renameAttributes(codeAttribute.getAttributes(), map, type); - } else if (attribute instanceof LocalVariableTypeAttribute) { - // lvt attributes have signature attributes too - LocalVariableTypeAttribute localVariableAttribute = (LocalVariableTypeAttribute) attribute; - renameLocalVariableTypeAttribute(localVariableAttribute, map); - } else { - renameClassMethod.invoke(attribute, map); - } - } - - } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { - throw new Error("Unable to call javassist methods by reflection!", ex); - } - } - - private static void renameLocalVariableTypeAttribute(LocalVariableTypeAttribute attribute, ReplacerClassMap map) { - - // adapted from LocalVariableAttribute.renameClass() - ConstPool cp = attribute.getConstPool(); - int n = attribute.tableLength(); - byte[] info = attribute.get(); - for (int i = 0; i < n; ++i) { - int pos = i * 10 + 2; - int index = ByteArray.readU16bit(info, pos + 6); - if (index != 0) { - String signature = cp.getUtf8Info(index); - String newSignature = renameLocalVariableSignature(signature, map); - if (newSignature != null) { - ByteArray.write16bit(cp.addUtf8Info(newSignature), info, pos + 6); - } - } - } - } - - private static String renameLocalVariableSignature(String signature, ReplacerClassMap map) { - - // for some reason, signatures with . in them don't count as field signatures - // looks like anonymous classes delimit with . in stead of $ - // convert the . to $, but keep track of how many we replace - // we need to put them back after we translate - int start = signature.lastIndexOf('$') + 1; - int numConverted = 0; - StringBuilder buf = new StringBuilder(signature); - for (int i = buf.length() - 1; i >= start; i--) { - char c = buf.charAt(i); - if (c == '.') { - buf.setCharAt(i, '$'); - numConverted++; - } - } - signature = buf.toString(); - - // translate - String newSignature = renameFieldSignature(signature, map); - if (newSignature != null) { - - // put the delimiters back - buf = new StringBuilder(newSignature); - for (int i = buf.length() - 1; i >= 0 && numConverted > 0; i--) { - char c = buf.charAt(i); - if (c == '$') { - buf.setCharAt(i, '.'); - numConverted--; - } - } - assert (numConverted == 0); - newSignature = buf.toString(); - - return newSignature; - } - - return null; - } - - private static String renameClassSignature(String signature, ReplacerClassMap map) { - try { - ClassSignature type = renameType(SignatureAttribute.toClassSignature(signature), map); - return type.encode(); - } catch (BadBytecode ex) { - throw new Error("Can't parse field signature: " + signature); - } - } - - private static String renameFieldSignature(String signature, ReplacerClassMap map) { - try { - ObjectType type = renameType(SignatureAttribute.toFieldSignature(signature), map); - if (type != null) { - return type.encode(); - } - return null; - } catch (BadBytecode ex) { - throw new Error("Can't parse class signature: " + signature); - } - } - - private static String renameMethodSignature(String signature, ReplacerClassMap map) { - try { - MethodSignature type = renameType(SignatureAttribute.toMethodSignature(signature), map); - return type.encode(); - } catch (BadBytecode ex) { - throw new Error("Can't parse method signature: " + signature); - } - } - - private static TypeParameter[] renameTypeParameter(TypeParameter[] typeParamTypes, ReplacerClassMap map) { - if (typeParamTypes != null) { - typeParamTypes = Arrays.copyOf(typeParamTypes, typeParamTypes.length); - for (int i = 0; i < typeParamTypes.length; i++) { - TypeParameter newParamType = renameType(typeParamTypes[i], map); - if (newParamType != null) { - typeParamTypes[i] = newParamType; - } - } - } - return typeParamTypes; - } - - private static ClassSignature renameType(ClassSignature type, ReplacerClassMap map) { - - TypeParameter[] typeParamTypes = renameTypeParameter(type.getParameters(), map); - - ClassType superclassType = type.getSuperClass(); - if (superclassType != ClassType.OBJECT) { - ClassType newSuperclassType = renameType(superclassType, map); - if (newSuperclassType != null) { - superclassType = newSuperclassType; - } - } - - ClassType[] interfaceTypes = type.getInterfaces(); - if (interfaceTypes != null) { - interfaceTypes = Arrays.copyOf(interfaceTypes, interfaceTypes.length); - for (int i = 0; i < interfaceTypes.length; i++) { - ClassType newInterfaceType = renameType(interfaceTypes[i], map); - if (newInterfaceType != null) { - interfaceTypes[i] = newInterfaceType; - } - } - } - - return new ClassSignature(typeParamTypes, superclassType, interfaceTypes); - } - - private static MethodSignature renameType(MethodSignature type, ReplacerClassMap map) { - - TypeParameter[] typeParamTypes = renameTypeParameter(type.getTypeParameters(), map); - - Type[] paramTypes = type.getParameterTypes(); - if (paramTypes != null) { - paramTypes = Arrays.copyOf(paramTypes, paramTypes.length); - for (int i = 0; i < paramTypes.length; i++) { - Type newParamType = renameType(paramTypes[i], map); - if (newParamType != null) { - paramTypes[i] = newParamType; - } - } - } - - Type returnType = type.getReturnType(); - if (returnType != null) { - Type newReturnType = renameType(returnType, map); - if (newReturnType != null) { - returnType = newReturnType; - } - } - - ObjectType[] exceptionTypes = type.getExceptionTypes(); - if (exceptionTypes != null) { - exceptionTypes = Arrays.copyOf(exceptionTypes, exceptionTypes.length); - for (int i = 0; i < exceptionTypes.length; i++) { - ObjectType newExceptionType = renameType(exceptionTypes[i], map); - if (newExceptionType != null) { - exceptionTypes[i] = newExceptionType; - } - } - } - - return new MethodSignature(typeParamTypes, paramTypes, returnType, exceptionTypes); - } - - private static Type renameType(Type type, ReplacerClassMap map) { - if (type instanceof ObjectType) { - return renameType((ObjectType) type, map); - } else if (type instanceof BaseType) { - return renameType((BaseType) type, map); - } else { - throw new Error("Don't know how to rename type " + type.getClass()); - } - } - - private static ObjectType renameType(ObjectType type, ReplacerClassMap map) { - if (type instanceof ArrayType) { - return renameType((ArrayType) type, map); - } else if (type instanceof ClassType) { - return renameType((ClassType) type, map); - } else if (type instanceof TypeVariable) { - return renameType((TypeVariable) type, map); - } else { - throw new Error("Don't know how to rename type " + type.getClass()); - } - } - - private static BaseType renameType(BaseType type, ReplacerClassMap map) { - // don't have to rename primitives - return null; - } - - private static TypeVariable renameType(TypeVariable type, ReplacerClassMap map) { - // don't have to rename template args - return null; - } - - private static ClassType renameType(ClassType type, ReplacerClassMap map) { - - // translate type args - TypeArgument[] args = type.getTypeArguments(); - if (args != null) { - args = Arrays.copyOf(args, args.length); - for (int i = 0; i < args.length; i++) { - TypeArgument newType = renameType(args[i], map); - if (newType != null) { - args[i] = newType; - } - } - } - - if (type instanceof NestedClassType) { - NestedClassType nestedType = (NestedClassType) type; - - // translate the name - String name = getClassName(type); - String newName = map.get(name); - if (newName != null) { - name = new ClassEntry(newName).getInnermostClassName(); - } - - // translate the parent class too - ClassType parent = renameType(nestedType.getDeclaringClass(), map); - if (parent == null) { - parent = nestedType.getDeclaringClass(); - } - - return new NestedClassType(parent, name, args); - } else { - - // translate the name - String name = type.getName(); - String newName = renameClassName(name, map); - if (newName != null) { - name = newName; - } - - return new ClassType(name, args); - } - } - - private static String getClassName(ClassType type) { - if (type instanceof NestedClassType) { - NestedClassType nestedType = (NestedClassType) type; - return getClassName(nestedType.getDeclaringClass()) + "$" + Descriptor.toJvmName(type.getName().replace('.', '$')); - } else { - return Descriptor.toJvmName(type.getName()); - } - } - - private static String renameClassName(String name, ReplacerClassMap map) { - String newName = map.get(Descriptor.toJvmName(name)); - if (newName != null) { - return Descriptor.toJavaName(newName); - } - return null; - } - - private static TypeArgument renameType(TypeArgument type, ReplacerClassMap map) { - ObjectType subType = type.getType(); - if (subType != null) { - ObjectType newSubType = renameType(subType, map); - if (newSubType != null) { - switch (type.getKind()) { - case ' ': - return new TypeArgument(newSubType); - case '+': - return TypeArgument.subclassOf(newSubType); - case '-': - return TypeArgument.superOf(newSubType); - default: - throw new Error("Unknown type kind: " + type.getKind()); - } - } - } - return null; - } - - private static ArrayType renameType(ArrayType type, ReplacerClassMap map) { - Type newSubType = renameType(type.getComponentType(), map); - if (newSubType != null) { - return new ArrayType(type.getDimension(), newSubType); - } - return null; - } - - private static TypeParameter renameType(TypeParameter type, ReplacerClassMap map) { - - ObjectType superclassType = type.getClassBound(); - if (superclassType != null) { - ObjectType newSuperclassType = renameType(superclassType, map); - if (newSuperclassType != null) { - superclassType = newSuperclassType; - } - } - - ObjectType[] interfaceTypes = type.getInterfaceBound(); - if (interfaceTypes != null) { - interfaceTypes = Arrays.copyOf(interfaceTypes, interfaceTypes.length); - for (int i = 0; i < interfaceTypes.length; i++) { - ObjectType newInterfaceType = renameType(interfaceTypes[i], map); - if (newInterfaceType != null) { - interfaceTypes[i] = newInterfaceType; - } - } - } - - return new TypeParameter(type.getName(), superclassType, interfaceTypes); - } - - private enum SignatureType { - Class { - @Override - public String rename(String signature, ReplacerClassMap map) { - return renameClassSignature(signature, map); - } - }, - Field { - @Override - public String rename(String signature, ReplacerClassMap map) { - return renameFieldSignature(signature, map); - } - }, - Method { - @Override - public String rename(String signature, ReplacerClassMap map) { - return renameMethodSignature(signature, map); - } - }; - - public abstract String rename(String signature, ReplacerClassMap map); - } - - private static class ReplacerClassMap extends HashMap { - - private ClassNameReplacer replacer; - - public ReplacerClassMap(ClassNameReplacer replacer) { - this.replacer = replacer; - } - - @Override - public String get(Object obj) { - if (obj instanceof String) { - return get((String) obj); - } - return null; - } - - public String get(String className) { - return replacer.replace(className); - } - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/ConstPoolEditor.java b/src/main/java/cuchaz/enigma/bytecode/ConstPoolEditor.java deleted file mode 100644 index 1932730d..00000000 --- a/src/main/java/cuchaz/enigma/bytecode/ConstPoolEditor.java +++ /dev/null @@ -1,264 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.bytecode; - -import cuchaz.enigma.bytecode.accessors.ClassInfoAccessor; -import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor; -import cuchaz.enigma.bytecode.accessors.MemberRefInfoAccessor; -import javassist.bytecode.ConstPool; -import javassist.bytecode.Descriptor; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.HashMap; - -public class ConstPoolEditor { - - private static Method getItem; - private static Method addItem; - private static Method addItem0; - private static Field items; - private static Field cache; - private static Field numItems; - private static Field objects; - private static Field elements; - private static Method methodWritePool; - private static Constructor constructorPool; - - static { - try { - getItem = ConstPool.class.getDeclaredMethod("getItem", int.class); - getItem.setAccessible(true); - - addItem = ConstPool.class.getDeclaredMethod("addItem", Class.forName("javassist.bytecode.ConstInfo")); - addItem.setAccessible(true); - - addItem0 = ConstPool.class.getDeclaredMethod("addItem0", Class.forName("javassist.bytecode.ConstInfo")); - addItem0.setAccessible(true); - - items = ConstPool.class.getDeclaredField("items"); - items.setAccessible(true); - - cache = ConstPool.class.getDeclaredField("itemsCache"); - cache.setAccessible(true); - - numItems = ConstPool.class.getDeclaredField("numOfItems"); - numItems.setAccessible(true); - - objects = Class.forName("javassist.bytecode.LongVector").getDeclaredField("objects"); - objects.setAccessible(true); - - elements = Class.forName("javassist.bytecode.LongVector").getDeclaredField("elements"); - elements.setAccessible(true); - - methodWritePool = ConstPool.class.getDeclaredMethod("write", DataOutputStream.class); - methodWritePool.setAccessible(true); - - constructorPool = ConstPool.class.getDeclaredConstructor(DataInputStream.class); - constructorPool.setAccessible(true); - } catch (Exception ex) { - throw new Error(ex); - } - } - - private ConstPool pool; - - public ConstPoolEditor(ConstPool pool) { - this.pool = pool; - } - - public static ConstPool readPool(DataInputStream in) { - try { - return constructorPool.newInstance(in); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public static ConstPool newConstPool() { - // const pool expects the name of a class to initialize itself - // but we want an empty pool - // so give it a bogus name, and then clear the entries afterwards - ConstPool pool = new ConstPool("a"); - - ConstPoolEditor editor = new ConstPoolEditor(pool); - int size = pool.getSize(); - for (int i = 0; i < size - 1; i++) { - editor.removeLastItem(); - } - - // make sure the pool is actually empty - // although, in this case "empty" means one thing in it - // the JVM spec says index 0 should be reserved - assert (pool.getSize() == 1); - assert (editor.getItem(0) == null); - assert (editor.getItem(1) == null); - assert (editor.getItem(2) == null); - assert (editor.getItem(3) == null); - - // also, clear the cache - editor.getCache().clear(); - - return pool; - } - - public void writePool(DataOutputStream out) { - try { - methodWritePool.invoke(this.pool, out); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public String getMemberrefClassname(int memberrefIndex) { - return Descriptor.toJvmName(this.pool.getClassInfo(this.pool.getMemberClass(memberrefIndex))); - } - - public String getMemberrefName(int memberrefIndex) { - return this.pool.getUtf8Info(this.pool.getNameAndTypeName(this.pool.getMemberNameAndType(memberrefIndex))); - } - - public String getMemberrefType(int memberrefIndex) { - return this.pool.getUtf8Info(this.pool.getNameAndTypeDescriptor(this.pool.getMemberNameAndType(memberrefIndex))); - } - - public ConstInfoAccessor getItem(int index) { - try { - Object entry = getItem.invoke(this.pool, index); - if (entry == null) { - return null; - } - return new ConstInfoAccessor(entry); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public int addItem(Object item) { - try { - return (Integer) addItem.invoke(this.pool, item); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public int addItemForceNew(Object item) { - try { - return (Integer) addItem0.invoke(this.pool, item); - } catch (Exception ex) { - throw new Error(ex); - } - } - - @SuppressWarnings("rawtypes") - public void removeLastItem() { - try { - // remove the item from the cache - HashMap cache = getCache(); - if (cache != null) { - Object item = getItem(this.pool.getSize() - 1); - cache.remove(item); - } - - // remove the actual item - // based off of LongVector.addElement() - Object item = items.get(this.pool); - Object[][] object = (Object[][]) objects.get(items); - int numElements = (Integer) elements.get(items) - 1; - int nth = numElements >> 7; - int offset = numElements & (128 - 1); - object[nth][offset] = null; - - // decrement the number of items - elements.set(item, numElements); - numItems.set(this.pool, (Integer) numItems.get(this.pool) - 1); - } catch (Exception ex) { - throw new Error(ex); - } - } - - @SuppressWarnings("rawtypes") - public HashMap getCache() { - try { - return (HashMap) cache.get(this.pool); - } catch (Exception ex) { - throw new Error(ex); - } - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - public void changeMemberrefNameAndType(int memberrefIndex, String newName, String newType) { - // NOTE: when changing values, we always need to copy-on-write - try { - // get the memberref item - Object item = getItem(memberrefIndex).getItem(); - - // update the cache - HashMap cache = getCache(); - if (cache != null) { - cache.remove(item); - } - - new MemberRefInfoAccessor(item).setNameAndTypeIndex(this.pool.addNameAndTypeInfo(newName, newType)); - - // update the cache - if (cache != null) { - cache.put(item, item); - } - } catch (Exception ex) { - throw new Error(ex); - } - - // make sure the change worked - assert (newName.equals(getMemberrefName(memberrefIndex))); - assert (newType.equals(getMemberrefType(memberrefIndex))); - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - public void changeClassName(int classNameIndex, String newName) { - // NOTE: when changing values, we always need to copy-on-write - try { - // get the class item - Object item = getItem(classNameIndex).getItem(); - - // update the cache - HashMap cache = getCache(); - if (cache != null) { - cache.remove(item); - } - - // add the new name and repoint the name-and-type to it - new ClassInfoAccessor(item).setNameIndex(this.pool.addUtf8Info(newName)); - - // update the cache - if (cache != null) { - cache.put(item, item); - } - } catch (Exception ex) { - throw new Error(ex); - } - } - - public String dump() { - StringBuilder buf = new StringBuilder(); - for (int i = 1; i < this.pool.getSize(); i++) { - buf.append(String.format("%4d", i)); - buf.append(" "); - buf.append(getItem(i)); - buf.append("\n"); - } - return buf.toString(); - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/InfoType.java b/src/main/java/cuchaz/enigma/bytecode/InfoType.java deleted file mode 100644 index 9013d581..00000000 --- a/src/main/java/cuchaz/enigma/bytecode/InfoType.java +++ /dev/null @@ -1,266 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.bytecode; - -import com.google.common.collect.Maps; -import cuchaz.enigma.bytecode.accessors.*; - -import java.util.Collection; -import java.util.Map; - -public enum InfoType { - - Utf8Info(1), - IntegerInfo(3), - FloatInfo(4), - LongInfo(5), - DoubleInfo(6), - ClassInfo(7) { - @Override - public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { - ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem()); - gatherIndexTree(indices, editor, accessor.getNameIndex()); - } - - @Override - public void remapIndices(Map map, ConstInfoAccessor entry) { - ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem()); - accessor.setNameIndex(remapIndex(map, accessor.getNameIndex())); - } - - @Override - public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { - ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem()); - ConstInfoAccessor nameEntry = pool.getItem(accessor.getNameIndex()); - return nameEntry != null && nameEntry.getTag() == Utf8Info.getTag(); - } - }, - StringInfo(8) { - @Override - public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { - StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem()); - gatherIndexTree(indices, editor, accessor.getStringIndex()); - } - - @Override - public void remapIndices(Map map, ConstInfoAccessor entry) { - StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem()); - accessor.setStringIndex(remapIndex(map, accessor.getStringIndex())); - } - - @Override - public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { - StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem()); - ConstInfoAccessor stringEntry = pool.getItem(accessor.getStringIndex()); - return stringEntry != null && stringEntry.getTag() == Utf8Info.getTag(); - } - }, - FieldRefInfo(9) { - @Override - public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { - MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem()); - gatherIndexTree(indices, editor, accessor.getClassIndex()); - gatherIndexTree(indices, editor, accessor.getNameAndTypeIndex()); - } - - @Override - public void remapIndices(Map map, ConstInfoAccessor entry) { - MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem()); - accessor.setClassIndex(remapIndex(map, accessor.getClassIndex())); - accessor.setNameAndTypeIndex(remapIndex(map, accessor.getNameAndTypeIndex())); - } - - @Override - public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { - MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem()); - ConstInfoAccessor classEntry = pool.getItem(accessor.getClassIndex()); - ConstInfoAccessor nameAndTypeEntry = pool.getItem(accessor.getNameAndTypeIndex()); - return classEntry != null && classEntry.getTag() == ClassInfo.getTag() && nameAndTypeEntry != null && nameAndTypeEntry.getTag() == NameAndTypeInfo.getTag(); - } - }, - // same as FieldRefInfo - MethodRefInfo(10) { - @Override - public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { - FieldRefInfo.gatherIndexTree(indices, editor, entry); - } - - @Override - public void remapIndices(Map map, ConstInfoAccessor entry) { - FieldRefInfo.remapIndices(map, entry); - } - - @Override - public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { - return FieldRefInfo.subIndicesAreValid(entry, pool); - } - }, - // same as FieldRefInfo - InterfaceMethodRefInfo(11) { - @Override - public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { - FieldRefInfo.gatherIndexTree(indices, editor, entry); - } - - @Override - public void remapIndices(Map map, ConstInfoAccessor entry) { - FieldRefInfo.remapIndices(map, entry); - } - - @Override - public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { - return FieldRefInfo.subIndicesAreValid(entry, pool); - } - }, - NameAndTypeInfo(12) { - @Override - public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { - NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem()); - gatherIndexTree(indices, editor, accessor.getNameIndex()); - gatherIndexTree(indices, editor, accessor.getTypeIndex()); - } - - @Override - public void remapIndices(Map map, ConstInfoAccessor entry) { - NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem()); - accessor.setNameIndex(remapIndex(map, accessor.getNameIndex())); - accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex())); - } - - @Override - public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { - NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem()); - ConstInfoAccessor nameEntry = pool.getItem(accessor.getNameIndex()); - ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex()); - return nameEntry != null && nameEntry.getTag() == Utf8Info.getTag() && typeEntry != null && typeEntry.getTag() == Utf8Info.getTag(); - } - }, - MethodHandleInfo(15) { - @Override - public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { - MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem()); - gatherIndexTree(indices, editor, accessor.getTypeIndex()); - gatherIndexTree(indices, editor, accessor.getMethodRefIndex()); - } - - @Override - public void remapIndices(Map map, ConstInfoAccessor entry) { - MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem()); - accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex())); - accessor.setMethodRefIndex(remapIndex(map, accessor.getMethodRefIndex())); - } - - @Override - public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { - MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem()); - ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex()); - ConstInfoAccessor methodRefEntry = pool.getItem(accessor.getMethodRefIndex()); - return typeEntry != null && typeEntry.getTag() == Utf8Info.getTag() && methodRefEntry != null && methodRefEntry.getTag() == MethodRefInfo.getTag(); - } - }, - MethodTypeInfo(16) { - @Override - public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { - MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem()); - gatherIndexTree(indices, editor, accessor.getTypeIndex()); - } - - @Override - public void remapIndices(Map map, ConstInfoAccessor entry) { - MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem()); - accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex())); - } - - @Override - public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { - MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem()); - ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex()); - return typeEntry != null && typeEntry.getTag() == Utf8Info.getTag(); - } - }, - InvokeDynamicInfo(18) { - @Override - public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { - InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem()); - gatherIndexTree(indices, editor, accessor.getBootstrapIndex()); - gatherIndexTree(indices, editor, accessor.getNameAndTypeIndex()); - } - - @Override - public void remapIndices(Map map, ConstInfoAccessor entry) { - InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem()); - accessor.setBootstrapIndex(remapIndex(map, accessor.getBootstrapIndex())); - accessor.setNameAndTypeIndex(remapIndex(map, accessor.getNameAndTypeIndex())); - } - - @Override - public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { - InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem()); - ConstInfoAccessor bootstrapEntry = pool.getItem(accessor.getBootstrapIndex()); - ConstInfoAccessor nameAndTypeEntry = pool.getItem(accessor.getNameAndTypeIndex()); - return bootstrapEntry != null && bootstrapEntry.getTag() == Utf8Info.getTag() && nameAndTypeEntry != null && nameAndTypeEntry.getTag() == NameAndTypeInfo.getTag(); - } - }; - - private static Map types; - - static { - types = Maps.newTreeMap(); - for (InfoType type : values()) { - types.put(type.getTag(), type); - } - } - - private int tag; - - InfoType(int tag) { - this.tag = tag; - } - - public static InfoType getByTag(int tag) { - return types.get(tag); - } - - public static void gatherIndexTree(Collection indices, ConstPoolEditor editor, int index) { - // add own index - indices.add(index); - - // recurse - ConstInfoAccessor entry = editor.getItem(index); - entry.getType().gatherIndexTree(indices, editor, entry); - } - - private static int remapIndex(Map map, int index) { - Integer newIndex = map.get(index); - if (newIndex == null) { - newIndex = index; - } - return newIndex; - } - - public int getTag() { - return this.tag; - } - - public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { - // by default, do nothing - } - - public void remapIndices(Map map, ConstInfoAccessor entry) { - // by default, do nothing - } - - public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { - // by default, everything is good - return true; - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/MethodParametersAttribute.java b/src/main/java/cuchaz/enigma/bytecode/MethodParametersAttribute.java deleted file mode 100644 index 57d60fdb..00000000 --- a/src/main/java/cuchaz/enigma/bytecode/MethodParametersAttribute.java +++ /dev/null @@ -1,85 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.bytecode; - -import javassist.bytecode.AttributeInfo; -import javassist.bytecode.ConstPool; -import javassist.bytecode.MethodInfo; - -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -public class MethodParametersAttribute extends AttributeInfo { - - private MethodParametersAttribute(ConstPool pool, List parameterNameIndices) { - super(pool, "MethodParameters", writeStruct(parameterNameIndices)); - } - - public static void updateClass(MethodInfo info, List names) { - - // add the names to the class const pool - ConstPool constPool = info.getConstPool(); - List parameterNameIndices = new ArrayList<>(); - for (String name : names) { - if (name != null) { - parameterNameIndices.add(constPool.addUtf8Info(name)); - } - } - - // add the attribute to the method - info.addAttribute(new MethodParametersAttribute(constPool, parameterNameIndices)); - } - - private static byte[] writeStruct(List parameterNameIndices) { - // JVM 8 Spec says the struct looks like this: - // http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.24 - // uint8 num_params - // for each param: - // uint16 name_index -> points to UTF8 entry in constant pool, or 0 for no entry - // uint16 access_flags -> don't care, just set to 0 - - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - DataOutputStream out = new DataOutputStream(buf); - - // NOTE: java hates unsigned integers, so we have to be careful here - // the writeShort(), writeByte() methods will read 16,8 low-order bits from the int argument - // as long as the int argument is in range of the unsigned short/byte type, it will be written as an unsigned short/byte - // if the int is out of range, the byte stream won't look the way we want and weird things will happen - final int SIZEOF_UINT8 = 1; - final int SIZEOF_UINT16 = 2; - final int MAX_UINT8 = (1 << 8) - 1; - final int MAX_UINT16 = (1 << 16) - 1; - - try { - assert (parameterNameIndices.size() >= 0 && parameterNameIndices.size() <= MAX_UINT8); - out.writeByte(parameterNameIndices.size()); - - for (Integer index : parameterNameIndices) { - assert (index >= 0 && index <= MAX_UINT16); - out.writeShort(index); - - // just write 0 for the access flags - out.writeShort(0); - } - - out.close(); - byte[] data = buf.toByteArray(); - assert (data.length == SIZEOF_UINT8 + parameterNameIndices.size() * (SIZEOF_UINT16 + SIZEOF_UINT16)); - return data; - } catch (IOException ex) { - throw new Error(ex); - } - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java deleted file mode 100644 index eaa6e901..00000000 --- a/src/main/java/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java +++ /dev/null @@ -1,56 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.bytecode.accessors; - -import java.lang.reflect.Field; - -public class ClassInfoAccessor { - - private static Class clazz; - private static Field nameIndex; - - static { - try { - clazz = Class.forName("javassist.bytecode.ClassInfo"); - nameIndex = clazz.getDeclaredField("name"); - nameIndex.setAccessible(true); - } catch (Exception ex) { - throw new Error(ex); - } - } - - private Object item; - - public ClassInfoAccessor(Object item) { - this.item = item; - } - - public static boolean isType(ConstInfoAccessor accessor) { - return clazz.isAssignableFrom(accessor.getItem().getClass()); - } - - public int getNameIndex() { - try { - return (Integer) nameIndex.get(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public void setNameIndex(int val) { - try { - nameIndex.set(this.item, val); - } catch (Exception ex) { - throw new Error(ex); - } - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java deleted file mode 100644 index 27d991a3..00000000 --- a/src/main/java/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java +++ /dev/null @@ -1,124 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.bytecode.accessors; - -import com.google.common.base.Charsets; -import cuchaz.enigma.bytecode.InfoType; - -import java.io.*; -import java.lang.reflect.Field; -import java.lang.reflect.Method; - -public class ConstInfoAccessor { - - private static Class clazz; - private static Field index; - private static Method getTag; - - static { - try { - clazz = Class.forName("javassist.bytecode.ConstInfo"); - index = clazz.getDeclaredField("index"); - index.setAccessible(true); - getTag = clazz.getMethod("getTag"); - getTag.setAccessible(true); - } catch (Exception ex) { - throw new Error(ex); - } - } - - private Object item; - - public ConstInfoAccessor(Object item) { - if (item == null) { - throw new IllegalArgumentException("item cannot be null!"); - } - this.item = item; - } - - public Object getItem() { - return this.item; - } - - public int getIndex() { - try { - return (Integer) index.get(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public int getTag() { - try { - return (Integer) getTag.invoke(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public ConstInfoAccessor copy() { - return new ConstInfoAccessor(copyItem()); - } - - public Object copyItem() { - // I don't know of a simpler way to copy one of these silly things... - try { - // serialize the item - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - DataOutputStream out = new DataOutputStream(buf); - write(out); - - // deserialize the item - DataInputStream in = new DataInputStream(new ByteArrayInputStream(buf.toByteArray())); - Object item = new ConstInfoAccessor(in).getItem(); - in.close(); - - return item; - } catch (Exception ex) { - throw new Error(ex); - } - } - - public void write(DataOutputStream out) throws IOException { - try { - out.writeUTF(this.item.getClass().getName()); - out.writeInt(getIndex()); - - Method method = this.item.getClass().getMethod("write", DataOutputStream.class); - method.setAccessible(true); - method.invoke(this.item, out); - } catch (IOException ex) { - throw ex; - } catch (Exception ex) { - throw new Error(ex); - } - } - - @Override - public String toString() { - try { - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - PrintWriter out = new PrintWriter(new OutputStreamWriter(buf, Charsets.UTF_8)); - Method print = this.item.getClass().getMethod("print", PrintWriter.class); - print.setAccessible(true); - print.invoke(this.item, out); - out.close(); - return buf.toString("UTF-8").replace("\n", ""); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public InfoType getType() { - return InfoType.getByTag(getTag()); - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java deleted file mode 100644 index aef35321..00000000 --- a/src/main/java/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java +++ /dev/null @@ -1,75 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.bytecode.accessors; - -import java.lang.reflect.Field; - -public class InvokeDynamicInfoAccessor { - - private static Class clazz; - private static Field bootstrapIndex; - private static Field nameAndTypeIndex; - - static { - try { - clazz = Class.forName("javassist.bytecode.InvokeDynamicInfo"); - bootstrapIndex = clazz.getDeclaredField("bootstrap"); - bootstrapIndex.setAccessible(true); - nameAndTypeIndex = clazz.getDeclaredField("nameAndType"); - nameAndTypeIndex.setAccessible(true); - } catch (Exception ex) { - throw new Error(ex); - } - } - - private Object item; - - public InvokeDynamicInfoAccessor(Object item) { - this.item = item; - } - - public static boolean isType(ConstInfoAccessor accessor) { - return clazz.isAssignableFrom(accessor.getItem().getClass()); - } - - public int getBootstrapIndex() { - try { - return (Integer) bootstrapIndex.get(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public void setBootstrapIndex(int val) { - try { - bootstrapIndex.set(this.item, val); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public int getNameAndTypeIndex() { - try { - return (Integer) nameAndTypeIndex.get(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public void setNameAndTypeIndex(int val) { - try { - nameAndTypeIndex.set(this.item, val); - } catch (Exception ex) { - throw new Error(ex); - } - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java deleted file mode 100644 index 058bb454..00000000 --- a/src/main/java/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java +++ /dev/null @@ -1,75 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.bytecode.accessors; - -import java.lang.reflect.Field; - -public class MemberRefInfoAccessor { - - private static Class clazz; - private static Field classIndex; - private static Field nameAndTypeIndex; - - static { - try { - clazz = Class.forName("javassist.bytecode.MemberrefInfo"); - classIndex = clazz.getDeclaredField("classIndex"); - classIndex.setAccessible(true); - nameAndTypeIndex = clazz.getDeclaredField("nameAndTypeIndex"); - nameAndTypeIndex.setAccessible(true); - } catch (Exception ex) { - throw new Error(ex); - } - } - - private Object item; - - public MemberRefInfoAccessor(Object item) { - this.item = item; - } - - public static boolean isType(ConstInfoAccessor accessor) { - return clazz.isAssignableFrom(accessor.getItem().getClass()); - } - - public int getClassIndex() { - try { - return (Integer) classIndex.get(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public void setClassIndex(int val) { - try { - classIndex.set(this.item, val); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public int getNameAndTypeIndex() { - try { - return (Integer) nameAndTypeIndex.get(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public void setNameAndTypeIndex(int val) { - try { - nameAndTypeIndex.set(this.item, val); - } catch (Exception ex) { - throw new Error(ex); - } - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java deleted file mode 100644 index 985e792e..00000000 --- a/src/main/java/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java +++ /dev/null @@ -1,75 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.bytecode.accessors; - -import java.lang.reflect.Field; - -public class MethodHandleInfoAccessor { - - private static Class clazz; - private static Field kindIndex; - private static Field indexIndex; - - static { - try { - clazz = Class.forName("javassist.bytecode.MethodHandleInfo"); - kindIndex = clazz.getDeclaredField("refKind"); - kindIndex.setAccessible(true); - indexIndex = clazz.getDeclaredField("refIndex"); - indexIndex.setAccessible(true); - } catch (Exception ex) { - throw new Error(ex); - } - } - - private Object item; - - public MethodHandleInfoAccessor(Object item) { - this.item = item; - } - - public static boolean isType(ConstInfoAccessor accessor) { - return clazz.isAssignableFrom(accessor.getItem().getClass()); - } - - public int getTypeIndex() { - try { - return (Integer) kindIndex.get(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public void setTypeIndex(int val) { - try { - kindIndex.set(this.item, val); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public int getMethodRefIndex() { - try { - return (Integer) indexIndex.get(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public void setMethodRefIndex(int val) { - try { - indexIndex.set(this.item, val); - } catch (Exception ex) { - throw new Error(ex); - } - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java deleted file mode 100644 index 10b0cb0c..00000000 --- a/src/main/java/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java +++ /dev/null @@ -1,57 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.bytecode.accessors; - -import java.lang.reflect.Field; - -public class MethodTypeInfoAccessor { - - private static Class clazz; - private static Field descriptorIndex; - - static { - try { - clazz = Class.forName("javassist.bytecode.MethodTypeInfo"); - descriptorIndex = clazz.getDeclaredField("descriptor"); - descriptorIndex.setAccessible(true); - } catch (Exception ex) { - throw new Error(ex); - } - } - - private Object item; - - public MethodTypeInfoAccessor(Object item) { - this.item = item; - } - - public static boolean isType(ConstInfoAccessor accessor) { - return clazz.isAssignableFrom(accessor.getItem().getClass()); - } - - public int getTypeIndex() { - try { - return (Integer) descriptorIndex.get(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public void setTypeIndex(int val) { - try { - descriptorIndex.set(this.item, val); - } catch (Exception ex) { - throw new Error(ex); - } - } - -} diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java deleted file mode 100644 index cc7fdbe8..00000000 --- a/src/main/java/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java +++ /dev/null @@ -1,75 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.bytecode.accessors; - -import java.lang.reflect.Field; - -public class NameAndTypeInfoAccessor { - - private static Class clazz; - private static Field nameIndex; - private static Field typeIndex; - - static { - try { - clazz = Class.forName("javassist.bytecode.NameAndTypeInfo"); - nameIndex = clazz.getDeclaredField("memberName"); - nameIndex.setAccessible(true); - typeIndex = clazz.getDeclaredField("typeDescriptor"); - typeIndex.setAccessible(true); - } catch (Exception ex) { - throw new Error(ex); - } - } - - private Object item; - - public NameAndTypeInfoAccessor(Object item) { - this.item = item; - } - - public static boolean isType(ConstInfoAccessor accessor) { - return clazz.isAssignableFrom(accessor.getItem().getClass()); - } - - public int getNameIndex() { - try { - return (Integer) nameIndex.get(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public void setNameIndex(int val) { - try { - nameIndex.set(this.item, val); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public int getTypeIndex() { - try { - return (Integer) typeIndex.get(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public void setTypeIndex(int val) { - try { - typeIndex.set(this.item, val); - } catch (Exception ex) { - throw new Error(ex); - } - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java deleted file mode 100644 index 5c68d4af..00000000 --- a/src/main/java/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java +++ /dev/null @@ -1,56 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.bytecode.accessors; - -import java.lang.reflect.Field; - -public class StringInfoAccessor { - - private static Class clazz; - private static Field stringIndex; - - static { - try { - clazz = Class.forName("javassist.bytecode.StringInfo"); - stringIndex = clazz.getDeclaredField("string"); - stringIndex.setAccessible(true); - } catch (Exception ex) { - throw new Error(ex); - } - } - - private Object item; - - public StringInfoAccessor(Object item) { - this.item = item; - } - - public static boolean isType(ConstInfoAccessor accessor) { - return clazz.isAssignableFrom(accessor.getItem().getClass()); - } - - public int getStringIndex() { - try { - return (Integer) stringIndex.get(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public void setStringIndex(int val) { - try { - stringIndex.set(this.item, val); - } catch (Exception ex) { - throw new Error(ex); - } - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java deleted file mode 100644 index cc3b41bc..00000000 --- a/src/main/java/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java +++ /dev/null @@ -1,29 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.bytecode.accessors; - -public class Utf8InfoAccessor { - - private static Class clazz; - - static { - try { - clazz = Class.forName("javassist.bytecode.Utf8Info"); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public static boolean isType(ConstInfoAccessor accessor) { - return clazz.isAssignableFrom(accessor.getItem().getClass()); - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/ClassTranslator.java b/src/main/java/cuchaz/enigma/bytecode/translators/ClassTranslator.java deleted file mode 100644 index 4ac5a8b0..00000000 --- a/src/main/java/cuchaz/enigma/bytecode/translators/ClassTranslator.java +++ /dev/null @@ -1,161 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.bytecode.translators; - -import cuchaz.enigma.bytecode.ClassRenamer; -import cuchaz.enigma.bytecode.ConstPoolEditor; -import cuchaz.enigma.mapping.*; -import javassist.CtBehavior; -import javassist.CtClass; -import javassist.CtField; -import javassist.CtMethod; -import javassist.bytecode.*; - -public class ClassTranslator { - - public static void translate(Translator translator, CtClass c) { - - // NOTE: the order of these translations is very important - - // translate all the field and method references in the code by editing the constant pool - ConstPool constants = c.getClassFile().getConstPool(); - ConstPoolEditor editor = new ConstPoolEditor(constants); - for (int i = 1; i < constants.getSize(); i++) { - switch (constants.getTag(i)) { - - case ConstPool.CONST_Fieldref: { - - // translate the name and type - FieldEntry entry = EntryFactory.getFieldEntry( - Descriptor.toJvmName(constants.getFieldrefClassName(i)), - constants.getFieldrefName(i), - constants.getFieldrefType(i) - ); - FieldEntry translatedEntry = translator.translateEntry(entry); - if (!entry.equals(translatedEntry)) { - editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedEntry.getType().toString()); - } - } - break; - - case ConstPool.CONST_Methodref: - case ConstPool.CONST_InterfaceMethodref: { - - // translate the name and type (ie signature) - BehaviorEntry entry = EntryFactory.getBehaviorEntry( - Descriptor.toJvmName(editor.getMemberrefClassname(i)), - editor.getMemberrefName(i), - editor.getMemberrefType(i) - ); - BehaviorEntry translatedEntry = translator.translateEntry(entry); - if (!entry.equals(translatedEntry)) { - editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedEntry.getSignature().toString()); - } - } - break; - default: - break; - } - } - - ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); - Mappings.EntryModifier modifier = translator.getModifier(classEntry); - if (modifier != null && modifier != Mappings.EntryModifier.UNCHANGED) - ClassRenamer.applyModifier(c, modifier); - - // translate all the fields - for (CtField field : c.getDeclaredFields()) { - - // translate the name - FieldEntry entry = EntryFactory.getFieldEntry(field); - String translatedName = translator.translate(entry); - modifier = translator.getModifier(entry); - if (modifier != null && modifier != Mappings.EntryModifier.UNCHANGED) - ClassRenamer.applyModifier(field, modifier); - - if (translatedName != null) { - field.setName(translatedName); - } - - // translate the type - Type translatedType = translator.translateType(entry.getType()); - field.getFieldInfo().setDescriptor(translatedType.toString()); - } - - // translate all the methods and constructors - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - - BehaviorEntry entry = EntryFactory.getBehaviorEntry(behavior); - - modifier = translator.getModifier(entry); - if (modifier != null && modifier != Mappings.EntryModifier.UNCHANGED) - ClassRenamer.applyModifier(behavior, modifier); - - if (behavior instanceof CtMethod) { - CtMethod method = (CtMethod) behavior; - - // translate the name - String translatedName = translator.translate(entry); - if (translatedName != null) { - method.setName(translatedName); - } - } - - if (entry.getSignature() != null) { - // translate the signature - Signature translatedSignature = translator.translateSignature(entry.getSignature()); - behavior.getMethodInfo().setDescriptor(translatedSignature.toString()); - } - } - - // translate the EnclosingMethod attribute - EnclosingMethodAttribute enclosingMethodAttr = (EnclosingMethodAttribute) c.getClassFile().getAttribute(EnclosingMethodAttribute.tag); - if (enclosingMethodAttr != null) { - - if (enclosingMethodAttr.methodIndex() == 0) { - BehaviorEntry obfBehaviorEntry = EntryFactory.getBehaviorEntry(Descriptor.toJvmName(enclosingMethodAttr.className())); - BehaviorEntry deobfBehaviorEntry = translator.translateEntry(obfBehaviorEntry); - c.getClassFile().addAttribute(new EnclosingMethodAttribute( - constants, - deobfBehaviorEntry.getClassName() - )); - } else { - BehaviorEntry obfBehaviorEntry = EntryFactory.getBehaviorEntry( - Descriptor.toJvmName(enclosingMethodAttr.className()), - enclosingMethodAttr.methodName(), - enclosingMethodAttr.methodDescriptor() - ); - BehaviorEntry deobfBehaviorEntry = translator.translateEntry(obfBehaviorEntry); - c.getClassFile().addAttribute(new EnclosingMethodAttribute( - constants, - deobfBehaviorEntry.getClassName(), - deobfBehaviorEntry.getName(), - deobfBehaviorEntry.getSignature().toString() - )); - } - } - - // translate all the class names referenced in the code - // the above code only changed method/field/reference names and types, but not the rest of the class references - ClassRenamer.renameClasses(c, translator); - - // translate the source file attribute too - ClassEntry deobfClassEntry = translator.translateEntry(classEntry); - if (deobfClassEntry != null) { - String sourceFile = Descriptor.toJvmName(deobfClassEntry.getOutermostClassEntry().getSimpleName()) + ".java"; - c.getClassFile().addAttribute(new SourceFileAttribute(constants, sourceFile)); - } - InnerClassesAttribute attr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag); - if (attr != null) - InnerClassWriter.changeModifier(c, attr, translator); - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/InnerClassWriter.java b/src/main/java/cuchaz/enigma/bytecode/translators/InnerClassWriter.java deleted file mode 100644 index 0e359386..00000000 --- a/src/main/java/cuchaz/enigma/bytecode/translators/InnerClassWriter.java +++ /dev/null @@ -1,144 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.bytecode.translators; - -import com.google.common.collect.Lists; -import cuchaz.enigma.analysis.JarIndex; -import cuchaz.enigma.bytecode.ClassRenamer; -import cuchaz.enigma.mapping.*; -import javassist.ClassPool; -import javassist.CtClass; -import javassist.NotFoundException; -import javassist.bytecode.*; - -import java.util.Collection; -import java.util.List; - -public class InnerClassWriter { - - // FIXME: modifier is not applied to inner class - public static void changeModifier(CtClass c, InnerClassesAttribute attr, Translator translator) { - ClassPool pool = c.getClassPool(); - for (int i = 0; i < attr.tableLength(); i++) { - - String innerName = attr.innerClass(i); - // get the inner class full name (which has already been translated) - ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(innerName)); - try { - CtClass innerClass = pool.get(innerName); - Mappings.EntryModifier modifier = translator.getModifier(classEntry); - if (modifier != null && modifier != Mappings.EntryModifier.UNCHANGED) - ClassRenamer.applyModifier(innerClass, modifier); - } catch (NotFoundException e) { - // This shouldn't be possible in theory - //e.printStackTrace(); - } - } - } - - public static void write(JarIndex index, CtClass c) { - - // don't change anything if there's already an attribute there - InnerClassesAttribute oldAttr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag); - if (oldAttr != null) { - // bail! - return; - } - - ClassEntry obfClassEntry = EntryFactory.getClassEntry(c); - List obfClassChain = index.getObfClassChain(obfClassEntry); - - boolean isInnerClass = obfClassChain.size() > 1; - if (isInnerClass) { - - // it's an inner class, rename it to the fully qualified name - c.setName(obfClassEntry.buildClassEntry(obfClassChain).getName()); - - BehaviorEntry caller = index.getAnonymousClassCaller(obfClassEntry); - if (caller != null) { - - // write the enclosing method attribute - if (caller.getName().equals("")) { - c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName())); - } else { - c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName(), caller.getName(), caller.getSignature().toString())); - } - } - } - - // does this class have any inner classes? - Collection obfInnerClassEntries = index.getInnerClasses(obfClassEntry); - - if (isInnerClass || !obfInnerClassEntries.isEmpty()) { - - // create an inner class attribute - InnerClassesAttribute attr = new InnerClassesAttribute(c.getClassFile().getConstPool()); - c.getClassFile().addAttribute(attr); - - // write the ancestry, but not the outermost class - for (int i = 1; i < obfClassChain.size(); i++) { - ClassEntry obfInnerClassEntry = obfClassChain.get(i); - writeInnerClass(index, attr, obfClassChain, obfInnerClassEntry); - - // update references to use the fully qualified inner class name - c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(obfClassChain).getName()); - } - - // write the inner classes - for (ClassEntry obfInnerClassEntry : obfInnerClassEntries) { - - // extend the class chain - List extendedObfClassChain = Lists.newArrayList(obfClassChain); - extendedObfClassChain.add(obfInnerClassEntry); - - writeInnerClass(index, attr, extendedObfClassChain, obfInnerClassEntry); - - // update references to use the fully qualified inner class name - c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(extendedObfClassChain).getName()); - } - } - } - - private static void writeInnerClass(JarIndex index, InnerClassesAttribute attr, List obfClassChain, ClassEntry obfClassEntry) { - - // get the new inner class name - ClassEntry obfInnerClassEntry = obfClassEntry.buildClassEntry(obfClassChain); - ClassEntry obfOuterClassEntry = obfInnerClassEntry.getOuterClassEntry(); - - // here's what the JVM spec says about the InnerClasses attribute - // append(inner, parent, 0 if anonymous else simple name, flags); - - // update the attribute with this inner class - ConstPool constPool = attr.getConstPool(); - int innerClassIndex = constPool.addClassInfo(obfInnerClassEntry.getName()); - int parentClassIndex = constPool.addClassInfo(obfOuterClassEntry.getName()); - int innerClassNameIndex = 0; - int accessFlags = AccessFlag.PUBLIC; - // TODO: need to figure out if we can put static or not - if (!index.isAnonymousClass(obfClassEntry)) { - innerClassNameIndex = constPool.addUtf8Info(obfInnerClassEntry.getInnermostClassName()); - } - - attr.append(innerClassIndex, parentClassIndex, innerClassNameIndex, accessFlags); - - /* DEBUG - System.out.println(String.format("\tOBF: %s -> ATTR: %s,%s,%s (replace %s with %s)", - obfClassEntry, - attr.innerClass(attr.tableLength() - 1), - attr.outerClass(attr.tableLength() - 1), - attr.innerName(attr.tableLength() - 1), - Constants.NonePackage + "/" + obfInnerClassName, - obfClassEntry.getName() - )); - */ - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableTranslator.java b/src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableTranslator.java deleted file mode 100644 index 51b3d2df..00000000 --- a/src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableTranslator.java +++ /dev/null @@ -1,142 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.bytecode.translators; - -import cuchaz.enigma.mapping.*; -import javassist.CtBehavior; -import javassist.CtClass; -import javassist.bytecode.*; - -public class LocalVariableTranslator { - - public static void translate(Translator translator, CtClass c) { - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - - // if there's a local variable table, just rename everything to v1, v2, v3, ... for now - CodeAttribute codeAttribute = behavior.getMethodInfo().getCodeAttribute(); - if (codeAttribute == null) { - continue; - } - - BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); - ConstPool constants = c.getClassFile().getConstPool(); - - LocalVariableAttribute table = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag); - if (table != null) { - renameLVT(translator, behaviorEntry, constants, table, c); - } - - LocalVariableTypeAttribute typeTable = (LocalVariableTypeAttribute) codeAttribute.getAttribute(LocalVariableAttribute.typeTag); - if (typeTable != null) { - renameLVTT(typeTable, table); - } - } - } - - // DEBUG - @SuppressWarnings("unused") - private static void dumpTable(LocalVariableAttribute table) { - for (int i = 0; i < table.tableLength(); i++) { - System.out.println(String.format("\t%d (%d): %s %s", - i, table.index(i), table.variableName(i), table.descriptor(i) - )); - } - } - - private static void renameLVT(Translator translator, BehaviorEntry behaviorEntry, ConstPool constants, LocalVariableAttribute table, CtClass ctClass) { - - // skip empty tables - if (table.tableLength() <= 0) { - return; - } - - // where do we start counting variables? - int starti = 0; - if (table.variableName(0).equals("this")) { - // skip the "this" variable - starti++; - } - - // rename method arguments first - int numArgs = 0; - if (behaviorEntry.getSignature() != null) { - numArgs = behaviorEntry.getSignature().getArgumentTypes().size(); - boolean isNestedClassConstructor = false; - - // If the behavior is a constructor and if it have more than one arg, it's probably from a nested! - if (behaviorEntry instanceof ConstructorEntry && behaviorEntry.getClassEntry() != null && behaviorEntry.getClassEntry().isInnerClass() && numArgs >= 1) { - // Get the first arg type - Type firstArg = behaviorEntry.getSignature().getArgumentTypes().get(0); - - // If the arg is a class and if the class name match the outer class name of the constructor, it's definitely a constructor of a nested class - if (firstArg.isClass() && firstArg.getClassEntry().equals(behaviorEntry.getClassEntry().getOuterClassEntry())) { - isNestedClassConstructor = true; - numArgs--; - } - } - - for (int i = starti; i < starti + numArgs && i < table.tableLength(); i++) { - int argi = i - starti; - if (ctClass.isEnum()) - argi += 2; - String argName = translator.translate(new ArgumentEntry(behaviorEntry, argi, "")); - if (argName == null) { - int argIndex = isNestedClassConstructor ? argi + 1 : argi; - if (ctClass.isEnum()) - argIndex -= 2; - Type argType = behaviorEntry.getSignature().getArgumentTypes().get(argIndex); - // Unfortunately each of these have different name getters, so they have different code paths - if (argType.isPrimitive()) { - Type.Primitive argCls = argType.getPrimitive(); - argName = "a" + argCls.name() + (argIndex + 1); - } else if (argType.isArray()) { - // List types would require this whole block again, so just go with aListx - argName = "aList" + (argIndex + 1); - } else if (argType.isClass()) { - ClassEntry argClsTrans = translator.translateEntry(argType.getClassEntry()); - argName = "a" + argClsTrans.getSimpleName().replace("$", "") + (argIndex + 1); - } else { - argName = "a" + (argIndex + 1); - } - } - renameVariable(table, i, constants.addUtf8Info(argName)); - } - } - - // then rename the rest of the args, if any - for (int i = starti + numArgs; i < table.tableLength(); i++) { - int firstIndex = Math.min(table.index(starti + numArgs), table.index(i)); - renameVariable(table, i, constants.addUtf8Info("v" + (table.index(i) - firstIndex + 1))); - } - } - - private static void renameLVTT(LocalVariableTypeAttribute typeTable, LocalVariableAttribute table) { - // rename args to the same names as in the LVT - for (int i = 0; i < typeTable.tableLength(); i++) { - renameVariable(typeTable, i, getNameIndex(table, typeTable.index(i))); - } - } - - private static void renameVariable(LocalVariableAttribute table, int i, int stringId) { - // based off of LocalVariableAttribute.nameIndex() - ByteArray.write16bit(stringId, table.get(), i * 10 + 6); - } - - private static int getNameIndex(LocalVariableAttribute table, int index) { - for (int i = 0; i < table.tableLength(); i++) { - if (table.index(i) == index) { - return table.nameIndex(i); - } - } - return 0; - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/MethodParameterTranslator.java b/src/main/java/cuchaz/enigma/bytecode/translators/MethodParameterTranslator.java deleted file mode 100644 index 4e632b94..00000000 --- a/src/main/java/cuchaz/enigma/bytecode/translators/MethodParameterTranslator.java +++ /dev/null @@ -1,62 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.bytecode.translators; - -import cuchaz.enigma.bytecode.MethodParametersAttribute; -import cuchaz.enigma.mapping.*; -import javassist.CtBehavior; -import javassist.CtClass; -import javassist.bytecode.CodeAttribute; -import javassist.bytecode.LocalVariableAttribute; - -import java.util.ArrayList; -import java.util.List; - -public class MethodParameterTranslator { - - public static void translate(Translator translator, CtClass c) { - - // Procyon will read method arguments from the "MethodParameters" attribute, so write those - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - - // if there's a local variable table here, don't write a MethodParameters attribute - // let the local variable writer deal with it instead - // procyon starts doing really weird things if we give it both attributes - CodeAttribute codeAttribute = behavior.getMethodInfo().getCodeAttribute(); - if (codeAttribute != null && codeAttribute.getAttribute(LocalVariableAttribute.tag) != null) { - continue; - } - - BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); - - // get the number of arguments - Signature signature = behaviorEntry.getSignature(); - if (signature == null) { - // static initializers have no signatures, or arguments - continue; - } - int numParams = signature.getArgumentTypes().size(); - if (numParams <= 0) { - continue; - } - - // get the list of argument names - List names = new ArrayList<>(numParams); - for (int i = 0; i < numParams; i++) { - names.add(translator.translate(new ArgumentEntry(behaviorEntry, i, ""))); - } - - // save the mappings to the class - MethodParametersAttribute.updateClass(behavior.getMethodInfo(), names); - } - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationAnnotationVisitor.java b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationAnnotationVisitor.java new file mode 100644 index 00000000..df5f8f7e --- /dev/null +++ b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationAnnotationVisitor.java @@ -0,0 +1,43 @@ +package cuchaz.enigma.bytecode.translators; + +import cuchaz.enigma.mapping.Translator; +import cuchaz.enigma.mapping.TypeDescriptor; +import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.mapping.entry.FieldEntry; +import org.objectweb.asm.AnnotationVisitor; + +public class TranslationAnnotationVisitor extends AnnotationVisitor { + private final Translator translator; + private final ClassEntry annotationEntry; + + public TranslationAnnotationVisitor(Translator translator, ClassEntry annotationEntry, int api, AnnotationVisitor av) { + super(api, av); + this.translator = translator; + this.annotationEntry = annotationEntry; + } + + @Override + public void visit(String name, Object value) { + super.visit(name, translator.getTranslatedValue(value)); + } + + @Override + public AnnotationVisitor visitArray(String name) { + return this; + } + + @Override + public AnnotationVisitor visitAnnotation(String name, String desc) { + TypeDescriptor type = new TypeDescriptor(desc); + FieldEntry annotationField = translator.getTranslatedField(new FieldEntry(annotationEntry, name, type)); + return super.visitAnnotation(annotationField.getName(), annotationField.getDesc().toString()); + } + + @Override + public void visitEnum(String name, String desc, String value) { + TypeDescriptor type = new TypeDescriptor(desc); + FieldEntry annotationField = translator.getTranslatedField(new FieldEntry(annotationEntry, name, type)); + FieldEntry enumField = translator.getTranslatedField(new FieldEntry(type.getTypeEntry(), value, type)); + super.visitEnum(annotationField.getName(), annotationField.getDesc().toString(), enumField.getName()); + } +} diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationClassVisitor.java b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationClassVisitor.java new file mode 100644 index 00000000..234d11f3 --- /dev/null +++ b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationClassVisitor.java @@ -0,0 +1,113 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.bytecode.translators; + +import cuchaz.enigma.analysis.JarIndex; +import cuchaz.enigma.bytecode.AccessFlags; +import cuchaz.enigma.mapping.MethodDescriptor; +import cuchaz.enigma.mapping.Signature; +import cuchaz.enigma.mapping.Translator; +import cuchaz.enigma.mapping.TypeDescriptor; +import cuchaz.enigma.mapping.entry.*; +import org.objectweb.asm.*; + +public class TranslationClassVisitor extends ClassVisitor { + private final Translator translator; + private final JarIndex jarIndex; + private final ReferencedEntryPool entryPool; + + private ClassDefEntry obfClassEntry; + private Signature obfSignature; + + public TranslationClassVisitor(Translator translator, JarIndex jarIndex, ReferencedEntryPool entryPool, int api, ClassVisitor cv) { + super(api, cv); + this.translator = translator; + this.jarIndex = jarIndex; + this.entryPool = entryPool; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + obfSignature = Signature.createSignature(signature); + obfClassEntry = new ClassDefEntry(name, obfSignature, new AccessFlags(access)); + ClassDefEntry translatedEntry = translator.getTranslatedClassDef(obfClassEntry); + ClassEntry superEntry = translator.getTranslatedClass(entryPool.getClass(superName)); + String[] translatedInterfaces = new String[interfaces.length]; + for (int i = 0; i < interfaces.length; i++) { + translatedInterfaces[i] = translator.getTranslatedClass(entryPool.getClass(interfaces[i])).getName(); + } + super.visit(version, translatedEntry.getAccess().getFlags(), translatedEntry.getName(), translatedEntry.getSignature().toString(), superEntry.getName(), translatedInterfaces); + } + + @Override + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { + FieldDefEntry entry = new FieldDefEntry(obfClassEntry, name, new TypeDescriptor(desc), Signature.createTypedSignature(signature), new AccessFlags(access)); + FieldDefEntry translatedEntry = translator.getTranslatedFieldDef(entry); + FieldVisitor fv = super.visitField(translatedEntry.getAccess().getFlags(), translatedEntry.getName(), translatedEntry.getDesc().toString(), translatedEntry.getSignature().toString(), value); + return new TranslationFieldVisitor(translator, translatedEntry, api, fv); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + MethodDefEntry entry = new MethodDefEntry(obfClassEntry, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access)); + MethodDefEntry translatedEntry = translator.getTranslatedMethodDef(entry); + if (jarIndex.getBridgedMethod(entry) != null) { + translatedEntry.getAccess().setBridged(); + } + String[] translatedExceptions = new String[exceptions.length]; + for (int i = 0; i < exceptions.length; i++) { + translatedExceptions[i] = translator.getTranslatedClass(entryPool.getClass(exceptions[i])).getName(); + } + MethodVisitor mv = super.visitMethod(translatedEntry.getAccess().getFlags(), translatedEntry.getName(), translatedEntry.getDesc().toString(), translatedEntry.getSignature().toString(), translatedExceptions); + return new TranslationMethodVisitor(translator, obfClassEntry, entry, api, mv); + } + + @Override + public void visitInnerClass(String name, String outerName, String innerName, int access) { + ClassDefEntry translatedEntry = translator.getTranslatedClassDef(new ClassDefEntry(name, obfSignature, new AccessFlags(access))); + String translatedName = translatedEntry.getName(); + int separatorIndex = translatedName.lastIndexOf("$"); + String parentName = translatedName.substring(0, separatorIndex); + String childName = translatedName.substring(separatorIndex + 1); + + ClassEntry outerEntry = translator.getTranslatedClass(entryPool.getClass(parentName)); + + // Anonymous classes do not specify an outer or inner name. As we do not translate from the given parameter, ignore if the input is null + String translatedOuterName = outerName != null ? outerEntry.getName() : null; + String translatedInnerName = innerName != null ? childName : null; + super.visitInnerClass(translatedName, translatedOuterName, translatedInnerName, translatedEntry.getAccess().getFlags()); + } + + @Override + public void visitOuterClass(String owner, String name, String desc) { + if (desc != null) { + MethodEntry translatedEntry = translator.getTranslatedMethod(new MethodEntry(new ClassEntry(owner), name, new MethodDescriptor(desc))); + super.visitOuterClass(translatedEntry.getClassName(), translatedEntry.getName(), translatedEntry.getDesc().toString()); + } else { + super.visitOuterClass(owner, name, desc); + } + } + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + TypeDescriptor translatedDesc = translator.getTranslatedTypeDesc(new TypeDescriptor(desc)); + AnnotationVisitor av = super.visitAnnotation(translatedDesc.toString(), visible); + return new TranslationAnnotationVisitor(translator, translatedDesc.getTypeEntry(), api, av); + } + + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { + TypeDescriptor translatedDesc = translator.getTranslatedTypeDesc(new TypeDescriptor(desc)); + AnnotationVisitor av = super.visitTypeAnnotation(typeRef, typePath, translatedDesc.toString(), visible); + return new TranslationAnnotationVisitor(translator, translatedDesc.getTypeEntry(), api, av); + } +} diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationFieldVisitor.java b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationFieldVisitor.java new file mode 100644 index 00000000..e4695fb6 --- /dev/null +++ b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationFieldVisitor.java @@ -0,0 +1,33 @@ +package cuchaz.enigma.bytecode.translators; + +import cuchaz.enigma.mapping.Translator; +import cuchaz.enigma.mapping.TypeDescriptor; +import cuchaz.enigma.mapping.entry.FieldDefEntry; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.TypePath; + +public class TranslationFieldVisitor extends FieldVisitor { + private final FieldDefEntry fieldEntry; + private final Translator translator; + + public TranslationFieldVisitor(Translator translator, FieldDefEntry fieldEntry, int api, FieldVisitor fv) { + super(api, fv); + this.translator = translator; + this.fieldEntry = fieldEntry; + } + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + TypeDescriptor typeDesc = translator.getTranslatedTypeDesc(new TypeDescriptor(desc)); + AnnotationVisitor av = super.visitAnnotation(typeDesc.toString(), visible); + return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av); + } + + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { + TypeDescriptor typeDesc = translator.getTranslatedTypeDesc(new TypeDescriptor(desc)); + AnnotationVisitor av = super.visitAnnotation(typeDesc.toString(), visible); + return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av); + } +} diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationMethodVisitor.java b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationMethodVisitor.java new file mode 100644 index 00000000..0141b45e --- /dev/null +++ b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationMethodVisitor.java @@ -0,0 +1,191 @@ +package cuchaz.enigma.bytecode.translators; + +import cuchaz.enigma.mapping.MethodDescriptor; +import cuchaz.enigma.mapping.Signature; +import cuchaz.enigma.mapping.Translator; +import cuchaz.enigma.mapping.TypeDescriptor; +import cuchaz.enigma.mapping.entry.*; +import org.objectweb.asm.*; + +import java.util.List; +import java.util.Locale; + +public class TranslationMethodVisitor extends MethodVisitor { + private final ClassDefEntry ownerEntry; + private final MethodDefEntry methodEntry; + private final Translator translator; + + private boolean hasParameterMeta; + + public TranslationMethodVisitor(Translator translator, ClassDefEntry ownerEntry, MethodDefEntry methodEntry, int api, MethodVisitor mv) { + super(api, mv); + this.translator = translator; + this.ownerEntry = ownerEntry; + this.methodEntry = methodEntry; + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + FieldEntry entry = new FieldEntry(new ClassEntry(owner), name, new TypeDescriptor(desc)); + FieldEntry translatedEntry = translator.getTranslatedField(entry); + super.visitFieldInsn(opcode, translatedEntry.getClassName(), translatedEntry.getName(), translatedEntry.getDesc().toString()); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + MethodEntry entry = new MethodEntry(new ClassEntry(owner), name, new MethodDescriptor(desc)); + MethodEntry translatedEntry = translator.getTranslatedMethod(entry); + super.visitMethodInsn(opcode, translatedEntry.getClassName(), translatedEntry.getName(), translatedEntry.getDesc().toString(), itf); + } + + @Override + public void visitFrame(int type, int localCount, Object[] locals, int stackCount, Object[] stack) { + Object[] translatedLocals = this.getTranslatedFrame(locals, localCount); + Object[] translatedStack = this.getTranslatedFrame(stack, stackCount); + super.visitFrame(type, localCount, translatedLocals, stackCount, translatedStack); + } + + private Object[] getTranslatedFrame(Object[] array, int count) { + if (array == null) { + return null; + } + for (int i = 0; i < count; i++) { + Object object = array[i]; + if (object instanceof String) { + String type = (String) object; + array[i] = translator.getTranslatedClass(new ClassEntry(type)).getName(); + } + } + return array; + } + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + TypeDescriptor typeDesc = translator.getTranslatedTypeDesc(new TypeDescriptor(desc)); + AnnotationVisitor av = super.visitAnnotation(typeDesc.toString(), visible); + return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av); + } + + @Override + public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { + TypeDescriptor typeDesc = translator.getTranslatedTypeDesc(new TypeDescriptor(desc)); + AnnotationVisitor av = super.visitParameterAnnotation(parameter, typeDesc.toString(), visible); + return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av); + } + + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { + TypeDescriptor typeDesc = translator.getTranslatedTypeDesc(new TypeDescriptor(desc)); + AnnotationVisitor av = super.visitTypeAnnotation(typeRef, typePath, typeDesc.toString(), visible); + return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av); + } + + @Override + public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { + hasParameterMeta = true; + + String translatedSignature = translator.getTranslatedSignature(Signature.createTypedSignature(signature)).toString(); + + int offset = methodEntry.getVariableOffset(ownerEntry); + + int offsetIndex = index - offset; + if (offsetIndex >= 0) { + LocalVariableDefEntry entry = new LocalVariableDefEntry(methodEntry, offsetIndex, name, new TypeDescriptor(desc)); + LocalVariableDefEntry translatedEntry = translator.getTranslatedVariableDef(entry); + String translatedName = translatedEntry.getName(); + + // TODO: Better name inference + if (translatedName.equals(entry.getName())) { + boolean argument = offsetIndex < methodEntry.getDesc().getArgumentDescs().size(); + translatedName = inferName(argument, offsetIndex, translatedEntry.getDesc()); + } + + super.visitLocalVariable(translatedName, translatedEntry.getDesc().toString(), translatedSignature, start, end, index); + } else { + // Handle "this" variable + TypeDescriptor translatedDesc = translator.getTranslatedTypeDesc(new TypeDescriptor(desc)); + super.visitLocalVariable(name, translatedDesc.toString(), translatedSignature, start, end, index); + } + } + + @Override + public void visitTypeInsn(int opcode, String type) { + ClassEntry translatedEntry = translator.getTranslatedClass(new ClassEntry(type)); + super.visitTypeInsn(opcode, translatedEntry.getName()); + } + + @Override + public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { + MethodDescriptor translatedMethodDesc = translator.getTranslatedMethodDesc(new MethodDescriptor(desc)); + Object[] translatedBsmArgs = new Object[bsmArgs.length]; + for (int i = 0; i < bsmArgs.length; i++) { + translatedBsmArgs[i] = translator.getTranslatedValue(bsmArgs[i]); + } + super.visitInvokeDynamicInsn(name, translatedMethodDesc.toString(), translator.getTranslatedHandle(bsm), translatedBsmArgs); + } + + @Override + public void visitLdcInsn(Object cst) { + super.visitLdcInsn(translator.getTranslatedValue(cst)); + } + + @Override + public void visitMultiANewArrayInsn(String desc, int dims) { + super.visitMultiANewArrayInsn(translator.getTranslatedTypeDesc(new TypeDescriptor(desc)).toString(), dims); + } + + @Override + public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { + if (type != null) { + ClassEntry translatedEntry = translator.getTranslatedClass(new ClassEntry(type)); + super.visitTryCatchBlock(start, end, handler, translatedEntry.getName()); + } else { + super.visitTryCatchBlock(start, end, handler, type); + } + } + + @Override + public void visitEnd() { + // If we didn't receive any parameter metadata, generate it + if (!hasParameterMeta) { + List arguments = methodEntry.getDesc().getArgumentDescs(); + for (int index = 0; index < arguments.size(); index++) { + LocalVariableEntry entry = new LocalVariableEntry(methodEntry, index, ""); + LocalVariableEntry translatedEntry = translator.getTranslatedVariable(entry); + String translatedName = translatedEntry.getName(); + if (translatedName.equals(entry.getName())) { + super.visitParameter(inferName(true, index, arguments.get(index)), 0); + } else { + super.visitParameter(translatedName, 0); + } + } + } + super.visitEnd(); + } + + private String inferName(boolean argument, int argumentIndex, TypeDescriptor desc) { + String translatedName; + int nameIndex = argumentIndex + 1; + StringBuilder nameBuilder = new StringBuilder(argument ? "a" : "v"); + // Unfortunately each of these have different name getters, so they have different code paths + if (desc.isPrimitive()) { + TypeDescriptor.Primitive argCls = desc.getPrimitive(); + nameBuilder.append(argCls.name()); + } else if (desc.isArray()) { + // List types would require this whole block again, so just go with aListx + nameBuilder.append("Arr"); + } else if (desc.isType()) { + String typeName = desc.getTypeEntry().getSimpleName().replace("$", ""); + typeName = typeName.substring(0, 1).toUpperCase(Locale.ROOT) + typeName.substring(1); + nameBuilder.append(typeName); + } else { + System.err.println("Encountered invalid argument type descriptor " + desc.toString()); + nameBuilder.append("Unk"); + } + if (!argument || methodEntry.getDesc().getArgumentDescs().size() > 1) { + nameBuilder.append(nameIndex); + } + translatedName = nameBuilder.toString(); + return translatedName; + } +} diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationSignatureVisitor.java b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationSignatureVisitor.java new file mode 100644 index 00000000..e66b085f --- /dev/null +++ b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationSignatureVisitor.java @@ -0,0 +1,129 @@ +package cuchaz.enigma.bytecode.translators; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.signature.SignatureVisitor; + +import java.util.Stack; +import java.util.function.Function; + +public class TranslationSignatureVisitor extends SignatureVisitor { + private final Function remapper; + + private final SignatureVisitor sv; + private final Stack classStack = new Stack<>(); + + public TranslationSignatureVisitor(Function remapper, SignatureVisitor sv) { + super(Opcodes.ASM5); + this.remapper = remapper; + this.sv = sv; + } + + @Override + public void visitClassType(String name) { + classStack.push(name); + String translatedEntry = this.remapper.apply(name); + this.sv.visitClassType(translatedEntry); + } + + @Override + public void visitInnerClassType(String name) { + String lastClass = classStack.pop(); + if (!name.startsWith(lastClass+"$")){//todo see if there's a way to base this on whether there were type params or not + name = lastClass+"$"+name; + } + String translatedEntry = this.remapper.apply(name); + if (translatedEntry.contains("/")){ + translatedEntry = translatedEntry.substring(translatedEntry.lastIndexOf("/")+1); + } + if (translatedEntry.contains("$")){ + translatedEntry = translatedEntry.substring(translatedEntry.lastIndexOf("$")+1); + } + this.sv.visitInnerClassType(translatedEntry); + } + + @Override + public void visitFormalTypeParameter(String name) { + this.sv.visitFormalTypeParameter(name); + } + + @Override + public void visitTypeVariable(String name) { + this.sv.visitTypeVariable(name); + } + + @Override + public SignatureVisitor visitArrayType() { + this.sv.visitArrayType(); + return this; + } + + @Override + public void visitBaseType(char descriptor) { + this.sv.visitBaseType(descriptor); + } + + @Override + public SignatureVisitor visitClassBound() { + this.sv.visitClassBound(); + return this; + } + + @Override + public SignatureVisitor visitExceptionType() { + this.sv.visitExceptionType(); + return this; + } + + @Override + public SignatureVisitor visitInterface() { + this.sv.visitInterface(); + return this; + } + + @Override + public SignatureVisitor visitInterfaceBound() { + this.sv.visitInterfaceBound(); + return this; + } + + @Override + public SignatureVisitor visitParameterType() { + this.sv.visitParameterType(); + return this; + } + + @Override + public SignatureVisitor visitReturnType() { + this.sv.visitReturnType(); + return this; + } + + @Override + public SignatureVisitor visitSuperclass() { + this.sv.visitSuperclass(); + return this; + } + + @Override + public void visitTypeArgument() { + this.sv.visitTypeArgument(); + } + + @Override + public SignatureVisitor visitTypeArgument(char wildcard) { + this.sv.visitTypeArgument(wildcard); + return this; + } + + @Override + public void visitEnd() { + this.sv.visitEnd(); + if (!classStack.empty()) + classStack.pop(); + } + + @Override + public String toString() { + return this.sv.toString(); + } +} diff --git a/src/main/java/cuchaz/enigma/gui/ClassSelector.java b/src/main/java/cuchaz/enigma/gui/ClassSelector.java index ed84ef24..93c5d4ba 100644 --- a/src/main/java/cuchaz/enigma/gui/ClassSelector.java +++ b/src/main/java/cuchaz/enigma/gui/ClassSelector.java @@ -17,7 +17,7 @@ import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import cuchaz.enigma.gui.node.ClassSelectorClassNode; import cuchaz.enigma.gui.node.ClassSelectorPackageNode; -import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.entry.ClassEntry; import cuchaz.enigma.throwables.IllegalNameException; import javax.swing.*; @@ -328,8 +328,12 @@ public class ClassSelector extends JTree { } public ClassSelectorPackageNode getPackageNode(ClassEntry entry) { + String packageName = entry.getPackageName(); + if (packageName == null){ + packageName = "(none)"; + } for (ClassSelectorPackageNode packageNode : packageNodes()) { - if (packageNode.getPackageName().equals(entry.getPackageName())) { + if (packageNode.getPackageName().equals(packageName)) { return packageNode; } } diff --git a/src/main/java/cuchaz/enigma/gui/CodeReader.java b/src/main/java/cuchaz/enigma/gui/CodeReader.java index f76dc897..ac45b4a7 100644 --- a/src/main/java/cuchaz/enigma/gui/CodeReader.java +++ b/src/main/java/cuchaz/enigma/gui/CodeReader.java @@ -17,8 +17,8 @@ import cuchaz.enigma.analysis.EntryReference; import cuchaz.enigma.analysis.SourceIndex; import cuchaz.enigma.analysis.Token; import cuchaz.enigma.gui.highlight.SelectionHighlightPainter; -import cuchaz.enigma.mapping.ClassEntry; -import cuchaz.enigma.mapping.Entry; +import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.mapping.entry.Entry; import de.sciss.syntaxpane.DefaultSyntaxKit; import javax.swing.*; @@ -162,7 +162,7 @@ public class CodeReader extends JEditorPane { // couldn't find the class declaration token, might be an anonymous class // look for any declaration in that class instead for (Entry entry : sourceIndex.declarations()) { - if (entry.getClassEntry().equals(classEntry)) { + if (entry.getOwnerClassEntry().equals(classEntry)) { token = sourceIndex.getDeclarationToken(entry); break; } diff --git a/src/main/java/cuchaz/enigma/gui/Gui.java b/src/main/java/cuchaz/enigma/gui/Gui.java index 4a891cf4..cfac8ad8 100644 --- a/src/main/java/cuchaz/enigma/gui/Gui.java +++ b/src/main/java/cuchaz/enigma/gui/Gui.java @@ -32,10 +32,10 @@ import cuchaz.enigma.gui.panels.PanelEditor; import cuchaz.enigma.gui.panels.PanelIdentifier; import cuchaz.enigma.gui.panels.PanelObf; import cuchaz.enigma.mapping.*; +import cuchaz.enigma.mapping.entry.*; import cuchaz.enigma.throwables.IllegalNameException; import cuchaz.enigma.utils.Utils; import de.sciss.syntaxpane.DefaultSyntaxKit; -import javassist.bytecode.Descriptor; import javax.swing.*; import javax.swing.text.BadLocationException; @@ -48,8 +48,10 @@ import java.awt.*; import java.awt.event.*; import java.io.File; import java.io.IOException; -import java.util.*; +import java.util.Collection; +import java.util.Collections; import java.util.List; +import java.util.Vector; import java.util.function.Function; public class Gui { @@ -438,14 +440,10 @@ public class Gui { showFieldEntry((FieldEntry) this.reference.entry); } else if (this.reference.entry instanceof MethodEntry) { showMethodEntry((MethodEntry) this.reference.entry); - } else if (this.reference.entry instanceof ConstructorEntry) { - showConstructorEntry((ConstructorEntry) this.reference.entry); - } else if (this.reference.entry instanceof ArgumentEntry) { - showArgumentEntry((ArgumentEntry) this.reference.entry); } else if (this.reference.entry instanceof LocalVariableEntry) { showLocalVariableEntry((LocalVariableEntry) this.reference.entry); } else { - throw new Error("Unknown entry type: " + this.reference.entry.getClass().getName()); + throw new Error("Unknown entry desc: " + this.reference.entry.getClass().getName()); } redraw(); @@ -453,10 +451,9 @@ public class Gui { private void showLocalVariableEntry(LocalVariableEntry entry) { addNameValue(infoPanel, "Variable", entry.getName()); - addNameValue(infoPanel, "Class", entry.getClassEntry().getName()); - addNameValue(infoPanel, "Method", entry.getBehaviorEntry().getName()); + addNameValue(infoPanel, "Class", entry.getOwnerClassEntry().getName()); + addNameValue(infoPanel, "Method", entry.getOwnerEntry().getName()); addNameValue(infoPanel, "Index", Integer.toString(entry.getIndex())); - addNameValue(infoPanel, "Type", entry.getType().toString()); } private void showClassEntry(ClassEntry entry) { @@ -466,32 +463,20 @@ public class Gui { private void showFieldEntry(FieldEntry entry) { addNameValue(infoPanel, "Field", entry.getName()); - addNameValue(infoPanel, "Class", entry.getClassEntry().getName()); - addNameValue(infoPanel, "Type", entry.getType().toString()); + addNameValue(infoPanel, "Class", entry.getOwnerClassEntry().getName()); + addNameValue(infoPanel, "TypeDescriptor", entry.getDesc().toString()); addModifierComboBox(infoPanel, "Modifier", entry); } private void showMethodEntry(MethodEntry entry) { - addNameValue(infoPanel, "Method", entry.getName()); - addNameValue(infoPanel, "Class", entry.getClassEntry().getName()); - addNameValue(infoPanel, "Signature", entry.getSignature().toString()); - addModifierComboBox(infoPanel, "Modifier", entry); - - } - - private void showConstructorEntry(ConstructorEntry entry) { - addNameValue(infoPanel, "Constructor", entry.getClassEntry().getName()); - if (!entry.isStatic()) { - addNameValue(infoPanel, "Signature", entry.getSignature().toString()); - addModifierComboBox(infoPanel, "Modifier", entry); + if (entry.isConstructor()) { + addNameValue(infoPanel, "Constructor", entry.getOwnerClassEntry().getName()); + } else { + addNameValue(infoPanel, "Method", entry.getName()); + addNameValue(infoPanel, "Class", entry.getOwnerClassEntry().getName()); } - } - - private void showArgumentEntry(ArgumentEntry entry) { - addNameValue(infoPanel, "Argument", entry.getName()); - addNameValue(infoPanel, "Class", entry.getClassEntry().getName()); - addNameValue(infoPanel, "Method", entry.getBehaviorEntry().getName()); - addNameValue(infoPanel, "Index", Integer.toString(entry.getIndex())); + addNameValue(infoPanel, "MethodDescriptor", entry.getDesc().toString()); + addModifierComboBox(infoPanel, "Modifier", entry); } private void addNameValue(JPanel container, String name, String value) { @@ -532,8 +517,8 @@ public class Gui { reference = this.controller.getDeobfReference(token); boolean isClassEntry = isToken && reference.entry instanceof ClassEntry; boolean isFieldEntry = isToken && reference.entry instanceof FieldEntry; - boolean isMethodEntry = isToken && reference.entry instanceof MethodEntry; - boolean isConstructorEntry = isToken && reference.entry instanceof ConstructorEntry; + boolean isMethodEntry = isToken && reference.entry instanceof MethodEntry && !((MethodEntry) reference.entry).isConstructor(); + boolean isConstructorEntry = isToken && reference.entry instanceof MethodEntry && ((MethodEntry) reference.entry).isConstructor(); boolean isInJar = isToken && this.controller.entryIsInJar(reference.entry); boolean isRenameable = isToken && this.controller.referenceIsRenameable(reference); @@ -710,16 +695,13 @@ public class Gui { if (reference.entry instanceof ClassEntry) { // look for calls to the default constructor // TODO: get a list of all the constructors and find calls to all of them - BehaviorReferenceTreeNode node = this.controller.getMethodReferences(new ConstructorEntry((ClassEntry) reference.entry, new Signature("()V"))); + MethodReferenceTreeNode node = this.controller.getMethodReferences(new MethodEntry((ClassEntry) reference.entry, "", new MethodDescriptor("()V"))); callsTree.setModel(new DefaultTreeModel(node)); } else if (reference.entry instanceof FieldEntry) { FieldReferenceTreeNode node = this.controller.getFieldReferences((FieldEntry) reference.entry); callsTree.setModel(new DefaultTreeModel(node)); } else if (reference.entry instanceof MethodEntry) { - BehaviorReferenceTreeNode node = this.controller.getMethodReferences((MethodEntry) reference.entry); - callsTree.setModel(new DefaultTreeModel(node)); - } else if (reference.entry instanceof ConstructorEntry) { - BehaviorReferenceTreeNode node = this.controller.getMethodReferences((ConstructorEntry) reference.entry); + MethodReferenceTreeNode node = this.controller.getMethodReferences((MethodEntry) reference.entry); callsTree.setModel(new DefaultTreeModel(node)); } @@ -790,7 +772,6 @@ public class Gui { // package rename if (data instanceof String) { for (int i = 0; i < node.getChildCount(); i++) { - data = Descriptor.toJvmName((String) data); DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) node.getChildAt(i); ClassEntry prevDataChild = (ClassEntry) childNode.getUserObject(); ClassEntry dataChild = new ClassEntry(data + "/" + prevDataChild.getSimpleName()); @@ -807,15 +788,15 @@ public class Gui { } public void moveClassTree(EntryReference deobfReference, String newName) { - String oldEntry = deobfReference.entry.getClassEntry().getPackageName(); - String newEntry = new ClassEntry(Descriptor.toJvmName(newName)).getPackageName(); + String oldEntry = deobfReference.entry.getOwnerClassEntry().getPackageName(); + String newEntry = new ClassEntry(newName).getPackageName(); moveClassTree(deobfReference, newName, oldEntry == null, newEntry == null); } public void moveClassTree(EntryReference deobfReference, String newName, boolean isOldOb, boolean isNewOb) { - ClassEntry oldEntry = deobfReference.entry.getClassEntry(); - ClassEntry newEntry = new ClassEntry(Descriptor.toJvmName(newName)); + ClassEntry oldEntry = deobfReference.entry.getOwnerClassEntry(); + ClassEntry newEntry = new ClassEntry(newName); // Ob -> deob if (isOldOb && !isNewOb) { diff --git a/src/main/java/cuchaz/enigma/gui/GuiController.java b/src/main/java/cuchaz/enigma/gui/GuiController.java index 6d98743d..ae1b6528 100644 --- a/src/main/java/cuchaz/enigma/gui/GuiController.java +++ b/src/main/java/cuchaz/enigma/gui/GuiController.java @@ -18,6 +18,10 @@ import cuchaz.enigma.Deobfuscator; import cuchaz.enigma.analysis.*; import cuchaz.enigma.gui.dialog.ProgressDialog; import cuchaz.enigma.mapping.*; +import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.mapping.entry.Entry; +import cuchaz.enigma.mapping.entry.FieldEntry; +import cuchaz.enigma.mapping.entry.MethodEntry; import cuchaz.enigma.throwables.MappingParseException; import cuchaz.enigma.utils.ReadableToken; @@ -51,10 +55,10 @@ public class GuiController { return this.isDirty; } - public void openJar(final JarFile jar) { + public void openJar(final JarFile jar) throws IOException { this.gui.onStartOpenJar(); this.deobfuscator = new Deobfuscator(jar); - this.gui.onFinishOpenJar(this.deobfuscator.getJarName()); + this.gui.onFinishOpenJar(jar.getName()); refreshClasses(); } @@ -161,24 +165,24 @@ public class GuiController { public ClassInheritanceTreeNode getClassInheritance(ClassEntry deobfClassEntry) { ClassEntry obfClassEntry = this.deobfuscator.obfuscateEntry(deobfClassEntry); - ClassInheritanceTreeNode rootNode = this.deobfuscator.getJarIndex().getClassInheritance(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfClassEntry); + ClassInheritanceTreeNode rootNode = this.deobfuscator.getJarIndex().getClassInheritance(this.deobfuscator.getTranslator(TranslationDirection.DEOBFUSCATING), obfClassEntry); return ClassInheritanceTreeNode.findNode(rootNode, obfClassEntry); } public ClassImplementationsTreeNode getClassImplementations(ClassEntry deobfClassEntry) { ClassEntry obfClassEntry = this.deobfuscator.obfuscateEntry(deobfClassEntry); - return this.deobfuscator.getJarIndex().getClassImplementations(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfClassEntry); + return this.deobfuscator.getJarIndex().getClassImplementations(this.deobfuscator.getTranslator(TranslationDirection.DEOBFUSCATING), obfClassEntry); } public MethodInheritanceTreeNode getMethodInheritance(MethodEntry deobfMethodEntry) { MethodEntry obfMethodEntry = this.deobfuscator.obfuscateEntry(deobfMethodEntry); - MethodInheritanceTreeNode rootNode = this.deobfuscator.getJarIndex().getMethodInheritance(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfMethodEntry); + MethodInheritanceTreeNode rootNode = this.deobfuscator.getJarIndex().getMethodInheritance(this.deobfuscator.getTranslator(TranslationDirection.DEOBFUSCATING), obfMethodEntry); return MethodInheritanceTreeNode.findNode(rootNode, obfMethodEntry); } public MethodImplementationsTreeNode getMethodImplementations(MethodEntry deobfMethodEntry) { MethodEntry obfMethodEntry = this.deobfuscator.obfuscateEntry(deobfMethodEntry); - List rootNodes = this.deobfuscator.getJarIndex().getMethodImplementations(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfMethodEntry); + List rootNodes = this.deobfuscator.getJarIndex().getMethodImplementations(this.deobfuscator.getTranslator(TranslationDirection.DEOBFUSCATING), obfMethodEntry); if (rootNodes.isEmpty()) { return null; } @@ -190,14 +194,14 @@ public class GuiController { public FieldReferenceTreeNode getFieldReferences(FieldEntry deobfFieldEntry) { FieldEntry obfFieldEntry = this.deobfuscator.obfuscateEntry(deobfFieldEntry); - FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfFieldEntry); + FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(this.deobfuscator.getTranslator(TranslationDirection.DEOBFUSCATING), obfFieldEntry); rootNode.load(this.deobfuscator.getJarIndex(), true); return rootNode; } - public BehaviorReferenceTreeNode getMethodReferences(BehaviorEntry deobfBehaviorEntry) { - BehaviorEntry obfBehaviorEntry = this.deobfuscator.obfuscateEntry(deobfBehaviorEntry); - BehaviorReferenceTreeNode rootNode = new BehaviorReferenceTreeNode(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfBehaviorEntry); + public MethodReferenceTreeNode getMethodReferences(MethodEntry deobfMethodEntry) { + MethodEntry obfMethodEntry = this.deobfuscator.obfuscateEntry(deobfMethodEntry); + MethodReferenceTreeNode rootNode = new MethodReferenceTreeNode(this.deobfuscator.getTranslator(TranslationDirection.DEOBFUSCATING), obfMethodEntry); rootNode.load(this.deobfuscator.getJarIndex(), true); return rootNode; } diff --git a/src/main/java/cuchaz/enigma/gui/GuiTricks.java b/src/main/java/cuchaz/enigma/gui/GuiTricks.java index 8bf57d38..92084559 100644 --- a/src/main/java/cuchaz/enigma/gui/GuiTricks.java +++ b/src/main/java/cuchaz/enigma/gui/GuiTricks.java @@ -14,7 +14,6 @@ package cuchaz.enigma.gui; import javax.swing.*; import java.awt.*; import java.awt.event.ActionListener; -import java.util.Arrays; public class GuiTricks { @@ -27,7 +26,7 @@ public class GuiTricks { public static void deactivateButton(JButton button) { button.setEnabled(false); button.setText(""); - for (ActionListener listener : Arrays.asList(button.getActionListeners())) { + for (ActionListener listener : button.getActionListeners()) { button.removeActionListener(listener); } } @@ -35,7 +34,7 @@ public class GuiTricks { public static void activateButton(JButton button, String text, ActionListener newListener) { button.setText(text); button.setEnabled(true); - for (ActionListener listener : Arrays.asList(button.getActionListeners())) { + for (ActionListener listener : button.getActionListeners()) { button.removeActionListener(listener); } button.addActionListener(newListener); diff --git a/src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java b/src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java index 1fd2fa85..34ec26ee 100644 --- a/src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java +++ b/src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java @@ -11,7 +11,7 @@ package cuchaz.enigma.gui; -import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.entry.ClassEntry; public class ScoredClassEntry extends ClassEntry { diff --git a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java index dc933cd5..a965a8fa 100644 --- a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java +++ b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java @@ -11,7 +11,7 @@ package cuchaz.enigma.gui.node; -import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.entry.ClassEntry; import javax.swing.tree.DefaultMutableTreeNode; diff --git a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java index f80abba6..caa985c9 100644 --- a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java +++ b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java @@ -11,8 +11,6 @@ package cuchaz.enigma.gui.node; -import javassist.bytecode.Descriptor; - import javax.swing.tree.DefaultMutableTreeNode; public class ClassSelectorPackageNode extends DefaultMutableTreeNode { @@ -41,7 +39,7 @@ public class ClassSelectorPackageNode extends DefaultMutableTreeNode { @Override public String toString() { - return !packageName.equals("(none)") ? Descriptor.toJavaName(this.packageName) : "(none)"; + return !packageName.equals("(none)") ? this.packageName : "(none)"; } @Override diff --git a/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java b/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java index 4bbd32bd..9eb8f8f8 100644 --- a/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java +++ b/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java @@ -2,7 +2,7 @@ package cuchaz.enigma.gui.panels; import cuchaz.enigma.gui.ClassSelector; import cuchaz.enigma.gui.Gui; -import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.entry.ClassEntry; import javax.swing.*; import java.awt.*; diff --git a/src/main/java/cuchaz/enigma/mapping/ArgumentEntry.java b/src/main/java/cuchaz/enigma/mapping/ArgumentEntry.java deleted file mode 100644 index 9154cc22..00000000 --- a/src/main/java/cuchaz/enigma/mapping/ArgumentEntry.java +++ /dev/null @@ -1,110 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.mapping; - -import cuchaz.enigma.utils.Utils; - -public class ArgumentEntry implements Entry { - - private BehaviorEntry behaviorEntry; - private int index; - private String name; - - public ArgumentEntry(BehaviorEntry behaviorEntry, int index, String name) { - if (behaviorEntry == null) { - throw new IllegalArgumentException("Behavior cannot be null!"); - } - if (index < 0) { - throw new IllegalArgumentException("Index must be non-negative!"); - } - if (name == null) { - throw new IllegalArgumentException("Argument name cannot be null!"); - } - - this.behaviorEntry = behaviorEntry; - this.index = index; - this.name = name; - } - - public ArgumentEntry(ArgumentEntry other) { - this.behaviorEntry = other.getBehaviorEntry(); - this.index = other.index; - this.name = other.name; - } - - public ArgumentEntry(ArgumentEntry other, String newClassName) { - this.behaviorEntry = (BehaviorEntry) other.behaviorEntry.cloneToNewClass(new ClassEntry(newClassName)); - this.index = other.index; - this.name = other.name; - } - - public ArgumentEntry(ArgumentEntry other, BehaviorEntry entry) { - this.behaviorEntry = entry; - this.index = other.index; - this.name = other.name; - } - - public BehaviorEntry getBehaviorEntry() { - return this.behaviorEntry; - } - - public int getIndex() { - return this.index; - } - - @Override - public String getName() { - return this.name; - } - - @Override - public ClassEntry getClassEntry() { - return this.behaviorEntry.getClassEntry(); - } - - @Override - public String getClassName() { - return this.behaviorEntry.getClassName(); - } - - @Override - public ArgumentEntry cloneToNewClass(ClassEntry classEntry) { - return new ArgumentEntry(this, classEntry.getName()); - } - - public String getMethodName() { - return this.behaviorEntry.getName(); - } - - public Signature getMethodSignature() { - return this.behaviorEntry.getSignature(); - } - - @Override - public int hashCode() { - return Utils.combineHashesOrdered(this.behaviorEntry, Integer.valueOf(this.index).hashCode(), this.name.hashCode()); - } - - @Override - public boolean equals(Object other) { - return other instanceof ArgumentEntry && equals((ArgumentEntry) other); - } - - public boolean equals(ArgumentEntry other) { - return this.behaviorEntry.equals(other.behaviorEntry) && this.index == other.index && this.name.equals(other.name); - } - - @Override - public String toString() { - return this.behaviorEntry + "(" + this.index + ":" + this.name + ")"; - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/ArgumentMapping.java b/src/main/java/cuchaz/enigma/mapping/ArgumentMapping.java deleted file mode 100644 index 91ecd106..00000000 --- a/src/main/java/cuchaz/enigma/mapping/ArgumentMapping.java +++ /dev/null @@ -1,50 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.mapping; - -public class ArgumentMapping implements Comparable { - - private int index; - private String name; - - // NOTE: this argument order is important for the MethodReader/MethodWriter - public ArgumentMapping(int index, String name) { - this.index = index; - this.name = NameValidator.validateArgumentName(name); - } - - public ArgumentMapping(ArgumentMapping other) { - this.index = other.index; - this.name = other.name; - } - - public int getIndex() { - return this.index; - } - - public String getName() { - return this.name; - } - - public void setName(String val) { - this.name = NameValidator.validateArgumentName(val); - } - - public ArgumentEntry getObfEntry(BehaviorEntry behaviorEntry) { - return new ArgumentEntry(behaviorEntry, index, name); - } - - @Override - public int compareTo(ArgumentMapping other) { - return Integer.compare(this.index, other.index); - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/BehaviorEntry.java b/src/main/java/cuchaz/enigma/mapping/BehaviorEntry.java deleted file mode 100644 index 04b4ebc7..00000000 --- a/src/main/java/cuchaz/enigma/mapping/BehaviorEntry.java +++ /dev/null @@ -1,16 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.mapping; - -public interface BehaviorEntry extends Entry { - Signature getSignature(); -} diff --git a/src/main/java/cuchaz/enigma/mapping/ClassEntry.java b/src/main/java/cuchaz/enigma/mapping/ClassEntry.java deleted file mode 100644 index 788811ff..00000000 --- a/src/main/java/cuchaz/enigma/mapping/ClassEntry.java +++ /dev/null @@ -1,167 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.mapping; - -import com.google.common.collect.Lists; - -import java.util.List; - -public class ClassEntry implements Entry { - - private String name; - - public ClassEntry(String className) { - if (className == null) { - throw new IllegalArgumentException("Class name cannot be null!"); - } - if (className.indexOf('.') >= 0) { - throw new IllegalArgumentException("Class name must be in JVM format. ie, path/to/package/class$inner : " + className); - } - - this.name = className; - - if (isInnerClass() && getInnermostClassName().indexOf('/') >= 0) { - throw new IllegalArgumentException("Inner class must not have a package: " + className); - } - } - - public ClassEntry(ClassEntry other) { - this.name = other.name; - } - - @Override - public String getName() { - return this.name; - } - - @Override - public String getClassName() { - return this.name; - } - - @Override - public ClassEntry getClassEntry() { - return this; - } - - @Override - public ClassEntry cloneToNewClass(ClassEntry classEntry) { - return classEntry; - } - - @Override - public int hashCode() { - return this.name.hashCode(); - } - - @Override - public boolean equals(Object other) { - return other instanceof ClassEntry && equals((ClassEntry) other); - } - - public boolean equals(ClassEntry other) { - return other != null && this.name.equals(other.name); - } - - @Override - public String toString() { - return this.name; - } - - public boolean isInnerClass() { - return this.name.lastIndexOf('$') >= 0; - } - - public List getClassChainNames() { - return Lists.newArrayList(this.name.split("\\$")); - } - - public List getClassChain() { - List entries = Lists.newArrayList(); - StringBuilder buf = new StringBuilder(); - for (String name : getClassChainNames()) { - if (buf.length() > 0) { - buf.append("$"); - } - buf.append(name); - entries.add(new ClassEntry(buf.toString())); - } - return entries; - } - - public String getOutermostClassName() { - if (isInnerClass()) { - return this.name.substring(0, this.name.indexOf('$')); - } - return this.name; - } - - public ClassEntry getOutermostClassEntry() { - return new ClassEntry(getOutermostClassName()); - } - - public String getOuterClassName() { - if (!isInnerClass()) { - throw new Error("This is not an inner class!"); - } - return this.name.substring(0, this.name.lastIndexOf('$')); - } - - public ClassEntry getOuterClassEntry() { - return new ClassEntry(getOuterClassName()); - } - - public String getInnermostClassName() { - if (!isInnerClass()) { - throw new Error("This is not an inner class!"); - } - return this.name.substring(this.name.lastIndexOf('$') + 1); - } - - public boolean isInDefaultPackage() { - return this.name.indexOf('/') < 0; - } - - public String getPackageName() { - int pos = this.name.lastIndexOf('/'); - if (pos > 0) { - return this.name.substring(0, pos); - } - return null; - } - - public String getSimpleName() { - int pos = this.name.lastIndexOf('/'); - if (pos > 0) { - return this.name.substring(pos + 1); - } - return this.name; - } - - public ClassEntry buildClassEntry(List classChain) { - assert (classChain.contains(this)); - StringBuilder buf = new StringBuilder(); - for (ClassEntry chainEntry : classChain) { - if (buf.length() == 0) { - buf.append(chainEntry.getName()); - } else { - buf.append("$"); - buf.append(chainEntry.isInnerClass() ? chainEntry.getInnermostClassName() : chainEntry.getSimpleName()); - } - - if (chainEntry == this) { - break; - } - } - return new ClassEntry(buf.toString()); - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/ClassMapping.java b/src/main/java/cuchaz/enigma/mapping/ClassMapping.java index 51751ca9..8f3f2b2b 100644 --- a/src/main/java/cuchaz/enigma/mapping/ClassMapping.java +++ b/src/main/java/cuchaz/enigma/mapping/ClassMapping.java @@ -12,6 +12,9 @@ package cuchaz.enigma.mapping; import com.google.common.collect.Maps; +import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.mapping.entry.FieldEntry; +import cuchaz.enigma.mapping.entry.MethodEntry; import cuchaz.enigma.throwables.MappingConflict; import java.util.ArrayList; @@ -34,7 +37,6 @@ public class ClassMapping implements Comparable { private Map methodsByDeobf; private boolean isDirty; private Mappings.EntryModifier modifier; - private boolean deobfInner; public ClassMapping(String obfFullName) { this(obfFullName, null, Mappings.EntryModifier.UNCHANGED); @@ -81,6 +83,10 @@ public class ClassMapping implements Comparable { return deobfName; } + public String getTranslatedName(TranslationDirection direction) { + return direction.choose(deobfName, obfFullName); + } + //// INNER CLASSES //////// public void setDeobfName(String val) { @@ -191,21 +197,21 @@ public class ClassMapping implements Comparable { return fieldsByObf.values(); } - public boolean containsObfField(String obfName, Type obfType) { - return fieldsByObf.containsKey(getFieldKey(obfName, obfType)); + public boolean containsObfField(String obfName, TypeDescriptor obfDesc) { + return fieldsByObf.containsKey(getFieldKey(obfName, obfDesc)); } - public boolean containsDeobfField(String deobfName, Type deobfType) { - return fieldsByDeobf.containsKey(getFieldKey(deobfName, deobfType)); + public boolean containsDeobfField(String deobfName, TypeDescriptor deobfDesc) { + return fieldsByDeobf.containsKey(getFieldKey(deobfName, deobfDesc)); } public void addFieldMapping(FieldMapping fieldMapping) { - String obfKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType()); + String obfKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfDesc()); if (fieldsByObf.containsKey(obfKey)) { throw new Error("Already have mapping for " + obfFullName + "." + obfKey); } if (fieldMapping.getDeobfName() != null) { - String deobfKey = getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfType()); + String deobfKey = getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfDesc()); if (fieldsByDeobf.containsKey(deobfKey)) { throw new Error("Already have mapping for " + deobfName + "." + deobfKey); } @@ -218,63 +224,67 @@ public class ClassMapping implements Comparable { } public void removeFieldMapping(FieldMapping fieldMapping) { - boolean obfWasRemoved = fieldsByObf.remove(getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType())) != null; + boolean obfWasRemoved = fieldsByObf.remove(getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfDesc())) != null; assert (obfWasRemoved); if (fieldMapping.getDeobfName() != null) { - boolean deobfWasRemoved = fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfType())) != null; + boolean deobfWasRemoved = fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfDesc())) != null; assert (deobfWasRemoved); } this.isDirty = true; } - public FieldMapping getFieldByObf(String obfName, Type obfType) { - return fieldsByObf.get(getFieldKey(obfName, obfType)); + public FieldMapping getFieldByObf(String obfName, TypeDescriptor obfDesc) { + return fieldsByObf.get(getFieldKey(obfName, obfDesc)); + } + + public FieldMapping getFieldByObf(FieldEntry field) { + return getFieldByObf(field.getName(), field.getDesc()); } - public FieldMapping getFieldByDeobf(String deobfName, Type obfType) { - return fieldsByDeobf.get(getFieldKey(deobfName, obfType)); + public FieldMapping getFieldByDeobf(String deobfName, TypeDescriptor obfDesc) { + return fieldsByDeobf.get(getFieldKey(deobfName, obfDesc)); } - public String getObfFieldName(String deobfName, Type obfType) { - FieldMapping fieldMapping = fieldsByDeobf.get(getFieldKey(deobfName, obfType)); + public String getObfFieldName(String deobfName, TypeDescriptor obfDesc) { + FieldMapping fieldMapping = fieldsByDeobf.get(getFieldKey(deobfName, obfDesc)); if (fieldMapping != null) { return fieldMapping.getObfName(); } return null; } - public String getDeobfFieldName(String obfName, Type obfType) { - FieldMapping fieldMapping = fieldsByObf.get(getFieldKey(obfName, obfType)); + public String getDeobfFieldName(String obfName, TypeDescriptor obfDesc) { + FieldMapping fieldMapping = fieldsByObf.get(getFieldKey(obfName, obfDesc)); if (fieldMapping != null) { return fieldMapping.getDeobfName(); } return null; } - private String getFieldKey(String name, Type type) { + private String getFieldKey(String name, TypeDescriptor desc) { if (name == null) { throw new IllegalArgumentException("name cannot be null!"); } - if (type == null) { - throw new IllegalArgumentException("type cannot be null!"); + if (desc == null) { + throw new IllegalArgumentException("desc cannot be null!"); } - return name + ":" + type; + return name + ":" + desc; } - public void setFieldName(String obfName, Type obfType, String deobfName) { + public void setFieldName(String obfName, TypeDescriptor obfDesc, String deobfName) { assert (deobfName != null); - FieldMapping fieldMapping = fieldsByObf.get(getFieldKey(obfName, obfType)); + FieldMapping fieldMapping = fieldsByObf.get(getFieldKey(obfName, obfDesc)); if (fieldMapping == null) { - fieldMapping = new FieldMapping(obfName, obfType, deobfName, Mappings.EntryModifier.UNCHANGED); - boolean obfWasAdded = fieldsByObf.put(getFieldKey(obfName, obfType), fieldMapping) == null; + fieldMapping = new FieldMapping(obfName, obfDesc, deobfName, Mappings.EntryModifier.UNCHANGED); + boolean obfWasAdded = fieldsByObf.put(getFieldKey(obfName, obfDesc), fieldMapping) == null; assert (obfWasAdded); } else { - boolean wasRemoved = fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), obfType)) != null; + boolean wasRemoved = fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), obfDesc)) != null; assert (wasRemoved); } fieldMapping.setDeobfName(deobfName); if (deobfName != null) { - boolean wasAdded = fieldsByDeobf.put(getFieldKey(deobfName, obfType), fieldMapping) == null; + boolean wasAdded = fieldsByDeobf.put(getFieldKey(deobfName, obfDesc), fieldMapping) == null; assert (wasAdded); } this.isDirty = true; @@ -282,13 +292,13 @@ public class ClassMapping implements Comparable { //// METHODS //////// - public void setFieldObfNameAndType(String oldObfName, Type obfType, String newObfName, Type newObfType) { + public void setFieldObfNameAndType(String oldObfName, TypeDescriptor obfDesc, String newObfName, TypeDescriptor newObfDesc) { assert (newObfName != null); - FieldMapping fieldMapping = fieldsByObf.remove(getFieldKey(oldObfName, obfType)); + FieldMapping fieldMapping = fieldsByObf.remove(getFieldKey(oldObfName, obfDesc)); assert (fieldMapping != null); fieldMapping.setObfName(newObfName); - fieldMapping.setObfType(newObfType); - boolean obfWasAdded = fieldsByObf.put(getFieldKey(newObfName, newObfType), fieldMapping) == null; + fieldMapping.setObfDesc(newObfDesc); + boolean obfWasAdded = fieldsByObf.put(getFieldKey(newObfName, newObfDesc), fieldMapping) == null; assert (obfWasAdded); this.isDirty = true; } @@ -298,23 +308,23 @@ public class ClassMapping implements Comparable { return methodsByObf.values(); } - public boolean containsObfMethod(String obfName, Signature obfSignature) { - return methodsByObf.containsKey(getMethodKey(obfName, obfSignature)); + public boolean containsObfMethod(String obfName, MethodDescriptor obfDescriptor) { + return methodsByObf.containsKey(getMethodKey(obfName, obfDescriptor)); } - public boolean containsDeobfMethod(String deobfName, Signature obfSignature) { - return methodsByDeobf.containsKey(getMethodKey(deobfName, obfSignature)); + public boolean containsDeobfMethod(String deobfName, MethodDescriptor obfDescriptor) { + return methodsByDeobf.containsKey(getMethodKey(deobfName, obfDescriptor)); } public void addMethodMapping(MethodMapping methodMapping) { - String obfKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()); + String obfKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfDesc()); if (methodsByObf.containsKey(obfKey)) { throw new Error("Already have mapping for " + obfFullName + "." + obfKey); } boolean wasAdded = methodsByObf.put(obfKey, methodMapping) == null; assert (wasAdded); - if (methodMapping.getDeobfName() != null) { - String deobfKey = getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature()); + if (!methodMapping.isObfuscated()) { + String deobfKey = getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfDesc()); if (methodsByDeobf.containsKey(deobfKey)) { throw new Error("Already have mapping for " + deobfName + "." + deobfKey); } @@ -326,44 +336,48 @@ public class ClassMapping implements Comparable { } public void removeMethodMapping(MethodMapping methodMapping) { - boolean obfWasRemoved = methodsByObf.remove(getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature())) != null; + boolean obfWasRemoved = methodsByObf.remove(getMethodKey(methodMapping.getObfName(), methodMapping.getObfDesc())) != null; assert (obfWasRemoved); - if (methodMapping.getDeobfName() != null) { - boolean deobfWasRemoved = methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature())) != null; + if (!methodMapping.isObfuscated()) { + boolean deobfWasRemoved = methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfDesc())) != null; assert (deobfWasRemoved); } this.isDirty = true; } - public MethodMapping getMethodByObf(String obfName, Signature obfSignature) { - return methodsByObf.get(getMethodKey(obfName, obfSignature)); + public MethodMapping getMethodByObf(String obfName, MethodDescriptor obfDescriptor) { + return methodsByObf.get(getMethodKey(obfName, obfDescriptor)); + } + + public MethodMapping getMethodByObf(MethodEntry method) { + return getMethodByObf(method.getName(), method.getDesc()); } - public MethodMapping getMethodByDeobf(String deobfName, Signature obfSignature) { - return methodsByDeobf.get(getMethodKey(deobfName, obfSignature)); + public MethodMapping getMethodByDeobf(String deobfName, MethodDescriptor obfDescriptor) { + return methodsByDeobf.get(getMethodKey(deobfName, obfDescriptor)); } - private String getMethodKey(String name, Signature signature) { + private String getMethodKey(String name, MethodDescriptor descriptor) { if (name == null) { throw new IllegalArgumentException("name cannot be null!"); } - if (signature == null) { - throw new IllegalArgumentException("signature cannot be null!"); + if (descriptor == null) { + throw new IllegalArgumentException("descriptor cannot be null!"); } - return name + signature; + return name + descriptor; } - public void setMethodName(String obfName, Signature obfSignature, String deobfName) { - MethodMapping methodMapping = methodsByObf.get(getMethodKey(obfName, obfSignature)); + public void setMethodName(String obfName, MethodDescriptor obfDescriptor, String deobfName) { + MethodMapping methodMapping = methodsByObf.get(getMethodKey(obfName, obfDescriptor)); if (methodMapping == null) { - methodMapping = createMethodMapping(obfName, obfSignature); - } else if (methodMapping.getDeobfName() != null) { - boolean wasRemoved = methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature())) != null; + methodMapping = createMethodMapping(obfName, obfDescriptor); + } else if (!methodMapping.isObfuscated()) { + boolean wasRemoved = methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfDesc())) != null; assert (wasRemoved); } methodMapping.setDeobfName(deobfName); if (deobfName != null) { - boolean wasAdded = methodsByDeobf.put(getMethodKey(deobfName, obfSignature), methodMapping) == null; + boolean wasAdded = methodsByDeobf.put(getMethodKey(deobfName, obfDescriptor), methodMapping) == null; assert (wasAdded); } this.isDirty = true; @@ -371,35 +385,35 @@ public class ClassMapping implements Comparable { //// ARGUMENTS //////// - public void setMethodObfNameAndSignature(String oldObfName, Signature obfSignature, String newObfName, Signature newObfSignature) { + public void setMethodObfNameAndSignature(String oldObfName, MethodDescriptor obfDescriptor, String newObfName, MethodDescriptor newObfDescriptor) { assert (newObfName != null); - MethodMapping methodMapping = methodsByObf.remove(getMethodKey(oldObfName, obfSignature)); + MethodMapping methodMapping = methodsByObf.remove(getMethodKey(oldObfName, obfDescriptor)); assert (methodMapping != null); methodMapping.setObfName(newObfName); - methodMapping.setObfSignature(newObfSignature); - boolean obfWasAdded = methodsByObf.put(getMethodKey(newObfName, newObfSignature), methodMapping) == null; + methodMapping.setObfDescriptor(newObfDescriptor); + boolean obfWasAdded = methodsByObf.put(getMethodKey(newObfName, newObfDescriptor), methodMapping) == null; assert (obfWasAdded); this.isDirty = true; } - public void setArgumentName(String obfMethodName, Signature obfMethodSignature, int argumentIndex, String argumentName) { + public void setArgumentName(String obfMethodName, MethodDescriptor obfMethodDescriptor, int argumentIndex, String argumentName) { assert (argumentName != null); - MethodMapping methodMapping = methodsByObf.get(getMethodKey(obfMethodName, obfMethodSignature)); + MethodMapping methodMapping = methodsByObf.get(getMethodKey(obfMethodName, obfMethodDescriptor)); if (methodMapping == null) { - methodMapping = createMethodMapping(obfMethodName, obfMethodSignature); + methodMapping = createMethodMapping(obfMethodName, obfMethodDescriptor); } - methodMapping.setArgumentName(argumentIndex, argumentName); + methodMapping.setLocalVariableName(argumentIndex, argumentName); this.isDirty = true; } - public void removeArgumentName(String obfMethodName, Signature obfMethodSignature, int argumentIndex) { - methodsByObf.get(getMethodKey(obfMethodName, obfMethodSignature)).removeArgumentName(argumentIndex); + public void removeArgumentName(String obfMethodName, MethodDescriptor obfMethodDescriptor, int argumentIndex) { + methodsByObf.get(getMethodKey(obfMethodName, obfMethodDescriptor)).removeLocalVariableName(argumentIndex); this.isDirty = true; } - private MethodMapping createMethodMapping(String obfName, Signature obfSignature) { - MethodMapping methodMapping = new MethodMapping(obfName, obfSignature); - boolean wasAdded = methodsByObf.put(getMethodKey(obfName, obfSignature), methodMapping) == null; + private MethodMapping createMethodMapping(String obfName, MethodDescriptor obfDescriptor) { + MethodMapping methodMapping = new MethodMapping(obfName, obfDescriptor); + boolean wasAdded = methodsByObf.put(getMethodKey(obfName, obfDescriptor), methodMapping) == null; assert (wasAdded); this.isDirty = true; return methodMapping; @@ -459,24 +473,24 @@ public class ClassMapping implements Comparable { // rename field types for (FieldMapping fieldMapping : new ArrayList<>(fieldsByObf.values())) { - String oldFieldKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType()); + String oldFieldKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfDesc()); if (fieldMapping.renameObfClass(oldObfClassName, newObfClassName)) { boolean wasRemoved = fieldsByObf.remove(oldFieldKey) != null; assert (wasRemoved); boolean wasAdded = fieldsByObf - .put(getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType()), fieldMapping) == null; + .put(getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfDesc()), fieldMapping) == null; assert (wasAdded); } } // rename method signatures for (MethodMapping methodMapping : new ArrayList<>(methodsByObf.values())) { - String oldMethodKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()); + String oldMethodKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfDesc()); if (methodMapping.renameObfClass(oldObfClassName, newObfClassName)) { boolean wasRemoved = methodsByObf.remove(oldMethodKey) != null; assert (wasRemoved); boolean wasAdded = methodsByObf - .put(getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()), methodMapping) == null; + .put(getMethodKey(methodMapping.getObfName(), methodMapping.getObfDesc()), methodMapping) == null; assert (wasAdded); } } @@ -490,9 +504,9 @@ public class ClassMapping implements Comparable { return false; } - public boolean containsArgument(BehaviorEntry obfBehaviorEntry, String name) { - MethodMapping methodMapping = methodsByObf.get(getMethodKey(obfBehaviorEntry.getName(), obfBehaviorEntry.getSignature())); - return methodMapping != null && methodMapping.containsArgument(name); + public boolean containsArgument(MethodEntry obfMethodEntry, String name) { + MethodMapping methodMapping = methodsByObf.get(getMethodKey(obfMethodEntry.getName(), obfMethodEntry.getDesc())); + return methodMapping != null && methodMapping.containsLocalVariable(name); } public ClassEntry getObfEntry() { @@ -503,6 +517,14 @@ public class ClassMapping implements Comparable { return deobfFullName != null ? new ClassEntry(deobfFullName) : null; } + public boolean isObfuscated() { + return this.deobfName == null || this.deobfName.equals(this.obfFullName); + } + + public String getSaveName() { + return this.isObfuscated() ? this.obfFullName : this.deobfName; + } + public boolean isDirty() { return isDirty; } @@ -511,6 +533,10 @@ public class ClassMapping implements Comparable { this.isDirty = false; } + public void markDirty() { + this.isDirty = true; + } + public Mappings.EntryModifier getModifier() { return modifier; } @@ -521,9 +547,9 @@ public class ClassMapping implements Comparable { this.modifier = modifier; } - public void setFieldModifier(String obfName, Type obfType, Mappings.EntryModifier modifier) { - FieldMapping fieldMapping = fieldsByObf.computeIfAbsent(getFieldKey(obfName, obfType), - k -> new FieldMapping(obfName, obfType, null, Mappings.EntryModifier.UNCHANGED)); + public void setFieldModifier(String obfName, TypeDescriptor obfDesc, Mappings.EntryModifier modifier) { + FieldMapping fieldMapping = fieldsByObf.computeIfAbsent(getFieldKey(obfName, obfDesc), + k -> new FieldMapping(obfName, obfDesc, null, Mappings.EntryModifier.UNCHANGED)); if (fieldMapping.getModifier() != modifier) { fieldMapping.setModifier(modifier); @@ -531,7 +557,7 @@ public class ClassMapping implements Comparable { } } - public void setMethodModifier(String obfName, Signature sig, Mappings.EntryModifier modifier) { + public void setMethodModifier(String obfName, MethodDescriptor sig, Mappings.EntryModifier modifier) { MethodMapping methodMapping = methodsByObf.computeIfAbsent(getMethodKey(obfName, sig), k -> new MethodMapping(obfName, sig, null, Mappings.EntryModifier.UNCHANGED)); @@ -546,4 +572,30 @@ public class ClassMapping implements Comparable { this.deobfFullName = deobName; return this; } + + public ClassMapping copy() { + ClassMapping copied = new ClassMapping(this.obfFullName); + copied.obfSimpleName= this.obfSimpleName; + copied.modifier = this.modifier; + copied.deobfFullName = this.deobfFullName; + copied.deobfName = this.deobfName; + copied.innerClassesByDeobf = this.innerClassesByDeobf; + copied.innerClassesByObfFull = this.innerClassesByObfFull; + copied.innerClassesByObfSimple = this.innerClassesByObfSimple; + copied.fieldsByObf = this.fieldsByObf; + copied.fieldsByDeobf = this.fieldsByDeobf; + copied.methodsByObf = this.methodsByObf; + copied.methodsByDeobf = this.methodsByDeobf; + return copied; + } + + @Override + public int hashCode() { + return this.obfFullName.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof ClassMapping && ((ClassMapping) obj).obfFullName.equals(this.obfFullName); + } } diff --git a/src/main/java/cuchaz/enigma/mapping/ClassNameReplacer.java b/src/main/java/cuchaz/enigma/mapping/ClassNameReplacer.java deleted file mode 100644 index 801c4104..00000000 --- a/src/main/java/cuchaz/enigma/mapping/ClassNameReplacer.java +++ /dev/null @@ -1,16 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.mapping; - -public interface ClassNameReplacer { - String replace(String className); -} diff --git a/src/main/java/cuchaz/enigma/mapping/ConstructorEntry.java b/src/main/java/cuchaz/enigma/mapping/ConstructorEntry.java deleted file mode 100644 index 20e51138..00000000 --- a/src/main/java/cuchaz/enigma/mapping/ConstructorEntry.java +++ /dev/null @@ -1,105 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.mapping; - -import cuchaz.enigma.utils.Utils; - -public class ConstructorEntry implements BehaviorEntry { - - private ClassEntry classEntry; - private Signature signature; - - public ConstructorEntry(ClassEntry classEntry) { - this(classEntry, null); - } - - public ConstructorEntry(ClassEntry classEntry, Signature signature) { - if (classEntry == null) { - throw new IllegalArgumentException("Class cannot be null!"); - } - - this.classEntry = classEntry; - this.signature = signature; - } - - public ConstructorEntry(ConstructorEntry other, String newClassName) { - this.classEntry = new ClassEntry(newClassName); - this.signature = other.signature; - } - - @Override - public ClassEntry getClassEntry() { - return this.classEntry; - } - - @Override - public String getName() { - if (isStatic()) { - return ""; - } - return ""; - } - - public boolean isStatic() { - return this.signature == null; - } - - @Override - public Signature getSignature() { - return this.signature; - } - - @Override - public String getClassName() { - return this.classEntry.getName(); - } - - @Override - public ConstructorEntry cloneToNewClass(ClassEntry classEntry) { - return new ConstructorEntry(this, classEntry.getName()); - } - - @Override - public int hashCode() { - if (isStatic()) { - return Utils.combineHashesOrdered(this.classEntry); - } else { - return Utils.combineHashesOrdered(this.classEntry, this.signature); - } - } - - @Override - public boolean equals(Object other) { - return other instanceof ConstructorEntry && equals((ConstructorEntry) other); - } - - public boolean equals(ConstructorEntry other) { - if (isStatic() != other.isStatic()) { - return false; - } - - if (isStatic()) { - return this.classEntry.equals(other.classEntry); - } else { - return this.classEntry.equals(other.classEntry) && this.signature.equals(other.signature); - } - } - - @Override - public String toString() { - if (isStatic()) { - return this.classEntry.getName() + "." + getName(); - } else { - return this.classEntry.getName() + "." + getName() + this.signature; - } - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/DirectionalTranslator.java b/src/main/java/cuchaz/enigma/mapping/DirectionalTranslator.java new file mode 100644 index 00000000..b0bb129d --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/DirectionalTranslator.java @@ -0,0 +1,370 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.mapping; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import cuchaz.enigma.analysis.TranslationIndex; +import cuchaz.enigma.bytecode.AccessFlags; +import cuchaz.enigma.mapping.entry.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class DirectionalTranslator implements Translator { + private final TranslationDirection direction; + private final Map classes; + private final TranslationIndex index; + + public DirectionalTranslator(ReferencedEntryPool entryPool) { + this.direction = null; + this.classes = Maps.newHashMap(); + this.index = new TranslationIndex(entryPool); + } + + public DirectionalTranslator(TranslationDirection direction, Map classes, TranslationIndex index) { + this.direction = direction; + this.classes = classes; + this.index = index; + } + + public TranslationDirection getDirection() { + return direction; + } + + public TranslationIndex getTranslationIndex() { + return index; + } + + @Override + public ClassEntry getTranslatedClass(ClassEntry entry) { + String className; + if (entry.isArray()) { + className = this.getTranslatedTypeDesc(new TypeDescriptor(entry.getName())).toString(); + } else { + className = entry.isInnerClass() ? translateInnerClassName(entry) : translateClassName(entry); + } + return new ClassEntry(className); + } + + @Override + public ClassDefEntry getTranslatedClassDef(ClassDefEntry entry) { + String className; + if (entry.isArray()) { + className = this.getTranslatedTypeDesc(new TypeDescriptor(entry.getName())).toString(); + } else { + className = entry.isInnerClass() ? translateInnerClassName(entry) : translateClassName(entry); + } + Signature translatedSignature = this.getTranslatedSignature(entry.getSignature()); + return new ClassDefEntry(className, translatedSignature, getClassModifier(entry).transform(entry.getAccess())); + } + + private String translateClassName(ClassEntry entry) { + // normal classes are easy + ClassMapping classMapping = this.classes.get(entry.getName()); + if (classMapping == null) { + return entry.getName(); + } + return classMapping.getTranslatedName(direction); + } + + private String translateInnerClassName(ClassEntry entry) { + // translate as much of the class chain as we can + List mappingsChain = getClassMappingChain(entry); + String[] obfClassNames = entry.getName().split("\\$"); + StringBuilder buf = new StringBuilder(); + for (int i = 0; i < obfClassNames.length; i++) { + boolean isFirstClass = buf.length() == 0; + String className = null; + ClassMapping classMapping = mappingsChain.get(i); + if (classMapping != null) { + className = this.direction.choose( + classMapping.getDeobfName(), + isFirstClass ? classMapping.getObfFullName() : classMapping.getObfSimpleName() + ); + } + if (className == null) { + className = obfClassNames[i]; + } + if (!isFirstClass) { + buf.append("$"); + } + buf.append(className); + } + return buf.toString(); + } + + @Override + public FieldDefEntry getTranslatedFieldDef(FieldDefEntry entry) { + String translatedName = translateFieldName(entry); + if (translatedName == null) { + translatedName = entry.getName(); + } + ClassEntry translatedOwner = getTranslatedClass(entry.getOwnerClassEntry()); + TypeDescriptor translatedDesc = getTranslatedTypeDesc(entry.getDesc()); + Signature translatedSignature = getTranslatedSignature(entry.getSignature()); + AccessFlags translatedAccess = getFieldModifier(entry).transform(entry.getAccess()); + return new FieldDefEntry(translatedOwner, translatedName, translatedDesc, translatedSignature, translatedAccess); + } + + @Override + public FieldEntry getTranslatedField(FieldEntry entry) { + String translatedName = translateFieldName(entry); + if (translatedName == null) { + translatedName = entry.getName(); + } + ClassEntry translatedOwner = getTranslatedClass(entry.getOwnerClassEntry()); + TypeDescriptor translatedDesc = getTranslatedTypeDesc(entry.getDesc()); + return new FieldEntry(translatedOwner, translatedName, translatedDesc); + } + + private String translateFieldName(FieldEntry entry) { + // resolve the class entry + ClassEntry resolvedClassEntry = this.index.resolveEntryOwner(entry, true); + if (resolvedClassEntry != null) { + // look for the class + ClassMapping classMapping = findClassMapping(resolvedClassEntry); + if (classMapping != null) { + // look for the field + FieldMapping mapping = this.direction.choose( + classMapping.getFieldByObf(entry.getName(), entry.getDesc()), + classMapping.getFieldByDeobf(entry.getName(), getTranslatedTypeDesc(entry.getDesc())) + ); + if (mapping != null) { + return this.direction.choose(mapping.getDeobfName(), mapping.getObfName()); + } + } + } + return null; + } + + @Override + public MethodDefEntry getTranslatedMethodDef(MethodDefEntry entry) { + String translatedName = translateMethodName(entry); + if (translatedName == null) { + translatedName = entry.getName(); + } + ClassEntry translatedOwner = getTranslatedClass(entry.getOwnerClassEntry()); + MethodDescriptor translatedDesc = getTranslatedMethodDesc(entry.getDesc()); + Signature translatedSignature = getTranslatedSignature(entry.getSignature()); + AccessFlags access = getMethodModifier(entry).transform(entry.getAccess()); + return new MethodDefEntry(translatedOwner, translatedName, translatedDesc, translatedSignature, access); + } + + @Override + public MethodEntry getTranslatedMethod(MethodEntry entry) { + String translatedName = translateMethodName(entry); + if (translatedName == null) { + translatedName = entry.getName(); + } + ClassEntry translatedOwner = getTranslatedClass(entry.getOwnerClassEntry()); + MethodDescriptor translatedDesc = getTranslatedMethodDesc(entry.getDesc()); + return new MethodEntry(translatedOwner, translatedName, translatedDesc); + } + + private String translateMethodName(MethodEntry entry) { + // resolve the class entry + ClassEntry resolvedOwner = this.index.resolveEntryOwner(entry, true); + if (resolvedOwner != null) { + // look for class + ClassMapping classMapping = findClassMapping(resolvedOwner); + if (classMapping != null) { + // look for the method + MethodMapping mapping = this.direction.choose( + classMapping.getMethodByObf(entry.getName(), entry.getDesc()), + classMapping.getMethodByDeobf(entry.getName(), getTranslatedMethodDesc(entry.getDesc())) + ); + if (mapping != null) { + return this.direction.choose(mapping.getDeobfName(), mapping.getObfName()); + } + } + } + return null; + } + + @Override + public LocalVariableEntry getTranslatedVariable(LocalVariableEntry entry) { + String translatedArgumentName = translateLocalVariableName(entry); + if (translatedArgumentName == null) { + translatedArgumentName = inheritLocalVariableName(entry); + } + if (translatedArgumentName == null) { + translatedArgumentName = entry.getName(); + } + // TODO: Translating arguments calls method translation.. Can we refactor the code in such a way that we don't need this? + MethodEntry translatedOwner = getTranslatedMethod(entry.getOwnerEntry()); + return new LocalVariableEntry(translatedOwner, entry.getIndex(), translatedArgumentName); + } + + @Override + public LocalVariableDefEntry getTranslatedVariableDef(LocalVariableDefEntry entry) { + String translatedArgumentName = translateLocalVariableName(entry); + if (translatedArgumentName == null) { + translatedArgumentName = inheritLocalVariableName(entry); + } + // TODO: Translating arguments calls method translation.. Can we refactor the code in such a way that we don't need this? + MethodDefEntry translatedOwner = getTranslatedMethodDef(entry.getOwnerEntry()); + TypeDescriptor translatedTypeDesc = getTranslatedTypeDesc(entry.getDesc()); + return new LocalVariableDefEntry(translatedOwner, entry.getIndex(), translatedArgumentName != null ? translatedArgumentName : entry.getName(), translatedTypeDesc); + } + + @Override + public boolean hasClassMapping(ClassEntry entry) { + return classes.containsKey(entry.getName()); + } + + @Override + public boolean hasFieldMapping(FieldEntry entry) { + return translateFieldName(entry) != null; + } + + @Override + public boolean hasMethodMapping(MethodEntry entry) { + return translateMethodName(entry) != null; + } + + @Override + public boolean hasLocalVariableMapping(LocalVariableEntry entry) { + return translateLocalVariableName(entry) != null || inheritLocalVariableName(entry) != null; + } + + // TODO: support not identical behavior (specific to constructor) + private String translateLocalVariableName(LocalVariableEntry entry) { + // look for identical behavior in superclasses + ClassEntry ownerEntry = entry.getOwnerClassEntry(); + if (ownerEntry != null) { + // look for the class + ClassMapping classMapping = findClassMapping(ownerEntry); + if (classMapping != null) { + // look for the method + MethodMapping methodMapping = this.direction.choose( + classMapping.getMethodByObf(entry.getMethodName(), entry.getMethodDesc()), + classMapping.getMethodByDeobf(entry.getMethodName(), getTranslatedMethodDesc(entry.getMethodDesc())) + ); + if (methodMapping != null) { + int index = entry.getIndex(); + return this.direction.choose( + methodMapping.getDeobfLocalVariableName(index), + methodMapping.getObfLocalVariableName(index) + ); + } + } + } + return null; + } + + private String inheritLocalVariableName(LocalVariableEntry entry) { + List ancestry = this.index.getAncestry(entry.getOwnerClassEntry()); + // Check in mother class for the arg + for (ClassEntry ancestorEntry : ancestry) { + LocalVariableEntry motherArg = entry.updateOwnership(ancestorEntry); + if (this.index.entryExists(motherArg)) { + String result = translateLocalVariableName(motherArg); + if (result != null) { + return result; + } + } + } + return null; + } + + @Override + public TypeDescriptor getTranslatedTypeDesc(TypeDescriptor desc) { + return desc.remap(this::remapClass); + } + + @Override + public MethodDescriptor getTranslatedMethodDesc(MethodDescriptor descriptor) { + List arguments = descriptor.getArgumentDescs(); + List translatedArguments = new ArrayList<>(arguments.size()); + for (TypeDescriptor argument : arguments) { + translatedArguments.add(getTranslatedTypeDesc(argument)); + } + return new MethodDescriptor(translatedArguments, getTranslatedTypeDesc(descriptor.getReturnDesc())); + } + + @Override + public Signature getTranslatedSignature(Signature signature) { + if (signature == null) { + return null; + } + return signature.remap(this::remapClass); + } + + private ClassMapping findClassMapping(ClassEntry entry) { + List mappingChain = getClassMappingChain(entry); + return mappingChain.get(mappingChain.size() - 1); + } + + private List getClassMappingChain(ClassEntry entry) { + + // get a list of all the classes in the hierarchy + String[] parts = entry.getName().split("\\$"); + List mappingsChain = Lists.newArrayList(); + + // get mappings for the outer class + ClassMapping outerClassMapping = this.classes.get(parts[0]); + mappingsChain.add(outerClassMapping); + + for (int i = 1; i < parts.length; i++) { + + // get mappings for the inner class + ClassMapping innerClassMapping = null; + if (outerClassMapping != null) { + innerClassMapping = this.direction.choose( + outerClassMapping.getInnerClassByObfSimple(parts[i]), + outerClassMapping.getInnerClassByDeobfThenObfSimple(parts[i]) + ); + } + mappingsChain.add(innerClassMapping); + outerClassMapping = innerClassMapping; + } + + assert (mappingsChain.size() == parts.length); + return mappingsChain; + } + + private Mappings.EntryModifier getClassModifier(ClassEntry entry) { + ClassMapping classMapping = findClassMapping(entry); + if (classMapping != null) { + return classMapping.getModifier(); + } + return Mappings.EntryModifier.UNCHANGED; + } + + private Mappings.EntryModifier getFieldModifier(FieldEntry entry) { + ClassMapping classMapping = findClassMapping(entry.getOwnerClassEntry()); + if (classMapping != null) { + FieldMapping fieldMapping = classMapping.getFieldByObf(entry); + if (fieldMapping != null) { + return fieldMapping.getModifier(); + } + } + return Mappings.EntryModifier.UNCHANGED; + } + + private Mappings.EntryModifier getMethodModifier(MethodEntry entry) { + ClassMapping classMapping = findClassMapping(entry.getOwnerClassEntry()); + if (classMapping != null) { + MethodMapping methodMapping = classMapping.getMethodByObf(entry); + if (methodMapping != null) { + return methodMapping.getModifier(); + } + } + return Mappings.EntryModifier.UNCHANGED; + } + + private String remapClass(String name) { + return getTranslatedClass(new ClassEntry(name)).getName(); + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/Entry.java b/src/main/java/cuchaz/enigma/mapping/Entry.java deleted file mode 100644 index c79510b9..00000000 --- a/src/main/java/cuchaz/enigma/mapping/Entry.java +++ /dev/null @@ -1,22 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.mapping; - -public interface Entry { - String getName(); - - String getClassName(); - - ClassEntry getClassEntry(); - - Entry cloneToNewClass(ClassEntry classEntry); -} diff --git a/src/main/java/cuchaz/enigma/mapping/EntryFactory.java b/src/main/java/cuchaz/enigma/mapping/EntryFactory.java deleted file mode 100644 index 993bb64b..00000000 --- a/src/main/java/cuchaz/enigma/mapping/EntryFactory.java +++ /dev/null @@ -1,132 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.mapping; - -import cuchaz.enigma.analysis.JarIndex; -import javassist.*; -import javassist.bytecode.Descriptor; -import javassist.expr.ConstructorCall; -import javassist.expr.FieldAccess; -import javassist.expr.MethodCall; -import javassist.expr.NewExpr; - -public class EntryFactory { - - public static ClassEntry getClassEntry(CtClass c) { - return new ClassEntry(Descriptor.toJvmName(c.getName())); - } - - public static ClassEntry getObfClassEntry(JarIndex jarIndex, ClassMapping classMapping) { - ClassEntry obfClassEntry = new ClassEntry(classMapping.getObfFullName()); - return obfClassEntry.buildClassEntry(jarIndex.getObfClassChain(obfClassEntry)); - } - - private static ClassEntry getObfClassEntry(ClassMapping classMapping) { - return new ClassEntry(classMapping.getObfFullName()); - } - - public static ClassEntry getDeobfClassEntry(ClassMapping classMapping) { - return new ClassEntry(classMapping.getDeobfName()); - } - - public static ClassEntry getSuperclassEntry(CtClass c) { - return new ClassEntry(Descriptor.toJvmName(c.getClassFile().getSuperclass())); - } - - public static FieldEntry getFieldEntry(CtField field) { - return new FieldEntry(getClassEntry(field.getDeclaringClass()), field.getName(), new Type(field.getFieldInfo().getDescriptor())); - } - - public static FieldEntry getFieldEntry(FieldAccess call) { - return new FieldEntry(new ClassEntry(Descriptor.toJvmName(call.getClassName())), call.getFieldName(), new Type(call.getSignature())); - } - - public static FieldEntry getFieldEntry(String className, String name, String type) { - return new FieldEntry(new ClassEntry(className), name, new Type(type)); - } - - public static FieldEntry getObfFieldEntry(ClassMapping classMapping, FieldMapping fieldMapping) { - return new FieldEntry(getObfClassEntry(classMapping), fieldMapping.getObfName(), fieldMapping.getObfType()); - } - - public static MethodEntry getMethodEntry(CtMethod method) { - return new MethodEntry(getClassEntry(method.getDeclaringClass()), method.getName(), new Signature(method.getMethodInfo().getDescriptor())); - } - - public static MethodEntry getMethodEntry(MethodCall call) { - return new MethodEntry(new ClassEntry(Descriptor.toJvmName(call.getClassName())), call.getMethodName(), new Signature(call.getSignature())); - } - - public static ConstructorEntry getConstructorEntry(CtConstructor constructor) { - if (constructor.isClassInitializer()) { - return new ConstructorEntry(getClassEntry(constructor.getDeclaringClass())); - } else { - return new ConstructorEntry(getClassEntry(constructor.getDeclaringClass()), new Signature(constructor.getMethodInfo().getDescriptor())); - } - } - - public static ConstructorEntry getConstructorEntry(ConstructorCall call) { - return new ConstructorEntry(new ClassEntry(Descriptor.toJvmName(call.getClassName())), new Signature(call.getSignature())); - } - - public static ConstructorEntry getConstructorEntry(NewExpr call) { - return new ConstructorEntry(new ClassEntry(Descriptor.toJvmName(call.getClassName())), new Signature(call.getSignature())); - } - - public static BehaviorEntry getBehaviorEntry(CtBehavior behavior) { - if (behavior instanceof CtMethod) { - return getMethodEntry((CtMethod) behavior); - } else if (behavior instanceof CtConstructor) { - return getConstructorEntry((CtConstructor) behavior); - } - throw new Error("behavior is neither Method nor Constructor!"); - } - - public static BehaviorEntry getBehaviorEntry(String className, String behaviorName, String behaviorSignature) { - return getBehaviorEntry(new ClassEntry(className), behaviorName, new Signature(behaviorSignature)); - } - - public static BehaviorEntry getBehaviorEntry(String className, String behaviorName) { - return getBehaviorEntry(new ClassEntry(className), behaviorName); - } - - public static BehaviorEntry getBehaviorEntry(String className) { - return new ConstructorEntry(new ClassEntry(className)); - } - - public static BehaviorEntry getBehaviorEntry(ClassEntry classEntry, String behaviorName, Signature behaviorSignature) { - switch (behaviorName) { - case "": - return new ConstructorEntry(classEntry, behaviorSignature); - case "": - return new ConstructorEntry(classEntry); - default: - return new MethodEntry(classEntry, behaviorName, behaviorSignature); - } - } - - public static BehaviorEntry getBehaviorEntry(ClassEntry classEntry, String behaviorName) { - if (behaviorName.equals("")) { - return new ConstructorEntry(classEntry); - } else { - throw new IllegalArgumentException("Only class initializers don't have signatures"); - } - } - - public static BehaviorEntry getObfBehaviorEntry(ClassEntry classEntry, MethodMapping methodMapping) { - return getBehaviorEntry(classEntry, methodMapping.getObfName(), methodMapping.getObfSignature()); - } - - public static BehaviorEntry getObfBehaviorEntry(ClassMapping classMapping, MethodMapping methodMapping) { - return getObfBehaviorEntry(getObfClassEntry(classMapping), methodMapping); - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/FieldEntry.java b/src/main/java/cuchaz/enigma/mapping/FieldEntry.java deleted file mode 100644 index 0f1f5065..00000000 --- a/src/main/java/cuchaz/enigma/mapping/FieldEntry.java +++ /dev/null @@ -1,87 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.mapping; - -import cuchaz.enigma.utils.Utils; - -public class FieldEntry implements Entry { - - private ClassEntry classEntry; - private String name; - private Type type; - - // NOTE: this argument order is important for the MethodReader/MethodWriter - public FieldEntry(ClassEntry classEntry, String name, Type type) { - if (classEntry == null) { - throw new IllegalArgumentException("Class cannot be null!"); - } - if (name == null) { - throw new IllegalArgumentException("Field name cannot be null!"); - } - if (type == null) { - throw new IllegalArgumentException("Field type cannot be null!"); - } - - this.classEntry = classEntry; - this.name = name; - this.type = type; - } - - public FieldEntry(FieldEntry other, ClassEntry newClassEntry) { - this.classEntry = newClassEntry; - this.name = other.name; - this.type = other.type; - } - - @Override - public ClassEntry getClassEntry() { - return this.classEntry; - } - - @Override - public String getName() { - return this.name; - } - - @Override - public String getClassName() { - return this.classEntry.getName(); - } - - public Type getType() { - return this.type; - } - - @Override - public FieldEntry cloneToNewClass(ClassEntry classEntry) { - return new FieldEntry(this, classEntry); - } - - @Override - public int hashCode() { - return Utils.combineHashesOrdered(this.classEntry, this.name, this.type); - } - - @Override - public boolean equals(Object other) { - return other instanceof FieldEntry && equals((FieldEntry) other); - } - - public boolean equals(FieldEntry other) { - return this.classEntry.equals(other.classEntry) && this.name.equals(other.name) && this.type.equals(other.type); - } - - @Override - public String toString() { - return this.classEntry.getName() + "." + this.name + ":" + this.type; - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/FieldMapping.java b/src/main/java/cuchaz/enigma/mapping/FieldMapping.java index cd761b47..8fbe095b 100644 --- a/src/main/java/cuchaz/enigma/mapping/FieldMapping.java +++ b/src/main/java/cuchaz/enigma/mapping/FieldMapping.java @@ -11,32 +11,27 @@ package cuchaz.enigma.mapping; +import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.mapping.entry.FieldEntry; import cuchaz.enigma.throwables.IllegalNameException; public class FieldMapping implements Comparable, MemberMapping { private String obfName; private String deobfName; - private Type obfType; + private TypeDescriptor obfDesc; private Mappings.EntryModifier modifier; - public FieldMapping(String obfName, Type obfType, String deobfName, Mappings.EntryModifier modifier) { + public FieldMapping(String obfName, TypeDescriptor obfDesc, String deobfName, Mappings.EntryModifier modifier) { this.obfName = obfName; this.deobfName = NameValidator.validateFieldName(deobfName); - this.obfType = obfType; + this.obfDesc = obfDesc; this.modifier = modifier; } - public FieldMapping(FieldMapping other, ClassNameReplacer obfClassNameReplacer) { - this.obfName = other.obfName; - this.deobfName = other.deobfName; - this.modifier = other.modifier; - this.obfType = new Type(other.obfType, obfClassNameReplacer); - } - @Override public FieldEntry getObfEntry(ClassEntry classEntry) { - return new FieldEntry(classEntry, this.obfName, this.obfType); + return new FieldEntry(classEntry, this.obfName, this.obfDesc); } @Override @@ -65,12 +60,12 @@ public class FieldMapping implements Comparable, MemberMapping, MemberMapping - { + // rename obf classes in the desc + TypeDescriptor newDesc = this.obfDesc.remap(className -> { if (className.equals(oldObfClassName)) { return newObfClassName; } - return null; + return className; }); - if (!newType.equals(this.obfType)) { - this.obfType = newType; + if (!newDesc.equals(this.obfDesc)) { + this.obfDesc = newDesc; return true; } return false; diff --git a/src/main/java/cuchaz/enigma/mapping/LocalVariableEntry.java b/src/main/java/cuchaz/enigma/mapping/LocalVariableEntry.java deleted file mode 100644 index 2bb5e3f7..00000000 --- a/src/main/java/cuchaz/enigma/mapping/LocalVariableEntry.java +++ /dev/null @@ -1,102 +0,0 @@ -package cuchaz.enigma.mapping; - -import cuchaz.enigma.utils.Utils; - -/** - * Desc... - * Created by Thog - * 19/10/2016 - */ -public class LocalVariableEntry implements Entry { - - protected final BehaviorEntry behaviorEntry; - protected final String name; - protected final Type type; - protected final int index; - - public LocalVariableEntry(BehaviorEntry behaviorEntry, int index, String name, Type type) { - if (behaviorEntry == null) { - throw new IllegalArgumentException("Behavior cannot be null!"); - } - if (index < 0) { - throw new IllegalArgumentException("Index must be non-negative!"); - } - if (name == null) { - throw new IllegalArgumentException("Variable name cannot be null!"); - } - if (type == null) { - throw new IllegalArgumentException("Variable type cannot be null!"); - } - - this.behaviorEntry = behaviorEntry; - this.name = name; - this.type = type; - this.index = index; - } - - public LocalVariableEntry(LocalVariableEntry other, ClassEntry newClassEntry) { - this.behaviorEntry = (BehaviorEntry) other.behaviorEntry.cloneToNewClass(newClassEntry); - this.name = other.name; - this.type = other.type; - this.index = other.index; - } - - public BehaviorEntry getBehaviorEntry() { - return this.behaviorEntry; - } - - public Type getType() { - return type; - } - - public int getIndex() { - return index; - } - - @Override - public String getName() { - return this.name; - } - - @Override - public ClassEntry getClassEntry() { - return this.behaviorEntry.getClassEntry(); - } - - @Override - public String getClassName() { - return this.behaviorEntry.getClassName(); - } - - @Override - public LocalVariableEntry cloneToNewClass(ClassEntry classEntry) { - return new LocalVariableEntry(this, classEntry); - } - - public String getMethodName() { - return this.behaviorEntry.getName(); - } - - public Signature getMethodSignature() { - return this.behaviorEntry.getSignature(); - } - - @Override - public int hashCode() { - return Utils.combineHashesOrdered(this.behaviorEntry, this.type.hashCode(), this.name.hashCode(), Integer.hashCode(this.index)); - } - - @Override - public boolean equals(Object other) { - return other instanceof LocalVariableEntry && equals((LocalVariableEntry) other); - } - - public boolean equals(LocalVariableEntry other) { - return this.behaviorEntry.equals(other.behaviorEntry) && this.type.equals(other.type) && this.name.equals(other.name) && this.index == other.index; - } - - @Override - public String toString() { - return this.behaviorEntry + "(" + this.index + ":" + this.name + ":" + this.type + ")"; - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/LocalVariableMapping.java b/src/main/java/cuchaz/enigma/mapping/LocalVariableMapping.java new file mode 100644 index 00000000..62dbcf31 --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/LocalVariableMapping.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.mapping; + +import cuchaz.enigma.mapping.entry.LocalVariableEntry; +import cuchaz.enigma.mapping.entry.MethodEntry; + +public class LocalVariableMapping implements Comparable { + + private int index; + private String name; + + // NOTE: this argument order is important for the MethodReader/MethodWriter + public LocalVariableMapping(int index, String name) { + this.index = index; + this.name = NameValidator.validateArgumentName(name); + } + + public LocalVariableMapping(LocalVariableMapping other) { + this.index = other.index; + this.name = other.name; + } + + public int getIndex() { + return this.index; + } + + public String getName() { + return this.name; + } + + public void setName(String val) { + this.name = NameValidator.validateArgumentName(val); + } + + public LocalVariableEntry getObfEntry(MethodEntry methodEntry) { + return new LocalVariableEntry(methodEntry, index, name); + } + + @Override + public int compareTo(LocalVariableMapping other) { + return Integer.compare(this.index, other.index); + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/Mappings.java b/src/main/java/cuchaz/enigma/mapping/Mappings.java index cf78ca30..3ef1be52 100644 --- a/src/main/java/cuchaz/enigma/mapping/Mappings.java +++ b/src/main/java/cuchaz/enigma/mapping/Mappings.java @@ -15,11 +15,15 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import cuchaz.enigma.analysis.TranslationIndex; +import cuchaz.enigma.bytecode.AccessFlags; +import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.mapping.entry.MethodEntry; import cuchaz.enigma.throwables.MappingConflict; import java.io.File; import java.io.IOException; import java.util.*; +import java.util.stream.Collectors; public class Mappings { @@ -96,11 +100,11 @@ public class Mappings { public Translator getTranslator(TranslationDirection direction, TranslationIndex index) { switch (direction) { - case Deobfuscating: + case DEOBFUSCATING: - return new Translator(direction, this.classesByObf, index); + return new DirectionalTranslator(direction, this.classesByObf, index); - case Obfuscating: + case OBFUSCATING: // fill in the missing deobf class entries with obf entries Map classes = Maps.newHashMap(); @@ -114,9 +118,9 @@ public class Mappings { // translate the translation index // NOTE: this isn't actually recursive - TranslationIndex deobfIndex = new TranslationIndex(index, getTranslator(TranslationDirection.Deobfuscating, index)); + TranslationIndex deobfIndex = new TranslationIndex(index, getTranslator(TranslationDirection.DEOBFUSCATING, index)); - return new Translator(direction, classes, deobfIndex); + return new DirectionalTranslator(direction, classes, deobfIndex); default: throw new Error("Invalid translation direction!"); @@ -151,9 +155,9 @@ public class Mappings { // add classes from method signatures for (MethodMapping methodMapping : classMapping.methods()) { - for (Type type : methodMapping.getObfSignature().types()) { - if (type.hasClass()) { - classNames.add(type.getClassEntry().getClassName()); + for (TypeDescriptor desc : methodMapping.getObfDesc().types()) { + if (desc.containsType()) { + classNames.add(desc.getTypeEntry().getClassName()); } } } @@ -165,9 +169,9 @@ public class Mappings { return this.classesByDeobf.containsKey(deobfName); } - public boolean containsDeobfField(ClassEntry obfClassEntry, String deobfName, Type obfType) { + public boolean containsDeobfField(ClassEntry obfClassEntry, String deobfName, TypeDescriptor obfDesc) { ClassMapping classMapping = this.classesByObf.get(obfClassEntry.getName()); - return classMapping != null && classMapping.containsDeobfField(deobfName, obfType); + return classMapping != null && classMapping.containsDeobfField(deobfName, obfDesc); } public boolean containsDeobfField(ClassEntry obfClassEntry, String deobfName) { @@ -180,14 +184,14 @@ public class Mappings { return false; } - public boolean containsDeobfMethod(ClassEntry obfClassEntry, String deobfName, Signature obfSignature) { + public boolean containsDeobfMethod(ClassEntry obfClassEntry, String deobfName, MethodDescriptor obfDescriptor) { ClassMapping classMapping = this.classesByObf.get(obfClassEntry.getName()); - return classMapping != null && classMapping.containsDeobfMethod(deobfName, obfSignature); + return classMapping != null && classMapping.containsDeobfMethod(deobfName, obfDescriptor); } - public boolean containsArgument(BehaviorEntry obfBehaviorEntry, String name) { - ClassMapping classMapping = this.classesByObf.get(obfBehaviorEntry.getClassName()); - return classMapping != null && classMapping.containsArgument(obfBehaviorEntry, name); + public boolean containsArgument(MethodEntry obfMethodEntry, String name) { + ClassMapping classMapping = this.classesByObf.get(obfMethodEntry.getClassName()); + return classMapping != null && classMapping.containsArgument(obfMethodEntry, name); } public List getClassMappingChain(ClassEntry obfClass) { @@ -210,8 +214,14 @@ public class Mappings { public void savePreviousState() { this.previousState = new Mappings(this.originMapping); - this.previousState.classesByDeobf = Maps.newHashMap(this.classesByDeobf); - this.previousState.classesByObf = Maps.newHashMap(this.classesByObf); + this.previousState.classesByDeobf = new HashMap<>(); + for (Map.Entry entry : this.classesByDeobf.entrySet()) { + this.previousState.classesByDeobf.put(entry.getKey(), entry.getValue().copy()); + } + this.previousState.classesByObf = new HashMap<>(); + for (Map.Entry entry : this.classesByObf.entrySet()) { + this.previousState.classesByObf.put(entry.getKey(), entry.getValue().copy()); + } classesByDeobf.values().forEach(ClassMapping::resetDirty); classesByObf.values().forEach(ClassMapping::resetDirty); } @@ -239,5 +249,19 @@ public class Mappings { public String getFormattedName() { return " ACC:" + super.toString(); } + + public AccessFlags transform(AccessFlags access) { + switch (this) { + case PUBLIC: + return access.setPublic(); + case PROTECTED: + return access.setProtected(); + case PRIVATE: + return access.setPrivate(); + case UNCHANGED: + default: + return access; + } + } } } diff --git a/src/main/java/cuchaz/enigma/mapping/MappingsChecker.java b/src/main/java/cuchaz/enigma/mapping/MappingsChecker.java index 172641bd..a42f255a 100644 --- a/src/main/java/cuchaz/enigma/mapping/MappingsChecker.java +++ b/src/main/java/cuchaz/enigma/mapping/MappingsChecker.java @@ -14,6 +14,10 @@ package cuchaz.enigma.mapping; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import cuchaz.enigma.analysis.JarIndex; +import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.mapping.entry.EntryFactory; +import cuchaz.enigma.mapping.entry.FieldEntry; +import cuchaz.enigma.mapping.entry.MethodEntry; import java.util.Map; @@ -23,7 +27,7 @@ public class MappingsChecker { private Map droppedClassMappings; private Map droppedInnerClassMappings; private Map droppedFieldMappings; - private Map droppedMethodMappings; + private Map droppedMethodMappings; public MappingsChecker(JarIndex index) { this.index = index; @@ -45,7 +49,7 @@ public class MappingsChecker { return this.droppedFieldMappings; } - public Map getDroppedMethodMappings() { + public Map getDroppedMethodMappings() { return this.droppedMethodMappings; } @@ -77,10 +81,10 @@ public class MappingsChecker { // check methods for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) { - BehaviorEntry obfBehaviorEntry = EntryFactory.getObfBehaviorEntry(classEntry, methodMapping); - if (!this.index.containsObfBehavior(obfBehaviorEntry)) { + MethodEntry obfMethodEntry = EntryFactory.getObfMethodEntry(classEntry, methodMapping); + if (!this.index.containsObfMethod(obfMethodEntry)) { classMapping.removeMethodMapping(methodMapping); - this.droppedMethodMappings.put(obfBehaviorEntry, methodMapping); + this.droppedMethodMappings.put(obfMethodEntry, methodMapping); } } diff --git a/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaReader.java b/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaReader.java index a0d43133..ddbee76f 100644 --- a/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaReader.java +++ b/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaReader.java @@ -5,8 +5,14 @@ import com.google.common.collect.Queues; import cuchaz.enigma.throwables.MappingConflict; import cuchaz.enigma.throwables.MappingParseException; -import java.io.*; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.util.Deque; +import java.util.function.Supplier; public class MappingsEnigmaReader { @@ -39,92 +45,95 @@ public class MappingsEnigmaReader { } public Mappings readFile(Mappings mappings, File file) throws IOException, MappingParseException { + return readFileStream(mappings, new FileInputStream(file), file::getAbsolutePath); + } - BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), Charsets.UTF_8)); - Deque mappingStack = Queues.newArrayDeque(); + public Mappings readFileStream(Mappings mappings, InputStream stream, Supplier filenameSupplier) throws IOException, MappingParseException { + try (BufferedReader in = new BufferedReader(new InputStreamReader(stream, Charsets.UTF_8))) { + Deque mappingStack = Queues.newArrayDeque(); - int lineNumber = 0; - String line; - while ((line = in.readLine()) != null) { - lineNumber++; + int lineNumber = 0; + String line; + while ((line = in.readLine()) != null) { + lineNumber++; - // strip comments - int commentPos = line.indexOf('#'); - if (commentPos >= 0) { - line = line.substring(0, commentPos); - } + // strip comments + int commentPos = line.indexOf('#'); + if (commentPos >= 0) { + line = line.substring(0, commentPos); + } - // skip blank lines - if (line.trim().length() <= 0) { - continue; - } + // skip blank lines + if (line.trim().length() <= 0) { + continue; + } - // get the indent of this line - int indent = 0; - for (int i = 0; i < line.length(); i++) { - if (line.charAt(i) != '\t') { - break; + // get the indent of this line + int indent = 0; + for (int i = 0; i < line.length(); i++) { + if (line.charAt(i) != '\t') { + break; + } + indent++; } - indent++; - } - // handle stack pops - while (indent < mappingStack.size()) { - mappingStack.pop(); - } + // handle stack pops + while (indent < mappingStack.size()) { + mappingStack.pop(); + } - String[] parts = line.trim().split("\\s"); - try { - // read the first token - String token = parts[0]; - - if (token.equalsIgnoreCase("CLASS")) { - ClassMapping classMapping; - if (indent <= 0) { - // outer class - classMapping = readClass(parts, false); - mappings.addClassMapping(classMapping); - } else { - - // inner class - if (!(mappingStack.peek() instanceof ClassMapping)) { - throw new MappingParseException(file, lineNumber, "Unexpected CLASS entry here!"); + String[] parts = line.trim().split("\\s"); + try { + // read the first token + String token = parts[0]; + + if (token.equalsIgnoreCase("CLASS")) { + ClassMapping classMapping; + if (indent <= 0) { + // outer class + classMapping = readClass(parts, false); + mappings.addClassMapping(classMapping); + } else { + + // inner class + if (!(mappingStack.peek() instanceof ClassMapping)) { + throw new MappingParseException(filenameSupplier, lineNumber, "Unexpected CLASS entry here!"); + } + + classMapping = readClass(parts, true); + ((ClassMapping) mappingStack.peek()).addInnerClassMapping(classMapping); } - - classMapping = readClass(parts, true); - ((ClassMapping) mappingStack.peek()).addInnerClassMapping(classMapping); - } - mappingStack.push(classMapping); - } else if (token.equalsIgnoreCase("FIELD")) { - if (mappingStack.isEmpty() || !(mappingStack.peek() instanceof ClassMapping)) { - throw new MappingParseException(file, lineNumber, "Unexpected FIELD entry here!"); - } - ((ClassMapping) mappingStack.peek()).addFieldMapping(readField(parts)); - } else if (token.equalsIgnoreCase("METHOD")) { - if (mappingStack.isEmpty() || !(mappingStack.peek() instanceof ClassMapping)) { - throw new MappingParseException(file, lineNumber, "Unexpected METHOD entry here!"); - } - MethodMapping methodMapping = readMethod(parts); - ((ClassMapping) mappingStack.peek()).addMethodMapping(methodMapping); - mappingStack.push(methodMapping); - } else if (token.equalsIgnoreCase("ARG")) { - if (mappingStack.isEmpty() || !(mappingStack.peek() instanceof MethodMapping)) { - throw new MappingParseException(file, lineNumber, "Unexpected ARG entry here!"); + mappingStack.push(classMapping); + } else if (token.equalsIgnoreCase("FIELD")) { + if (mappingStack.isEmpty() || !(mappingStack.peek() instanceof ClassMapping)) { + throw new MappingParseException(filenameSupplier, lineNumber, "Unexpected FIELD entry here!"); + } + ((ClassMapping) mappingStack.peek()).addFieldMapping(readField(parts)); + } else if (token.equalsIgnoreCase("METHOD")) { + if (mappingStack.isEmpty() || !(mappingStack.peek() instanceof ClassMapping)) { + throw new MappingParseException(filenameSupplier, lineNumber, "Unexpected METHOD entry here!"); + } + MethodMapping methodMapping = readMethod(parts); + ((ClassMapping) mappingStack.peek()).addMethodMapping(methodMapping); + mappingStack.push(methodMapping); + } else if (token.equalsIgnoreCase("ARG")) { + if (mappingStack.isEmpty() || !(mappingStack.peek() instanceof MethodMapping)) { + throw new MappingParseException(filenameSupplier, lineNumber, "Unexpected ARG entry here!"); + } + ((MethodMapping) mappingStack.peek()).addArgumentMapping(readArgument(parts)); } - ((MethodMapping) mappingStack.peek()).addArgumentMapping(readArgument(parts)); + } catch (ArrayIndexOutOfBoundsException | IllegalArgumentException ex) { + throw new MappingParseException(filenameSupplier, lineNumber, "Malformed line:\n" + line); + } catch (MappingConflict e) { + throw new MappingParseException(filenameSupplier, lineNumber, e.getMessage()); } - } catch (ArrayIndexOutOfBoundsException | IllegalArgumentException ex) { - throw new MappingParseException(file, lineNumber, "Malformed line:\n" + line); - } catch (MappingConflict e) { - throw new MappingParseException(file, lineNumber, e.getMessage()); } + return mappings; } - in.close(); - return mappings; } - private ArgumentMapping readArgument(String[] parts) { - return new ArgumentMapping(Integer.parseInt(parts[1]), parts[2]); + private LocalVariableMapping readArgument(String[] parts) { + return new LocalVariableMapping(Integer.parseInt(parts[1]), parts[2]); } private ClassMapping readClass(String[] parts, boolean makeSimple) { @@ -150,27 +159,27 @@ public class MappingsEnigmaReader { if (parts.length == 4) { boolean access = parts[3].startsWith("ACC:"); if (access) - mapping = new FieldMapping(parts[1], new Type(parts[2]), null, + mapping = new FieldMapping(parts[1], new TypeDescriptor(parts[2]), null, Mappings.EntryModifier.valueOf(parts[3].substring(4))); else - mapping = new FieldMapping(parts[1], new Type(parts[3]), parts[2], Mappings.EntryModifier.UNCHANGED); + mapping = new FieldMapping(parts[1], new TypeDescriptor(parts[3]), parts[2], Mappings.EntryModifier.UNCHANGED); } else if (parts.length == 5) - mapping = new FieldMapping(parts[1], new Type(parts[3]), parts[2], Mappings.EntryModifier.valueOf(parts[4].substring(4))); + mapping = new FieldMapping(parts[1], new TypeDescriptor(parts[3]), parts[2], Mappings.EntryModifier.valueOf(parts[4].substring(4))); return mapping; } private MethodMapping readMethod(String[] parts) { MethodMapping mapping = null; if (parts.length == 3) - mapping = new MethodMapping(parts[1], new Signature(parts[2])); + mapping = new MethodMapping(parts[1], new MethodDescriptor(parts[2])); else if (parts.length == 4) { boolean access = parts[3].startsWith("ACC:"); if (access) - mapping = new MethodMapping(parts[1], new Signature(parts[2]), null, Mappings.EntryModifier.valueOf(parts[3].substring(4))); + mapping = new MethodMapping(parts[1], new MethodDescriptor(parts[2]), null, Mappings.EntryModifier.valueOf(parts[3].substring(4))); else - mapping = new MethodMapping(parts[1], new Signature(parts[3]), parts[2]); + mapping = new MethodMapping(parts[1], new MethodDescriptor(parts[3]), parts[2]); } else if (parts.length == 5) - mapping = new MethodMapping(parts[1], new Signature(parts[3]), parts[2], + mapping = new MethodMapping(parts[1], new MethodDescriptor(parts[3]), parts[2], Mappings.EntryModifier.valueOf(parts[4].substring(4))); return mapping; } diff --git a/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaWriter.java b/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaWriter.java index ba1b258b..b29990f5 100644 --- a/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaWriter.java +++ b/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaWriter.java @@ -14,9 +14,7 @@ package cuchaz.enigma.mapping; import com.google.common.base.Charsets; import java.io.*; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; +import java.util.*; public class MappingsEnigmaWriter { @@ -33,83 +31,67 @@ public class MappingsEnigmaWriter { if (!target.exists() && !target.mkdirs()) throw new IOException("Cannot create mapping directory!"); + Mappings previousState = mappings.getPreviousState(); for (ClassMapping classMapping : sorted(mappings.classes())) { - if (!classMapping.isDirty()) + if (!classMapping.isDirty()) { continue; - this.deletePreviousClassMapping(target, classMapping); - File obFile = new File(target, classMapping.getObfFullName() + ".mapping"); - File result; - if (classMapping.getDeobfName() == null) - result = obFile; - else { - // Make sure that old version of the file doesn't exist - if (obFile.exists()) - obFile.delete(); - result = new File(target, classMapping.getDeobfName() + ".mapping"); } - if (!result.getParentFile().exists()) - result.getParentFile().mkdirs(); + if (previousState != null) { + ClassMapping previousClass = previousState.classesByObf.get(classMapping.getObfFullName()); + File previousFile; + if (previousClass != null) { + previousFile = new File(target, previousClass.getSaveName() + ".mapping"); + } else { + previousFile = new File(target, classMapping.getObfFullName() + ".mapping"); + } + if (previousFile.exists() && !previousFile.delete()) { + System.err.println("Failed to delete old class mapping " + previousFile.getName()); + } + } + + File result = new File(target, classMapping.getSaveName() + ".mapping"); + + File packageFile = result.getParentFile(); + if (!packageFile.exists()) { + packageFile.mkdirs(); + } result.createNewFile(); - PrintWriter outputWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(result), Charsets.UTF_8)); - write(outputWriter, classMapping, 0); - outputWriter.close(); + + try (PrintWriter outputWriter = new PrintWriter(new BufferedWriter(new FileWriter(result)))) { + write(outputWriter, classMapping, 0); + } } // Remove dropped mappings - if (mappings.getPreviousState() != null) { - List droppedClassMappings = new ArrayList<>(mappings.getPreviousState().classes()); - List classMappings = new ArrayList<>(mappings.classes()); - droppedClassMappings.removeAll(classMappings); - for (ClassMapping classMapping : droppedClassMappings) { - File obFile = new File(target, classMapping.getObfFullName() + ".mapping"); - File result; - if (classMapping.getDeobfName() == null) - result = obFile; - else { - // Make sure that old version of the file doesn't exist - if (obFile.exists()) - obFile.delete(); - result = new File(target, classMapping.getDeobfName() + ".mapping"); + if (previousState != null) { + Set droppedClassMappings = new HashSet<>(previousState.classes()); + droppedClassMappings.removeAll(mappings.classes()); + for (ClassMapping droppedMapping : droppedClassMappings) { + File result = new File(target, droppedMapping.getSaveName() + ".mapping"); + if (!result.exists()) { + continue; + } + if (!result.delete()) { + System.err.println("Failed to delete dropped class mapping " + result.getName()); } - if (result.exists()) - result.delete(); } } } - private void deletePreviousClassMapping(File target, ClassMapping classMapping) { - File prevFile = null; - // Deob rename - if (classMapping.getDeobfName() != null && classMapping.getPreviousDeobfName() != null && !classMapping.getPreviousDeobfName().equals(classMapping.getDeobfName())) { - prevFile = new File(target, classMapping.getPreviousDeobfName() + ".mapping"); - } - // Deob to ob rename - else if (classMapping.getDeobfName() == null && classMapping.getPreviousDeobfName() != null) { - prevFile = new File(target, classMapping.getPreviousDeobfName() + ".mapping"); - } - // Ob to Deob rename - else if (classMapping.getDeobfName() != null && classMapping.getPreviousDeobfName() == null) { - prevFile = new File(target, classMapping.getObfFullName() + ".mapping"); - } - - if (prevFile != null && prevFile.exists()) - prevFile.delete(); - } - public void write(PrintWriter out, Mappings mappings) throws IOException { for (ClassMapping classMapping : sorted(mappings.classes())) { write(out, classMapping, 0); } } - private void write(PrintWriter out, ClassMapping classMapping, int depth) throws IOException { + protected void write(PrintWriter out, ClassMapping classMapping, int depth) throws IOException { if (classMapping.getDeobfName() == null) { out.format("%sCLASS %s%s\n", getIndent(depth), classMapping.getObfFullName(), - classMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : classMapping.getModifier().getFormattedName()); + classMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : classMapping.getModifier().getFormattedName()); } else { out.format("%sCLASS %s %s%s\n", getIndent(depth), classMapping.getObfFullName(), classMapping.getDeobfName(), - classMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : classMapping.getModifier().getFormattedName()); + classMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : classMapping.getModifier().getFormattedName()); } for (ClassMapping innerClassMapping : sorted(classMapping.innerClasses())) { @@ -127,32 +109,32 @@ public class MappingsEnigmaWriter { private void write(PrintWriter out, FieldMapping fieldMapping, int depth) { if (fieldMapping.getDeobfName() == null) - out.format("%sFIELD %s %s%s\n", getIndent(depth), fieldMapping.getObfName(), fieldMapping.getObfType().toString(), - fieldMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : fieldMapping.getModifier().getFormattedName()); + out.format("%sFIELD %s %s%s\n", getIndent(depth), fieldMapping.getObfName(), fieldMapping.getObfDesc().toString(), + fieldMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : fieldMapping.getModifier().getFormattedName()); else - out.format("%sFIELD %s %s %s%s\n", getIndent(depth), fieldMapping.getObfName(), fieldMapping.getDeobfName(), fieldMapping.getObfType().toString(), - fieldMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : fieldMapping.getModifier().getFormattedName()); + out.format("%sFIELD %s %s %s%s\n", getIndent(depth), fieldMapping.getObfName(), fieldMapping.getDeobfName(), fieldMapping.getObfDesc().toString(), + fieldMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : fieldMapping.getModifier().getFormattedName()); } private void write(PrintWriter out, MethodMapping methodMapping, int depth) throws IOException { - if (methodMapping.getDeobfName() == null) { - out.format("%sMETHOD %s %s%s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getObfSignature(), - methodMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : methodMapping.getModifier().getFormattedName()); + if (methodMapping.isObfuscated()) { + out.format("%sMETHOD %s %s%s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getObfDesc(), + methodMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : methodMapping.getModifier().getFormattedName()); } else { - out.format("%sMETHOD %s %s %s%s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getDeobfName(), methodMapping.getObfSignature(), - methodMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : methodMapping.getModifier().getFormattedName()); + out.format("%sMETHOD %s %s %s%s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getDeobfName(), methodMapping.getObfDesc(), + methodMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : methodMapping.getModifier().getFormattedName()); } - for (ArgumentMapping argumentMapping : sorted(methodMapping.arguments())) { - write(out, argumentMapping, depth + 1); + for (LocalVariableMapping localVariableMapping : sorted(methodMapping.arguments())) { + write(out, localVariableMapping, depth + 1); } } - private void write(PrintWriter out, ArgumentMapping argumentMapping, int depth) { - out.format("%sARG %d %s\n", getIndent(depth), argumentMapping.getIndex(), argumentMapping.getName()); + private void write(PrintWriter out, LocalVariableMapping localVariableMapping, int depth) { + out.format("%sARG %d %s\n", getIndent(depth), localVariableMapping.getIndex(), localVariableMapping.getName()); } - private > List sorted(Iterable classes) { + protected > List sorted(Iterable classes) { List out = new ArrayList<>(); for (T t : classes) { out.add(t); diff --git a/src/main/java/cuchaz/enigma/mapping/MappingsRenamer.java b/src/main/java/cuchaz/enigma/mapping/MappingsRenamer.java index 7126d2b6..85b6d2ab 100644 --- a/src/main/java/cuchaz/enigma/mapping/MappingsRenamer.java +++ b/src/main/java/cuchaz/enigma/mapping/MappingsRenamer.java @@ -13,6 +13,7 @@ package cuchaz.enigma.mapping; import com.google.common.collect.Lists; import cuchaz.enigma.analysis.JarIndex; +import cuchaz.enigma.mapping.entry.*; import cuchaz.enigma.throwables.IllegalNameException; import cuchaz.enigma.throwables.MappingConflict; @@ -25,12 +26,14 @@ import java.util.zip.GZIPOutputStream; public class MappingsRenamer { - private JarIndex index; + private final JarIndex index; + private final ReferencedEntryPool entryPool; private Mappings mappings; - public MappingsRenamer(JarIndex index, Mappings mappings) { + public MappingsRenamer(JarIndex index, Mappings mappings, ReferencedEntryPool entryPool) { this.index = index; this.mappings = mappings; + this.entryPool = entryPool; } public void setMappings(Mappings mappings) { @@ -46,7 +49,7 @@ public class MappingsRenamer { if (deobfName != null) { // make sure we don't rename to an existing obf or deobf class - if (mappings.containsDeobfClass(deobfName) || index.containsObfClass(new ClassEntry(deobfName))) { + if (mappings.containsDeobfClass(deobfName) || index.containsObfClass(entryPool.getClass(deobfName))) { throw new IllegalNameException(deobfName, "There is already a class with that name"); } } @@ -87,13 +90,13 @@ public class MappingsRenamer { public void setFieldName(FieldEntry obf, String deobfName) { deobfName = NameValidator.validateFieldName(deobfName); - FieldEntry targetEntry = new FieldEntry(obf.getClassEntry(), deobfName, obf.getType()); + FieldEntry targetEntry = entryPool.getField(obf.getOwnerClassEntry(), deobfName, obf.getDesc()); ClassEntry definedClass = null; - if (mappings.containsDeobfField(obf.getClassEntry(), deobfName) || index.containsEntryWithSameName(targetEntry)) - definedClass = obf.getClassEntry(); + if (mappings.containsDeobfField(obf.getOwnerClassEntry(), deobfName) || index.containsEntryWithSameName(targetEntry)) + definedClass = obf.getOwnerClassEntry(); else { - for (ClassEntry ancestorEntry : this.index.getTranslationIndex().getAncestry(obf.getClassEntry())) { - if (mappings.containsDeobfField(ancestorEntry, deobfName) || index.containsEntryWithSameName(targetEntry.cloneToNewClass(ancestorEntry))) { + for (ClassEntry ancestorEntry : this.index.getTranslationIndex().getAncestry(obf.getOwnerClassEntry())) { + if (mappings.containsDeobfField(ancestorEntry, deobfName) || index.containsEntryWithSameName(targetEntry.updateOwnership(ancestorEntry))) { definedClass = ancestorEntry; break; } @@ -101,42 +104,44 @@ public class MappingsRenamer { } if (definedClass != null) { - String className = mappings.getTranslator(TranslationDirection.Deobfuscating, index.getTranslationIndex()).translateClass(definedClass.getClassName()); + Translator translator = mappings.getTranslator(TranslationDirection.DEOBFUSCATING, index.getTranslationIndex()); + String className = translator.getTranslatedClass(entryPool.getClass(definedClass.getClassName())).getName(); if (className == null) className = definedClass.getClassName(); throw new IllegalNameException(deobfName, "There is already a field with that name in " + className); } - ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); - classMapping.setFieldName(obf.getName(), obf.getType(), deobfName); + ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); + classMapping.setFieldName(obf.getName(), obf.getDesc(), deobfName); } public void removeFieldMapping(FieldEntry obf) { - ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); - classMapping.removeFieldMapping(classMapping.getFieldByObf(obf.getName(), obf.getType())); + ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); + classMapping.removeFieldMapping(classMapping.getFieldByObf(obf.getName(), obf.getDesc())); } public void markFieldAsDeobfuscated(FieldEntry obf) { - ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); - classMapping.setFieldName(obf.getName(), obf.getType(), obf.getName()); + ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); + classMapping.setFieldName(obf.getName(), obf.getDesc(), obf.getName()); } private void validateMethodTreeName(MethodEntry entry, String deobfName) { - MethodEntry targetEntry = new MethodEntry(entry.getClassEntry(), deobfName, entry.getSignature()); + MethodEntry targetEntry = entryPool.getMethod(entry.getOwnerClassEntry(), deobfName, entry.getDesc()); // TODO: Verify if I don't break things - ClassMapping classMapping = mappings.getClassByObf(entry.getClassEntry()); - if ((classMapping != null && classMapping.containsDeobfMethod(deobfName, entry.getSignature()) && classMapping.getMethodByObf(entry.getName(), entry.getSignature()) != classMapping.getMethodByDeobf(deobfName, entry.getSignature())) - || index.containsObfBehavior(targetEntry)) { - String deobfClassName = mappings.getTranslator(TranslationDirection.Deobfuscating, index.getTranslationIndex()).translateClass(entry.getClassName()); + ClassMapping classMapping = mappings.getClassByObf(entry.getOwnerClassEntry()); + if ((classMapping != null && classMapping.containsDeobfMethod(deobfName, entry.getDesc()) && classMapping.getMethodByObf(entry.getName(), entry.getDesc()) != classMapping.getMethodByDeobf(deobfName, entry.getDesc())) + || index.containsObfMethod(targetEntry)) { + Translator translator = mappings.getTranslator(TranslationDirection.DEOBFUSCATING, index.getTranslationIndex()); + String deobfClassName = translator.getTranslatedClass(entryPool.getClass(entry.getClassName())).getClassName(); if (deobfClassName == null) { deobfClassName = entry.getClassName(); } throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName); } - for (ClassEntry child : index.getTranslationIndex().getSubclass(entry.getClassEntry())) { - validateMethodTreeName(entry.cloneToNewClass(child), deobfName); + for (ClassEntry child : index.getTranslationIndex().getSubclass(entry.getOwnerClassEntry())) { + validateMethodTreeName(entry.updateOwnership(child), deobfName); } } @@ -155,20 +160,21 @@ public class MappingsRenamer { public void setMethodName(MethodEntry obf, String deobfName) { deobfName = NameValidator.validateMethodName(deobfName); - MethodEntry targetEntry = new MethodEntry(obf.getClassEntry(), deobfName, obf.getSignature()); - ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); + MethodEntry targetEntry = entryPool.getMethod(obf.getOwnerClassEntry(), deobfName, obf.getDesc()); + ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); // TODO: Verify if I don't break things - if ((mappings.containsDeobfMethod(obf.getClassEntry(), deobfName, obf.getSignature()) && classMapping.getMethodByObf(obf.getName(), obf.getSignature()) != classMapping.getMethodByDeobf(deobfName, obf.getSignature())) - || index.containsObfBehavior(targetEntry)) { - String deobfClassName = mappings.getTranslator(TranslationDirection.Deobfuscating, index.getTranslationIndex()).translateClass(obf.getClassName()); + if ((mappings.containsDeobfMethod(obf.getOwnerClassEntry(), deobfName, obf.getDesc()) && classMapping.getMethodByObf(obf.getName(), obf.getDesc()) != classMapping.getMethodByDeobf(deobfName, obf.getDesc())) + || index.containsObfMethod(targetEntry)) { + Translator translator = mappings.getTranslator(TranslationDirection.DEOBFUSCATING, index.getTranslationIndex()); + String deobfClassName = translator.getTranslatedClass(entryPool.getClass(obf.getClassName())).getClassName(); if (deobfClassName == null) { deobfClassName = obf.getClassName(); } throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName); } - classMapping.setMethodName(obf.getName(), obf.getSignature(), deobfName); + classMapping.setMethodName(obf.getName(), obf.getDesc(), deobfName); } public void removeMethodTreeMapping(MethodEntry obf) { @@ -176,8 +182,8 @@ public class MappingsRenamer { } public void removeMethodMapping(MethodEntry obf) { - ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); - classMapping.setMethodName(obf.getName(), obf.getSignature(), null); + ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); + classMapping.setMethodName(obf.getName(), obf.getDesc(), null); } public void markMethodTreeAsDeobfuscated(MethodEntry obf) { @@ -185,30 +191,25 @@ public class MappingsRenamer { } public void markMethodAsDeobfuscated(MethodEntry obf) { - ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); - classMapping.setMethodName(obf.getName(), obf.getSignature(), obf.getName()); + ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); + classMapping.setMethodName(obf.getName(), obf.getDesc(), obf.getName()); } - public void setArgumentTreeName(ArgumentEntry obf, String deobfName) { - if (!(obf.getBehaviorEntry() instanceof MethodEntry)) { - setArgumentName(obf, deobfName); - return; - } - - MethodEntry obfMethod = (MethodEntry) obf.getBehaviorEntry(); + public void setLocalVariableTreeName(LocalVariableEntry obf, String deobfName) { + MethodEntry obfMethod = obf.getOwnerEntry(); Set implementations = index.getRelatedMethodImplementations(obfMethod); for (MethodEntry entry : implementations) { - ClassMapping classMapping = mappings.getClassByObf(entry.getClassEntry()); + ClassMapping classMapping = mappings.getClassByObf(entry.getOwnerClassEntry()); if (classMapping != null) { - MethodMapping mapping = classMapping.getMethodByObf(entry.getName(), entry.getSignature()); + MethodMapping mapping = classMapping.getMethodByObf(entry.getName(), entry.getDesc()); // NOTE: don't need to check arguments for name collisions with names determined by Procyon // TODO: Verify if I don't break things if (mapping != null) { - for (ArgumentMapping argumentMapping : Lists.newArrayList(mapping.arguments())) { - if (argumentMapping.getIndex() != obf.getIndex()) { - if (mapping.getDeobfArgumentName(argumentMapping.getIndex()).equals(deobfName) - || argumentMapping.getName().equals(deobfName)) { + for (LocalVariableMapping localVariableMapping : Lists.newArrayList(mapping.arguments())) { + if (localVariableMapping.getIndex() != obf.getIndex()) { + if (mapping.getDeobfLocalVariableName(localVariableMapping.getIndex()).equals(deobfName) + || localVariableMapping.getName().equals(deobfName)) { throw new IllegalNameException(deobfName, "There is already an argument with that name"); } } @@ -218,45 +219,45 @@ public class MappingsRenamer { } for (MethodEntry entry : implementations) { - setArgumentName(new ArgumentEntry(obf, entry), deobfName); + setLocalVariableName(new LocalVariableEntry(entry, obf.getIndex(), obf.getName()), deobfName); } } - public void setArgumentName(ArgumentEntry obf, String deobfName) { + public void setLocalVariableName(LocalVariableEntry obf, String deobfName) { deobfName = NameValidator.validateArgumentName(deobfName); - ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); - MethodMapping mapping = classMapping.getMethodByObf(obf.getMethodName(), obf.getMethodSignature()); + ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); + MethodMapping mapping = classMapping.getMethodByObf(obf.getMethodName(), obf.getMethodDesc()); // NOTE: don't need to check arguments for name collisions with names determined by Procyon // TODO: Verify if I don't break things if (mapping != null) { - for (ArgumentMapping argumentMapping : Lists.newArrayList(mapping.arguments())) { - if (argumentMapping.getIndex() != obf.getIndex()) { - if (mapping.getDeobfArgumentName(argumentMapping.getIndex()).equals(deobfName) - || argumentMapping.getName().equals(deobfName)) { + for (LocalVariableMapping localVariableMapping : Lists.newArrayList(mapping.arguments())) { + if (localVariableMapping.getIndex() != obf.getIndex()) { + if (mapping.getDeobfLocalVariableName(localVariableMapping.getIndex()).equals(deobfName) + || localVariableMapping.getName().equals(deobfName)) { throw new IllegalNameException(deobfName, "There is already an argument with that name"); } } } } - classMapping.setArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex(), deobfName); + classMapping.setArgumentName(obf.getMethodName(), obf.getMethodDesc(), obf.getIndex(), deobfName); } - public void removeArgumentMapping(ArgumentEntry obf) { - ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); - classMapping.removeArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex()); + public void removeLocalVariableMapping(LocalVariableEntry obf) { + ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); + classMapping.removeArgumentName(obf.getMethodName(), obf.getMethodDesc(), obf.getIndex()); } - public void markArgumentAsDeobfuscated(ArgumentEntry obf) { - ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); - classMapping.setArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex(), obf.getName()); + public void markArgumentAsDeobfuscated(LocalVariableEntry obf) { + ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); + classMapping.setArgumentName(obf.getMethodName(), obf.getMethodDesc(), obf.getIndex(), obf.getName()); } public boolean moveFieldToObfClass(ClassMapping classMapping, FieldMapping fieldMapping, ClassEntry obfClass) { classMapping.removeFieldMapping(fieldMapping); ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass); - if (!targetClassMapping.containsObfField(fieldMapping.getObfName(), fieldMapping.getObfType())) { - if (!targetClassMapping.containsDeobfField(fieldMapping.getDeobfName(), fieldMapping.getObfType())) { + if (!targetClassMapping.containsObfField(fieldMapping.getObfName(), fieldMapping.getObfDesc())) { + if (!targetClassMapping.containsDeobfField(fieldMapping.getDeobfName(), fieldMapping.getObfDesc())) { targetClassMapping.addFieldMapping(fieldMapping); return true; } else { @@ -269,12 +270,12 @@ public class MappingsRenamer { public boolean moveMethodToObfClass(ClassMapping classMapping, MethodMapping methodMapping, ClassEntry obfClass) { classMapping.removeMethodMapping(methodMapping); ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass); - if (!targetClassMapping.containsObfMethod(methodMapping.getObfName(), methodMapping.getObfSignature())) { - if (!targetClassMapping.containsDeobfMethod(methodMapping.getDeobfName(), methodMapping.getObfSignature())) { + if (!targetClassMapping.containsObfMethod(methodMapping.getObfName(), methodMapping.getObfDesc())) { + if (!targetClassMapping.containsDeobfMethod(methodMapping.getDeobfName(), methodMapping.getObfDesc())) { targetClassMapping.addMethodMapping(methodMapping); return true; } else { - System.err.println("WARNING: deobf method was already there: " + obfClass + "." + methodMapping.getDeobfName() + methodMapping.getObfSignature()); + System.err.println("WARNING: deobf method was already there: " + obfClass + "." + methodMapping.getDeobfName() + methodMapping.getObfDesc()); } } return false; @@ -326,12 +327,35 @@ public class MappingsRenamer { } public void setFieldModifier(FieldEntry obEntry, Mappings.EntryModifier modifier) { - ClassMapping classMapping = getOrCreateClassMapping(obEntry.getClassEntry()); - classMapping.setFieldModifier(obEntry.getName(), obEntry.getType(), modifier); + ClassMapping classMapping = getOrCreateClassMapping(obEntry.getOwnerClassEntry()); + classMapping.setFieldModifier(obEntry.getName(), obEntry.getDesc(), modifier); + } + + public void setMethodModifier(MethodEntry obEntry, Mappings.EntryModifier modifier) { + ClassMapping classMapping = getOrCreateClassMapping(obEntry.getOwnerClassEntry()); + classMapping.setMethodModifier(obEntry.getName(), obEntry.getDesc(), modifier); + } + + public Mappings.EntryModifier getClassModifier(ClassEntry obfEntry) { + ClassMapping classMapping = getOrCreateClassMapping(obfEntry); + return classMapping.getModifier(); } - public void setMethodModifier(BehaviorEntry obEntry, Mappings.EntryModifier modifier) { - ClassMapping classMapping = getOrCreateClassMapping(obEntry.getClassEntry()); - classMapping.setMethodModifier(obEntry.getName(), obEntry.getSignature(), modifier); + public Mappings.EntryModifier getFieldModifier(FieldEntry obfEntry) { + ClassMapping classMapping = getOrCreateClassMapping(obfEntry.getOwnerClassEntry()); + FieldMapping fieldMapping = classMapping.getFieldByObf(obfEntry); + if (fieldMapping == null) { + return Mappings.EntryModifier.UNCHANGED; + } + return fieldMapping.getModifier(); + } + + public Mappings.EntryModifier getMethodModfifier(MethodEntry obfEntry) { + ClassMapping classMapping = getOrCreateClassMapping(obfEntry.getOwnerClassEntry()); + MethodMapping methodMapping = classMapping.getMethodByObf(obfEntry); + if (methodMapping == null) { + return Mappings.EntryModifier.UNCHANGED; + } + return methodMapping.getModifier(); } } diff --git a/src/main/java/cuchaz/enigma/mapping/MappingsSRGWriter.java b/src/main/java/cuchaz/enigma/mapping/MappingsSRGWriter.java index b0eb826e..32f0ee9f 100644 --- a/src/main/java/cuchaz/enigma/mapping/MappingsSRGWriter.java +++ b/src/main/java/cuchaz/enigma/mapping/MappingsSRGWriter.java @@ -2,6 +2,7 @@ package cuchaz.enigma.mapping; import com.google.common.base.Charsets; import cuchaz.enigma.analysis.TranslationIndex; +import cuchaz.enigma.mapping.entry.ReferencedEntryPool; import java.io.*; import java.util.ArrayList; @@ -19,7 +20,7 @@ public class MappingsSRGWriter { } file.createNewFile(); - TranslationIndex index = new TranslationIndex(); + TranslationIndex index = new TranslationIndex(new ReferencedEntryPool()); PrintWriter writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(file), Charsets.UTF_8)); List fieldMappings = new ArrayList<>(); @@ -43,7 +44,7 @@ public class MappingsSRGWriter { } for (MethodMapping methodMapping : sorted(innerClassMapping.methods())) { - methodMappings.add("MD: " + innerClassName + "/" + methodMapping.getObfName() + " " + methodMapping.getObfSignature() + " " + innerDeobfClassName + "/" + methodMapping.getDeobfName() + " " + mappings.getTranslator(TranslationDirection.Deobfuscating, index).translateSignature(methodMapping.getObfSignature())); + methodMappings.add("MD: " + innerClassName + "/" + methodMapping.getObfName() + " " + methodMapping.getObfDesc() + " " + innerDeobfClassName + "/" + methodMapping.getDeobfName() + " " + mappings.getTranslator(TranslationDirection.DEOBFUSCATING, index).getTranslatedMethodDesc(methodMapping.getObfDesc())); } } @@ -52,7 +53,7 @@ public class MappingsSRGWriter { } for (MethodMapping methodMapping : sorted(classMapping.methods())) { - methodMappings.add("MD: " + classMapping.getObfFullName() + "/" + methodMapping.getObfName() + " " + methodMapping.getObfSignature() + " " + classMapping.getDeobfName() + "/" + methodMapping.getDeobfName() + " " + mappings.getTranslator(TranslationDirection.Deobfuscating, index).translateSignature(methodMapping.getObfSignature())); + methodMappings.add("MD: " + classMapping.getObfFullName() + "/" + methodMapping.getObfName() + " " + methodMapping.getObfDesc() + " " + classMapping.getDeobfName() + "/" + methodMapping.getDeobfName() + " " + mappings.getTranslator(TranslationDirection.DEOBFUSCATING, index).getTranslatedMethodDesc(methodMapping.getObfDesc())); } } for (String fd : fieldMappings) { diff --git a/src/main/java/cuchaz/enigma/mapping/MappingsTinyReader.java b/src/main/java/cuchaz/enigma/mapping/MappingsTinyReader.java index befc92ab..69d5684b 100644 --- a/src/main/java/cuchaz/enigma/mapping/MappingsTinyReader.java +++ b/src/main/java/cuchaz/enigma/mapping/MappingsTinyReader.java @@ -2,6 +2,7 @@ package cuchaz.enigma.mapping; import com.google.common.base.Charsets; import com.google.common.collect.Maps; +import cuchaz.enigma.mapping.entry.ClassEntry; import cuchaz.enigma.throwables.MappingConflict; import cuchaz.enigma.throwables.MappingParseException; @@ -20,11 +21,11 @@ public class MappingsTinyReader { } public FieldMapping readField(String[] parts) { - return new FieldMapping(parts[3], new Type(parts[2]), parts[4], Mappings.EntryModifier.UNCHANGED); + return new FieldMapping(parts[3], new TypeDescriptor(parts[2]), parts[4], Mappings.EntryModifier.UNCHANGED); } public MethodMapping readMethod(String[] parts) { - return new MethodMapping(parts[3], new Signature(parts[2]), parts[4]); + return new MethodMapping(parts[3], new MethodDescriptor(parts[2]), parts[4]); } public Mappings read(File file) throws IOException, MappingParseException { @@ -72,7 +73,7 @@ public class MappingsTinyReader { break; case "MTH-ARG": classMapping = classMappingMap.computeIfAbsent(parts[1], k -> new ClassMapping(parts[1])); - classMapping.setArgumentName(parts[3], new Signature(parts[2]), Integer.parseInt(parts[4]), parts[5]); + classMapping.setArgumentName(parts[3], new MethodDescriptor(parts[2]), Integer.parseInt(parts[4]), parts[5]); break; default: throw new MappingParseException(file, lineNumber, "Unknown token '" + token + "' !"); diff --git a/src/main/java/cuchaz/enigma/mapping/MemberMapping.java b/src/main/java/cuchaz/enigma/mapping/MemberMapping.java index d4514d42..6effb91f 100644 --- a/src/main/java/cuchaz/enigma/mapping/MemberMapping.java +++ b/src/main/java/cuchaz/enigma/mapping/MemberMapping.java @@ -11,6 +11,9 @@ package cuchaz.enigma.mapping; +import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.mapping.entry.Entry; + public interface MemberMapping { T getObfEntry(ClassEntry classEntry); diff --git a/src/main/java/cuchaz/enigma/mapping/MethodDescriptor.java b/src/main/java/cuchaz/enigma/mapping/MethodDescriptor.java new file mode 100644 index 00000000..0fc03517 --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/MethodDescriptor.java @@ -0,0 +1,114 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.mapping; + +import com.google.common.collect.Lists; +import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.utils.Utils; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +public class MethodDescriptor { + + private List argumentDescs; + private TypeDescriptor returnDesc; + + public MethodDescriptor(String desc) { + try { + this.argumentDescs = Lists.newArrayList(); + int i = 0; + while (i < desc.length()) { + char c = desc.charAt(i); + if (c == '(') { + assert (this.argumentDescs.isEmpty()); + assert (this.returnDesc == null); + i++; + } else if (c == ')') { + i++; + break; + } else { + String type = TypeDescriptor.parseFirst(desc.substring(i)); + this.argumentDescs.add(new TypeDescriptor(type)); + i += type.length(); + } + } + this.returnDesc = new TypeDescriptor(TypeDescriptor.parseFirst(desc.substring(i))); + } catch (Exception ex) { + throw new IllegalArgumentException("Unable to parse method descriptor: " + desc, ex); + } + } + + public MethodDescriptor(List argumentDescs, TypeDescriptor returnDesc) { + this.argumentDescs = argumentDescs; + this.returnDesc = returnDesc; + } + + public List getArgumentDescs() { + return this.argumentDescs; + } + + public TypeDescriptor getReturnDesc() { + return this.returnDesc; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append("("); + for (TypeDescriptor desc : this.argumentDescs) { + buf.append(desc); + } + buf.append(")"); + buf.append(this.returnDesc); + return buf.toString(); + } + + public Iterable types() { + List descs = Lists.newArrayList(); + descs.addAll(this.argumentDescs); + descs.add(this.returnDesc); + return descs; + } + + @Override + public boolean equals(Object other) { + return other instanceof MethodDescriptor && equals((MethodDescriptor) other); + } + + public boolean equals(MethodDescriptor other) { + return this.argumentDescs.equals(other.argumentDescs) && this.returnDesc.equals(other.returnDesc); + } + + @Override + public int hashCode() { + return Utils.combineHashesOrdered(this.argumentDescs.hashCode(), this.returnDesc.hashCode()); + } + + public boolean hasClass(ClassEntry classEntry) { + for (TypeDescriptor desc : types()) { + if (desc.containsType() && desc.getTypeEntry().equals(classEntry)) { + return true; + } + } + return false; + } + + public MethodDescriptor remap(Function remapper) { + List argumentDescs = new ArrayList<>(this.argumentDescs.size()); + for (TypeDescriptor desc : this.argumentDescs) { + argumentDescs.add(desc.remap(remapper)); + } + return new MethodDescriptor(argumentDescs, returnDesc.remap(remapper)); + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/MethodEntry.java b/src/main/java/cuchaz/enigma/mapping/MethodEntry.java deleted file mode 100644 index 9c3058c4..00000000 --- a/src/main/java/cuchaz/enigma/mapping/MethodEntry.java +++ /dev/null @@ -1,90 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.mapping; - -import cuchaz.enigma.utils.Utils; - -public class MethodEntry implements BehaviorEntry { - - private ClassEntry classEntry; - private String name; - private Signature signature; - - public MethodEntry(ClassEntry classEntry, String name, Signature signature) { - if (classEntry == null) { - throw new IllegalArgumentException("Class cannot be null!"); - } - if (name == null) { - throw new IllegalArgumentException("Method name cannot be null!"); - } - if (signature == null) { - throw new IllegalArgumentException("Method signature cannot be null!"); - } - if (name.startsWith("<")) { - throw new IllegalArgumentException("Don't use MethodEntry for a constructor!"); - } - - this.classEntry = classEntry; - this.name = name; - this.signature = signature; - } - - public MethodEntry(MethodEntry other, String newClassName) { - this.classEntry = new ClassEntry(newClassName); - this.name = other.name; - this.signature = other.signature; - } - - @Override - public ClassEntry getClassEntry() { - return this.classEntry; - } - - @Override - public String getName() { - return this.name; - } - - @Override - public Signature getSignature() { - return this.signature; - } - - @Override - public String getClassName() { - return this.classEntry.getName(); - } - - @Override - public MethodEntry cloneToNewClass(ClassEntry classEntry) { - return new MethodEntry(this, classEntry.getName()); - } - - @Override - public int hashCode() { - return Utils.combineHashesOrdered(this.classEntry, this.name, this.signature); - } - - @Override - public boolean equals(Object other) { - return other instanceof MethodEntry && equals((MethodEntry) other); - } - - public boolean equals(MethodEntry other) { - return this.classEntry.equals(other.classEntry) && this.name.equals(other.name) && this.signature.equals(other.signature); - } - - @Override - public String toString() { - return this.classEntry.getName() + "." + this.name + this.signature; - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/MethodMapping.java b/src/main/java/cuchaz/enigma/mapping/MethodMapping.java index 1524ce63..2f10144e 100644 --- a/src/main/java/cuchaz/enigma/mapping/MethodMapping.java +++ b/src/main/java/cuchaz/enigma/mapping/MethodMapping.java @@ -11,50 +11,49 @@ package cuchaz.enigma.mapping; +import com.google.common.base.Preconditions; import com.google.common.collect.Maps; +import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.mapping.entry.MethodEntry; import cuchaz.enigma.throwables.IllegalNameException; import cuchaz.enigma.throwables.MappingConflict; import java.util.Map; -public class MethodMapping implements Comparable, MemberMapping { +public class MethodMapping implements Comparable, MemberMapping { private String obfName; private String deobfName; - private Signature obfSignature; - private Map arguments; + private MethodDescriptor obfDescriptor; + private Map localVariables; private Mappings.EntryModifier modifier; - public MethodMapping(String obfName, Signature obfSignature) { - this(obfName, obfSignature, null, Mappings.EntryModifier.UNCHANGED); + public MethodMapping(String obfName, MethodDescriptor obfDescriptor) { + this(obfName, obfDescriptor, null, Mappings.EntryModifier.UNCHANGED); } - public MethodMapping(String obfName, Signature obfSignature, String deobfName) { - this(obfName, obfSignature, deobfName, Mappings.EntryModifier.UNCHANGED); + public MethodMapping(String obfName, MethodDescriptor obfDescriptor, String deobfName) { + this(obfName, obfDescriptor, deobfName, Mappings.EntryModifier.UNCHANGED); } - public MethodMapping(String obfName, Signature obfSignature, String deobfName, Mappings.EntryModifier modifier) { - if (obfName == null) { - throw new IllegalArgumentException("obf name cannot be null!"); - } - if (obfSignature == null) { - throw new IllegalArgumentException("obf signature cannot be null!"); - } + public MethodMapping(String obfName, MethodDescriptor obfDescriptor, String deobfName, Mappings.EntryModifier modifier) { + Preconditions.checkNotNull(obfName, "Method obf name cannot be null"); + Preconditions.checkNotNull(obfDescriptor, "Method obf desc cannot be null"); this.obfName = obfName; this.deobfName = NameValidator.validateMethodName(deobfName); - this.obfSignature = obfSignature; - this.arguments = Maps.newTreeMap(); + this.obfDescriptor = obfDescriptor; + this.localVariables = Maps.newTreeMap(); this.modifier = modifier; } - public MethodMapping(MethodMapping other, ClassNameReplacer obfClassNameReplacer) { + public MethodMapping(MethodMapping other, Translator translator) { this.obfName = other.obfName; this.deobfName = other.deobfName; this.modifier = other.modifier; - this.obfSignature = new Signature(other.obfSignature, obfClassNameReplacer); - this.arguments = Maps.newTreeMap(); - for (Map.Entry entry : other.arguments.entrySet()) { - this.arguments.put(entry.getKey(), new ArgumentMapping(entry.getValue())); + this.obfDescriptor = translator.getTranslatedMethodDesc(other.obfDescriptor); + this.localVariables = Maps.newTreeMap(); + for (Map.Entry entry : other.localVariables.entrySet()) { + this.localVariables.put(entry.getKey(), new LocalVariableMapping(entry.getValue())); } } @@ -77,6 +76,9 @@ public class MethodMapping implements Comparable, MemberMapping, MemberMapping arguments() { - return this.arguments.values(); + public Iterable arguments() { + return this.localVariables.values(); } - public void addArgumentMapping(ArgumentMapping argumentMapping) throws MappingConflict { - if (this.arguments.containsKey(argumentMapping.getIndex())) { - throw new MappingConflict("argument", argumentMapping.getName(), this.arguments.get(argumentMapping.getIndex()).getName()); + public void addArgumentMapping(LocalVariableMapping localVariableMapping) throws MappingConflict { + if (this.localVariables.containsKey(localVariableMapping.getIndex())) { + throw new MappingConflict("argument", localVariableMapping.getName(), this.localVariables.get(localVariableMapping.getIndex()).getName()); } - this.arguments.put(argumentMapping.getIndex(), argumentMapping); + this.localVariables.put(localVariableMapping.getIndex(), localVariableMapping); } - public String getObfArgumentName(int index) { - ArgumentMapping argumentMapping = this.arguments.get(index); - if (argumentMapping != null) { - return argumentMapping.getName(); + public String getObfLocalVariableName(int index) { + LocalVariableMapping localVariableMapping = this.localVariables.get(index); + if (localVariableMapping != null) { + return localVariableMapping.getName(); } return null; } - public String getDeobfArgumentName(int index) { - ArgumentMapping argumentMapping = this.arguments.get(index); - if (argumentMapping != null) { - return argumentMapping.getName(); + public String getDeobfLocalVariableName(int index) { + LocalVariableMapping localVariableMapping = this.localVariables.get(index); + if (localVariableMapping != null) { + return localVariableMapping.getName(); } return null; } - public void setArgumentName(int index, String name) { - ArgumentMapping argumentMapping = this.arguments.get(index); - if (argumentMapping == null) { - argumentMapping = new ArgumentMapping(index, name); - boolean wasAdded = this.arguments.put(index, argumentMapping) == null; + public void setLocalVariableName(int index, String name) { + LocalVariableMapping localVariableMapping = this.localVariables.get(index); + if (localVariableMapping == null) { + localVariableMapping = new LocalVariableMapping(index, name); + boolean wasAdded = this.localVariables.put(index, localVariableMapping) == null; assert (wasAdded); } else { - argumentMapping.setName(name); + localVariableMapping.setName(name); } } - public void removeArgumentName(int index) { - boolean wasRemoved = this.arguments.remove(index) != null; + public void removeLocalVariableName(int index) { + boolean wasRemoved = this.localVariables.remove(index) != null; assert (wasRemoved); } @@ -146,14 +148,14 @@ public class MethodMapping implements Comparable, MemberMapping "); - buf.append(argumentMapping.getName()); + buf.append(localVariableMapping.getName()); buf.append("\n"); } return buf.toString(); @@ -161,12 +163,12 @@ public class MethodMapping implements Comparable, MemberMapping, MemberMapping - { + MethodDescriptor newDescriptor = obfDescriptor.remap(className -> { if (className.equals(oldObfClassName)) { return newObfClassName; } - return null; + return className; }); - if (!newSignature.equals(this.obfSignature)) { - this.obfSignature = newSignature; + if (!newDescriptor.equals(this.obfDescriptor)) { + this.obfDescriptor = newDescriptor; return true; } return false; } - public boolean isConstructor() { - return this.obfName.startsWith("<"); - } - @Override - public BehaviorEntry getObfEntry(ClassEntry classEntry) { - if (isConstructor()) { - return new ConstructorEntry(classEntry, this.obfSignature); - } else { - return new MethodEntry(classEntry, this.obfName, this.obfSignature); - } + public MethodEntry getObfEntry(ClassEntry classEntry) { + return new MethodEntry(classEntry, this.obfName, this.obfDescriptor); } public Mappings.EntryModifier getModifier() { @@ -210,4 +203,8 @@ public class MethodMapping implements Comparable, MemberMapping ReservedWords = Arrays.asList( - "abstract", "continue", "for", "new", "switch", "assert", "default", "goto", "package", "synchronized", - "boolean", "do", "if", "private", "this", "break", "double", "implements", "protected", "throw", "byte", - "else", "import", "public", "throws", "case", "enum", "instanceof", "return", "transient", "catch", - "extends", "int", "short", "try", "char", "final", "interface", "static", "void", "class", "finally", - "long", "strictfp", "volatile", "const", "float", "native", "super", "while" + "abstract", "continue", "for", "new", "switch", "assert", "default", "goto", "package", "synchronized", + "boolean", "do", "if", "private", "this", "break", "double", "implements", "protected", "throw", "byte", + "else", "import", "public", "throws", "case", "enum", "instanceof", "return", "transient", "catch", + "extends", "int", "short", "try", "char", "final", "interface", "static", "void", "class", "finally", + "long", "strictfp", "volatile", "const", "float", "native", "super", "while" ); static { @@ -43,10 +43,10 @@ public class NameValidator { if (!ClassPattern.matcher(name).matches() || ReservedWords.contains(name)) { throw new IllegalNameException(name, "This doesn't look like a legal class name"); } - if (packageRequired && new ClassEntry(name).getPackageName() == null) { + if (packageRequired && ClassEntry.getPackageName(name) == null) { throw new IllegalNameException(name, "Class must be in a package"); } - return Descriptor.toJvmName(name); + return name; } public static String validateFieldName(String name) { diff --git a/src/main/java/cuchaz/enigma/mapping/ProcyonEntryFactory.java b/src/main/java/cuchaz/enigma/mapping/ProcyonEntryFactory.java deleted file mode 100644 index 33d930dc..00000000 --- a/src/main/java/cuchaz/enigma/mapping/ProcyonEntryFactory.java +++ /dev/null @@ -1,67 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.mapping; - -import com.strobel.assembler.metadata.*; - -import java.util.List; - -public class ProcyonEntryFactory { - - private static String getErasedSignature(MemberReference def) { - if (!(def instanceof MethodReference)) - return def.getErasedSignature(); - MethodReference methodReference = (MethodReference) def; - StringBuilder builder = new StringBuilder("("); - for (ParameterDefinition param : methodReference.getParameters()) { - TypeReference paramType = param.getParameterType(); - if (paramType.getErasedSignature().equals("Ljava/lang/Object;") && paramType.hasExtendsBound() && paramType.getExtendsBound() instanceof CompoundTypeReference) { - List interfaces = ((CompoundTypeReference) paramType.getExtendsBound()).getInterfaces(); - interfaces.forEach((inter) -> builder.append(inter.getErasedSignature())); - } else - builder.append(paramType.getErasedSignature()); - } - builder.append(")"); - - TypeReference returnType = methodReference.getReturnType(); - if (returnType.getErasedSignature().equals("Ljava/lang/Object;") && returnType.hasExtendsBound() && returnType.getExtendsBound() instanceof CompoundTypeReference) { - List interfaces = ((CompoundTypeReference) returnType.getExtendsBound()).getInterfaces(); - interfaces.forEach((inter) -> builder.append(inter.getErasedSignature())); - } else - builder.append(returnType.getErasedSignature()); - return builder.toString(); - } - - public static FieldEntry getFieldEntry(MemberReference def) { - return new FieldEntry(new ClassEntry(def.getDeclaringType().getInternalName()), def.getName(), new Type(def.getErasedSignature())); - } - - public static MethodEntry getMethodEntry(MemberReference def) { - return new MethodEntry(new ClassEntry(def.getDeclaringType().getInternalName()), def.getName(), new Signature(getErasedSignature(def))); - } - - public static ConstructorEntry getConstructorEntry(MethodReference def) { - if (def.isTypeInitializer()) { - return new ConstructorEntry(new ClassEntry(def.getDeclaringType().getInternalName())); - } else { - return new ConstructorEntry(new ClassEntry(def.getDeclaringType().getInternalName()), new Signature(def.getErasedSignature())); - } - } - - public static BehaviorEntry getBehaviorEntry(MethodReference def) { - if (def.isConstructor() || def.isTypeInitializer()) { - return getConstructorEntry(def); - } else { - return getMethodEntry(def); - } - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/Signature.java b/src/main/java/cuchaz/enigma/mapping/Signature.java index 78130d6b..071e4afa 100644 --- a/src/main/java/cuchaz/enigma/mapping/Signature.java +++ b/src/main/java/cuchaz/enigma/mapping/Signature.java @@ -1,106 +1,82 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - package cuchaz.enigma.mapping; -import com.google.common.collect.Lists; -import cuchaz.enigma.utils.Utils; +import cuchaz.enigma.bytecode.translators.TranslationSignatureVisitor; +import org.objectweb.asm.signature.SignatureReader; +import org.objectweb.asm.signature.SignatureVisitor; +import org.objectweb.asm.signature.SignatureWriter; -import java.util.List; +import java.util.function.Function; +import java.util.regex.Pattern; public class Signature { + private static final Pattern OBJECT_PATTERN = Pattern.compile(".*:Ljava/lang/Object;:.*"); - private List argumentTypes; - private Type returnType; + private final String signature; + private final boolean isType; - public Signature(String signature) { - try { - this.argumentTypes = Lists.newArrayList(); - int i = 0; - while (i < signature.length()) { - char c = signature.charAt(i); - if (c == '(') { - assert (this.argumentTypes.isEmpty()); - assert (this.returnType == null); - i++; - } else if (c == ')') { - i++; - break; - } else { - String type = Type.parseFirst(signature.substring(i)); - this.argumentTypes.add(new Type(type)); - i += type.length(); - } - } - this.returnType = new Type(Type.parseFirst(signature.substring(i))); - } catch (Exception ex) { - throw new IllegalArgumentException("Unable to parse signature: " + signature, ex); + private Signature(String signature, boolean isType) { + if (signature != null && OBJECT_PATTERN.matcher(signature).matches()) { + signature = signature.replaceAll(":Ljava/lang/Object;:", "::"); } + + this.signature = signature; + this.isType = isType; } - public Signature(Signature other, ClassNameReplacer replacer) { - this.argumentTypes = Lists.newArrayList(other.argumentTypes); - for (int i = 0; i < this.argumentTypes.size(); i++) { - this.argumentTypes.set(i, new Type(this.argumentTypes.get(i), replacer)); + public static Signature createTypedSignature(String signature) { + if (signature != null && !signature.isEmpty()) { + return new Signature(signature, true); } - this.returnType = new Type(other.returnType, replacer); + return new Signature(null, true); } - public List getArgumentTypes() { - return this.argumentTypes; + public static Signature createSignature(String signature) { + if (signature != null && !signature.isEmpty()) { + return new Signature(signature, false); + } + return new Signature(null, false); } - public Type getReturnType() { - return this.returnType; + public String getSignature() { + return signature; } - @Override - public String toString() { - StringBuilder buf = new StringBuilder(); - buf.append("("); - for (Type type : this.argumentTypes) { - buf.append(type); - } - buf.append(")"); - buf.append(this.returnType); - return buf.toString(); + public boolean isType() { + return isType; } - public Iterable types() { - List types = Lists.newArrayList(); - types.addAll(this.argumentTypes); - types.add(this.returnType); - return types; + public Signature remap(Function remapper) { + if (signature == null) { + return this; + } + SignatureWriter writer = new SignatureWriter(); + SignatureVisitor visitor = new TranslationSignatureVisitor(remapper, writer); + if (isType) { + new SignatureReader(signature).acceptType(visitor); + } else { + new SignatureReader(signature).accept(visitor); + } + return new Signature(writer.toString(), isType); } @Override - public boolean equals(Object other) { - return other instanceof Signature && equals((Signature) other); - } - - public boolean equals(Signature other) { - return this.argumentTypes.equals(other.argumentTypes) && this.returnType.equals(other.returnType); + public boolean equals(Object obj) { + if (obj instanceof Signature) { + Signature other = (Signature) obj; + return (other.signature == null && signature == null || other.signature != null + && signature != null && other.signature.equals(signature)) + && other.isType == this.isType; + } + return false; } @Override public int hashCode() { - return Utils.combineHashesOrdered(this.argumentTypes.hashCode(), this.returnType.hashCode()); + return signature.hashCode() | (isType ? 1 : 0) << 16; } - public boolean hasClass(ClassEntry classEntry) { - for (Type type : types()) { - if (type.hasClass() && type.getClassEntry().equals(classEntry)) { - return true; - } - } - return false; + @Override + public String toString() { + return signature; } } diff --git a/src/main/java/cuchaz/enigma/mapping/TranslationDirection.java b/src/main/java/cuchaz/enigma/mapping/TranslationDirection.java index 17e31876..4bbde548 100644 --- a/src/main/java/cuchaz/enigma/mapping/TranslationDirection.java +++ b/src/main/java/cuchaz/enigma/mapping/TranslationDirection.java @@ -13,15 +13,21 @@ package cuchaz.enigma.mapping; public enum TranslationDirection { - Deobfuscating { + DEOBFUSCATING { @Override public T choose(T deobfChoice, T obfChoice) { + if (deobfChoice == null) { + return obfChoice; + } return deobfChoice; } }, - Obfuscating { + OBFUSCATING { @Override public T choose(T deobfChoice, T obfChoice) { + if (obfChoice == null) { + return deobfChoice; + } return obfChoice; } }; diff --git a/src/main/java/cuchaz/enigma/mapping/Translator.java b/src/main/java/cuchaz/enigma/mapping/Translator.java index 8d464fc4..a9ff1cbb 100644 --- a/src/main/java/cuchaz/enigma/mapping/Translator.java +++ b/src/main/java/cuchaz/enigma/mapping/Translator.java @@ -11,332 +11,99 @@ package cuchaz.enigma.mapping; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import cuchaz.enigma.analysis.TranslationIndex; +import cuchaz.enigma.mapping.entry.*; +import org.objectweb.asm.Handle; +import org.objectweb.asm.Type; -import java.util.List; -import java.util.Map; +public interface Translator { + ClassEntry getTranslatedClass(ClassEntry entry); -public class Translator { + ClassDefEntry getTranslatedClassDef(ClassDefEntry entry); - private TranslationDirection direction; - private Map classes; - private TranslationIndex index; + FieldEntry getTranslatedField(FieldEntry entry); - private ClassNameReplacer classNameReplacer = className -> translateEntry(new ClassEntry(className)).getName(); + FieldDefEntry getTranslatedFieldDef(FieldDefEntry entry); - public Translator() { - this.direction = null; - this.classes = Maps.newHashMap(); - this.index = new TranslationIndex(); - } + MethodEntry getTranslatedMethod(MethodEntry entry); - public Translator(TranslationDirection direction, Map classes, TranslationIndex index) { - this.direction = direction; - this.classes = classes; - this.index = index; - } + MethodDefEntry getTranslatedMethodDef(MethodDefEntry entry); - public TranslationDirection getDirection() { - return direction; - } + LocalVariableEntry getTranslatedVariable(LocalVariableEntry entry); - public TranslationIndex getTranslationIndex() { - return index; - } + LocalVariableDefEntry getTranslatedVariableDef(LocalVariableDefEntry entry); - @SuppressWarnings("unchecked") - public T translateEntry(T entry) { - if (entry instanceof ClassEntry) { - return (T) translateEntry((ClassEntry) entry); - } else if (entry instanceof FieldEntry) { - return (T) translateEntry((FieldEntry) entry); - } else if (entry instanceof MethodEntry) { - return (T) translateEntry((MethodEntry) entry); - } else if (entry instanceof ConstructorEntry) { - return (T) translateEntry((ConstructorEntry) entry); - } else if (entry instanceof ArgumentEntry) { - return (T) translateEntry((ArgumentEntry) entry); - } else if (entry instanceof LocalVariableEntry) { - return (T) translateEntry((LocalVariableEntry) entry); - } else { - throw new Error("Unknown entry type: " + entry.getClass().getName()); - } - } + boolean hasClassMapping(ClassEntry entry); - public String translate(T entry) { - if (entry instanceof ClassEntry) { - return translate((ClassEntry) entry); - } else if (entry instanceof FieldEntry) { - return translate((FieldEntry) entry); - } else if (entry instanceof MethodEntry) { - return translate((MethodEntry) entry); - } else if (entry instanceof ConstructorEntry) { - return translate(entry); - } else if (entry instanceof ArgumentEntry) { - return translate((ArgumentEntry) entry); - } else if (entry instanceof LocalVariableEntry) { - return translate((LocalVariableEntry) entry); - } else { - throw new Error("Unknown entry type: " + entry.getClass().getName()); - } - } + boolean hasFieldMapping(FieldEntry entry); - public String translate(LocalVariableEntry in) { - LocalVariableEntry translated = translateEntry(in); - if (translated.equals(in)) { - return null; - } - return translated.getName(); - } + boolean hasMethodMapping(MethodEntry entry); - public LocalVariableEntry translateEntry(LocalVariableEntry in) { - // TODO: Implement it - return in; - } + boolean hasLocalVariableMapping(LocalVariableEntry entry); - public String translate(ClassEntry in) { - ClassEntry translated = translateEntry(in); - if (translated.equals(in)) { - return null; - } - return translated.getName(); - } + TypeDescriptor getTranslatedTypeDesc(TypeDescriptor desc); - public String translateClass(String className) { - return translate(new ClassEntry(className)); - } - - public ClassEntry translateEntry(ClassEntry in) { + MethodDescriptor getTranslatedMethodDesc(MethodDescriptor descriptor); - if (in.isInnerClass()) { + Signature getTranslatedSignature(Signature signature); - // translate as much of the class chain as we can - List mappingsChain = getClassMappingChain(in); - String[] obfClassNames = in.getName().split("\\$"); - StringBuilder buf = new StringBuilder(); - for (int i = 0; i < obfClassNames.length; i++) { - boolean isFirstClass = buf.length() == 0; - String className = null; - ClassMapping classMapping = mappingsChain.get(i); - if (classMapping != null) { - className = this.direction.choose( - classMapping.getDeobfName(), - isFirstClass ? classMapping.getObfFullName() : classMapping.getObfSimpleName() - ); - } - if (className == null) { - className = obfClassNames[i]; - } - if (!isFirstClass) { - buf.append("$"); - } - buf.append(className); + default Type getTranslatedType(Type type) { + String descString = type.getDescriptor(); + switch (type.getSort()) { + case Type.OBJECT: { + ClassEntry classEntry = new ClassEntry(type.getInternalName()); + return Type.getObjectType(getTranslatedClass(classEntry).getName()); } - return new ClassEntry(buf.toString()); - - } else { - - // normal classes are easy - ClassMapping classMapping = this.classes.get(in.getName()); - if (classMapping == null) { - return in; + case Type.ARRAY: { + TypeDescriptor descriptor = new TypeDescriptor(descString); + return Type.getType(getTranslatedTypeDesc(descriptor).toString()); } - return this.direction.choose( - classMapping.getDeobfName() != null ? new ClassEntry(classMapping.getDeobfName()) : in, - new ClassEntry(classMapping.getObfFullName()) - ); - } - } - - public String translate(FieldEntry in) { - - // resolve the class entry - ClassEntry resolvedClassEntry = this.index.resolveEntryClass(in); - if (resolvedClassEntry != null) { - - // look for the class - ClassMapping classMapping = findClassMapping(resolvedClassEntry); - if (classMapping != null) { - - // look for the field - String translatedName = this.direction.choose( - classMapping.getDeobfFieldName(in.getName(), in.getType()), - classMapping.getObfFieldName(in.getName(), translateType(in.getType())) - ); - if (translatedName != null) { - return translatedName; - } + case Type.METHOD: { + MethodDescriptor descriptor = new MethodDescriptor(descString); + return Type.getMethodType(getTranslatedMethodDesc(descriptor).toString()); } } - return null; + return type; } - public FieldEntry translateEntry(FieldEntry in) { - String name = translate(in); - if (name == null) { - name = in.getName(); - } - return new FieldEntry(translateEntry(in.getClassEntry()), name, translateType(in.getType())); + default Handle getTranslatedHandle(Handle handle) { + MethodEntry entry = new MethodEntry(new ClassEntry(handle.getOwner()), handle.getName(), new MethodDescriptor(handle.getDesc())); + MethodEntry translatedMethod = getTranslatedMethod(entry); + ClassEntry ownerClass = translatedMethod.getOwnerClassEntry(); + return new Handle(handle.getTag(), ownerClass.getName(), translatedMethod.getName(), translatedMethod.getDesc().toString(), handle.isInterface()); } - public String translate(MethodEntry in) { - // resolve the class entry - ClassEntry resolvedClassEntry = this.index.resolveEntryClass(in, true); - if (resolvedClassEntry != null) { - - // look for class - ClassMapping classMapping = findClassMapping(resolvedClassEntry); - if (classMapping != null) { - - // look for the method - MethodMapping methodMapping = this.direction.choose( - classMapping.getMethodByObf(in.getName(), in.getSignature()), - classMapping.getMethodByDeobf(in.getName(), translateSignature(in.getSignature())) - ); - if (methodMapping != null) { - return this.direction.choose(methodMapping.getDeobfName(), methodMapping.getObfName()); - } - } + default Object getTranslatedValue(Object value) { + if (value instanceof Type) { + return this.getTranslatedType((Type) value); + } else if (value instanceof Handle) { + return getTranslatedHandle((Handle) value); } - return null; + return value; } - public MethodEntry translateEntry(MethodEntry in) { - String name = translate(in); - if (name == null) { - name = in.getName(); - } - return new MethodEntry(translateEntry(in.getClassEntry()), name, translateSignature(in.getSignature())); - } - - public ConstructorEntry translateEntry(ConstructorEntry in) { - if (in.isStatic()) { - return new ConstructorEntry(translateEntry(in.getClassEntry())); - } else { - return new ConstructorEntry(translateEntry(in.getClassEntry()), translateSignature(in.getSignature())); - } - } - - public BehaviorEntry translateEntry(BehaviorEntry in) { - if (in instanceof MethodEntry) { - return translateEntry((MethodEntry) in); - } else if (in instanceof ConstructorEntry) { - return translateEntry((ConstructorEntry) in); - } - throw new Error("Wrong entry type!"); - } - - // TODO: support not identical behavior (specific to constructor) - public String translate(ArgumentEntry in) { - String classTranslate = translateArgument(in); - - // Not found in this class - if (classTranslate == null) { - List ancestry = this.index.getAncestry(in.getClassEntry()); - - // Check in mother class for the arg - for (ClassEntry entry : ancestry) { - ArgumentEntry motherArg = in.cloneToNewClass(entry); - if (this.index.entryExists(motherArg)) { - String result = translateArgument(motherArg); - if (result != null) - return result; - } - } - } - return classTranslate; - } - - public String translateArgument(ArgumentEntry in) { - // look for identical behavior in superclasses - ClassEntry entry = in.getClassEntry(); - - if (entry != null) { - // look for the class - ClassMapping classMapping = findClassMapping(entry); - if (classMapping != null) { - - // look for the method - MethodMapping methodMapping = this.direction.choose( - classMapping.getMethodByObf(in.getMethodName(), in.getMethodSignature()), - classMapping.getMethodByDeobf(in.getMethodName(), translateSignature(in.getMethodSignature())) - ); - if (methodMapping != null) { - return this.direction.choose( - methodMapping.getDeobfArgumentName(in.getIndex()), - methodMapping.getObfArgumentName(in.getIndex()) - ); - } - } - } - return null; - } - - public ArgumentEntry translateEntry(ArgumentEntry in) { - String name = translate(in); - if (name == null) { - name = in.getName(); - } - return new ArgumentEntry(translateEntry(in.getBehaviorEntry()), in.getIndex(), name); - } - - public Type translateType(Type type) { - return new Type(type, this.classNameReplacer); - } - - public Signature translateSignature(Signature signature) { - return new Signature(signature, this.classNameReplacer); - } - - private ClassMapping findClassMapping(ClassEntry in) { - List mappingChain = getClassMappingChain(in); - return mappingChain.get(mappingChain.size() - 1); - } - - private List getClassMappingChain(ClassEntry in) { - - // get a list of all the classes in the hierarchy - String[] parts = in.getName().split("\\$"); - List mappingsChain = Lists.newArrayList(); - - // get mappings for the outer class - ClassMapping outerClassMapping = this.classes.get(parts[0]); - mappingsChain.add(outerClassMapping); - - for (int i = 1; i < parts.length; i++) { - - // get mappings for the inner class - ClassMapping innerClassMapping = null; - if (outerClassMapping != null) { - innerClassMapping = this.direction.choose( - outerClassMapping.getInnerClassByObfSimple(parts[i]), - outerClassMapping.getInnerClassByDeobfThenObfSimple(parts[i]) - ); - } - mappingsChain.add(innerClassMapping); - outerClassMapping = innerClassMapping; - } - - assert (mappingsChain.size() == parts.length); - return mappingsChain; - } - - public Mappings.EntryModifier getModifier(Entry entry) { - ClassMapping classMapping = findClassMapping(entry.getClassEntry()); - if (classMapping != null && !entry.getName().equals("")) { - if (entry instanceof ClassEntry) - return classMapping.getModifier(); - else if (entry instanceof FieldEntry) { - FieldMapping fieldMapping = classMapping.getFieldByObf(entry.getName(), ((FieldEntry) entry).getType()); - return fieldMapping != null ? fieldMapping.getModifier() : Mappings.EntryModifier.UNCHANGED; - } else if (entry instanceof BehaviorEntry) { - MethodMapping methodMapping = classMapping.getMethodByObf(entry.getName(), ((BehaviorEntry) entry).getSignature()); - return methodMapping != null ? methodMapping.getModifier() : Mappings.EntryModifier.UNCHANGED; - } else - throw new Error("Unknown entry type: " + entry.getClass().getName()); + @SuppressWarnings("unchecked") + default T getTranslatedEntry(T entry) { + if (entry instanceof ClassDefEntry) { + return (T) getTranslatedClassDef((ClassDefEntry) entry); + } else if (entry instanceof ClassEntry) { + return (T) getTranslatedClass((ClassEntry) entry); + } else if (entry instanceof FieldDefEntry) { + return (T) getTranslatedFieldDef((FieldDefEntry) entry); + } else if (entry instanceof MethodDefEntry) { + return (T) getTranslatedMethodDef((MethodDefEntry) entry); + } else if (entry instanceof FieldEntry) { + return (T) getTranslatedField((FieldEntry) entry); + } else if (entry instanceof MethodEntry) { + return (T) getTranslatedMethod((MethodEntry) entry); + } else if (entry instanceof LocalVariableDefEntry) { + return (T) getTranslatedVariableDef((LocalVariableDefEntry) entry); + } else if (entry instanceof LocalVariableEntry) { + return (T) getTranslatedVariable((LocalVariableEntry) entry); + } else if (entry instanceof TypeDescriptor) { + return (T) getTranslatedTypeDesc((TypeDescriptor) entry); + } else if (entry instanceof MethodDescriptor) { + return (T) getTranslatedMethodDesc((MethodDescriptor) entry); } - return Mappings.EntryModifier.UNCHANGED; + throw new IllegalArgumentException("Cannot translate unknown entry type"); } } diff --git a/src/main/java/cuchaz/enigma/mapping/Type.java b/src/main/java/cuchaz/enigma/mapping/Type.java deleted file mode 100644 index 609bd64e..00000000 --- a/src/main/java/cuchaz/enigma/mapping/Type.java +++ /dev/null @@ -1,235 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.mapping; - -import com.google.common.collect.Maps; - -import java.util.Map; - -public class Type { - - protected String name; - - public Type(String name) { - - // don't deal with generics - // this is just for raw jvm types - if (name.charAt(0) == 'T' || name.indexOf('<') >= 0 || name.indexOf('>') >= 0) { - throw new IllegalArgumentException("don't use with generic types or templates: " + name); - } - - this.name = name; - } - - public Type(Type other, ClassNameReplacer replacer) { - this.name = other.name; - if (other.isClass()) { - String replacedName = replacer.replace(other.getClassEntry().getClassName()); - if (replacedName != null) { - this.name = "L" + replacedName + ";"; - } - } else if (other.isArray() && other.hasClass()) { - String replacedName = replacer.replace(other.getClassEntry().getClassName()); - if (replacedName != null) { - this.name = Type.getArrayPrefix(other.getArrayDimension()) + "L" + replacedName + ";"; - } - } - } - - public static String parseFirst(String in) { - - if (in == null || in.length() <= 0) { - throw new IllegalArgumentException("No type to parse, input is empty!"); - } - - // read one type from the input - - char c = in.charAt(0); - - // first check for void - if (c == 'V') { - return "V"; - } - - // then check for primitives - Primitive primitive = Primitive.get(c); - if (primitive != null) { - return in.substring(0, 1); - } - - // then check for classes - if (c == 'L') { - return readClass(in); - } - - // then check for templates - if (c == 'T') { - return readClass(in); - } - - // then check for arrays - int dim = countArrayDimension(in); - if (dim > 0) { - String arrayType = Type.parseFirst(in.substring(dim)); - return in.substring(0, dim + arrayType.length()); - } - - throw new IllegalArgumentException("don't know how to parse: " + in); - } - - private static String getArrayPrefix(int dimension) { - StringBuilder buf = new StringBuilder(); - for (int i = 0; i < dimension; i++) { - buf.append("["); - } - return buf.toString(); - } - - private static int countArrayDimension(String in) { - int i = 0; - while (i < in.length() && in.charAt(i) == '[') - i++; - return i; - } - - private static String readClass(String in) { - // read all the characters in the buffer until we hit a ';' - // include the parameters too - StringBuilder buf = new StringBuilder(); - int depth = 0; - for (int i = 0; i < in.length(); i++) { - char c = in.charAt(i); - buf.append(c); - - if (c == '<') { - depth++; - } else if (c == '>') { - depth--; - } else if (depth == 0 && c == ';') { - return buf.toString(); - } - } - return null; - } - - @Override - public String toString() { - return this.name; - } - - public boolean isVoid() { - return this.name.length() == 1 && this.name.charAt(0) == 'V'; - } - - public boolean isPrimitive() { - return this.name.length() == 1 && Primitive.get(this.name.charAt(0)) != null; - } - - public Primitive getPrimitive() { - if (!isPrimitive()) { - throw new IllegalStateException("not a primitive"); - } - return Primitive.get(this.name.charAt(0)); - } - - public boolean isClass() { - return this.name.charAt(0) == 'L' && this.name.charAt(this.name.length() - 1) == ';'; - } - - public ClassEntry getClassEntry() { - if (isClass()) { - String name = this.name.substring(1, this.name.length() - 1); - - int pos = name.indexOf('<'); - if (pos >= 0) { - // remove the parameters from the class name - name = name.substring(0, pos); - } - - return new ClassEntry(name); - - } else if (isArray() && getArrayType().isClass()) { - return getArrayType().getClassEntry(); - } else { - throw new IllegalStateException("type doesn't have a class"); - } - } - - public boolean isArray() { - return this.name.charAt(0) == '['; - } - - public int getArrayDimension() { - if (!isArray()) { - throw new IllegalStateException("not an array"); - } - return countArrayDimension(this.name); - } - - public Type getArrayType() { - if (!isArray()) { - throw new IllegalStateException("not an array"); - } - return new Type(this.name.substring(getArrayDimension(), this.name.length())); - } - - public boolean hasClass() { - return isClass() || (isArray() && getArrayType().hasClass()); - } - - @Override - public boolean equals(Object other) { - return other instanceof Type && equals((Type) other); - } - - public boolean equals(Type other) { - return this.name.equals(other.name); - } - - public int hashCode() { - return this.name.hashCode(); - } - - public enum Primitive { - Byte('B'), - Character('C'), - Short('S'), - Integer('I'), - Long('J'), - Float('F'), - Double('D'), - Boolean('Z'); - - private static final Map lookup; - - static { - lookup = Maps.newTreeMap(); - for (Primitive val : values()) { - lookup.put(val.getCode(), val); - } - } - - private char code; - - Primitive(char code) { - this.code = code; - } - - public static Primitive get(char code) { - return lookup.get(code); - } - - public char getCode() { - return this.code; - } - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/TypeDescriptor.java b/src/main/java/cuchaz/enigma/mapping/TypeDescriptor.java new file mode 100644 index 00000000..b7b1255a --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/TypeDescriptor.java @@ -0,0 +1,244 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.mapping; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; +import cuchaz.enigma.mapping.entry.ClassEntry; + +import java.util.Map; +import java.util.function.Function; + +public class TypeDescriptor { + + protected final String desc; + + public TypeDescriptor(String desc) { + Preconditions.checkNotNull(desc, "Desc cannot be null"); + + // don't deal with generics + // this is just for raw jvm types + if (desc.charAt(0) == 'T' || desc.indexOf('<') >= 0 || desc.indexOf('>') >= 0) { + throw new IllegalArgumentException("don't use with generic types or templates: " + desc); + } + + this.desc = desc; + } + + public static String parseFirst(String in) { + + if (in == null || in.length() <= 0) { + throw new IllegalArgumentException("No desc to parse, input is empty!"); + } + + // read one desc from the input + + char c = in.charAt(0); + + // first check for void + if (c == 'V') { + return "V"; + } + + // then check for primitives + Primitive primitive = Primitive.get(c); + if (primitive != null) { + return in.substring(0, 1); + } + + // then check for classes + if (c == 'L') { + return readClass(in); + } + + // then check for templates + if (c == 'T') { + return readClass(in); + } + + // then check for arrays + int dim = countArrayDimension(in); + if (dim > 0) { + String arrayType = TypeDescriptor.parseFirst(in.substring(dim)); + return in.substring(0, dim + arrayType.length()); + } + + throw new IllegalArgumentException("don't know how to parse: " + in); + } + + private static int countArrayDimension(String in) { + int i = 0; + while (i < in.length() && in.charAt(i) == '[') + i++; + return i; + } + + private static String readClass(String in) { + // read all the characters in the buffer until we hit a ';' + // include the parameters too + StringBuilder buf = new StringBuilder(); + int depth = 0; + for (int i = 0; i < in.length(); i++) { + char c = in.charAt(i); + buf.append(c); + + if (c == '<') { + depth++; + } else if (c == '>') { + depth--; + } else if (depth == 0 && c == ';') { + return buf.toString(); + } + } + return null; + } + + public static TypeDescriptor of(String name) { + return new TypeDescriptor("L" + name + ";"); + } + + @Override + public String toString() { + return this.desc; + } + + public boolean isVoid() { + return this.desc.length() == 1 && this.desc.charAt(0) == 'V'; + } + + public boolean isPrimitive() { + return this.desc.length() == 1 && Primitive.get(this.desc.charAt(0)) != null; + } + + public Primitive getPrimitive() { + if (!isPrimitive()) { + throw new IllegalStateException("not a primitive"); + } + return Primitive.get(this.desc.charAt(0)); + } + + public boolean isType() { + return this.desc.charAt(0) == 'L' && this.desc.charAt(this.desc.length() - 1) == ';'; + } + + public ClassEntry getTypeEntry() { + if (isType()) { + String name = this.desc.substring(1, this.desc.length() - 1); + + int pos = name.indexOf('<'); + if (pos >= 0) { + // remove the parameters from the class name + name = name.substring(0, pos); + } + + return new ClassEntry(name); + + } else if (isArray() && getArrayType().isType()) { + return getArrayType().getTypeEntry(); + } else { + throw new IllegalStateException("desc doesn't have a class"); + } + } + + public boolean isArray() { + return this.desc.charAt(0) == '['; + } + + public int getArrayDimension() { + if (!isArray()) { + throw new IllegalStateException("not an array"); + } + return countArrayDimension(this.desc); + } + + public TypeDescriptor getArrayType() { + if (!isArray()) { + throw new IllegalStateException("not an array"); + } + return new TypeDescriptor(this.desc.substring(getArrayDimension(), this.desc.length())); + } + + public boolean containsType() { + return isType() || (isArray() && getArrayType().containsType()); + } + + @Override + public boolean equals(Object other) { + return other instanceof TypeDescriptor && equals((TypeDescriptor) other); + } + + public boolean equals(TypeDescriptor other) { + return this.desc.equals(other.desc); + } + + @Override + public int hashCode() { + return this.desc.hashCode(); + } + + public TypeDescriptor remap(Function remapper) { + String desc = this.desc; + if (isType() || (isArray() && containsType())) { + String replacedName = remapper.apply(this.getTypeEntry().getName()); + if (replacedName != null) { + if (this.isType()) { + desc = "L" + replacedName + ";"; + } else { + desc = getArrayPrefix(this.getArrayDimension()) + "L" + replacedName + ";"; + } + } + } + return new TypeDescriptor(desc); + } + + private static String getArrayPrefix(int dimension) { + StringBuilder buf = new StringBuilder(); + for (int i = 0; i < dimension; i++) { + buf.append("["); + } + return buf.toString(); + } + + public enum Primitive { + Byte('B'), + Character('C'), + Short('S'), + Integer('I'), + Long('J'), + Float('F'), + Double('D'), + Boolean('Z'); + + private static final Map lookup; + + static { + lookup = Maps.newTreeMap(); + for (Primitive val : values()) { + lookup.put(val.getCode(), val); + } + } + + private char code; + + Primitive(char code) { + this.code = code; + } + + public static Primitive get(char code) { + return lookup.get(code); + } + + public char getCode() { + return this.code; + } + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/entry/ClassDefEntry.java b/src/main/java/cuchaz/enigma/mapping/entry/ClassDefEntry.java new file mode 100644 index 00000000..ac1fe2ab --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/entry/ClassDefEntry.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.mapping.entry; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.bytecode.AccessFlags; +import cuchaz.enigma.mapping.Signature; + +public class ClassDefEntry extends ClassEntry { + private final AccessFlags access; + private final Signature signature; + + public ClassDefEntry(String className, Signature signature, AccessFlags access) { + super(className); + Preconditions.checkNotNull(signature, "Class signature cannot be null"); + Preconditions.checkNotNull(access, "Class access cannot be null"); + this.signature = signature; + this.access = access; + } + + public Signature getSignature() { + return signature; + } + + public AccessFlags getAccess() { + return access; + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/entry/ClassEntry.java b/src/main/java/cuchaz/enigma/mapping/entry/ClassEntry.java new file mode 100644 index 00000000..c7958256 --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/entry/ClassEntry.java @@ -0,0 +1,175 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.mapping.entry; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; + +import java.util.List; + +public class ClassEntry implements Entry { + + private final String name; + + public ClassEntry(String className) { + Preconditions.checkNotNull(className, "Class name cannot be null"); + + if (className.indexOf('.') >= 0) { + throw new IllegalArgumentException("Class name must be in JVM format. ie, path/to/package/class$inner : " + className); + } + + this.name = className; + + if (isInnerClass() && getInnermostClassName().indexOf('/') >= 0) { + throw new IllegalArgumentException("Inner class must not have a package: " + className); + } + } + + public ClassEntry(ClassEntry other) { + this.name = other.name; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public String getClassName() { + return this.name; + } + + @Override + public ClassEntry getOwnerClassEntry() { + return this; + } + + @Override + public ClassEntry updateOwnership(ClassEntry classEntry) { + return classEntry; + } + + @Override + public int hashCode() { + return this.name.hashCode(); + } + + @Override + public boolean equals(Object other) { + return other instanceof ClassEntry && equals((ClassEntry) other); + } + + public boolean equals(ClassEntry other) { + return other != null && this.name.equals(other.name); + } + + @Override + public String toString() { + return this.name; + } + + public boolean isArray() { + return this.name.lastIndexOf('[') >= 0; + } + + public boolean isInnerClass() { + return this.name.lastIndexOf('$') >= 0; + } + + public List getClassChainNames() { + return Lists.newArrayList(this.name.split("\\$")); + } + + public List getClassChain() { + List entries = Lists.newArrayList(); + StringBuilder buf = new StringBuilder(); + for (String name : getClassChainNames()) { + if (buf.length() > 0) { + buf.append("$"); + } + buf.append(name); + entries.add(new ClassEntry(buf.toString())); + } + return entries; + } + + public String getOutermostClassName() { + if (isInnerClass()) { + return this.name.substring(0, this.name.indexOf('$')); + } + return this.name; + } + + public ClassEntry getOutermostClassEntry() { + return new ClassEntry(getOutermostClassName()); + } + + public String getOuterClassName() { + if (!isInnerClass()) { + throw new Error("This is not an inner class!"); + } + return this.name.substring(0, this.name.lastIndexOf('$')); + } + + public ClassEntry getOuterClassEntry() { + return new ClassEntry(getOuterClassName()); + } + + public String getInnermostClassName() { + if (!isInnerClass()) { + throw new Error("This is not an inner class!"); + } + return this.name.substring(this.name.lastIndexOf('$') + 1); + } + + public boolean isInDefaultPackage() { + return this.name.indexOf('/') < 0; + } + + public String getPackageName() { + return getPackageName(this.name); + } + + public String getSimpleName() { + int pos = this.name.lastIndexOf('/'); + if (pos > 0) { + return this.name.substring(pos + 1); + } + return this.name; + } + + public static String getPackageName(String name) { + int pos = name.lastIndexOf('/'); + if (pos > 0) { + return name.substring(0, pos); + } + return null; + } + + public ClassEntry buildClassEntry(List classChain) { + assert (classChain.contains(this)); + StringBuilder buf = new StringBuilder(); + for (ClassEntry chainEntry : classChain) { + if (buf.length() == 0) { + buf.append(chainEntry.getName()); + } else { + buf.append("$"); + buf.append(chainEntry.isInnerClass() ? chainEntry.getInnermostClassName() : chainEntry.getSimpleName()); + } + + if (chainEntry == this) { + break; + } + } + return new ClassEntry(buf.toString()); + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/entry/Entry.java b/src/main/java/cuchaz/enigma/mapping/entry/Entry.java new file mode 100644 index 00000000..b612140f --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/entry/Entry.java @@ -0,0 +1,22 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.mapping.entry; + +public interface Entry { + String getName(); + + String getClassName(); + + ClassEntry getOwnerClassEntry(); + + Entry updateOwnership(ClassEntry classEntry); +} diff --git a/src/main/java/cuchaz/enigma/mapping/entry/EntryFactory.java b/src/main/java/cuchaz/enigma/mapping/entry/EntryFactory.java new file mode 100644 index 00000000..5bd159f4 --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/entry/EntryFactory.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.mapping.entry; + +import cuchaz.enigma.analysis.JarIndex; +import cuchaz.enigma.mapping.ClassMapping; +import cuchaz.enigma.mapping.FieldMapping; +import cuchaz.enigma.mapping.MethodDescriptor; +import cuchaz.enigma.mapping.MethodMapping; + +public class EntryFactory { + public static ClassEntry getObfClassEntry(JarIndex jarIndex, ClassMapping classMapping) { + ClassEntry obfClassEntry = new ClassEntry(classMapping.getObfFullName()); + return obfClassEntry.buildClassEntry(jarIndex.getObfClassChain(obfClassEntry)); + } + + private static ClassEntry getObfClassEntry(ClassMapping classMapping) { + return new ClassEntry(classMapping.getObfFullName()); + } + + public static ClassEntry getDeobfClassEntry(ClassMapping classMapping) { + return new ClassEntry(classMapping.getDeobfName()); + } + + public static FieldEntry getObfFieldEntry(ClassMapping classMapping, FieldMapping fieldMapping) { + return new FieldEntry(getObfClassEntry(classMapping), fieldMapping.getObfName(), fieldMapping.getObfDesc()); + } + + public static MethodEntry getMethodEntry(ClassEntry classEntry, String name, MethodDescriptor desc) { + return new MethodEntry(classEntry, name, desc); + } + + public static MethodEntry getObfMethodEntry(ClassEntry classEntry, MethodMapping methodMapping) { + return getMethodEntry(classEntry, methodMapping.getObfName(), methodMapping.getObfDesc()); + } + + public static MethodEntry getObfMethodEntry(ClassMapping classMapping, MethodMapping methodMapping) { + return getObfMethodEntry(getObfClassEntry(classMapping), methodMapping); + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/entry/FieldDefEntry.java b/src/main/java/cuchaz/enigma/mapping/entry/FieldDefEntry.java new file mode 100644 index 00000000..d18115bf --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/entry/FieldDefEntry.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.mapping.entry; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.bytecode.AccessFlags; +import cuchaz.enigma.mapping.Signature; +import cuchaz.enigma.mapping.TypeDescriptor; + +public class FieldDefEntry extends FieldEntry { + private final AccessFlags access; + private final Signature signature; + + public FieldDefEntry(ClassEntry ownerEntry, String name, TypeDescriptor desc, Signature signature, AccessFlags access) { + super(ownerEntry, name, desc); + Preconditions.checkNotNull(access, "Field access cannot be null"); + Preconditions.checkNotNull(signature, "Field signature cannot be null"); + this.access = access; + this.signature = signature; + } + + public AccessFlags getAccess() { + return access; + } + + public Signature getSignature() { + return signature; + } + + @Override + public FieldDefEntry updateOwnership(ClassEntry owner) { + return new FieldDefEntry(owner, this.name, this.desc, signature, access); + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/entry/FieldEntry.java b/src/main/java/cuchaz/enigma/mapping/entry/FieldEntry.java new file mode 100644 index 00000000..b6e1554d --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/entry/FieldEntry.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.mapping.entry; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.mapping.TypeDescriptor; +import cuchaz.enigma.utils.Utils; + +public class FieldEntry implements Entry { + + protected final ClassEntry ownerEntry; + protected final String name; + protected final TypeDescriptor desc; + + // NOTE: this argument order is important for the MethodReader/MethodWriter + public FieldEntry(ClassEntry ownerEntry, String name, TypeDescriptor desc) { + Preconditions.checkNotNull(ownerEntry, "Owner cannot be null"); + Preconditions.checkNotNull(name, "Field name cannot be null"); + Preconditions.checkNotNull(desc, "Field descriptor cannot be null"); + + this.ownerEntry = ownerEntry; + this.name = name; + this.desc = desc; + } + + @Override + public ClassEntry getOwnerClassEntry() { + return this.ownerEntry; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public String getClassName() { + return this.ownerEntry.getName(); + } + + public TypeDescriptor getDesc() { + return this.desc; + } + + @Override + public FieldEntry updateOwnership(ClassEntry owner) { + return new FieldEntry(owner, this.name, this.desc); + } + + @Override + public int hashCode() { + return Utils.combineHashesOrdered(this.ownerEntry, this.name, this.desc); + } + + @Override + public boolean equals(Object other) { + return other instanceof FieldEntry && equals((FieldEntry) other); + } + + public boolean equals(FieldEntry other) { + return this.ownerEntry.equals(other.ownerEntry) && this.name.equals(other.name) && this.desc.equals(other.desc); + } + + @Override + public String toString() { + return this.ownerEntry.getName() + "." + this.name + ":" + this.desc; + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/entry/LocalVariableDefEntry.java b/src/main/java/cuchaz/enigma/mapping/entry/LocalVariableDefEntry.java new file mode 100644 index 00000000..77422720 --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/entry/LocalVariableDefEntry.java @@ -0,0 +1,57 @@ +package cuchaz.enigma.mapping.entry; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.mapping.TypeDescriptor; +import cuchaz.enigma.utils.Utils; + +/** + * TypeDescriptor... + * Created by Thog + * 19/10/2016 + */ +public class LocalVariableDefEntry extends LocalVariableEntry { + + protected final MethodDefEntry ownerEntry; + protected final TypeDescriptor desc; + + public LocalVariableDefEntry(MethodDefEntry ownerEntry, int index, String name, TypeDescriptor desc) { + super(ownerEntry, index, name); + Preconditions.checkNotNull(desc, "Variable desc cannot be null"); + + this.ownerEntry = ownerEntry; + this.desc = desc; + } + + @Override + public MethodDefEntry getOwnerEntry() { + return this.ownerEntry; + } + + public TypeDescriptor getDesc() { + return desc; + } + + @Override + public LocalVariableDefEntry updateOwnership(ClassEntry classEntry) { + return new LocalVariableDefEntry(ownerEntry.updateOwnership(classEntry), index, name, desc); + } + + @Override + public int hashCode() { + return Utils.combineHashesOrdered(this.ownerEntry, this.desc.hashCode(), this.name.hashCode(), Integer.hashCode(this.index)); + } + + @Override + public boolean equals(Object other) { + return other instanceof LocalVariableDefEntry && equals((LocalVariableDefEntry) other); + } + + public boolean equals(LocalVariableDefEntry other) { + return this.ownerEntry.equals(other.ownerEntry) && this.desc.equals(other.desc) && this.name.equals(other.name) && this.index == other.index; + } + + @Override + public String toString() { + return this.ownerEntry + "(" + this.index + ":" + this.name + ":" + this.desc + ")"; + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/entry/LocalVariableEntry.java b/src/main/java/cuchaz/enigma/mapping/entry/LocalVariableEntry.java new file mode 100644 index 00000000..a794d0a0 --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/entry/LocalVariableEntry.java @@ -0,0 +1,82 @@ +package cuchaz.enigma.mapping.entry; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.mapping.MethodDescriptor; +import cuchaz.enigma.utils.Utils; + +/** + * TypeDescriptor... + * Created by Thog + * 19/10/2016 + */ +public class LocalVariableEntry implements Entry { + + protected final MethodEntry ownerEntry; + protected final String name; + protected final int index; + + public LocalVariableEntry(MethodEntry ownerEntry, int index, String name) { + Preconditions.checkNotNull(ownerEntry, "Variable owner cannot be null"); + Preconditions.checkNotNull(name, "Variable name cannot be null"); + Preconditions.checkArgument(index >= 0, "Index must be positive"); + + this.ownerEntry = ownerEntry; + this.name = name; + this.index = index; + } + + public MethodEntry getOwnerEntry() { + return this.ownerEntry; + } + + public int getIndex() { + return index; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public ClassEntry getOwnerClassEntry() { + return this.ownerEntry.getOwnerClassEntry(); + } + + @Override + public String getClassName() { + return this.ownerEntry.getClassName(); + } + + @Override + public LocalVariableEntry updateOwnership(ClassEntry classEntry) { + return new LocalVariableEntry(ownerEntry.updateOwnership(classEntry), index, name); + } + + public String getMethodName() { + return this.ownerEntry.getName(); + } + + public MethodDescriptor getMethodDesc() { + return this.ownerEntry.getDesc(); + } + + @Override + public int hashCode() { + return Utils.combineHashesOrdered(this.ownerEntry, this.name.hashCode(), Integer.hashCode(this.index)); + } + + @Override + public boolean equals(Object other) { + return other instanceof LocalVariableEntry && equals((LocalVariableEntry) other); + } + + public boolean equals(LocalVariableEntry other) { + return this.ownerEntry.equals(other.ownerEntry) && this.name.equals(other.name) && this.index == other.index; + } + + @Override + public String toString() { + return this.ownerEntry + "(" + this.index + ":" + this.name + ")"; + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/entry/MethodDefEntry.java b/src/main/java/cuchaz/enigma/mapping/entry/MethodDefEntry.java new file mode 100644 index 00000000..bb7c85eb --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/entry/MethodDefEntry.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.mapping.entry; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.bytecode.AccessFlags; +import cuchaz.enigma.mapping.MethodDescriptor; +import cuchaz.enigma.mapping.Signature; + +public class MethodDefEntry extends MethodEntry { + + private final AccessFlags access; + private final Signature signature; + + public MethodDefEntry(ClassEntry classEntry, String name, MethodDescriptor descriptor, Signature signature, AccessFlags access) { + super(classEntry, name, descriptor); + Preconditions.checkNotNull(access, "Method access cannot be null"); + Preconditions.checkNotNull(signature, "Method signature cannot be null"); + this.access = access; + this.signature = signature; + } + + public AccessFlags getAccess() { + return access; + } + + public Signature getSignature() { + return signature; + } + + public int getVariableOffset(ClassDefEntry ownerEntry) { + // Enum constructors have an implicit "name" and "ordinal" parameter as well as "this" + if (ownerEntry.getAccess().isEnum() && getName().startsWith("<")) { + return 3; + } else { + // If we're not static, "this" is bound to index 0 + return getAccess().isStatic() ? 0 : 1; + } + } + + @Override + public MethodDefEntry updateOwnership(ClassEntry classEntry) { + return new MethodDefEntry(new ClassEntry(classEntry.getName()), name, descriptor, signature, access); + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/entry/MethodEntry.java b/src/main/java/cuchaz/enigma/mapping/entry/MethodEntry.java new file mode 100644 index 00000000..1abc5b12 --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/entry/MethodEntry.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.mapping.entry; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.mapping.MethodDescriptor; +import cuchaz.enigma.utils.Utils; + +public class MethodEntry implements Entry { + + protected final ClassEntry classEntry; + protected final String name; + protected final MethodDescriptor descriptor; + + public MethodEntry(ClassEntry classEntry, String name, MethodDescriptor descriptor) { + Preconditions.checkNotNull(classEntry, "Class cannot be null"); + Preconditions.checkNotNull(name, "Method name cannot be null"); + Preconditions.checkNotNull(descriptor, "Method descriptor cannot be null"); + + this.classEntry = classEntry; + this.name = name; + this.descriptor = descriptor; + } + + @Override + public ClassEntry getOwnerClassEntry() { + return this.classEntry; + } + + @Override + public String getName() { + return this.name; + } + + public MethodDescriptor getDesc() { + return this.descriptor; + } + + public boolean isConstructor() { + return name.equals("") || name.equals(""); + } + + @Override + public String getClassName() { + return this.classEntry.getName(); + } + + @Override + public MethodEntry updateOwnership(ClassEntry classEntry) { + return new MethodEntry(new ClassEntry(classEntry.getName()), name, descriptor); + } + + @Override + public int hashCode() { + return Utils.combineHashesOrdered(this.classEntry, this.name, this.descriptor); + } + + @Override + public boolean equals(Object other) { + return other instanceof MethodEntry && equals((MethodEntry) other); + } + + public boolean equals(MethodEntry other) { + return this.classEntry.equals(other.getOwnerClassEntry()) && this.name.equals(other.getName()) && this.descriptor.equals(other.getDesc()); + } + + @Override + public String toString() { + return this.classEntry.getName() + "." + this.name + this.descriptor; + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/entry/ProcyonEntryFactory.java b/src/main/java/cuchaz/enigma/mapping/entry/ProcyonEntryFactory.java new file mode 100644 index 00000000..73770c53 --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/entry/ProcyonEntryFactory.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.mapping.entry; + +import com.strobel.assembler.metadata.FieldDefinition; +import com.strobel.assembler.metadata.MemberReference; +import com.strobel.assembler.metadata.MethodDefinition; +import cuchaz.enigma.bytecode.AccessFlags; +import cuchaz.enigma.mapping.MethodDescriptor; +import cuchaz.enigma.mapping.Signature; +import cuchaz.enigma.mapping.TypeDescriptor; + +public class ProcyonEntryFactory { + private final ReferencedEntryPool entryPool; + + public ProcyonEntryFactory(ReferencedEntryPool entryPool) { + this.entryPool = entryPool; + } + + public FieldEntry getFieldEntry(MemberReference def) { + ClassEntry classEntry = entryPool.getClass(def.getDeclaringType().getInternalName()); + return entryPool.getField(classEntry, def.getName(), def.getErasedSignature()); + } + + public FieldDefEntry getFieldDefEntry(FieldDefinition def) { + ClassEntry classEntry = entryPool.getClass(def.getDeclaringType().getInternalName()); + return new FieldDefEntry(classEntry, def.getName(), new TypeDescriptor(def.getErasedSignature()), Signature.createTypedSignature(def.getSignature()), new AccessFlags(def.getModifiers())); + } + + public MethodEntry getMethodEntry(MemberReference def) { + ClassEntry classEntry = entryPool.getClass(def.getDeclaringType().getInternalName()); + return entryPool.getMethod(classEntry, def.getName(), def.getErasedSignature()); + } + + public MethodDefEntry getMethodDefEntry(MethodDefinition def) { + ClassEntry classEntry = entryPool.getClass(def.getDeclaringType().getInternalName()); + return new MethodDefEntry(classEntry, def.getName(), new MethodDescriptor(def.getErasedSignature()), Signature.createSignature(def.getSignature()), new AccessFlags(def.getModifiers())); + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/entry/ReferencedEntryPool.java b/src/main/java/cuchaz/enigma/mapping/entry/ReferencedEntryPool.java new file mode 100644 index 00000000..338d209e --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/entry/ReferencedEntryPool.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.mapping.entry; + +import cuchaz.enigma.mapping.MethodDescriptor; +import cuchaz.enigma.mapping.TypeDescriptor; + +import java.util.HashMap; +import java.util.Map; + +public class ReferencedEntryPool { + private final Map classEntries = new HashMap<>(); + private final Map> methodEntries = new HashMap<>(); + private final Map> fieldEntries = new HashMap<>(); + + public ClassEntry getClass(String name) { + return this.classEntries.computeIfAbsent(name, s -> new ClassEntry(name)); + } + + public MethodEntry getMethod(ClassEntry ownerEntry, String name, String desc) { + return getMethod(ownerEntry, name, new MethodDescriptor(desc)); + } + + public MethodEntry getMethod(ClassEntry ownerEntry, String name, MethodDescriptor desc) { + String key = name + desc.toString(); + return getClassMethods(ownerEntry.getName()).computeIfAbsent(key, s -> new MethodEntry(ownerEntry, name, desc)); + } + + public FieldEntry getField(ClassEntry ownerEntry, String name, String desc) { + return getField(ownerEntry, name, new TypeDescriptor(desc)); + } + + public FieldEntry getField(ClassEntry ownerEntry, String name, TypeDescriptor desc) { + return getClassFields(ownerEntry.getName()).computeIfAbsent(name, s -> new FieldEntry(ownerEntry, name, desc)); + } + + private Map getClassMethods(String name) { + return methodEntries.computeIfAbsent(name, s -> new HashMap<>()); + } + + private Map getClassFields(String name) { + return fieldEntries.computeIfAbsent(name, s -> new HashMap<>()); + } +} diff --git a/src/main/java/cuchaz/enigma/throwables/MappingParseException.java b/src/main/java/cuchaz/enigma/throwables/MappingParseException.java index cc5f650a..b7e6d426 100644 --- a/src/main/java/cuchaz/enigma/throwables/MappingParseException.java +++ b/src/main/java/cuchaz/enigma/throwables/MappingParseException.java @@ -12,6 +12,7 @@ package cuchaz.enigma.throwables; import java.io.File; +import java.util.function.Supplier; public class MappingParseException extends Exception { @@ -25,6 +26,12 @@ public class MappingParseException extends Exception { filePath = file.getAbsolutePath(); } + public MappingParseException(Supplier filenameProvider, int line, String message) { + this.line = line; + this.message = message; + filePath = filenameProvider.get(); + } + @Override public String getMessage() { return "Line " + line + ": " + message + " in file " + filePath; diff --git a/src/main/java/oml/ast/transformers/ObfuscatedEnumSwitchRewriterTransform.java b/src/main/java/oml/ast/transformers/ObfuscatedEnumSwitchRewriterTransform.java new file mode 100644 index 00000000..6005b7f7 --- /dev/null +++ b/src/main/java/oml/ast/transformers/ObfuscatedEnumSwitchRewriterTransform.java @@ -0,0 +1,414 @@ +/* + * Originally: + * EnumSwitchRewriterTransform.java + * + * Copyright (c) 2013 Mike Strobel + * + * This source code is based on Mono.Cecil from Jb Evain, Copyright (c) Jb Evain; + * and ILSpy/ICSharpCode from SharpDevelop, Copyright (c) AlphaSierraPapa. + * + * This source code is subject to terms and conditions of the Apache License, Version 2.0. + * A copy of the license can be found in the License.html file at the root of this distribution. + * By using this source code in any fashion, you are agreeing to be bound by the terms of the + * Apache License, Version 2.0. + * + * You must not remove this notice, or any other, from this software. + */ + +package oml.ast.transformers; + +import com.strobel.assembler.metadata.BuiltinTypes; +import com.strobel.assembler.metadata.FieldDefinition; +import com.strobel.assembler.metadata.MethodDefinition; +import com.strobel.assembler.metadata.TypeDefinition; +import com.strobel.assembler.metadata.TypeReference; +import com.strobel.core.SafeCloseable; +import com.strobel.core.VerifyArgument; +import com.strobel.decompiler.DecompilerContext; +import com.strobel.decompiler.languages.java.ast.AssignmentExpression; +import com.strobel.decompiler.languages.java.ast.AstBuilder; +import com.strobel.decompiler.languages.java.ast.AstNode; +import com.strobel.decompiler.languages.java.ast.CaseLabel; +import com.strobel.decompiler.languages.java.ast.ContextTrackingVisitor; +import com.strobel.decompiler.languages.java.ast.Expression; +import com.strobel.decompiler.languages.java.ast.IdentifierExpression; +import com.strobel.decompiler.languages.java.ast.IndexerExpression; +import com.strobel.decompiler.languages.java.ast.InvocationExpression; +import com.strobel.decompiler.languages.java.ast.Keys; +import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression; +import com.strobel.decompiler.languages.java.ast.PrimitiveExpression; +import com.strobel.decompiler.languages.java.ast.SwitchSection; +import com.strobel.decompiler.languages.java.ast.SwitchStatement; +import com.strobel.decompiler.languages.java.ast.TypeDeclaration; +import com.strobel.decompiler.languages.java.ast.TypeReferenceExpression; +import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; + +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Copy of {@link com.strobel.decompiler.languages.java.ast.transforms.EnumSwitchRewriterTransform} modified to: + * - Not rely on a field containing "$SwitchMap$" (Proguard strips it) + * - Ignore classes *with* SwitchMap$ names (so the original can handle it) + * - Ignores inner synthetics that are not package private + */ +@SuppressWarnings("Duplicates") +public class ObfuscatedEnumSwitchRewriterTransform implements IAstTransform { + private final DecompilerContext _context; + + public ObfuscatedEnumSwitchRewriterTransform(final DecompilerContext context) { + _context = VerifyArgument.notNull(context, "context"); + } + + @Override + public void run(final AstNode compilationUnit) { + compilationUnit.acceptVisitor(new Visitor(_context), null); + } + + private final static class Visitor extends ContextTrackingVisitor { + private final static class SwitchMapInfo { + final String enclosingType; + final Map> switches = new LinkedHashMap<>(); + final Map> mappings = new LinkedHashMap<>(); + + TypeDeclaration enclosingTypeDeclaration; + + SwitchMapInfo(final String enclosingType) { + this.enclosingType = enclosingType; + } + } + + private final Map _switchMaps = new LinkedHashMap<>(); + private boolean _isSwitchMapWrapper; + + protected Visitor(final DecompilerContext context) { + super(context); + } + + @Override + public Void visitTypeDeclaration(final TypeDeclaration typeDeclaration, final Void p) { + final boolean oldIsSwitchMapWrapper = _isSwitchMapWrapper; + final TypeDefinition typeDefinition = typeDeclaration.getUserData(Keys.TYPE_DEFINITION); + final boolean isSwitchMapWrapper = isSwitchMapWrapper(typeDefinition); + + if (isSwitchMapWrapper) { + final String internalName = typeDefinition.getInternalName(); + + SwitchMapInfo info = _switchMaps.get(internalName); + + if (info == null) { + _switchMaps.put(internalName, info = new SwitchMapInfo(internalName)); + } + + info.enclosingTypeDeclaration = typeDeclaration; + } + + _isSwitchMapWrapper = isSwitchMapWrapper; + + try { + super.visitTypeDeclaration(typeDeclaration, p); + } + finally { + _isSwitchMapWrapper = oldIsSwitchMapWrapper; + } + + rewrite(); + + return null; + } + + @Override + public Void visitSwitchStatement(final SwitchStatement node, final Void data) { + final Expression test = node.getExpression(); + + if (test instanceof IndexerExpression) { + final IndexerExpression indexer = (IndexerExpression) test; + final Expression array = indexer.getTarget(); + final Expression argument = indexer.getArgument(); + + if (!(array instanceof MemberReferenceExpression)) { + return super.visitSwitchStatement(node, data); + } + + final MemberReferenceExpression arrayAccess = (MemberReferenceExpression) array; + final Expression arrayOwner = arrayAccess.getTarget(); + final String mapName = arrayAccess.getMemberName(); + + if (mapName == null || mapName.startsWith("$SwitchMap$") || !(arrayOwner instanceof TypeReferenceExpression)) { + return super.visitSwitchStatement(node, data); + } + + final TypeReferenceExpression enclosingTypeExpression = (TypeReferenceExpression) arrayOwner; + final TypeReference enclosingType = enclosingTypeExpression.getType().getUserData(Keys.TYPE_REFERENCE); + + if (!isSwitchMapWrapper(enclosingType) || !(argument instanceof InvocationExpression)) { + return super.visitSwitchStatement(node, data); + } + + final InvocationExpression invocation = (InvocationExpression) argument; + final Expression invocationTarget = invocation.getTarget(); + + if (!(invocationTarget instanceof MemberReferenceExpression)) { + return super.visitSwitchStatement(node, data); + } + + final MemberReferenceExpression memberReference = (MemberReferenceExpression) invocationTarget; + + if (!"ordinal".equals(memberReference.getMemberName())) { + return super.visitSwitchStatement(node, data); + } + + final String enclosingTypeName = enclosingType.getInternalName(); + + SwitchMapInfo info = _switchMaps.get(enclosingTypeName); + + if (info == null) { + _switchMaps.put(enclosingTypeName, info = new SwitchMapInfo(enclosingTypeName)); + + final TypeDefinition resolvedType = enclosingType.resolve(); + + if (resolvedType != null) { + AstBuilder astBuilder = context.getUserData(Keys.AST_BUILDER); + + if (astBuilder == null) { + astBuilder = new AstBuilder(context); + } + + try (final SafeCloseable importSuppression = astBuilder.suppressImports()) { + final TypeDeclaration declaration = astBuilder.createType(resolvedType); + + declaration.acceptVisitor(this, data); + } + } + } + + List switches = info.switches.get(mapName); + + if (switches == null) { + info.switches.put(mapName, switches = new ArrayList<>()); + } + + switches.add(node); + } + + return super.visitSwitchStatement(node, data); + } + + @Override + public Void visitAssignmentExpression(final AssignmentExpression node, final Void data) { + final TypeDefinition currentType = context.getCurrentType(); + final MethodDefinition currentMethod = context.getCurrentMethod(); + + if (_isSwitchMapWrapper && + currentType != null && + currentMethod != null && + currentMethod.isTypeInitializer()) { + + final Expression left = node.getLeft(); + final Expression right = node.getRight(); + + if (left instanceof IndexerExpression && + right instanceof PrimitiveExpression) { + + String mapName = null; + + final Expression array = ((IndexerExpression) left).getTarget(); + final Expression argument = ((IndexerExpression) left).getArgument(); + + if (array instanceof MemberReferenceExpression) { + mapName = ((MemberReferenceExpression) array).getMemberName(); + } + else if (array instanceof IdentifierExpression) { + mapName = ((IdentifierExpression) array).getIdentifier(); + } + + if (mapName == null || mapName.startsWith("$SwitchMap$")) { + return super.visitAssignmentExpression(node, data); + } + + if (!(argument instanceof InvocationExpression)) { + return super.visitAssignmentExpression(node, data); + } + + final InvocationExpression invocation = (InvocationExpression) argument; + final Expression invocationTarget = invocation.getTarget(); + + if (!(invocationTarget instanceof MemberReferenceExpression)) { + return super.visitAssignmentExpression(node, data); + } + + final MemberReferenceExpression memberReference = (MemberReferenceExpression) invocationTarget; + final Expression memberTarget = memberReference.getTarget(); + + if (!(memberTarget instanceof MemberReferenceExpression) || !"ordinal".equals(memberReference.getMemberName())) { + return super.visitAssignmentExpression(node, data); + } + + final MemberReferenceExpression outerMemberReference = (MemberReferenceExpression) memberTarget; + final Expression outerMemberTarget = outerMemberReference.getTarget(); + + if (!(outerMemberTarget instanceof TypeReferenceExpression)) { + return super.visitAssignmentExpression(node, data); + } + + final String enclosingType = currentType.getInternalName(); + + SwitchMapInfo info = _switchMaps.get(enclosingType); + + if (info == null) { + _switchMaps.put(enclosingType, info = new SwitchMapInfo(enclosingType)); + + AstBuilder astBuilder = context.getUserData(Keys.AST_BUILDER); + + if (astBuilder == null) { + astBuilder = new AstBuilder(context); + } + + info.enclosingTypeDeclaration = astBuilder.createType(currentType); + } + + final PrimitiveExpression value = (PrimitiveExpression) right; + + assert value.getValue() instanceof Integer; + + Map mapping = info.mappings.get(mapName); + + if (mapping == null) { + info.mappings.put(mapName, mapping = new LinkedHashMap<>()); + } + + final IdentifierExpression enumValue = new IdentifierExpression( Expression.MYSTERY_OFFSET, outerMemberReference.getMemberName()); + + enumValue.putUserData(Keys.MEMBER_REFERENCE, outerMemberReference.getUserData(Keys.MEMBER_REFERENCE)); + + mapping.put(((Number) value.getValue()).intValue(), enumValue); + } + } + + return super.visitAssignmentExpression(node, data); + } + + private void rewrite() { + if (_switchMaps.isEmpty()) { + return; + } + + for (final SwitchMapInfo info : _switchMaps.values()) { + rewrite(info); + } + + // + // Remove switch map type wrappers that are no longer referenced. + // + + outer: + for (final SwitchMapInfo info : _switchMaps.values()) { + for (final String mapName : info.switches.keySet()) { + final List switches = info.switches.get(mapName); + + if (switches != null && !switches.isEmpty()) { + continue outer; + } + } + + final TypeDeclaration enclosingTypeDeclaration = info.enclosingTypeDeclaration; + + if (enclosingTypeDeclaration != null) { + enclosingTypeDeclaration.remove(); + } + } + } + + private void rewrite(final SwitchMapInfo info) { + if (info.switches.isEmpty()) { + return; + } + + for (final String mapName : info.switches.keySet()) { + final List switches = info.switches.get(mapName); + final Map mappings = info.mappings.get(mapName); + + if (switches != null && mappings != null) { + for (int i = 0; i < switches.size(); i++) { + if (rewriteSwitch(switches.get(i), mappings)) { + switches.remove(i--); + } + } + } + } + } + + private boolean rewriteSwitch(final SwitchStatement s, final Map mappings) { + final Map replacements = new IdentityHashMap<>(); + + for (final SwitchSection section : s.getSwitchSections()) { + for (final CaseLabel caseLabel : section.getCaseLabels()) { + final Expression expression = caseLabel.getExpression(); + + if (expression.isNull()) { + continue; + } + + if (expression instanceof PrimitiveExpression) { + final Object value = ((PrimitiveExpression) expression).getValue(); + + if (value instanceof Integer) { + final Expression replacement = mappings.get(value); + + if (replacement != null) { + replacements.put(expression, replacement); + continue; + } + } + } + + // + // If we can't rewrite all cases, we abort. + // + + return false; + } + } + + final IndexerExpression indexer = (IndexerExpression) s.getExpression(); + final InvocationExpression argument = (InvocationExpression) indexer.getArgument(); + final MemberReferenceExpression memberReference = (MemberReferenceExpression) argument.getTarget(); + final Expression newTest = memberReference.getTarget(); + + newTest.remove(); + indexer.replaceWith(newTest); + + for (final Map.Entry entry : replacements.entrySet()) { + entry.getKey().replaceWith(entry.getValue().clone()); + } + + return true; + } + + private static boolean isSwitchMapWrapper(final TypeReference type) { + if (type == null) { + return false; + } + + final TypeDefinition definition = type instanceof TypeDefinition ? (TypeDefinition) type + : type.resolve(); + + if (definition == null || !definition.isSynthetic() || !definition.isInnerClass() || !definition.isPackagePrivate()) { + return false; + } + + for (final FieldDefinition field : definition.getDeclaredFields()) { + if (!field.getName().startsWith("$SwitchMap$") && + BuiltinTypes.Integer.makeArrayType().equals(field.getFieldType())) { + + return true; + } + } + + return false; + } + } +} \ No newline at end of file diff --git a/src/test/java/cuchaz/enigma/TestDeobfed.java b/src/test/java/cuchaz/enigma/TestDeobfed.java index e6c1b746..9babf1e3 100644 --- a/src/test/java/cuchaz/enigma/TestDeobfed.java +++ b/src/test/java/cuchaz/enigma/TestDeobfed.java @@ -12,6 +12,8 @@ package cuchaz.enigma; import cuchaz.enigma.analysis.JarIndex; +import cuchaz.enigma.analysis.ParsedJar; +import cuchaz.enigma.mapping.entry.ReferencedEntryPool; import org.junit.BeforeClass; import org.junit.Test; @@ -23,14 +25,14 @@ import static org.hamcrest.Matchers.containsInAnyOrder; public class TestDeobfed { - private static JarFile jar; + private static ParsedJar jar; private static JarIndex index; @BeforeClass public static void beforeClass() throws Exception { - jar = new JarFile("build/test-deobf/translation.jar"); - index = new JarIndex(); + jar = new ParsedJar(new JarFile("build/test-deobf/translation.jar")); + index = new JarIndex(new ReferencedEntryPool()); index.indexJar(jar, true); } diff --git a/src/test/java/cuchaz/enigma/TestDeobfuscator.java b/src/test/java/cuchaz/enigma/TestDeobfuscator.java index 62a52861..63a6f552 100644 --- a/src/test/java/cuchaz/enigma/TestDeobfuscator.java +++ b/src/test/java/cuchaz/enigma/TestDeobfuscator.java @@ -12,7 +12,7 @@ package cuchaz.enigma; import com.google.common.collect.Lists; -import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.entry.ClassEntry; import org.junit.Test; import java.io.IOException; diff --git a/src/test/java/cuchaz/enigma/TestEntryFactory.java b/src/test/java/cuchaz/enigma/TestEntryFactory.java index 1c527f53..4f52609e 100644 --- a/src/test/java/cuchaz/enigma/TestEntryFactory.java +++ b/src/test/java/cuchaz/enigma/TestEntryFactory.java @@ -13,6 +13,9 @@ package cuchaz.enigma; import cuchaz.enigma.analysis.EntryReference; import cuchaz.enigma.mapping.*; +import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.mapping.entry.FieldEntry; +import cuchaz.enigma.mapping.entry.MethodEntry; public class TestEntryFactory { @@ -25,7 +28,7 @@ public class TestEntryFactory { } public static FieldEntry newField(ClassEntry classEntry, String fieldName, String fieldType) { - return new FieldEntry(classEntry, fieldName, new Type(fieldType)); + return new FieldEntry(classEntry, fieldName, new TypeDescriptor(fieldType)); } public static MethodEntry newMethod(String className, String methodName, String methodSignature) { @@ -33,30 +36,14 @@ public class TestEntryFactory { } public static MethodEntry newMethod(ClassEntry classEntry, String methodName, String methodSignature) { - return new MethodEntry(classEntry, methodName, new Signature(methodSignature)); + return new MethodEntry(classEntry, methodName, new MethodDescriptor(methodSignature)); } - public static ConstructorEntry newConstructor(String className, String signature) { - return newConstructor(newClass(className), signature); + public static EntryReference newFieldReferenceByMethod(FieldEntry fieldEntry, String callerClassName, String callerName, String callerSignature) { + return new EntryReference<>(fieldEntry, "", newMethod(callerClassName, callerName, callerSignature)); } - public static ConstructorEntry newConstructor(ClassEntry classEntry, String signature) { - return new ConstructorEntry(classEntry, new Signature(signature)); - } - - public static EntryReference newFieldReferenceByMethod(FieldEntry fieldEntry, String callerClassName, String callerName, String callerSignature) { - return new EntryReference(fieldEntry, "", newMethod(callerClassName, callerName, callerSignature)); - } - - public static EntryReference newFieldReferenceByConstructor(FieldEntry fieldEntry, String callerClassName, String callerSignature) { - return new EntryReference(fieldEntry, "", newConstructor(callerClassName, callerSignature)); - } - - public static EntryReference newBehaviorReferenceByMethod(BehaviorEntry behaviorEntry, String callerClassName, String callerName, String callerSignature) { - return new EntryReference(behaviorEntry, "", newMethod(callerClassName, callerName, callerSignature)); - } - - public static EntryReference newBehaviorReferenceByConstructor(BehaviorEntry behaviorEntry, String callerClassName, String callerSignature) { - return new EntryReference(behaviorEntry, "", newConstructor(callerClassName, callerSignature)); + public static EntryReference newBehaviorReferenceByMethod(MethodEntry methodEntry, String callerClassName, String callerName, String callerSignature) { + return new EntryReference<>(methodEntry, "", newMethod(callerClassName, callerName, callerSignature)); } } diff --git a/src/test/java/cuchaz/enigma/TestInnerClasses.java b/src/test/java/cuchaz/enigma/TestInnerClasses.java index 38db0df9..843a63c7 100644 --- a/src/test/java/cuchaz/enigma/TestInnerClasses.java +++ b/src/test/java/cuchaz/enigma/TestInnerClasses.java @@ -12,7 +12,9 @@ package cuchaz.enigma; import cuchaz.enigma.analysis.JarIndex; -import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.analysis.ParsedJar; +import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.mapping.entry.ReferencedEntryPool; import org.junit.Test; import java.util.jar.JarFile; @@ -26,16 +28,10 @@ import static org.hamcrest.Matchers.nullValue; public class TestInnerClasses { - private static final ClassEntry AnonymousOuter = newClass("a"); - private static final ClassEntry AnonymousInner = newClass("a$1"); private static final ClassEntry SimpleOuter = newClass("d"); private static final ClassEntry SimpleInner = newClass("d$a"); private static final ClassEntry ConstructorArgsOuter = newClass("c"); private static final ClassEntry ConstructorArgsInner = newClass("c$a"); - private static final ClassEntry AnonymousWithScopeArgsOuter = newClass("b"); - private static final ClassEntry AnonymousWithScopeArgsInner = newClass("b$1"); - private static final ClassEntry AnonymousWithOuterAccessOuter = newClass("e"); - private static final ClassEntry AnonymousWithOuterAccessInner = newClass("e$1"); private static final ClassEntry ClassTreeRoot = newClass("f"); private static final ClassEntry ClassTreeLevel1 = newClass("f$a"); private static final ClassEntry ClassTreeLevel2 = newClass("f$a$a"); @@ -45,8 +41,8 @@ public class TestInnerClasses { public TestInnerClasses() throws Exception { - index = new JarIndex(); - JarFile jar = new JarFile("build/test-obf/innerClasses.jar"); + index = new JarIndex(new ReferencedEntryPool()); + ParsedJar jar = new ParsedJar(new JarFile("build/test-obf/innerClasses.jar")); index.indexJar(jar, true); deobfuscator = new Deobfuscator(jar); } @@ -55,42 +51,16 @@ public class TestInnerClasses { public void simple() { assertThat(index.getOuterClass(SimpleInner), is(SimpleOuter)); assertThat(index.getInnerClasses(SimpleOuter), containsInAnyOrder(SimpleInner)); - assertThat(index.isAnonymousClass(SimpleInner), is(false)); decompile(SimpleOuter); } - @Test - public void anonymous() { - assertThat(index.getOuterClass(AnonymousInner), is(AnonymousOuter)); - assertThat(index.getInnerClasses(AnonymousOuter), containsInAnyOrder(AnonymousInner)); - assertThat(index.isAnonymousClass(AnonymousInner), is(true)); - decompile(AnonymousOuter); - } - @Test public void constructorArgs() { assertThat(index.getOuterClass(ConstructorArgsInner), is(ConstructorArgsOuter)); assertThat(index.getInnerClasses(ConstructorArgsOuter), containsInAnyOrder(ConstructorArgsInner)); - assertThat(index.isAnonymousClass(ConstructorArgsInner), is(false)); decompile(ConstructorArgsOuter); } - @Test - public void anonymousWithScopeArgs() { - assertThat(index.getOuterClass(AnonymousWithScopeArgsInner), is(AnonymousWithScopeArgsOuter)); - assertThat(index.getInnerClasses(AnonymousWithScopeArgsOuter), containsInAnyOrder(AnonymousWithScopeArgsInner)); - assertThat(index.isAnonymousClass(AnonymousWithScopeArgsInner), is(true)); - decompile(AnonymousWithScopeArgsOuter); - } - - @Test - public void anonymousWithOuterAccess() { - assertThat(index.getOuterClass(AnonymousWithOuterAccessInner), is(AnonymousWithOuterAccessOuter)); - assertThat(index.getInnerClasses(AnonymousWithOuterAccessOuter), containsInAnyOrder(AnonymousWithOuterAccessInner)); - assertThat(index.isAnonymousClass(AnonymousWithOuterAccessInner), is(true)); - decompile(AnonymousWithOuterAccessOuter); - } - @Test public void classTree() { @@ -101,8 +71,7 @@ public class TestInnerClasses { // level 1 ClassEntry fullClassEntry = new ClassEntry(ClassTreeRoot.getName() - + "$" + ClassTreeLevel1.getInnermostClassName() - ); + + "$" + ClassTreeLevel1.getInnermostClassName()); assertThat(index.containsObfClass(fullClassEntry), is(true)); assertThat(index.getOuterClass(ClassTreeLevel1), is(ClassTreeRoot)); assertThat(index.getInnerClasses(ClassTreeLevel1), containsInAnyOrder(ClassTreeLevel2)); @@ -110,8 +79,7 @@ public class TestInnerClasses { // level 2 fullClassEntry = new ClassEntry(ClassTreeRoot.getName() + "$" + ClassTreeLevel1.getInnermostClassName() - + "$" + ClassTreeLevel2.getInnermostClassName() - ); + + "$" + ClassTreeLevel2.getInnermostClassName()); assertThat(index.containsObfClass(fullClassEntry), is(true)); assertThat(index.getOuterClass(ClassTreeLevel2), is(ClassTreeLevel1)); assertThat(index.getInnerClasses(ClassTreeLevel2), containsInAnyOrder(ClassTreeLevel3)); @@ -120,8 +88,7 @@ public class TestInnerClasses { fullClassEntry = new ClassEntry(ClassTreeRoot.getName() + "$" + ClassTreeLevel1.getInnermostClassName() + "$" + ClassTreeLevel2.getInnermostClassName() - + "$" + ClassTreeLevel3.getInnermostClassName() - ); + + "$" + ClassTreeLevel3.getInnermostClassName()); assertThat(index.containsObfClass(fullClassEntry), is(true)); assertThat(index.getOuterClass(ClassTreeLevel3), is(ClassTreeLevel2)); assertThat(index.getInnerClasses(ClassTreeLevel3), is(empty())); diff --git a/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java b/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java index edb859a7..763639a4 100644 --- a/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java +++ b/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java @@ -13,22 +13,20 @@ package cuchaz.enigma; import cuchaz.enigma.analysis.EntryReference; import cuchaz.enigma.analysis.JarIndex; -import cuchaz.enigma.mapping.BehaviorEntry; -import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.analysis.ParsedJar; +import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.mapping.entry.MethodDefEntry; +import cuchaz.enigma.mapping.entry.MethodEntry; +import cuchaz.enigma.mapping.entry.ReferencedEntryPool; import org.junit.Test; import java.io.File; import java.util.Collection; import java.util.jar.JarFile; -import static cuchaz.enigma.TestEntryFactory.newBehaviorReferenceByConstructor; -import static cuchaz.enigma.TestEntryFactory.newBehaviorReferenceByMethod; -import static cuchaz.enigma.TestEntryFactory.newClass; -import static cuchaz.enigma.TestEntryFactory.newConstructor; +import static cuchaz.enigma.TestEntryFactory.*; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.*; public class TestJarIndexConstructorReferences { @@ -41,90 +39,90 @@ public class TestJarIndexConstructorReferences { private ClassEntry callerClass = newClass("b"); public TestJarIndexConstructorReferences() - throws Exception { + throws Exception { File jarFile = new File("build/test-obf/constructors.jar"); - index = new JarIndex(); - index.indexJar(new JarFile(jarFile), false); + index = new JarIndex(new ReferencedEntryPool()); + index.indexJar(new ParsedJar(new JarFile(jarFile)), false); } @Test public void obfEntries() { assertThat(index.getObfClassEntries(), containsInAnyOrder(newClass("cuchaz/enigma/inputs/Keep"), baseClass, - subClass, subsubClass, defaultClass, callerClass)); + subClass, subsubClass, defaultClass, callerClass)); } @Test @SuppressWarnings("unchecked") public void baseDefault() { - BehaviorEntry source = newConstructor(baseClass, "()V"); - Collection> references = index.getBehaviorReferences(source); + MethodEntry source = newMethod(baseClass, "", "()V"); + Collection> references = index.getMethodsReferencing(source); assertThat(references, containsInAnyOrder( - newBehaviorReferenceByMethod(source, callerClass.getName(), "a", "()V"), - newBehaviorReferenceByConstructor(source, subClass.getName(), "()V"), - newBehaviorReferenceByConstructor(source, subClass.getName(), "(III)V") + newBehaviorReferenceByMethod(source, callerClass.getName(), "a", "()V"), + newBehaviorReferenceByMethod(source, subClass.getName(), "", "()V"), + newBehaviorReferenceByMethod(source, subClass.getName(), "", "(III)V") )); } @Test @SuppressWarnings("unchecked") public void baseInt() { - BehaviorEntry source = newConstructor(baseClass, "(I)V"); - assertThat(index.getBehaviorReferences(source), containsInAnyOrder( - newBehaviorReferenceByMethod(source, callerClass.getName(), "b", "()V") + MethodEntry source = newMethod(baseClass, "", "(I)V"); + assertThat(index.getMethodsReferencing(source), containsInAnyOrder( + newBehaviorReferenceByMethod(source, callerClass.getName(), "b", "()V") )); } @Test @SuppressWarnings("unchecked") public void subDefault() { - BehaviorEntry source = newConstructor(subClass, "()V"); - assertThat(index.getBehaviorReferences(source), containsInAnyOrder( - newBehaviorReferenceByMethod(source, callerClass.getName(), "c", "()V"), - newBehaviorReferenceByConstructor(source, subClass.getName(), "(I)V") + MethodEntry source = newMethod(subClass, "", "()V"); + assertThat(index.getMethodsReferencing(source), containsInAnyOrder( + newBehaviorReferenceByMethod(source, callerClass.getName(), "c", "()V"), + newBehaviorReferenceByMethod(source, subClass.getName(), "", "(I)V") )); } @Test @SuppressWarnings("unchecked") public void subInt() { - BehaviorEntry source = newConstructor(subClass, "(I)V"); - assertThat(index.getBehaviorReferences(source), containsInAnyOrder( - newBehaviorReferenceByMethod(source, callerClass.getName(), "d", "()V"), - newBehaviorReferenceByConstructor(source, subClass.getName(), "(II)V"), - newBehaviorReferenceByConstructor(source, subsubClass.getName(), "(I)V") + MethodEntry source = newMethod(subClass, "", "(I)V"); + assertThat(index.getMethodsReferencing(source), containsInAnyOrder( + newBehaviorReferenceByMethod(source, callerClass.getName(), "d", "()V"), + newBehaviorReferenceByMethod(source, subClass.getName(), "", "(II)V"), + newBehaviorReferenceByMethod(source, subsubClass.getName(), "", "(I)V") )); } @Test @SuppressWarnings("unchecked") public void subIntInt() { - BehaviorEntry source = newConstructor(subClass, "(II)V"); - assertThat(index.getBehaviorReferences(source), containsInAnyOrder( - newBehaviorReferenceByMethod(source, callerClass.getName(), "e", "()V") + MethodEntry source = newMethod(subClass, "", "(II)V"); + assertThat(index.getMethodsReferencing(source), containsInAnyOrder( + newBehaviorReferenceByMethod(source, callerClass.getName(), "e", "()V") )); } @Test public void subIntIntInt() { - BehaviorEntry source = newConstructor(subClass, "(III)V"); - assertThat(index.getBehaviorReferences(source), is(empty())); + MethodEntry source = newMethod(subClass, "", "(III)V"); + assertThat(index.getMethodsReferencing(source), is(empty())); } @Test @SuppressWarnings("unchecked") public void subsubInt() { - BehaviorEntry source = newConstructor(subsubClass, "(I)V"); - assertThat(index.getBehaviorReferences(source), containsInAnyOrder( - newBehaviorReferenceByMethod(source, callerClass.getName(), "f", "()V") + MethodEntry source = newMethod(subsubClass, "", "(I)V"); + assertThat(index.getMethodsReferencing(source), containsInAnyOrder( + newBehaviorReferenceByMethod(source, callerClass.getName(), "f", "()V") )); } @Test @SuppressWarnings("unchecked") public void defaultConstructable() { - BehaviorEntry source = newConstructor(defaultClass, "()V"); - assertThat(index.getBehaviorReferences(source), containsInAnyOrder( - newBehaviorReferenceByMethod(source, callerClass.getName(), "g", "()V") + MethodEntry source = newMethod(defaultClass, "", "()V"); + assertThat(index.getMethodsReferencing(source), containsInAnyOrder( + newBehaviorReferenceByMethod(source, callerClass.getName(), "g", "()V") )); } } diff --git a/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java b/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java index 62469780..23df1a99 100644 --- a/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java +++ b/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java @@ -11,14 +11,8 @@ package cuchaz.enigma; -import cuchaz.enigma.analysis.Access; -import cuchaz.enigma.analysis.EntryReference; -import cuchaz.enigma.analysis.JarIndex; -import cuchaz.enigma.analysis.TranslationIndex; -import cuchaz.enigma.mapping.BehaviorEntry; -import cuchaz.enigma.mapping.ClassEntry; -import cuchaz.enigma.mapping.FieldEntry; -import cuchaz.enigma.mapping.MethodEntry; +import cuchaz.enigma.analysis.*; +import cuchaz.enigma.mapping.entry.*; import org.junit.Test; import java.util.Collection; @@ -27,10 +21,7 @@ import java.util.jar.JarFile; import static cuchaz.enigma.TestEntryFactory.*; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.*; public class TestJarIndexInheritanceTree { @@ -45,15 +36,15 @@ public class TestJarIndexInheritanceTree { private FieldEntry numThingsField = newField(subClassB, "a", "I"); public TestJarIndexInheritanceTree() - throws Exception { - index = new JarIndex(); - index.indexJar(new JarFile("build/test-obf/inheritanceTree.jar"), false); + throws Exception { + index = new JarIndex(new ReferencedEntryPool()); + index.indexJar(new ParsedJar(new JarFile("build/test-obf/inheritanceTree.jar")), false); } @Test public void obfEntries() { assertThat(index.getObfClassEntries(), containsInAnyOrder( - newClass("cuchaz/enigma/inputs/Keep"), baseClass, subClassA, subClassAA, subClassB + newClass("cuchaz/enigma/inputs/Keep"), baseClass, subClassA, subClassAA, subClassB )); } @@ -98,33 +89,33 @@ public class TestJarIndexInheritanceTree { // getName() entries = index.getRelatedMethodImplementations(newMethod(baseClass, "a", "()Ljava/lang/String;")); assertThat(entries, containsInAnyOrder( - newMethod(baseClass, "a", "()Ljava/lang/String;"), - newMethod(subClassAA, "a", "()Ljava/lang/String;") + newMethod(baseClass, "a", "()Ljava/lang/String;"), + newMethod(subClassAA, "a", "()Ljava/lang/String;") )); entries = index.getRelatedMethodImplementations(newMethod(subClassAA, "a", "()Ljava/lang/String;")); assertThat(entries, containsInAnyOrder( - newMethod(baseClass, "a", "()Ljava/lang/String;"), - newMethod(subClassAA, "a", "()Ljava/lang/String;") + newMethod(baseClass, "a", "()Ljava/lang/String;"), + newMethod(subClassAA, "a", "()Ljava/lang/String;") )); // doBaseThings() entries = index.getRelatedMethodImplementations(newMethod(baseClass, "a", "()V")); assertThat(entries, containsInAnyOrder( - newMethod(baseClass, "a", "()V"), - newMethod(subClassAA, "a", "()V"), - newMethod(subClassB, "a", "()V") + newMethod(baseClass, "a", "()V"), + newMethod(subClassAA, "a", "()V"), + newMethod(subClassB, "a", "()V") )); entries = index.getRelatedMethodImplementations(newMethod(subClassAA, "a", "()V")); assertThat(entries, containsInAnyOrder( - newMethod(baseClass, "a", "()V"), - newMethod(subClassAA, "a", "()V"), - newMethod(subClassB, "a", "()V") + newMethod(baseClass, "a", "()V"), + newMethod(subClassAA, "a", "()V"), + newMethod(subClassB, "a", "()V") )); entries = index.getRelatedMethodImplementations(newMethod(subClassB, "a", "()V")); assertThat(entries, containsInAnyOrder( - newMethod(baseClass, "a", "()V"), - newMethod(subClassAA, "a", "()V"), - newMethod(subClassB, "a", "()V") + newMethod(baseClass, "a", "()V"), + newMethod(subClassAA, "a", "()V"), + newMethod(subClassB, "a", "()V") )); // doBThings @@ -135,20 +126,20 @@ public class TestJarIndexInheritanceTree { @Test @SuppressWarnings("unchecked") public void fieldReferences() { - Collection> references; + Collection> references; // name references = index.getFieldReferences(nameField); assertThat(references, containsInAnyOrder( - newFieldReferenceByConstructor(nameField, baseClass.getName(), "(Ljava/lang/String;)V"), - newFieldReferenceByMethod(nameField, baseClass.getName(), "a", "()Ljava/lang/String;") + newFieldReferenceByMethod(nameField, baseClass.getName(), "", "(Ljava/lang/String;)V"), + newFieldReferenceByMethod(nameField, baseClass.getName(), "a", "()Ljava/lang/String;") )); // numThings references = index.getFieldReferences(numThingsField); assertThat(references, containsInAnyOrder( - newFieldReferenceByConstructor(numThingsField, subClassB.getName(), "()V"), - newFieldReferenceByMethod(numThingsField, subClassB.getName(), "b", "()V") + newFieldReferenceByMethod(numThingsField, subClassB.getName(), "", "()V"), + newFieldReferenceByMethod(numThingsField, subClassB.getName(), "b", "()V") )); } @@ -156,37 +147,37 @@ public class TestJarIndexInheritanceTree { @SuppressWarnings("unchecked") public void behaviorReferences() { - BehaviorEntry source; - Collection> references; + MethodEntry source; + Collection> references; // baseClass constructor - source = newConstructor(baseClass, "(Ljava/lang/String;)V"); - references = index.getBehaviorReferences(source); + source = newMethod(baseClass, "", "(Ljava/lang/String;)V"); + references = index.getMethodsReferencing(source); assertThat(references, containsInAnyOrder( - newBehaviorReferenceByConstructor(source, subClassA.getName(), "(Ljava/lang/String;)V"), - newBehaviorReferenceByConstructor(source, subClassB.getName(), "()V") + newBehaviorReferenceByMethod(source, subClassA.getName(), "", "(Ljava/lang/String;)V"), + newBehaviorReferenceByMethod(source, subClassB.getName(), "", "()V") )); // subClassA constructor - source = newConstructor(subClassA, "(Ljava/lang/String;)V"); - references = index.getBehaviorReferences(source); + source = newMethod(subClassA, "", "(Ljava/lang/String;)V"); + references = index.getMethodsReferencing(source); assertThat(references, containsInAnyOrder( - newBehaviorReferenceByConstructor(source, subClassAA.getName(), "()V") + newBehaviorReferenceByMethod(source, subClassAA.getName(), "", "()V") )); // baseClass.getName() source = newMethod(baseClass, "a", "()Ljava/lang/String;"); - references = index.getBehaviorReferences(source); + references = index.getMethodsReferencing(source); assertThat(references, containsInAnyOrder( - newBehaviorReferenceByMethod(source, subClassAA.getName(), "a", "()Ljava/lang/String;"), - newBehaviorReferenceByMethod(source, subClassB.getName(), "a", "()V") + newBehaviorReferenceByMethod(source, subClassAA.getName(), "a", "()Ljava/lang/String;"), + newBehaviorReferenceByMethod(source, subClassB.getName(), "a", "()V") )); // subclassAA.getName() source = newMethod(subClassAA, "a", "()Ljava/lang/String;"); - references = index.getBehaviorReferences(source); + references = index.getMethodsReferencing(source); assertThat(references, containsInAnyOrder( - newBehaviorReferenceByMethod(source, subClassAA.getName(), "a", "()V") + newBehaviorReferenceByMethod(source, subClassAA.getName(), "a", "()V") )); } @@ -205,22 +196,22 @@ public class TestJarIndexInheritanceTree { // methods // getName() - assertThat(index.containsObfBehavior(newMethod(baseClass, "a", "()Ljava/lang/String;")), is(true)); - assertThat(index.containsObfBehavior(newMethod(subClassA, "a", "()Ljava/lang/String;")), is(false)); - assertThat(index.containsObfBehavior(newMethod(subClassAA, "a", "()Ljava/lang/String;")), is(true)); - assertThat(index.containsObfBehavior(newMethod(subClassB, "a", "()Ljava/lang/String;")), is(false)); + assertThat(index.containsObfMethod(newMethod(baseClass, "a", "()Ljava/lang/String;")), is(true)); + assertThat(index.containsObfMethod(newMethod(subClassA, "a", "()Ljava/lang/String;")), is(false)); + assertThat(index.containsObfMethod(newMethod(subClassAA, "a", "()Ljava/lang/String;")), is(true)); + assertThat(index.containsObfMethod(newMethod(subClassB, "a", "()Ljava/lang/String;")), is(false)); // doBaseThings() - assertThat(index.containsObfBehavior(newMethod(baseClass, "a", "()V")), is(true)); - assertThat(index.containsObfBehavior(newMethod(subClassA, "a", "()V")), is(false)); - assertThat(index.containsObfBehavior(newMethod(subClassAA, "a", "()V")), is(true)); - assertThat(index.containsObfBehavior(newMethod(subClassB, "a", "()V")), is(true)); + assertThat(index.containsObfMethod(newMethod(baseClass, "a", "()V")), is(true)); + assertThat(index.containsObfMethod(newMethod(subClassA, "a", "()V")), is(false)); + assertThat(index.containsObfMethod(newMethod(subClassAA, "a", "()V")), is(true)); + assertThat(index.containsObfMethod(newMethod(subClassB, "a", "()V")), is(true)); // doBThings() - assertThat(index.containsObfBehavior(newMethod(baseClass, "b", "()V")), is(false)); - assertThat(index.containsObfBehavior(newMethod(subClassA, "b", "()V")), is(false)); - assertThat(index.containsObfBehavior(newMethod(subClassAA, "b", "()V")), is(false)); - assertThat(index.containsObfBehavior(newMethod(subClassB, "b", "()V")), is(true)); + assertThat(index.containsObfMethod(newMethod(baseClass, "b", "()V")), is(false)); + assertThat(index.containsObfMethod(newMethod(subClassA, "b", "()V")), is(false)); + assertThat(index.containsObfMethod(newMethod(subClassAA, "b", "()V")), is(false)); + assertThat(index.containsObfMethod(newMethod(subClassB, "b", "()V")), is(true)); } } diff --git a/src/test/java/cuchaz/enigma/TestJarIndexLoneClass.java b/src/test/java/cuchaz/enigma/TestJarIndexLoneClass.java index 6cab1c84..b4529ddc 100644 --- a/src/test/java/cuchaz/enigma/TestJarIndexLoneClass.java +++ b/src/test/java/cuchaz/enigma/TestJarIndexLoneClass.java @@ -13,6 +13,7 @@ package cuchaz.enigma; import cuchaz.enigma.analysis.*; import cuchaz.enigma.mapping.*; +import cuchaz.enigma.mapping.entry.*; import org.junit.Test; import java.util.Collection; @@ -28,16 +29,16 @@ public class TestJarIndexLoneClass { private JarIndex index; public TestJarIndexLoneClass() - throws Exception { - index = new JarIndex(); - index.indexJar(new JarFile("build/test-obf/loneClass.jar"), false); + throws Exception { + index = new JarIndex(new ReferencedEntryPool()); + index.indexJar(new ParsedJar(new JarFile("build/test-obf/loneClass.jar")), false); } @Test public void obfEntries() { assertThat(index.getObfClassEntries(), containsInAnyOrder( - newClass("cuchaz/enigma/inputs/Keep"), - newClass("a") + newClass("cuchaz/enigma/inputs/Keep"), + newClass("a") )); } @@ -61,7 +62,7 @@ public class TestJarIndexLoneClass { @Test public void classInheritance() { - ClassInheritanceTreeNode node = index.getClassInheritance(new Translator(), newClass("a")); + ClassInheritanceTreeNode node = index.getClassInheritance(new DirectionalTranslator(new ReferencedEntryPool()), newClass("a")); assertThat(node, is(not(nullValue()))); assertThat(node.getObfClassName(), is("a")); assertThat(node.getChildCount(), is(0)); @@ -70,7 +71,7 @@ public class TestJarIndexLoneClass { @Test public void methodInheritance() { MethodEntry source = newMethod("a", "a", "()Ljava/lang/String;"); - MethodInheritanceTreeNode node = index.getMethodInheritance(new Translator(), source); + MethodInheritanceTreeNode node = index.getMethodInheritance(new DirectionalTranslator(new ReferencedEntryPool()), source); assertThat(node, is(not(nullValue()))); assertThat(node.getMethodEntry(), is(source)); assertThat(node.getChildCount(), is(0)); @@ -78,21 +79,21 @@ public class TestJarIndexLoneClass { @Test public void classImplementations() { - ClassImplementationsTreeNode node = index.getClassImplementations(new Translator(), newClass("a")); + ClassImplementationsTreeNode node = index.getClassImplementations(new DirectionalTranslator(new ReferencedEntryPool()), newClass("a")); assertThat(node, is(nullValue())); } @Test public void methodImplementations() { MethodEntry source = newMethod("a", "a", "()Ljava/lang/String;"); - assertThat(index.getMethodImplementations(new Translator(), source), is(empty())); + assertThat(index.getMethodImplementations(new DirectionalTranslator(new ReferencedEntryPool()), source), is(empty())); } @Test public void relatedMethodImplementations() { Set entries = index.getRelatedMethodImplementations(newMethod("a", "a", "()Ljava/lang/String;")); assertThat(entries, containsInAnyOrder( - newMethod("a", "a", "()Ljava/lang/String;") + newMethod("a", "a", "()Ljava/lang/String;") )); } @@ -100,16 +101,16 @@ public class TestJarIndexLoneClass { @SuppressWarnings("unchecked") public void fieldReferences() { FieldEntry source = newField("a", "a", "Ljava/lang/String;"); - Collection> references = index.getFieldReferences(source); + Collection> references = index.getFieldReferences(source); assertThat(references, containsInAnyOrder( - newFieldReferenceByConstructor(source, "a", "(Ljava/lang/String;)V"), - newFieldReferenceByMethod(source, "a", "a", "()Ljava/lang/String;") + newFieldReferenceByMethod(source, "a", "", "(Ljava/lang/String;)V"), + newFieldReferenceByMethod(source, "a", "a", "()Ljava/lang/String;") )); } @Test public void behaviorReferences() { - assertThat(index.getBehaviorReferences(newMethod("a", "a", "()Ljava/lang/String;")), is(empty())); + assertThat(index.getMethodsReferencing(newMethod("a", "a", "()Ljava/lang/String;")), is(empty())); } @Test @@ -122,11 +123,6 @@ public class TestJarIndexLoneClass { assertThat(index.getOuterClass(newClass("a")), is(nullValue())); } - @Test - public void isAnonymousClass() { - assertThat(index.isAnonymousClass(newClass("a")), is(false)); - } - @Test public void interfaces() { assertThat(index.getInterfaces("a"), is(empty())); @@ -149,7 +145,7 @@ public class TestJarIndexLoneClass { assertThat(index.containsObfField(newField("a", "a", "Ljava/lang/String;")), is(true)); assertThat(index.containsObfField(newField("a", "b", "Ljava/lang/String;")), is(false)); assertThat(index.containsObfField(newField("a", "a", "LFoo;")), is(false)); - assertThat(index.containsObfBehavior(newMethod("a", "a", "()Ljava/lang/String;")), is(true)); - assertThat(index.containsObfBehavior(newMethod("a", "b", "()Ljava/lang/String;")), is(false)); + assertThat(index.containsObfMethod(newMethod("a", "a", "()Ljava/lang/String;")), is(true)); + assertThat(index.containsObfMethod(newMethod("a", "b", "()Ljava/lang/String;")), is(false)); } } diff --git a/src/test/java/cuchaz/enigma/TestMethodDescriptor.java b/src/test/java/cuchaz/enigma/TestMethodDescriptor.java new file mode 100644 index 00000000..48c46e52 --- /dev/null +++ b/src/test/java/cuchaz/enigma/TestMethodDescriptor.java @@ -0,0 +1,247 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma; + +import cuchaz.enigma.mapping.MethodDescriptor; +import cuchaz.enigma.mapping.TypeDescriptor; +import org.junit.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +public class TestMethodDescriptor { + + @Test + public void easiest() { + final MethodDescriptor sig = new MethodDescriptor("()V"); + assertThat(sig.getArgumentDescs(), is(empty())); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("V"))); + } + + @Test + public void primitives() { + { + final MethodDescriptor sig = new MethodDescriptor("(I)V"); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("I") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("V"))); + } + { + final MethodDescriptor sig = new MethodDescriptor("(I)I"); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("I") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("I"))); + } + { + final MethodDescriptor sig = new MethodDescriptor("(IBCJ)Z"); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("I"), + new TypeDescriptor("B"), + new TypeDescriptor("C"), + new TypeDescriptor("J") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("Z"))); + } + } + + @Test + public void classes() { + { + final MethodDescriptor sig = new MethodDescriptor("([LFoo;)V"); + assertThat(sig.getArgumentDescs().size(), is(1)); + assertThat(sig.getArgumentDescs().get(0), is(new TypeDescriptor("[LFoo;"))); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("V"))); + } + { + final MethodDescriptor sig = new MethodDescriptor("(LFoo;)LBar;"); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("LFoo;") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("LBar;"))); + } + { + final MethodDescriptor sig = new MethodDescriptor("(LFoo;LMoo;LZoo;)LBar;"); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("LFoo;"), + new TypeDescriptor("LMoo;"), + new TypeDescriptor("LZoo;") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("LBar;"))); + } + } + + @Test + public void arrays() { + { + final MethodDescriptor sig = new MethodDescriptor("([I)V"); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("[I") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("V"))); + } + { + final MethodDescriptor sig = new MethodDescriptor("([I)[J"); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("[I") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("[J"))); + } + { + final MethodDescriptor sig = new MethodDescriptor("([I[Z[F)[D"); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("[I"), + new TypeDescriptor("[Z"), + new TypeDescriptor("[F") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("[D"))); + } + } + + @Test + public void mixed() { + { + final MethodDescriptor sig = new MethodDescriptor("(I[JLFoo;)Z"); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("I"), + new TypeDescriptor("[J"), + new TypeDescriptor("LFoo;") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("Z"))); + } + { + final MethodDescriptor sig = new MethodDescriptor("(III)[LFoo;"); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("I"), + new TypeDescriptor("I"), + new TypeDescriptor("I") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("[LFoo;"))); + } + } + + @Test + public void replaceClasses() { + { + final MethodDescriptor oldSig = new MethodDescriptor("()V"); + final MethodDescriptor sig = oldSig.remap(s -> null); + assertThat(sig.getArgumentDescs(), is(empty())); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("V"))); + } + { + final MethodDescriptor oldSig = new MethodDescriptor("(IJLFoo;)V"); + final MethodDescriptor sig = oldSig.remap(s -> null); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("I"), + new TypeDescriptor("J"), + new TypeDescriptor("LFoo;") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("V"))); + } + { + final MethodDescriptor oldSig = new MethodDescriptor("(LFoo;LBar;)LMoo;"); + final MethodDescriptor sig = oldSig.remap(s -> { + if (s.equals("Foo")) { + return "Bar"; + } + return null; + }); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("LBar;"), + new TypeDescriptor("LBar;") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("LMoo;"))); + } + { + final MethodDescriptor oldSig = new MethodDescriptor("(LFoo;LBar;)LMoo;"); + final MethodDescriptor sig = oldSig.remap(s -> { + if (s.equals("Moo")) { + return "Cow"; + } + return null; + }); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("LFoo;"), + new TypeDescriptor("LBar;") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("LCow;"))); + } + } + + @Test + public void replaceArrayClasses() { + { + final MethodDescriptor oldSig = new MethodDescriptor("([LFoo;)[[[LBar;"); + final MethodDescriptor sig = oldSig.remap(s -> { + if (s.equals("Foo")) { + return "Food"; + } else if (s.equals("Bar")) { + return "Beer"; + } + return null; + }); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("[LFood;") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("[[[LBeer;"))); + } + } + + @Test + public void equals() { + + // base + assertThat(new MethodDescriptor("()V"), is(new MethodDescriptor("()V"))); + + // arguments + assertThat(new MethodDescriptor("(I)V"), is(new MethodDescriptor("(I)V"))); + assertThat(new MethodDescriptor("(ZIZ)V"), is(new MethodDescriptor("(ZIZ)V"))); + assertThat(new MethodDescriptor("(LFoo;)V"), is(new MethodDescriptor("(LFoo;)V"))); + assertThat(new MethodDescriptor("(LFoo;LBar;)V"), is(new MethodDescriptor("(LFoo;LBar;)V"))); + assertThat(new MethodDescriptor("([I)V"), is(new MethodDescriptor("([I)V"))); + assertThat(new MethodDescriptor("([[D[[[J)V"), is(new MethodDescriptor("([[D[[[J)V"))); + + assertThat(new MethodDescriptor("()V"), is(not(new MethodDescriptor("(I)V")))); + assertThat(new MethodDescriptor("(I)V"), is(not(new MethodDescriptor("()V")))); + assertThat(new MethodDescriptor("(IJ)V"), is(not(new MethodDescriptor("(JI)V")))); + assertThat(new MethodDescriptor("([[Z)V"), is(not(new MethodDescriptor("([[LFoo;)V")))); + assertThat(new MethodDescriptor("(LFoo;LBar;)V"), is(not(new MethodDescriptor("(LFoo;LCow;)V")))); + assertThat(new MethodDescriptor("([LFoo;LBar;)V"), is(not(new MethodDescriptor("(LFoo;LCow;)V")))); + + // return desc + assertThat(new MethodDescriptor("()I"), is(new MethodDescriptor("()I"))); + assertThat(new MethodDescriptor("()Z"), is(new MethodDescriptor("()Z"))); + assertThat(new MethodDescriptor("()[D"), is(new MethodDescriptor("()[D"))); + assertThat(new MethodDescriptor("()[[[Z"), is(new MethodDescriptor("()[[[Z"))); + assertThat(new MethodDescriptor("()LFoo;"), is(new MethodDescriptor("()LFoo;"))); + assertThat(new MethodDescriptor("()[LFoo;"), is(new MethodDescriptor("()[LFoo;"))); + + assertThat(new MethodDescriptor("()I"), is(not(new MethodDescriptor("()Z")))); + assertThat(new MethodDescriptor("()Z"), is(not(new MethodDescriptor("()I")))); + assertThat(new MethodDescriptor("()[D"), is(not(new MethodDescriptor("()[J")))); + assertThat(new MethodDescriptor("()[[[Z"), is(not(new MethodDescriptor("()[[Z")))); + assertThat(new MethodDescriptor("()LFoo;"), is(not(new MethodDescriptor("()LBar;")))); + assertThat(new MethodDescriptor("()[LFoo;"), is(not(new MethodDescriptor("()[LBar;")))); + } + + @Test + public void testToString() { + assertThat(new MethodDescriptor("()V").toString(), is("()V")); + assertThat(new MethodDescriptor("(I)V").toString(), is("(I)V")); + assertThat(new MethodDescriptor("(ZIZ)V").toString(), is("(ZIZ)V")); + assertThat(new MethodDescriptor("(LFoo;)V").toString(), is("(LFoo;)V")); + assertThat(new MethodDescriptor("(LFoo;LBar;)V").toString(), is("(LFoo;LBar;)V")); + assertThat(new MethodDescriptor("([I)V").toString(), is("([I)V")); + assertThat(new MethodDescriptor("([[D[[[J)V").toString(), is("([[D[[[J)V")); + } +} diff --git a/src/test/java/cuchaz/enigma/TestSignature.java b/src/test/java/cuchaz/enigma/TestSignature.java deleted file mode 100644 index 534b43ae..00000000 --- a/src/test/java/cuchaz/enigma/TestSignature.java +++ /dev/null @@ -1,270 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma; - -import cuchaz.enigma.mapping.ClassNameReplacer; -import cuchaz.enigma.mapping.Signature; -import cuchaz.enigma.mapping.Type; -import org.junit.Test; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; - -public class TestSignature { - - @Test - public void easiest() { - final Signature sig = new Signature("()V"); - assertThat(sig.getArgumentTypes(), is(empty())); - assertThat(sig.getReturnType(), is(new Type("V"))); - } - - @Test - public void primitives() { - { - final Signature sig = new Signature("(I)V"); - assertThat(sig.getArgumentTypes(), contains( - new Type("I") - )); - assertThat(sig.getReturnType(), is(new Type("V"))); - } - { - final Signature sig = new Signature("(I)I"); - assertThat(sig.getArgumentTypes(), contains( - new Type("I") - )); - assertThat(sig.getReturnType(), is(new Type("I"))); - } - { - final Signature sig = new Signature("(IBCJ)Z"); - assertThat(sig.getArgumentTypes(), contains( - new Type("I"), - new Type("B"), - new Type("C"), - new Type("J") - )); - assertThat(sig.getReturnType(), is(new Type("Z"))); - } - } - - @Test - public void classes() { - { - final Signature sig = new Signature("([LFoo;)V"); - assertThat(sig.getArgumentTypes().size(), is(1)); - assertThat(sig.getArgumentTypes().get(0), is(new Type("[LFoo;"))); - assertThat(sig.getReturnType(), is(new Type("V"))); - } - { - final Signature sig = new Signature("(LFoo;)LBar;"); - assertThat(sig.getArgumentTypes(), contains( - new Type("LFoo;") - )); - assertThat(sig.getReturnType(), is(new Type("LBar;"))); - } - { - final Signature sig = new Signature("(LFoo;LMoo;LZoo;)LBar;"); - assertThat(sig.getArgumentTypes(), contains( - new Type("LFoo;"), - new Type("LMoo;"), - new Type("LZoo;") - )); - assertThat(sig.getReturnType(), is(new Type("LBar;"))); - } - } - - @Test - public void arrays() { - { - final Signature sig = new Signature("([I)V"); - assertThat(sig.getArgumentTypes(), contains( - new Type("[I") - )); - assertThat(sig.getReturnType(), is(new Type("V"))); - } - { - final Signature sig = new Signature("([I)[J"); - assertThat(sig.getArgumentTypes(), contains( - new Type("[I") - )); - assertThat(sig.getReturnType(), is(new Type("[J"))); - } - { - final Signature sig = new Signature("([I[Z[F)[D"); - assertThat(sig.getArgumentTypes(), contains( - new Type("[I"), - new Type("[Z"), - new Type("[F") - )); - assertThat(sig.getReturnType(), is(new Type("[D"))); - } - } - - @Test - public void mixed() { - { - final Signature sig = new Signature("(I[JLFoo;)Z"); - assertThat(sig.getArgumentTypes(), contains( - new Type("I"), - new Type("[J"), - new Type("LFoo;") - )); - assertThat(sig.getReturnType(), is(new Type("Z"))); - } - { - final Signature sig = new Signature("(III)[LFoo;"); - assertThat(sig.getArgumentTypes(), contains( - new Type("I"), - new Type("I"), - new Type("I") - )); - assertThat(sig.getReturnType(), is(new Type("[LFoo;"))); - } - } - - @Test - public void replaceClasses() { - { - final Signature oldSig = new Signature("()V"); - final Signature sig = new Signature(oldSig, new ClassNameReplacer() { - @Override - public String replace(String val) { - return null; - } - }); - assertThat(sig.getArgumentTypes(), is(empty())); - assertThat(sig.getReturnType(), is(new Type("V"))); - } - { - final Signature oldSig = new Signature("(IJLFoo;)V"); - final Signature sig = new Signature(oldSig, new ClassNameReplacer() { - @Override - public String replace(String val) { - return null; - } - }); - assertThat(sig.getArgumentTypes(), contains( - new Type("I"), - new Type("J"), - new Type("LFoo;") - )); - assertThat(sig.getReturnType(), is(new Type("V"))); - } - { - final Signature oldSig = new Signature("(LFoo;LBar;)LMoo;"); - final Signature sig = new Signature(oldSig, new ClassNameReplacer() { - @Override - public String replace(String val) { - if (val.equals("Foo")) { - return "Bar"; - } - return null; - } - }); - assertThat(sig.getArgumentTypes(), contains( - new Type("LBar;"), - new Type("LBar;") - )); - assertThat(sig.getReturnType(), is(new Type("LMoo;"))); - } - { - final Signature oldSig = new Signature("(LFoo;LBar;)LMoo;"); - final Signature sig = new Signature(oldSig, new ClassNameReplacer() { - @Override - public String replace(String val) { - if (val.equals("Moo")) { - return "Cow"; - } - return null; - } - }); - assertThat(sig.getArgumentTypes(), contains( - new Type("LFoo;"), - new Type("LBar;") - )); - assertThat(sig.getReturnType(), is(new Type("LCow;"))); - } - } - - @Test - public void replaceArrayClasses() { - { - final Signature oldSig = new Signature("([LFoo;)[[[LBar;"); - final Signature sig = new Signature(oldSig, new ClassNameReplacer() { - @Override - public String replace(String val) { - if (val.equals("Foo")) { - return "Food"; - } else if (val.equals("Bar")) { - return "Beer"; - } - return null; - } - }); - assertThat(sig.getArgumentTypes(), contains( - new Type("[LFood;") - )); - assertThat(sig.getReturnType(), is(new Type("[[[LBeer;"))); - } - } - - @Test - public void equals() { - - // base - assertThat(new Signature("()V"), is(new Signature("()V"))); - - // arguments - assertThat(new Signature("(I)V"), is(new Signature("(I)V"))); - assertThat(new Signature("(ZIZ)V"), is(new Signature("(ZIZ)V"))); - assertThat(new Signature("(LFoo;)V"), is(new Signature("(LFoo;)V"))); - assertThat(new Signature("(LFoo;LBar;)V"), is(new Signature("(LFoo;LBar;)V"))); - assertThat(new Signature("([I)V"), is(new Signature("([I)V"))); - assertThat(new Signature("([[D[[[J)V"), is(new Signature("([[D[[[J)V"))); - - assertThat(new Signature("()V"), is(not(new Signature("(I)V")))); - assertThat(new Signature("(I)V"), is(not(new Signature("()V")))); - assertThat(new Signature("(IJ)V"), is(not(new Signature("(JI)V")))); - assertThat(new Signature("([[Z)V"), is(not(new Signature("([[LFoo;)V")))); - assertThat(new Signature("(LFoo;LBar;)V"), is(not(new Signature("(LFoo;LCow;)V")))); - assertThat(new Signature("([LFoo;LBar;)V"), is(not(new Signature("(LFoo;LCow;)V")))); - - // return type - assertThat(new Signature("()I"), is(new Signature("()I"))); - assertThat(new Signature("()Z"), is(new Signature("()Z"))); - assertThat(new Signature("()[D"), is(new Signature("()[D"))); - assertThat(new Signature("()[[[Z"), is(new Signature("()[[[Z"))); - assertThat(new Signature("()LFoo;"), is(new Signature("()LFoo;"))); - assertThat(new Signature("()[LFoo;"), is(new Signature("()[LFoo;"))); - - assertThat(new Signature("()I"), is(not(new Signature("()Z")))); - assertThat(new Signature("()Z"), is(not(new Signature("()I")))); - assertThat(new Signature("()[D"), is(not(new Signature("()[J")))); - assertThat(new Signature("()[[[Z"), is(not(new Signature("()[[Z")))); - assertThat(new Signature("()LFoo;"), is(not(new Signature("()LBar;")))); - assertThat(new Signature("()[LFoo;"), is(not(new Signature("()[LBar;")))); - } - - @Test - public void testToString() { - assertThat(new Signature("()V").toString(), is("()V")); - assertThat(new Signature("(I)V").toString(), is("(I)V")); - assertThat(new Signature("(ZIZ)V").toString(), is("(ZIZ)V")); - assertThat(new Signature("(LFoo;)V").toString(), is("(LFoo;)V")); - assertThat(new Signature("(LFoo;LBar;)V").toString(), is("(LFoo;LBar;)V")); - assertThat(new Signature("([I)V").toString(), is("([I)V")); - assertThat(new Signature("([[D[[[J)V").toString(), is("([[D[[[J)V")); - } -} diff --git a/src/test/java/cuchaz/enigma/TestSourceIndex.java b/src/test/java/cuchaz/enigma/TestSourceIndex.java index 6e9e5aec..07542753 100644 --- a/src/test/java/cuchaz/enigma/TestSourceIndex.java +++ b/src/test/java/cuchaz/enigma/TestSourceIndex.java @@ -13,7 +13,7 @@ package cuchaz.enigma; import com.google.common.collect.Sets; import com.strobel.decompiler.languages.java.ast.CompilationUnit; -import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.entry.ClassEntry; import org.junit.Test; import java.io.File; diff --git a/src/test/java/cuchaz/enigma/TestTokensConstructors.java b/src/test/java/cuchaz/enigma/TestTokensConstructors.java index e40d5fdc..0e98da7f 100644 --- a/src/test/java/cuchaz/enigma/TestTokensConstructors.java +++ b/src/test/java/cuchaz/enigma/TestTokensConstructors.java @@ -11,131 +11,127 @@ package cuchaz.enigma; -import cuchaz.enigma.mapping.BehaviorEntry; +import cuchaz.enigma.mapping.entry.MethodEntry; import org.junit.Test; import java.util.jar.JarFile; -import static cuchaz.enigma.TestEntryFactory.newBehaviorReferenceByConstructor; import static cuchaz.enigma.TestEntryFactory.newBehaviorReferenceByMethod; -import static cuchaz.enigma.TestEntryFactory.newConstructor; +import static cuchaz.enigma.TestEntryFactory.newMethod; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.Matchers.*; public class TestTokensConstructors extends TokenChecker { public TestTokensConstructors() - throws Exception { + throws Exception { super(new JarFile("build/test-obf/constructors.jar")); } @Test public void baseDeclarations() { - assertThat(getDeclarationToken(newConstructor("a", "()V")), is("a")); - assertThat(getDeclarationToken(newConstructor("a", "(I)V")), is("a")); + assertThat(getDeclarationToken(newMethod("a", "", "()V")), is("a")); + assertThat(getDeclarationToken(newMethod("a", "", "(I)V")), is("a")); } @Test public void subDeclarations() { - assertThat(getDeclarationToken(newConstructor("d", "()V")), is("d")); - assertThat(getDeclarationToken(newConstructor("d", "(I)V")), is("d")); - assertThat(getDeclarationToken(newConstructor("d", "(II)V")), is("d")); - assertThat(getDeclarationToken(newConstructor("d", "(III)V")), is("d")); + assertThat(getDeclarationToken(newMethod("d", "", "()V")), is("d")); + assertThat(getDeclarationToken(newMethod("d", "", "(I)V")), is("d")); + assertThat(getDeclarationToken(newMethod("d", "", "(II)V")), is("d")); + assertThat(getDeclarationToken(newMethod("d", "", "(III)V")), is("d")); } @Test public void subsubDeclarations() { - assertThat(getDeclarationToken(newConstructor("e", "(I)V")), is("e")); + assertThat(getDeclarationToken(newMethod("e", "", "(I)V")), is("e")); } @Test public void defaultDeclarations() { - assertThat(getDeclarationToken(newConstructor("c", "()V")), nullValue()); + assertThat(getDeclarationToken(newMethod("c", "", "()V")), nullValue()); } @Test public void baseDefaultReferences() { - BehaviorEntry source = newConstructor("a", "()V"); + MethodEntry source = newMethod("a", "", "()V"); assertThat( - getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "a", "()V")), - containsInAnyOrder("a") + getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "a", "()V")), + containsInAnyOrder("a") ); assertThat( - getReferenceTokens(newBehaviorReferenceByConstructor(source, "d", "()V")), - is(empty()) // implicit call, not decompiled to token + getReferenceTokens(newBehaviorReferenceByMethod(source, "d", "", "()V")), + is(empty()) // implicit call, not decompiled to token ); assertThat( - getReferenceTokens(newBehaviorReferenceByConstructor(source, "d", "(III)V")), - is(empty()) // implicit call, not decompiled to token + getReferenceTokens(newBehaviorReferenceByMethod(source, "d", "", "(III)V")), + is(empty()) // implicit call, not decompiled to token ); } @Test public void baseIntReferences() { - BehaviorEntry source = newConstructor("a", "(I)V"); + MethodEntry source = newMethod("a", "", "(I)V"); assertThat( - getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "b", "()V")), - containsInAnyOrder("a") + getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "b", "()V")), + containsInAnyOrder("a") ); } @Test public void subDefaultReferences() { - BehaviorEntry source = newConstructor("d", "()V"); + MethodEntry source = newMethod("d", "", "()V"); assertThat( - getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "c", "()V")), - containsInAnyOrder("d") + getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "c", "()V")), + containsInAnyOrder("d") ); assertThat( - getReferenceTokens(newBehaviorReferenceByConstructor(source, "d", "(I)V")), - containsInAnyOrder("this") + getReferenceTokens(newBehaviorReferenceByMethod(source, "d", "", "(I)V")), + containsInAnyOrder("this") ); } @Test public void subIntReferences() { - BehaviorEntry source = newConstructor("d", "(I)V"); + MethodEntry source = newMethod("d", "", "(I)V"); assertThat(getReferenceTokens( - newBehaviorReferenceByMethod(source, "b", "d", "()V")), - containsInAnyOrder("d") + newBehaviorReferenceByMethod(source, "b", "d", "()V")), + containsInAnyOrder("d") ); assertThat(getReferenceTokens( - newBehaviorReferenceByConstructor(source, "d", "(II)V")), - containsInAnyOrder("this") + newBehaviorReferenceByMethod(source, "d", "", "(II)V")), + containsInAnyOrder("this") ); assertThat(getReferenceTokens( - newBehaviorReferenceByConstructor(source, "e", "(I)V")), - containsInAnyOrder("super") + newBehaviorReferenceByMethod(source, "e", "", "(I)V")), + containsInAnyOrder("super") ); } @Test public void subIntIntReferences() { - BehaviorEntry source = newConstructor("d", "(II)V"); + MethodEntry source = newMethod("d", "", "(II)V"); assertThat( - getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "e", "()V")), - containsInAnyOrder("d") + getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "e", "()V")), + containsInAnyOrder("d") ); } @Test public void subsubIntReferences() { - BehaviorEntry source = newConstructor("e", "(I)V"); + MethodEntry source = newMethod("e", "", "(I)V"); assertThat( - getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "f", "()V")), - containsInAnyOrder("e") + getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "f", "()V")), + containsInAnyOrder("e") ); } @Test public void defaultConstructableReferences() { - BehaviorEntry source = newConstructor("c", "()V"); + MethodEntry source = newMethod("c", "", "()V"); assertThat( - getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "g", "()V")), - containsInAnyOrder("c") + getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "g", "()V")), + containsInAnyOrder("c") ); } } diff --git a/src/test/java/cuchaz/enigma/TestTranslator.java b/src/test/java/cuchaz/enigma/TestTranslator.java index b63dff86..9b6eb916 100644 --- a/src/test/java/cuchaz/enigma/TestTranslator.java +++ b/src/test/java/cuchaz/enigma/TestTranslator.java @@ -11,7 +11,7 @@ package cuchaz.enigma; -import cuchaz.enigma.mapping.Entry; +import cuchaz.enigma.mapping.entry.Entry; import cuchaz.enigma.mapping.Mappings; import cuchaz.enigma.mapping.Translator; import org.junit.BeforeClass; diff --git a/src/test/java/cuchaz/enigma/TestType.java b/src/test/java/cuchaz/enigma/TestType.java deleted file mode 100644 index 43dacb0c..00000000 --- a/src/test/java/cuchaz/enigma/TestType.java +++ /dev/null @@ -1,243 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma; - -import cuchaz.enigma.mapping.Type; -import org.junit.Test; - -import static cuchaz.enigma.TestEntryFactory.newClass; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; - -public class TestType { - - @Test - public void isVoid() { - assertThat(new Type("V").isVoid(), is(true)); - assertThat(new Type("Z").isVoid(), is(false)); - assertThat(new Type("B").isVoid(), is(false)); - assertThat(new Type("C").isVoid(), is(false)); - assertThat(new Type("I").isVoid(), is(false)); - assertThat(new Type("J").isVoid(), is(false)); - assertThat(new Type("F").isVoid(), is(false)); - assertThat(new Type("D").isVoid(), is(false)); - assertThat(new Type("LFoo;").isVoid(), is(false)); - assertThat(new Type("[I").isVoid(), is(false)); - } - - @Test - public void isPrimitive() { - assertThat(new Type("V").isPrimitive(), is(false)); - assertThat(new Type("Z").isPrimitive(), is(true)); - assertThat(new Type("B").isPrimitive(), is(true)); - assertThat(new Type("C").isPrimitive(), is(true)); - assertThat(new Type("I").isPrimitive(), is(true)); - assertThat(new Type("J").isPrimitive(), is(true)); - assertThat(new Type("F").isPrimitive(), is(true)); - assertThat(new Type("D").isPrimitive(), is(true)); - assertThat(new Type("LFoo;").isPrimitive(), is(false)); - assertThat(new Type("[I").isPrimitive(), is(false)); - } - - @Test - public void getPrimitive() { - assertThat(new Type("Z").getPrimitive(), is(Type.Primitive.Boolean)); - assertThat(new Type("B").getPrimitive(), is(Type.Primitive.Byte)); - assertThat(new Type("C").getPrimitive(), is(Type.Primitive.Character)); - assertThat(new Type("I").getPrimitive(), is(Type.Primitive.Integer)); - assertThat(new Type("J").getPrimitive(), is(Type.Primitive.Long)); - assertThat(new Type("F").getPrimitive(), is(Type.Primitive.Float)); - assertThat(new Type("D").getPrimitive(), is(Type.Primitive.Double)); - } - - @Test - public void isClass() { - assertThat(new Type("V").isClass(), is(false)); - assertThat(new Type("Z").isClass(), is(false)); - assertThat(new Type("B").isClass(), is(false)); - assertThat(new Type("C").isClass(), is(false)); - assertThat(new Type("I").isClass(), is(false)); - assertThat(new Type("J").isClass(), is(false)); - assertThat(new Type("F").isClass(), is(false)); - assertThat(new Type("D").isClass(), is(false)); - assertThat(new Type("LFoo;").isClass(), is(true)); - assertThat(new Type("[I").isClass(), is(false)); - } - - @Test - public void getClassEntry() { - assertThat(new Type("LFoo;").getClassEntry(), is(newClass("Foo"))); - assertThat(new Type("Ljava/lang/String;").getClassEntry(), is(newClass("java/lang/String"))); - } - - @Test - public void getArrayClassEntry() { - assertThat(new Type("[LFoo;").getClassEntry(), is(newClass("Foo"))); - assertThat(new Type("[[[Ljava/lang/String;").getClassEntry(), is(newClass("java/lang/String"))); - } - - @Test - public void isArray() { - assertThat(new Type("V").isArray(), is(false)); - assertThat(new Type("Z").isArray(), is(false)); - assertThat(new Type("B").isArray(), is(false)); - assertThat(new Type("C").isArray(), is(false)); - assertThat(new Type("I").isArray(), is(false)); - assertThat(new Type("J").isArray(), is(false)); - assertThat(new Type("F").isArray(), is(false)); - assertThat(new Type("D").isArray(), is(false)); - assertThat(new Type("LFoo;").isArray(), is(false)); - assertThat(new Type("[I").isArray(), is(true)); - } - - @Test - public void getArrayDimension() { - assertThat(new Type("[I").getArrayDimension(), is(1)); - assertThat(new Type("[[I").getArrayDimension(), is(2)); - assertThat(new Type("[[[I").getArrayDimension(), is(3)); - } - - @Test - public void getArrayType() { - assertThat(new Type("[I").getArrayType(), is(new Type("I"))); - assertThat(new Type("[[I").getArrayType(), is(new Type("I"))); - assertThat(new Type("[[[I").getArrayType(), is(new Type("I"))); - assertThat(new Type("[Ljava/lang/String;").getArrayType(), is(new Type("Ljava/lang/String;"))); - } - - @Test - public void hasClass() { - assertThat(new Type("LFoo;").hasClass(), is(true)); - assertThat(new Type("Ljava/lang/String;").hasClass(), is(true)); - assertThat(new Type("[LBar;").hasClass(), is(true)); - assertThat(new Type("[[[LCat;").hasClass(), is(true)); - - assertThat(new Type("V").hasClass(), is(false)); - assertThat(new Type("[I").hasClass(), is(false)); - assertThat(new Type("[[[I").hasClass(), is(false)); - assertThat(new Type("Z").hasClass(), is(false)); - } - - @Test - public void parseVoid() { - final String answer = "V"; - assertThat(Type.parseFirst("V"), is(answer)); - assertThat(Type.parseFirst("VVV"), is(answer)); - assertThat(Type.parseFirst("VIJ"), is(answer)); - assertThat(Type.parseFirst("V[I"), is(answer)); - assertThat(Type.parseFirst("VLFoo;"), is(answer)); - assertThat(Type.parseFirst("V[LFoo;"), is(answer)); - } - - @Test - public void parsePrimitive() { - final String answer = "I"; - assertThat(Type.parseFirst("I"), is(answer)); - assertThat(Type.parseFirst("III"), is(answer)); - assertThat(Type.parseFirst("IJZ"), is(answer)); - assertThat(Type.parseFirst("I[I"), is(answer)); - assertThat(Type.parseFirst("ILFoo;"), is(answer)); - assertThat(Type.parseFirst("I[LFoo;"), is(answer)); - } - - @Test - public void parseClass() { - { - final String answer = "LFoo;"; - assertThat(Type.parseFirst("LFoo;"), is(answer)); - assertThat(Type.parseFirst("LFoo;I"), is(answer)); - assertThat(Type.parseFirst("LFoo;JZ"), is(answer)); - assertThat(Type.parseFirst("LFoo;[I"), is(answer)); - assertThat(Type.parseFirst("LFoo;LFoo;"), is(answer)); - assertThat(Type.parseFirst("LFoo;[LFoo;"), is(answer)); - } - { - final String answer = "Ljava/lang/String;"; - assertThat(Type.parseFirst("Ljava/lang/String;"), is(answer)); - assertThat(Type.parseFirst("Ljava/lang/String;I"), is(answer)); - assertThat(Type.parseFirst("Ljava/lang/String;JZ"), is(answer)); - assertThat(Type.parseFirst("Ljava/lang/String;[I"), is(answer)); - assertThat(Type.parseFirst("Ljava/lang/String;LFoo;"), is(answer)); - assertThat(Type.parseFirst("Ljava/lang/String;[LFoo;"), is(answer)); - } - } - - @Test - public void parseArray() { - { - final String answer = "[I"; - assertThat(Type.parseFirst("[I"), is(answer)); - assertThat(Type.parseFirst("[III"), is(answer)); - assertThat(Type.parseFirst("[IJZ"), is(answer)); - assertThat(Type.parseFirst("[I[I"), is(answer)); - assertThat(Type.parseFirst("[ILFoo;"), is(answer)); - } - { - final String answer = "[[I"; - assertThat(Type.parseFirst("[[I"), is(answer)); - assertThat(Type.parseFirst("[[III"), is(answer)); - assertThat(Type.parseFirst("[[IJZ"), is(answer)); - assertThat(Type.parseFirst("[[I[I"), is(answer)); - assertThat(Type.parseFirst("[[ILFoo;"), is(answer)); - } - { - final String answer = "[LFoo;"; - assertThat(Type.parseFirst("[LFoo;"), is(answer)); - assertThat(Type.parseFirst("[LFoo;II"), is(answer)); - assertThat(Type.parseFirst("[LFoo;JZ"), is(answer)); - assertThat(Type.parseFirst("[LFoo;[I"), is(answer)); - assertThat(Type.parseFirst("[LFoo;LFoo;"), is(answer)); - } - } - - @Test - public void equals() { - assertThat(new Type("V"), is(new Type("V"))); - assertThat(new Type("Z"), is(new Type("Z"))); - assertThat(new Type("B"), is(new Type("B"))); - assertThat(new Type("C"), is(new Type("C"))); - assertThat(new Type("I"), is(new Type("I"))); - assertThat(new Type("J"), is(new Type("J"))); - assertThat(new Type("F"), is(new Type("F"))); - assertThat(new Type("D"), is(new Type("D"))); - assertThat(new Type("LFoo;"), is(new Type("LFoo;"))); - assertThat(new Type("[I"), is(new Type("[I"))); - assertThat(new Type("[[[I"), is(new Type("[[[I"))); - assertThat(new Type("[LFoo;"), is(new Type("[LFoo;"))); - - assertThat(new Type("V"), is(not(new Type("I")))); - assertThat(new Type("I"), is(not(new Type("J")))); - assertThat(new Type("I"), is(not(new Type("LBar;")))); - assertThat(new Type("I"), is(not(new Type("[I")))); - assertThat(new Type("LFoo;"), is(not(new Type("LBar;")))); - assertThat(new Type("[I"), is(not(new Type("[Z")))); - assertThat(new Type("[[[I"), is(not(new Type("[I")))); - assertThat(new Type("[LFoo;"), is(not(new Type("[LBar;")))); - } - - @Test - public void testToString() { - assertThat(new Type("V").toString(), is("V")); - assertThat(new Type("Z").toString(), is("Z")); - assertThat(new Type("B").toString(), is("B")); - assertThat(new Type("C").toString(), is("C")); - assertThat(new Type("I").toString(), is("I")); - assertThat(new Type("J").toString(), is("J")); - assertThat(new Type("F").toString(), is("F")); - assertThat(new Type("D").toString(), is("D")); - assertThat(new Type("LFoo;").toString(), is("LFoo;")); - assertThat(new Type("[I").toString(), is("[I")); - assertThat(new Type("[[[I").toString(), is("[[[I")); - assertThat(new Type("[LFoo;").toString(), is("[LFoo;")); - } -} diff --git a/src/test/java/cuchaz/enigma/TestTypeDescriptor.java b/src/test/java/cuchaz/enigma/TestTypeDescriptor.java new file mode 100644 index 00000000..90fd6351 --- /dev/null +++ b/src/test/java/cuchaz/enigma/TestTypeDescriptor.java @@ -0,0 +1,243 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma; + +import cuchaz.enigma.mapping.TypeDescriptor; +import org.junit.Test; + +import static cuchaz.enigma.TestEntryFactory.newClass; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; + +public class TestTypeDescriptor { + + @Test + public void isVoid() { + assertThat(new TypeDescriptor("V").isVoid(), is(true)); + assertThat(new TypeDescriptor("Z").isVoid(), is(false)); + assertThat(new TypeDescriptor("B").isVoid(), is(false)); + assertThat(new TypeDescriptor("C").isVoid(), is(false)); + assertThat(new TypeDescriptor("I").isVoid(), is(false)); + assertThat(new TypeDescriptor("J").isVoid(), is(false)); + assertThat(new TypeDescriptor("F").isVoid(), is(false)); + assertThat(new TypeDescriptor("D").isVoid(), is(false)); + assertThat(new TypeDescriptor("LFoo;").isVoid(), is(false)); + assertThat(new TypeDescriptor("[I").isVoid(), is(false)); + } + + @Test + public void isPrimitive() { + assertThat(new TypeDescriptor("V").isPrimitive(), is(false)); + assertThat(new TypeDescriptor("Z").isPrimitive(), is(true)); + assertThat(new TypeDescriptor("B").isPrimitive(), is(true)); + assertThat(new TypeDescriptor("C").isPrimitive(), is(true)); + assertThat(new TypeDescriptor("I").isPrimitive(), is(true)); + assertThat(new TypeDescriptor("J").isPrimitive(), is(true)); + assertThat(new TypeDescriptor("F").isPrimitive(), is(true)); + assertThat(new TypeDescriptor("D").isPrimitive(), is(true)); + assertThat(new TypeDescriptor("LFoo;").isPrimitive(), is(false)); + assertThat(new TypeDescriptor("[I").isPrimitive(), is(false)); + } + + @Test + public void getPrimitive() { + assertThat(new TypeDescriptor("Z").getPrimitive(), is(TypeDescriptor.Primitive.Boolean)); + assertThat(new TypeDescriptor("B").getPrimitive(), is(TypeDescriptor.Primitive.Byte)); + assertThat(new TypeDescriptor("C").getPrimitive(), is(TypeDescriptor.Primitive.Character)); + assertThat(new TypeDescriptor("I").getPrimitive(), is(TypeDescriptor.Primitive.Integer)); + assertThat(new TypeDescriptor("J").getPrimitive(), is(TypeDescriptor.Primitive.Long)); + assertThat(new TypeDescriptor("F").getPrimitive(), is(TypeDescriptor.Primitive.Float)); + assertThat(new TypeDescriptor("D").getPrimitive(), is(TypeDescriptor.Primitive.Double)); + } + + @Test + public void isClass() { + assertThat(new TypeDescriptor("V").isType(), is(false)); + assertThat(new TypeDescriptor("Z").isType(), is(false)); + assertThat(new TypeDescriptor("B").isType(), is(false)); + assertThat(new TypeDescriptor("C").isType(), is(false)); + assertThat(new TypeDescriptor("I").isType(), is(false)); + assertThat(new TypeDescriptor("J").isType(), is(false)); + assertThat(new TypeDescriptor("F").isType(), is(false)); + assertThat(new TypeDescriptor("D").isType(), is(false)); + assertThat(new TypeDescriptor("LFoo;").isType(), is(true)); + assertThat(new TypeDescriptor("[I").isType(), is(false)); + } + + @Test + public void getClassEntry() { + assertThat(new TypeDescriptor("LFoo;").getTypeEntry(), is(newClass("Foo"))); + assertThat(new TypeDescriptor("Ljava/lang/String;").getTypeEntry(), is(newClass("java/lang/String"))); + } + + @Test + public void getArrayClassEntry() { + assertThat(new TypeDescriptor("[LFoo;").getTypeEntry(), is(newClass("Foo"))); + assertThat(new TypeDescriptor("[[[Ljava/lang/String;").getTypeEntry(), is(newClass("java/lang/String"))); + } + + @Test + public void isArray() { + assertThat(new TypeDescriptor("V").isArray(), is(false)); + assertThat(new TypeDescriptor("Z").isArray(), is(false)); + assertThat(new TypeDescriptor("B").isArray(), is(false)); + assertThat(new TypeDescriptor("C").isArray(), is(false)); + assertThat(new TypeDescriptor("I").isArray(), is(false)); + assertThat(new TypeDescriptor("J").isArray(), is(false)); + assertThat(new TypeDescriptor("F").isArray(), is(false)); + assertThat(new TypeDescriptor("D").isArray(), is(false)); + assertThat(new TypeDescriptor("LFoo;").isArray(), is(false)); + assertThat(new TypeDescriptor("[I").isArray(), is(true)); + } + + @Test + public void getArrayDimension() { + assertThat(new TypeDescriptor("[I").getArrayDimension(), is(1)); + assertThat(new TypeDescriptor("[[I").getArrayDimension(), is(2)); + assertThat(new TypeDescriptor("[[[I").getArrayDimension(), is(3)); + } + + @Test + public void getArrayType() { + assertThat(new TypeDescriptor("[I").getArrayType(), is(new TypeDescriptor("I"))); + assertThat(new TypeDescriptor("[[I").getArrayType(), is(new TypeDescriptor("I"))); + assertThat(new TypeDescriptor("[[[I").getArrayType(), is(new TypeDescriptor("I"))); + assertThat(new TypeDescriptor("[Ljava/lang/String;").getArrayType(), is(new TypeDescriptor("Ljava/lang/String;"))); + } + + @Test + public void hasClass() { + assertThat(new TypeDescriptor("LFoo;").containsType(), is(true)); + assertThat(new TypeDescriptor("Ljava/lang/String;").containsType(), is(true)); + assertThat(new TypeDescriptor("[LBar;").containsType(), is(true)); + assertThat(new TypeDescriptor("[[[LCat;").containsType(), is(true)); + + assertThat(new TypeDescriptor("V").containsType(), is(false)); + assertThat(new TypeDescriptor("[I").containsType(), is(false)); + assertThat(new TypeDescriptor("[[[I").containsType(), is(false)); + assertThat(new TypeDescriptor("Z").containsType(), is(false)); + } + + @Test + public void parseVoid() { + final String answer = "V"; + assertThat(TypeDescriptor.parseFirst("V"), is(answer)); + assertThat(TypeDescriptor.parseFirst("VVV"), is(answer)); + assertThat(TypeDescriptor.parseFirst("VIJ"), is(answer)); + assertThat(TypeDescriptor.parseFirst("V[I"), is(answer)); + assertThat(TypeDescriptor.parseFirst("VLFoo;"), is(answer)); + assertThat(TypeDescriptor.parseFirst("V[LFoo;"), is(answer)); + } + + @Test + public void parsePrimitive() { + final String answer = "I"; + assertThat(TypeDescriptor.parseFirst("I"), is(answer)); + assertThat(TypeDescriptor.parseFirst("III"), is(answer)); + assertThat(TypeDescriptor.parseFirst("IJZ"), is(answer)); + assertThat(TypeDescriptor.parseFirst("I[I"), is(answer)); + assertThat(TypeDescriptor.parseFirst("ILFoo;"), is(answer)); + assertThat(TypeDescriptor.parseFirst("I[LFoo;"), is(answer)); + } + + @Test + public void parseClass() { + { + final String answer = "LFoo;"; + assertThat(TypeDescriptor.parseFirst("LFoo;"), is(answer)); + assertThat(TypeDescriptor.parseFirst("LFoo;I"), is(answer)); + assertThat(TypeDescriptor.parseFirst("LFoo;JZ"), is(answer)); + assertThat(TypeDescriptor.parseFirst("LFoo;[I"), is(answer)); + assertThat(TypeDescriptor.parseFirst("LFoo;LFoo;"), is(answer)); + assertThat(TypeDescriptor.parseFirst("LFoo;[LFoo;"), is(answer)); + } + { + final String answer = "Ljava/lang/String;"; + assertThat(TypeDescriptor.parseFirst("Ljava/lang/String;"), is(answer)); + assertThat(TypeDescriptor.parseFirst("Ljava/lang/String;I"), is(answer)); + assertThat(TypeDescriptor.parseFirst("Ljava/lang/String;JZ"), is(answer)); + assertThat(TypeDescriptor.parseFirst("Ljava/lang/String;[I"), is(answer)); + assertThat(TypeDescriptor.parseFirst("Ljava/lang/String;LFoo;"), is(answer)); + assertThat(TypeDescriptor.parseFirst("Ljava/lang/String;[LFoo;"), is(answer)); + } + } + + @Test + public void parseArray() { + { + final String answer = "[I"; + assertThat(TypeDescriptor.parseFirst("[I"), is(answer)); + assertThat(TypeDescriptor.parseFirst("[III"), is(answer)); + assertThat(TypeDescriptor.parseFirst("[IJZ"), is(answer)); + assertThat(TypeDescriptor.parseFirst("[I[I"), is(answer)); + assertThat(TypeDescriptor.parseFirst("[ILFoo;"), is(answer)); + } + { + final String answer = "[[I"; + assertThat(TypeDescriptor.parseFirst("[[I"), is(answer)); + assertThat(TypeDescriptor.parseFirst("[[III"), is(answer)); + assertThat(TypeDescriptor.parseFirst("[[IJZ"), is(answer)); + assertThat(TypeDescriptor.parseFirst("[[I[I"), is(answer)); + assertThat(TypeDescriptor.parseFirst("[[ILFoo;"), is(answer)); + } + { + final String answer = "[LFoo;"; + assertThat(TypeDescriptor.parseFirst("[LFoo;"), is(answer)); + assertThat(TypeDescriptor.parseFirst("[LFoo;II"), is(answer)); + assertThat(TypeDescriptor.parseFirst("[LFoo;JZ"), is(answer)); + assertThat(TypeDescriptor.parseFirst("[LFoo;[I"), is(answer)); + assertThat(TypeDescriptor.parseFirst("[LFoo;LFoo;"), is(answer)); + } + } + + @Test + public void equals() { + assertThat(new TypeDescriptor("V"), is(new TypeDescriptor("V"))); + assertThat(new TypeDescriptor("Z"), is(new TypeDescriptor("Z"))); + assertThat(new TypeDescriptor("B"), is(new TypeDescriptor("B"))); + assertThat(new TypeDescriptor("C"), is(new TypeDescriptor("C"))); + assertThat(new TypeDescriptor("I"), is(new TypeDescriptor("I"))); + assertThat(new TypeDescriptor("J"), is(new TypeDescriptor("J"))); + assertThat(new TypeDescriptor("F"), is(new TypeDescriptor("F"))); + assertThat(new TypeDescriptor("D"), is(new TypeDescriptor("D"))); + assertThat(new TypeDescriptor("LFoo;"), is(new TypeDescriptor("LFoo;"))); + assertThat(new TypeDescriptor("[I"), is(new TypeDescriptor("[I"))); + assertThat(new TypeDescriptor("[[[I"), is(new TypeDescriptor("[[[I"))); + assertThat(new TypeDescriptor("[LFoo;"), is(new TypeDescriptor("[LFoo;"))); + + assertThat(new TypeDescriptor("V"), is(not(new TypeDescriptor("I")))); + assertThat(new TypeDescriptor("I"), is(not(new TypeDescriptor("J")))); + assertThat(new TypeDescriptor("I"), is(not(new TypeDescriptor("LBar;")))); + assertThat(new TypeDescriptor("I"), is(not(new TypeDescriptor("[I")))); + assertThat(new TypeDescriptor("LFoo;"), is(not(new TypeDescriptor("LBar;")))); + assertThat(new TypeDescriptor("[I"), is(not(new TypeDescriptor("[Z")))); + assertThat(new TypeDescriptor("[[[I"), is(not(new TypeDescriptor("[I")))); + assertThat(new TypeDescriptor("[LFoo;"), is(not(new TypeDescriptor("[LBar;")))); + } + + @Test + public void testToString() { + assertThat(new TypeDescriptor("V").toString(), is("V")); + assertThat(new TypeDescriptor("Z").toString(), is("Z")); + assertThat(new TypeDescriptor("B").toString(), is("B")); + assertThat(new TypeDescriptor("C").toString(), is("C")); + assertThat(new TypeDescriptor("I").toString(), is("I")); + assertThat(new TypeDescriptor("J").toString(), is("J")); + assertThat(new TypeDescriptor("F").toString(), is("F")); + assertThat(new TypeDescriptor("D").toString(), is("D")); + assertThat(new TypeDescriptor("LFoo;").toString(), is("LFoo;")); + assertThat(new TypeDescriptor("[I").toString(), is("[I")); + assertThat(new TypeDescriptor("[[[I").toString(), is("[[[I")); + assertThat(new TypeDescriptor("[LFoo;").toString(), is("[LFoo;")); + } +} diff --git a/src/test/java/cuchaz/enigma/TokenChecker.java b/src/test/java/cuchaz/enigma/TokenChecker.java index c6ced488..d863a5ae 100644 --- a/src/test/java/cuchaz/enigma/TokenChecker.java +++ b/src/test/java/cuchaz/enigma/TokenChecker.java @@ -16,7 +16,7 @@ import com.strobel.decompiler.languages.java.ast.CompilationUnit; import cuchaz.enigma.analysis.EntryReference; import cuchaz.enigma.analysis.SourceIndex; import cuchaz.enigma.analysis.Token; -import cuchaz.enigma.mapping.Entry; +import cuchaz.enigma.mapping.entry.Entry; import java.io.IOException; import java.util.Collection; -- cgit v1.2.3 From 8abd6860ce190c171124281709f01902358849ab Mon Sep 17 00:00:00 2001 From: Thiakil Date: Thu, 12 Jul 2018 07:35:04 +0800 Subject: remove raw casts to Object (cherry picked from commit 268e8bd3a292162c215723638665e32415207c28) --- src/main/java/cuchaz/enigma/Deobfuscator.java | 4 ++- .../oml/ast/transformers/RemoveObjectCasts.java | 39 ++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 src/main/java/oml/ast/transformers/RemoveObjectCasts.java (limited to 'src') diff --git a/src/main/java/cuchaz/enigma/Deobfuscator.java b/src/main/java/cuchaz/enigma/Deobfuscator.java index 6ea1c40b..5b210110 100644 --- a/src/main/java/cuchaz/enigma/Deobfuscator.java +++ b/src/main/java/cuchaz/enigma/Deobfuscator.java @@ -34,6 +34,7 @@ import cuchaz.enigma.mapping.entry.*; import cuchaz.enigma.throwables.IllegalNameException; import cuchaz.enigma.utils.Utils; import oml.ast.transformers.ObfuscatedEnumSwitchRewriterTransform; +import oml.ast.transformers.RemoveObjectCasts; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.ClassNode; @@ -661,7 +662,8 @@ public class Deobfuscator { public static void runCustomTransforms(AstBuilder builder, DecompilerContext context){ List transformers = Arrays.asList( - new ObfuscatedEnumSwitchRewriterTransform(context) + new ObfuscatedEnumSwitchRewriterTransform(context), + new RemoveObjectCasts(context) ); for (IAstTransform transform : transformers){ transform.run(builder.getCompilationUnit()); diff --git a/src/main/java/oml/ast/transformers/RemoveObjectCasts.java b/src/main/java/oml/ast/transformers/RemoveObjectCasts.java new file mode 100644 index 00000000..d7c3c4a6 --- /dev/null +++ b/src/main/java/oml/ast/transformers/RemoveObjectCasts.java @@ -0,0 +1,39 @@ +package oml.ast.transformers; + +import com.strobel.assembler.metadata.BuiltinTypes; +import com.strobel.decompiler.DecompilerContext; +import com.strobel.decompiler.languages.java.ast.AstNode; +import com.strobel.decompiler.languages.java.ast.CastExpression; +import com.strobel.decompiler.languages.java.ast.ContextTrackingVisitor; +import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; + +/** + * Created by Thiakil on 11/07/2018. + */ +public class RemoveObjectCasts implements IAstTransform { + private final DecompilerContext _context; + + public RemoveObjectCasts(DecompilerContext context) { + _context = context; + } + + @Override + public void run(AstNode compilationUnit) { + compilationUnit.acceptVisitor(new Visitor(_context), null); + } + + private final static class Visitor extends ContextTrackingVisitor{ + + protected Visitor(DecompilerContext context) { + super(context); + } + + @Override + public Void visitCastExpression(CastExpression node, Void data) { + if (node.getType().toTypeReference().equals(BuiltinTypes.Object)){ + node.replaceWith(node.getExpression()); + } + return super.visitCastExpression(node, data); + } + } +} -- cgit v1.2.3 From d0dfab41da9ba7ad5458287fa027a1ee4fd834e0 Mon Sep 17 00:00:00 2001 From: Thiakil Date: Fri, 20 Jul 2018 12:14:38 +0800 Subject: recursively check ClassMapping dirty state (cherry picked from commit 4a8ee4303ca1ab82da9499181122bfd7e3214a05) --- src/main/java/cuchaz/enigma/mapping/ClassMapping.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/main/java/cuchaz/enigma/mapping/ClassMapping.java b/src/main/java/cuchaz/enigma/mapping/ClassMapping.java index 8f3f2b2b..369ba8c6 100644 --- a/src/main/java/cuchaz/enigma/mapping/ClassMapping.java +++ b/src/main/java/cuchaz/enigma/mapping/ClassMapping.java @@ -526,7 +526,16 @@ public class ClassMapping implements Comparable { } public boolean isDirty() { - return isDirty; + return isDirty || areInnersDirty(); + } + + private boolean areInnersDirty(){ + for (ClassMapping c : this.innerClasses()){ + if (c.isDirty()){ + return true; + } + } + return false; } public void resetDirty() { -- cgit v1.2.3