summaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
authorGravatar gegy10002018-07-17 19:14:08 +0200
committerGravatar GitHub2018-07-17 19:14:08 +0200
commita88175ffc95792b88a8724f66db6dda2b8cc32ee (patch)
tree65895bbc6cf1766f4ca01e1257619ab1993e71dc /src/main
parentMerge pull request #3 from thiakil/src-jar (diff)
downloadenigma-a88175ffc95792b88a8724f66db6dda2b8cc32ee.tar.gz
enigma-a88175ffc95792b88a8724f66db6dda2b8cc32ee.tar.xz
enigma-a88175ffc95792b88a8724f66db6dda2b8cc32ee.zip
ASM Based Class Translator (#1)
* Initial port to ASM * Package updates * Annotation + inner class translation * Fix inner class mapping * More bytecode translation * Signature refactoring * Fix highlighting of mapped names * Fix parameter name offset * Fix anonymous class generation * Fix issues with inner class signature transformation * Fix bridged method detection * Fix compile issues * Resolve all failed tests * Apply deobfuscated name to transformed classes * Fix class signatures not being translated * Fix frame array type translation * Fix frame array type translation * Fix array translation in method calls * Fix method reference and bridge detection * Fix handling of null deobf mappings * Parameter translation in interfaces * Fix enum parameter index offset * Fix parsed local variable indexing * Fix stackoverflow on rebuilding method names * Ignore invalid decompiled variable indices * basic source jar * Output directly to file on source export * Make decompile parallel * fix incorrect super calls * Use previous save state to delete old mapping files * Fix old mappings not properly being removed * Fix old mappings not properly being removed * make isMethodProvider public (cherry picked from commit ebad6a9) * speed up Deobfuscator's getSources by using a single TranslatingTypeloader and caching the ClassLoaderTypeloader * ignore .idea project folders * move SynchronizedTypeLoader to a non-inner * fix signature remap of inners for now * index & resolve method/field references for usages view * Allow reader/writer subclasses to provide the underlying file operations * fix giving obf classes a name not removing them from the panel * buffer the ParsedJar class entry inputstream, allow use with a jarinputstream * make CachingClasspathTypeLoader public * make CachingClasspathTypeLoader public * support enum switches with obfuscated SwitchMaps
Diffstat (limited to 'src/main')
-rw-r--r--src/main/java/cuchaz/enigma/CachingClasspathTypeLoader.java20
-rw-r--r--src/main/java/cuchaz/enigma/CachingTypeLoader.java38
-rw-r--r--src/main/java/cuchaz/enigma/CommandMain.java2
-rw-r--r--src/main/java/cuchaz/enigma/Deobfuscator.java287
-rw-r--r--src/main/java/cuchaz/enigma/ITranslatingTypeLoader.java19
-rw-r--r--src/main/java/cuchaz/enigma/SynchronizedTypeLoader.java39
-rw-r--r--src/main/java/cuchaz/enigma/TranslatingTypeLoader.java187
-rw-r--r--src/main/java/cuchaz/enigma/analysis/Access.java11
-rw-r--r--src/main/java/cuchaz/enigma/analysis/BridgeMarker.java38
-rw-r--r--src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java12
-rw-r--r--src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java16
-rw-r--r--src/main/java/cuchaz/enigma/analysis/EntryReference.java18
-rw-r--r--src/main/java/cuchaz/enigma/analysis/EntryRenamer.java63
-rw-r--r--src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java29
-rw-r--r--src/main/java/cuchaz/enigma/analysis/IndexClassVisitor.java38
-rw-r--r--src/main/java/cuchaz/enigma/analysis/IndexInnerClassVisitor.java23
-rw-r--r--src/main/java/cuchaz/enigma/analysis/IndexReferenceVisitor.java77
-rw-r--r--src/main/java/cuchaz/enigma/analysis/JarClassIterator.java123
-rw-r--r--src/main/java/cuchaz/enigma/analysis/JarIndex.java635
-rw-r--r--src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java13
-rw-r--r--src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java16
-rw-r--r--src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java (renamed from src/main/java/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java)41
-rw-r--r--src/main/java/cuchaz/enigma/analysis/ParsedJar.java95
-rw-r--r--src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java2
-rw-r--r--src/main/java/cuchaz/enigma/analysis/SourceIndex.java2
-rw-r--r--src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java44
-rw-r--r--src/main/java/cuchaz/enigma/analysis/SourceIndexMethodVisitor.java (renamed from src/main/java/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java)154
-rw-r--r--src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java14
-rw-r--r--src/main/java/cuchaz/enigma/analysis/TranslationIndex.java122
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/AccessFlags.java80
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/ClassProtectifier.java54
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/ClassPublifier.java58
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/ClassRenamer.java539
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/ConstPoolEditor.java264
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/InfoType.java266
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/MethodParametersAttribute.java85
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java56
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java124
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java75
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java75
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java75
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java57
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java75
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java56
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java29
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/translators/ClassTranslator.java161
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/translators/InnerClassWriter.java144
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableTranslator.java142
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/translators/MethodParameterTranslator.java62
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/translators/TranslationAnnotationVisitor.java43
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/translators/TranslationClassVisitor.java113
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/translators/TranslationFieldVisitor.java33
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/translators/TranslationMethodVisitor.java191
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/translators/TranslationSignatureVisitor.java129
-rw-r--r--src/main/java/cuchaz/enigma/gui/ClassSelector.java8
-rw-r--r--src/main/java/cuchaz/enigma/gui/CodeReader.java6
-rw-r--r--src/main/java/cuchaz/enigma/gui/Gui.java67
-rw-r--r--src/main/java/cuchaz/enigma/gui/GuiController.java24
-rw-r--r--src/main/java/cuchaz/enigma/gui/GuiTricks.java5
-rw-r--r--src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java2
-rw-r--r--src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java2
-rw-r--r--src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java4
-rw-r--r--src/main/java/cuchaz/enigma/gui/panels/PanelObf.java2
-rw-r--r--src/main/java/cuchaz/enigma/mapping/ArgumentEntry.java110
-rw-r--r--src/main/java/cuchaz/enigma/mapping/BehaviorEntry.java16
-rw-r--r--src/main/java/cuchaz/enigma/mapping/ClassMapping.java210
-rw-r--r--src/main/java/cuchaz/enigma/mapping/ClassNameReplacer.java16
-rw-r--r--src/main/java/cuchaz/enigma/mapping/ConstructorEntry.java105
-rw-r--r--src/main/java/cuchaz/enigma/mapping/DirectionalTranslator.java370
-rw-r--r--src/main/java/cuchaz/enigma/mapping/EntryFactory.java132
-rw-r--r--src/main/java/cuchaz/enigma/mapping/FieldEntry.java87
-rw-r--r--src/main/java/cuchaz/enigma/mapping/FieldMapping.java38
-rw-r--r--src/main/java/cuchaz/enigma/mapping/LocalVariableEntry.java102
-rw-r--r--src/main/java/cuchaz/enigma/mapping/LocalVariableMapping.java (renamed from src/main/java/cuchaz/enigma/mapping/ArgumentMapping.java)15
-rw-r--r--src/main/java/cuchaz/enigma/mapping/Mappings.java58
-rw-r--r--src/main/java/cuchaz/enigma/mapping/MappingsChecker.java14
-rw-r--r--src/main/java/cuchaz/enigma/mapping/MappingsEnigmaReader.java167
-rw-r--r--src/main/java/cuchaz/enigma/mapping/MappingsEnigmaWriter.java124
-rw-r--r--src/main/java/cuchaz/enigma/mapping/MappingsRenamer.java164
-rw-r--r--src/main/java/cuchaz/enigma/mapping/MappingsSRGWriter.java7
-rw-r--r--src/main/java/cuchaz/enigma/mapping/MappingsTinyReader.java7
-rw-r--r--src/main/java/cuchaz/enigma/mapping/MemberMapping.java3
-rw-r--r--src/main/java/cuchaz/enigma/mapping/MethodDescriptor.java114
-rw-r--r--src/main/java/cuchaz/enigma/mapping/MethodEntry.java90
-rw-r--r--src/main/java/cuchaz/enigma/mapping/MethodMapping.java139
-rw-r--r--src/main/java/cuchaz/enigma/mapping/NameValidator.java16
-rw-r--r--src/main/java/cuchaz/enigma/mapping/ProcyonEntryFactory.java67
-rw-r--r--src/main/java/cuchaz/enigma/mapping/Signature.java128
-rw-r--r--src/main/java/cuchaz/enigma/mapping/TranslationDirection.java10
-rw-r--r--src/main/java/cuchaz/enigma/mapping/Translator.java365
-rw-r--r--src/main/java/cuchaz/enigma/mapping/TypeDescriptor.java (renamed from src/main/java/cuchaz/enigma/mapping/Type.java)117
-rw-r--r--src/main/java/cuchaz/enigma/mapping/entry/ClassDefEntry.java37
-rw-r--r--src/main/java/cuchaz/enigma/mapping/entry/ClassEntry.java (renamed from src/main/java/cuchaz/enigma/mapping/ClassEntry.java)32
-rw-r--r--src/main/java/cuchaz/enigma/mapping/entry/Entry.java (renamed from src/main/java/cuchaz/enigma/mapping/Entry.java)6
-rw-r--r--src/main/java/cuchaz/enigma/mapping/entry/EntryFactory.java49
-rw-r--r--src/main/java/cuchaz/enigma/mapping/entry/FieldDefEntry.java43
-rw-r--r--src/main/java/cuchaz/enigma/mapping/entry/FieldEntry.java77
-rw-r--r--src/main/java/cuchaz/enigma/mapping/entry/LocalVariableDefEntry.java57
-rw-r--r--src/main/java/cuchaz/enigma/mapping/entry/LocalVariableEntry.java82
-rw-r--r--src/main/java/cuchaz/enigma/mapping/entry/MethodDefEntry.java54
-rw-r--r--src/main/java/cuchaz/enigma/mapping/entry/MethodEntry.java80
-rw-r--r--src/main/java/cuchaz/enigma/mapping/entry/ProcyonEntryFactory.java48
-rw-r--r--src/main/java/cuchaz/enigma/mapping/entry/ReferencedEntryPool.java53
-rw-r--r--src/main/java/cuchaz/enigma/throwables/MappingParseException.java7
-rw-r--r--src/main/java/oml/ast/transformers/ObfuscatedEnumSwitchRewriterTransform.java414
105 files changed, 3941 insertions, 5173 deletions
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 @@
1package cuchaz.enigma;
2
3import com.strobel.assembler.metadata.Buffer;
4import com.strobel.assembler.metadata.ClasspathTypeLoader;
5import com.strobel.assembler.metadata.ITypeLoader;
6
7/**
8 * Caching version of {@link ClasspathTypeLoader}
9 */
10public class CachingClasspathTypeLoader extends CachingTypeLoader {
11 private final ITypeLoader classpathLoader = new ClasspathTypeLoader();
12
13 protected byte[] doLoad(String className) {
14 Buffer parentBuf = new Buffer();
15 if (classpathLoader.tryLoadType(className, parentBuf)) {
16 return parentBuf.array();
17 }
18 return EMPTY_ARRAY;//need to return *something* as null means no store
19 }
20}
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 @@
1package cuchaz.enigma;
2
3import com.google.common.collect.Maps;
4import com.strobel.assembler.metadata.Buffer;
5import com.strobel.assembler.metadata.ITypeLoader;
6
7import java.util.Map;
8
9/**
10 * Common cache functions
11 */
12public abstract class CachingTypeLoader implements ITypeLoader {
13 protected static final byte[] EMPTY_ARRAY = {};
14
15 private final Map<String, byte[]> cache = Maps.newHashMap();
16
17 protected abstract byte[] doLoad(String className);
18
19 @Override
20 public boolean tryLoadType(String className, Buffer out) {
21
22 // check the cache
23 byte[] data = this.cache.computeIfAbsent(className, this::doLoad);
24
25 if (data == EMPTY_ARRAY) {
26 return false;
27 }
28
29 out.reset(data.length);
30 System.arraycopy(data, 0, out.array(), out.position(), data.length);
31 out.position(0);
32 return true;
33 }
34
35 public void clearCache() {
36 this.cache.clear();
37 }
38}
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 {
99 private static void convertMappings(String[] args) throws Exception { 99 private static void convertMappings(String[] args) throws Exception {
100 File fileMappings = getReadableFile(getArg(args, 1, "enigma mapping", true)); 100 File fileMappings = getReadableFile(getArg(args, 1, "enigma mapping", true));
101 File result = getWritableFile(getArg(args, 2, "enigma mapping", true)); 101 File result = getWritableFile(getArg(args, 2, "enigma mapping", true));
102 String name = getArg(args, 3, "format type", true); 102 String name = getArg(args, 3, "format desc", true);
103 Mappings.FormatType formatType; 103 Mappings.FormatType formatType;
104 try { 104 try {
105 formatType = Mappings.FormatType.valueOf(name.toUpperCase()); 105 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 @@
11 11
12package cuchaz.enigma; 12package cuchaz.enigma;
13 13
14import com.google.common.base.Charsets; 14import com.google.common.base.Stopwatch;
15import com.google.common.collect.Lists; 15import com.google.common.collect.Lists;
16import com.google.common.collect.Maps; 16import com.google.common.collect.Maps;
17import com.google.common.collect.Sets; 17import com.google.common.collect.Sets;
@@ -25,60 +25,66 @@ import com.strobel.decompiler.languages.java.JavaOutputVisitor;
25import com.strobel.decompiler.languages.java.ast.AstBuilder; 25import com.strobel.decompiler.languages.java.ast.AstBuilder;
26import com.strobel.decompiler.languages.java.ast.CompilationUnit; 26import com.strobel.decompiler.languages.java.ast.CompilationUnit;
27import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor; 27import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor;
28import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform;
28import cuchaz.enigma.analysis.*; 29import cuchaz.enigma.analysis.*;
29import cuchaz.enigma.bytecode.ClassProtectifier; 30import cuchaz.enigma.bytecode.ClassProtectifier;
30import cuchaz.enigma.bytecode.ClassPublifier; 31import cuchaz.enigma.bytecode.ClassPublifier;
31import cuchaz.enigma.mapping.*; 32import cuchaz.enigma.mapping.*;
33import cuchaz.enigma.mapping.entry.*;
32import cuchaz.enigma.throwables.IllegalNameException; 34import cuchaz.enigma.throwables.IllegalNameException;
33import cuchaz.enigma.utils.Utils; 35import cuchaz.enigma.utils.Utils;
34import javassist.CtClass; 36import oml.ast.transformers.ObfuscatedEnumSwitchRewriterTransform;
35import javassist.bytecode.Descriptor; 37import org.objectweb.asm.ClassWriter;
38import org.objectweb.asm.Opcodes;
39import org.objectweb.asm.tree.ClassNode;
36 40
37import java.io.*; 41import java.io.*;
38import java.util.*; 42import java.util.*;
43import java.util.concurrent.atomic.AtomicInteger;
39import java.util.jar.JarEntry; 44import java.util.jar.JarEntry;
40import java.util.jar.JarFile; 45import java.util.jar.JarFile;
41import java.util.jar.JarOutputStream; 46import java.util.jar.JarOutputStream;
42 47
43public class Deobfuscator { 48public class Deobfuscator {
44 49
45 private final JarFile jar; 50 private final ReferencedEntryPool entryPool = new ReferencedEntryPool();
51 private final ParsedJar parsedJar;
46 private final DecompilerSettings settings; 52 private final DecompilerSettings settings;
47 private final JarIndex jarIndex; 53 private final JarIndex jarIndex;
48 private final MappingsRenamer renamer; 54 private final MappingsRenamer renamer;
49 private final Map<TranslationDirection, Translator> translatorCache; 55 private final Map<TranslationDirection, Translator> translatorCache;
50 private Mappings mappings; 56 private Mappings mappings;
51 57
52 public Deobfuscator(JarFile jar) { 58 public Deobfuscator(ParsedJar jar) {
53 this.jar = jar; 59 this.parsedJar = jar;
54 60
55 // build the jar index 61 // build the jar index
56 this.jarIndex = new JarIndex(); 62 this.jarIndex = new JarIndex(entryPool);
57 this.jarIndex.indexJar(this.jar, true); 63 this.jarIndex.indexJar(this.parsedJar, true);
58 64
59 // config the decompiler 65 // config the decompiler
60 this.settings = DecompilerSettings.javaDefaults(); 66 this.settings = DecompilerSettings.javaDefaults();
61 this.settings.setMergeVariables(Utils.getSystemPropertyAsBoolean("enigma.mergeVariables", true)); 67 this.settings.setMergeVariables(Utils.getSystemPropertyAsBoolean("enigma.mergeVariables", true));
62 this.settings.setForceExplicitImports(Utils.getSystemPropertyAsBoolean("enigma.forceExplicitImports", true)); 68 this.settings.setForceExplicitImports(Utils.getSystemPropertyAsBoolean("enigma.forceExplicitImports", true));
63 this.settings.setForceExplicitTypeArguments( 69 this.settings.setForceExplicitTypeArguments(
64 Utils.getSystemPropertyAsBoolean("enigma.forceExplicitTypeArguments", true)); 70 Utils.getSystemPropertyAsBoolean("enigma.forceExplicitTypeArguments", true));
65 // DEBUG 71 // DEBUG
66 this.settings.setShowDebugLineNumbers(Utils.getSystemPropertyAsBoolean("enigma.showDebugLineNumbers", false)); 72 this.settings.setShowDebugLineNumbers(Utils.getSystemPropertyAsBoolean("enigma.showDebugLineNumbers", false));
67 this.settings.setShowSyntheticMembers(Utils.getSystemPropertyAsBoolean("enigma.showSyntheticMembers", false)); 73 this.settings.setShowSyntheticMembers(Utils.getSystemPropertyAsBoolean("enigma.showSyntheticMembers", false));
68 74
69 // init defaults 75 // init defaults
70 this.translatorCache = Maps.newTreeMap(); 76 this.translatorCache = Maps.newTreeMap();
71 this.renamer = new MappingsRenamer(this.jarIndex, null); 77 this.renamer = new MappingsRenamer(this.jarIndex, null, this.entryPool);
72 // init mappings 78 // init mappings
73 setMappings(new Mappings()); 79 setMappings(new Mappings());
74 } 80 }
75 81
76 public JarFile getJar() { 82 public Deobfuscator(JarFile jar) throws IOException {
77 return this.jar; 83 this(new ParsedJar(jar));
78 } 84 }
79 85
80 public String getJarName() { 86 public ParsedJar getJar() {
81 return this.jar.getName(); 87 return this.parsedJar;
82 } 88 }
83 89
84 public JarIndex getJarIndex() { 90 public JarIndex getJarIndex() {
@@ -102,16 +108,16 @@ public class Deobfuscator {
102 MappingsChecker checker = new MappingsChecker(this.jarIndex); 108 MappingsChecker checker = new MappingsChecker(this.jarIndex);
103 checker.dropBrokenMappings(val); 109 checker.dropBrokenMappings(val);
104 if (warnAboutDrops) { 110 if (warnAboutDrops) {
105 for (java.util.Map.Entry<ClassEntry, ClassMapping> mapping : checker.getDroppedClassMappings().entrySet()) { 111 for (Map.Entry<ClassEntry, ClassMapping> mapping : checker.getDroppedClassMappings().entrySet()) {
106 System.out.println("WARNING: Couldn't find class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped."); 112 System.out.println("WARNING: Couldn't find class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped.");
107 } 113 }
108 for (java.util.Map.Entry<ClassEntry, ClassMapping> mapping : checker.getDroppedInnerClassMappings().entrySet()) { 114 for (Map.Entry<ClassEntry, ClassMapping> mapping : checker.getDroppedInnerClassMappings().entrySet()) {
109 System.out.println("WARNING: Couldn't find inner class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped."); 115 System.out.println("WARNING: Couldn't find inner class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped.");
110 } 116 }
111 for (java.util.Map.Entry<FieldEntry, FieldMapping> mapping : checker.getDroppedFieldMappings().entrySet()) { 117 for (Map.Entry<FieldEntry, FieldMapping> mapping : checker.getDroppedFieldMappings().entrySet()) {
112 System.out.println("WARNING: Couldn't find field entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped."); 118 System.out.println("WARNING: Couldn't find field entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped.");
113 } 119 }
114 for (java.util.Map.Entry<BehaviorEntry, MethodMapping> mapping : checker.getDroppedMethodMappings().entrySet()) { 120 for (Map.Entry<MethodEntry, MethodMapping> mapping : checker.getDroppedMethodMappings().entrySet()) {
115 System.out.println("WARNING: Couldn't find behavior entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped."); 121 System.out.println("WARNING: Couldn't find behavior entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped.");
116 } 122 }
117 } 123 }
@@ -123,7 +129,7 @@ public class Deobfuscator {
123 129
124 public Translator getTranslator(TranslationDirection direction) { 130 public Translator getTranslator(TranslationDirection direction) {
125 return this.translatorCache.computeIfAbsent(direction, 131 return this.translatorCache.computeIfAbsent(direction,
126 k -> this.mappings.getTranslator(direction, this.jarIndex.getTranslationIndex())); 132 k -> this.mappings.getTranslator(direction, this.jarIndex.getTranslationIndex()));
127 } 133 }
128 134
129 public void getSeparatedClasses(List<ClassEntry> obfClasses, List<ClassEntry> deobfClasses) { 135 public void getSeparatedClasses(List<ClassEntry> obfClasses, List<ClassEntry> deobfClasses) {
@@ -150,14 +156,19 @@ public class Deobfuscator {
150 156
151 public TranslatingTypeLoader createTypeLoader() { 157 public TranslatingTypeLoader createTypeLoader() {
152 return new TranslatingTypeLoader( 158 return new TranslatingTypeLoader(
153 this.jar, 159 this.parsedJar,
154 this.jarIndex, 160 this.jarIndex,
155 getTranslator(TranslationDirection.Obfuscating), 161 this.entryPool,
156 getTranslator(TranslationDirection.Deobfuscating) 162 getTranslator(TranslationDirection.OBFUSCATING),
163 getTranslator(TranslationDirection.DEOBFUSCATING)
157 ); 164 );
158 } 165 }
159 166
160 public CompilationUnit getSourceTree(String className) { 167 public CompilationUnit getSourceTree(String className) {
168 return getSourceTree(className, createTypeLoader());
169 }
170
171 public CompilationUnit getSourceTree(String className, ITranslatingTypeLoader loader) {
161 172
162 // we don't know if this class name is obfuscated or deobfuscated 173 // we don't know if this class name is obfuscated or deobfuscated
163 // we need to tell the decompiler the deobfuscated name so it doesn't get freaked out 174 // we need to tell the decompiler the deobfuscated name so it doesn't get freaked out
@@ -172,15 +183,14 @@ public class Deobfuscator {
172 deobfClassName = classMapping.getDeobfName(); 183 deobfClassName = classMapping.getDeobfName();
173 } 184 }
174 185
175 // set the type loader 186 // set the desc loader
176 TranslatingTypeLoader loader = createTypeLoader();
177 this.settings.setTypeLoader(loader); 187 this.settings.setTypeLoader(loader);
178 188
179 // see if procyon can find the type 189 // see if procyon can find the desc
180 TypeReference type = new MetadataSystem(loader).lookupType(deobfClassName); 190 TypeReference type = new MetadataSystem(loader).lookupType(deobfClassName);
181 if (type == null) { 191 if (type == null) {
182 throw new Error(String.format("Unable to find type: %s (deobf: %s)\nTried class names: %s", 192 throw new Error(String.format("Unable to find desc: %s (deobf: %s)\nTried class names: %s",
183 className, deobfClassName, loader.getClassNamesToTry(deobfClassName) 193 className, deobfClassName, loader.getClassNamesToTry(deobfClassName)
184 )); 194 ));
185 } 195 }
186 TypeDefinition resolvedType = type.resolve(); 196 TypeDefinition resolvedType = type.resolve();
@@ -192,6 +202,7 @@ public class Deobfuscator {
192 AstBuilder builder = new AstBuilder(context); 202 AstBuilder builder = new AstBuilder(context);
193 builder.addType(resolvedType); 203 builder.addType(resolvedType);
194 builder.runTransformations(null); 204 builder.runTransformations(null);
205 runCustomTransforms(builder, context);
195 return builder.getCompilationUnit(); 206 return builder.getCompilationUnit();
196 } 207 }
197 208
@@ -208,7 +219,7 @@ public class Deobfuscator {
208 } else { 219 } else {
209 index = new SourceIndex(source); 220 index = new SourceIndex(source);
210 } 221 }
211 sourceTree.acceptVisitor(new SourceIndexVisitor(), index); 222 sourceTree.acceptVisitor(new SourceIndexVisitor(entryPool), index);
212 223
213 // DEBUG 224 // DEBUG
214 // sourceTree.acceptVisitor( new TreeDumpVisitor( new File( "tree.txt" ) ), null ); 225 // sourceTree.acceptVisitor( new TreeDumpVisitor( new File( "tree.txt" ) ), null );
@@ -221,10 +232,10 @@ public class Deobfuscator {
221 Entry obfEntry = obfuscateEntry(deobfReference.entry); 232 Entry obfEntry = obfuscateEntry(deobfReference.entry);
222 233
223 // try to resolve the class 234 // try to resolve the class
224 ClassEntry resolvedObfClassEntry = this.jarIndex.getTranslationIndex().resolveEntryClass(obfEntry); 235 ClassEntry resolvedObfClassEntry = this.jarIndex.getTranslationIndex().resolveEntryOwner(obfEntry);
225 if (resolvedObfClassEntry != null && !resolvedObfClassEntry.equals(obfEntry.getClassEntry())) { 236 if (resolvedObfClassEntry != null && !resolvedObfClassEntry.equals(obfEntry.getOwnerClassEntry())) {
226 // change the class of the entry 237 // change the class of the entry
227 obfEntry = obfEntry.cloneToNewClass(resolvedObfClassEntry); 238 obfEntry = obfEntry.updateOwnership(resolvedObfClassEntry);
228 239
229 // save the new deobfuscated reference 240 // save the new deobfuscated reference
230 deobfReference.entry = deobfuscateEntry(obfEntry); 241 deobfReference.entry = deobfuscateEntry(obfEntry);
@@ -262,23 +273,29 @@ public class Deobfuscator {
262 progress.init(classEntries.size(), "Decompiling classes..."); 273 progress.init(classEntries.size(), "Decompiling classes...");
263 } 274 }
264 275
276 //create a common instance outside the loop as mappings shouldn't be changing while this is happening
277 //synchronized to make sure the parallelStream doesn't CME with the cache
278 ITranslatingTypeLoader typeLoader = new SynchronizedTypeLoader(createTypeLoader());
279
265 // DEOBFUSCATE ALL THE THINGS!! @_@ 280 // DEOBFUSCATE ALL THE THINGS!! @_@
266 int i = 0; 281 Stopwatch stopwatch = Stopwatch.createStarted();
267 for (ClassEntry obfClassEntry : classEntries) { 282 AtomicInteger count = new AtomicInteger();
283 classEntries.parallelStream().forEach(obfClassEntry -> {
268 ClassEntry deobfClassEntry = deobfuscateEntry(new ClassEntry(obfClassEntry)); 284 ClassEntry deobfClassEntry = deobfuscateEntry(new ClassEntry(obfClassEntry));
269 if (progress != null) { 285 if (progress != null) {
270 progress.onProgress(i++, deobfClassEntry.toString()); 286 progress.onProgress(count.getAndIncrement(), deobfClassEntry.toString());
271 } 287 }
272 288
273 try { 289 try {
274 // get the source 290 // get the source
275 String source = getSource(getSourceTree(obfClassEntry.getName())); 291 CompilationUnit sourceTree = getSourceTree(obfClassEntry.getName(), typeLoader);
276 292
277 // write the file 293 // write the file
278 File file = new File(dirOut, deobfClassEntry.getName().replace('.', '/') + ".java"); 294 File file = new File(dirOut, deobfClassEntry.getName().replace('.', '/') + ".java");
279 file.getParentFile().mkdirs(); 295 file.getParentFile().mkdirs();
280 try (OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(file), Charsets.UTF_8)) { 296 try (Writer writer = new BufferedWriter(new FileWriter(file))) {
281 out.write(source); 297 sourceTree.acceptVisitor(new InsertParenthesesVisitor(), null);
298 sourceTree.acceptVisitor(new JavaOutputVisitor(new PlainTextOutput(writer), settings), null);
282 } 299 }
283 } catch (Throwable t) { 300 } catch (Throwable t) {
284 // don't crash the whole world here, just log the error and keep going 301 // don't crash the whole world here, just log the error and keep going
@@ -286,9 +303,11 @@ public class Deobfuscator {
286 System.err.println("Unable to deobfuscate class " + deobfClassEntry + " (" + obfClassEntry + ")"); 303 System.err.println("Unable to deobfuscate class " + deobfClassEntry + " (" + obfClassEntry + ")");
287 t.printStackTrace(System.err); 304 t.printStackTrace(System.err);
288 } 305 }
289 } 306 });
307 stopwatch.stop();
308 System.out.println("writeSources Done in : " + stopwatch.toString());
290 if (progress != null) { 309 if (progress != null) {
291 progress.onProgress(i, "Done!"); 310 progress.onProgress(count.get(), "Done:");
292 } 311 }
293 } 312 }
294 313
@@ -305,18 +324,14 @@ public class Deobfuscator {
305 } 324 }
306 } 325 }
307 326
308 private boolean isBehaviorProvider(ClassEntry classObfEntry, BehaviorEntry behaviorEntry) { 327 public boolean isMethodProvider(ClassEntry classObfEntry, MethodEntry methodEntry) {
309 if (behaviorEntry instanceof MethodEntry) { 328 Set<ClassEntry> classEntries = new HashSet<>();
310 MethodEntry methodEntry = (MethodEntry) behaviorEntry; 329 addAllPotentialAncestors(classEntries, classObfEntry);
311
312 Set<ClassEntry> classEntries = new HashSet<>();
313 addAllPotentialAncestors(classEntries, classObfEntry);
314 330
315 for (ClassEntry parentEntry : classEntries) { 331 for (ClassEntry parentEntry : classEntries) {
316 MethodEntry ancestorMethodEntry = new MethodEntry(parentEntry, methodEntry.getName(), methodEntry.getSignature()); 332 MethodEntry ancestorMethodEntry = entryPool.getMethod(parentEntry, methodEntry.getName(), methodEntry.getDesc());
317 if (jarIndex.containsObfBehavior(ancestorMethodEntry)) { 333 if (jarIndex.containsObfMethod(ancestorMethodEntry)) {
318 return false; 334 return false;
319 }
320 } 335 }
321 } 336 }
322 337
@@ -332,9 +347,6 @@ public class Deobfuscator {
332 for (ClassMapping classMapping : Lists.newArrayList(getMappings().classes())) { 347 for (ClassMapping classMapping : Lists.newArrayList(getMappings().classes())) {
333 progress.onProgress(i++, classMapping.getDeobfName()); 348 progress.onProgress(i++, classMapping.getDeobfName());
334 rebuildMethodNames(classMapping, renameClassMap); 349 rebuildMethodNames(classMapping, renameClassMap);
335 for(ClassMapping innerClass : classMapping.innerClasses()){
336 rebuildMethodNames(innerClass, renameClassMap);
337 }
338 } 350 }
339 351
340 for (Map.Entry<ClassMapping, Map<Entry, String>> renameClassMapEntry : renameClassMap.entrySet()) { 352 for (Map.Entry<ClassMapping, Map<Entry, String>> renameClassMapEntry : renameClassMap.entrySet()) {
@@ -356,29 +368,29 @@ public class Deobfuscator {
356 368
357 try { 369 try {
358 rename(obfEntry, name); 370 rename(obfEntry, name);
359 } catch (IllegalNameException exception) 371 } catch (IllegalNameException exception) {
360 {
361 System.out.println("WARNING: " + exception.getMessage()); 372 System.out.println("WARNING: " + exception.getMessage());
362 } 373 }
363 } 374 }
364 } 375 }
365 } 376 }
366 377
367 private void rebuildMethodNames(ClassMapping classMapping, Map<ClassMapping, Map<Entry, String>> renameClassMap){ 378 private void rebuildMethodNames(ClassMapping classMapping, Map<ClassMapping, Map<Entry, String>> renameClassMap) {
368 Map<Entry, String> renameEntries = new HashMap<>(); 379 Map<Entry, String> renameEntries = new HashMap<>();
369 380
370 for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) { 381 for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) {
371 ClassEntry classObfEntry = classMapping.getObfEntry(); 382 ClassEntry classObfEntry = classMapping.getObfEntry();
372 BehaviorEntry obfEntry = methodMapping.getObfEntry(classObfEntry); 383 MethodEntry obfEntry = methodMapping.getObfEntry(classObfEntry);
373 384
374 if (isBehaviorProvider(classObfEntry, obfEntry)) { 385 if (isMethodProvider(classObfEntry, obfEntry)) {
375 if (hasDeobfuscatedName(obfEntry) && !(obfEntry instanceof ConstructorEntry) 386 if (hasDeobfuscatedName(obfEntry)
376 && !(methodMapping.getDeobfName().equals(methodMapping.getObfName()))) { 387 && !(methodMapping.getDeobfName().equals(methodMapping.getObfName()))) {
377 renameEntries.put(obfEntry, methodMapping.getDeobfName()); 388 renameEntries.put(obfEntry, methodMapping.getDeobfName());
378 } 389 }
379 390
380 for (ArgumentMapping argumentMapping : Lists.newArrayList(methodMapping.arguments())) { 391 ArrayList<LocalVariableMapping> arguments = Lists.newArrayList(methodMapping.arguments());
381 Entry argObfEntry = argumentMapping.getObfEntry(obfEntry); 392 for (LocalVariableMapping localVariableMapping : arguments) {
393 Entry argObfEntry = localVariableMapping.getObfEntry(obfEntry);
382 if (hasDeobfuscatedName(argObfEntry)) { 394 if (hasDeobfuscatedName(argObfEntry)) {
383 renameEntries.put(argObfEntry, deobfuscateEntry(argObfEntry).getName()); 395 renameEntries.put(argObfEntry, deobfuscateEntry(argObfEntry).getName());
384 } 396 }
@@ -386,49 +398,58 @@ public class Deobfuscator {
386 } 398 }
387 } 399 }
388 400
401 classMapping.markDirty();
389 renameClassMap.put(classMapping, renameEntries); 402 renameClassMap.put(classMapping, renameEntries);
403 for (ClassMapping innerClass : classMapping.innerClasses()) {
404 rebuildMethodNames(innerClass, renameClassMap);
405 }
390 } 406 }
391 407
392 408
393
394 public void writeJar(File out, ProgressListener progress) { 409 public void writeJar(File out, ProgressListener progress) {
395 transformJar(out, progress, createTypeLoader()::transformClass); 410 transformJar(out, progress, createTypeLoader()::transformInto);
396 } 411 }
397 412
398 public void protectifyJar(File out, ProgressListener progress) { 413 public void protectifyJar(File out, ProgressListener progress) {
399 transformJar(out, progress, ClassProtectifier::protectify); 414 transformJar(out, progress, (node, writer) -> {
415 node.accept(new ClassProtectifier(Opcodes.ASM5, writer));
416 return node.name;
417 });
400 } 418 }
401 419
402 public void publifyJar(File out, ProgressListener progress) { 420 public void publifyJar(File out, ProgressListener progress) {
403 transformJar(out, progress, ClassPublifier::publify); 421 transformJar(out, progress, (node, writer) -> {
422 node.accept(new ClassPublifier(Opcodes.ASM5, writer));
423 return node.name;
424 });
404 } 425 }
405 426
406 public void transformJar(File out, ProgressListener progress, ClassTransformer transformer) { 427 public void transformJar(File out, ProgressListener progress, ClassTransformer transformer) {
407 try (JarOutputStream outJar = new JarOutputStream(new FileOutputStream(out))) { 428 try (JarOutputStream outJar = new JarOutputStream(new FileOutputStream(out))) {
408 if (progress != null) { 429 if (progress != null) {
409 progress.init(JarClassIterator.getClassEntries(this.jar).size(), "Transforming classes..."); 430 progress.init(parsedJar.getClassCount(), "Transforming classes...");
410 } 431 }
411 432
412 int i = 0; 433 AtomicInteger i = new AtomicInteger();
413 for (CtClass c : JarClassIterator.classes(this.jar)) { 434 parsedJar.visit(node -> {
414 if (progress != null) { 435 if (progress != null) {
415 progress.onProgress(i++, c.getName()); 436 progress.onProgress(i.getAndIncrement(), node.name);
416 } 437 }
417 438
418 try { 439 try {
419 c = transformer.transform(c); 440 ClassWriter writer = new ClassWriter(0);
420 outJar.putNextEntry(new JarEntry(c.getName().replace('.', '/') + ".class")); 441 String transformedName = transformer.transform(node, writer);
421 outJar.write(c.toBytecode()); 442 outJar.putNextEntry(new JarEntry(transformedName.replace('.', '/') + ".class"));
443 outJar.write(writer.toByteArray());
422 outJar.closeEntry(); 444 outJar.closeEntry();
423 } catch (Throwable t) { 445 } catch (Throwable t) {
424 throw new Error("Unable to transform class " + c.getName(), t); 446 throw new Error("Unable to transform class " + node.name, t);
425 } 447 }
426 } 448 });
449
427 if (progress != null) { 450 if (progress != null) {
428 progress.onProgress(i, "Done!"); 451 progress.onProgress(i.get(), "Done!");
429 } 452 }
430
431 outJar.close();
432 } catch (IOException ex) { 453 } catch (IOException ex) {
433 throw new Error("Unable to write to Jar file!"); 454 throw new Error("Unable to write to Jar file!");
434 } 455 }
@@ -438,14 +459,22 @@ public class Deobfuscator {
438 if (deobfEntry == null) { 459 if (deobfEntry == null) {
439 return null; 460 return null;
440 } 461 }
441 return getTranslator(TranslationDirection.Obfuscating).translateEntry(deobfEntry); 462 T translatedEntry = getTranslator(TranslationDirection.OBFUSCATING).getTranslatedEntry(deobfEntry);
463 if (translatedEntry == null) {
464 return deobfEntry;
465 }
466 return translatedEntry;
442 } 467 }
443 468
444 public <T extends Entry> T deobfuscateEntry(T obfEntry) { 469 public <T extends Entry> T deobfuscateEntry(T obfEntry) {
445 if (obfEntry == null) { 470 if (obfEntry == null) {
446 return null; 471 return null;
447 } 472 }
448 return getTranslator(TranslationDirection.Deobfuscating).translateEntry(obfEntry); 473 T translatedEntry = getTranslator(TranslationDirection.DEOBFUSCATING).getTranslatedEntry(obfEntry);
474 if (translatedEntry == null) {
475 return obfEntry;
476 }
477 return translatedEntry;
449 } 478 }
450 479
451 public <E extends Entry, C extends Entry> EntryReference<E, C> obfuscateReference(EntryReference<E, C> deobfReference) { 480 public <E extends Entry, C extends Entry> EntryReference<E, C> obfuscateReference(EntryReference<E, C> deobfReference) {
@@ -473,7 +502,7 @@ public class Deobfuscator {
473 // HACKHACK: Object methods are not obfuscated identifiers 502 // HACKHACK: Object methods are not obfuscated identifiers
474 MethodEntry obfMethodEntry = (MethodEntry) obfEntry; 503 MethodEntry obfMethodEntry = (MethodEntry) obfEntry;
475 String name = obfMethodEntry.getName(); 504 String name = obfMethodEntry.getName();
476 String sig = obfMethodEntry.getSignature().toString(); 505 String sig = obfMethodEntry.getDesc().toString();
477 if (name.equals("clone") && sig.equals("()Ljava/lang/Object;")) { 506 if (name.equals("clone") && sig.equals("()Ljava/lang/Object;")) {
478 return false; 507 return false;
479 } else if (name.equals("equals") && sig.equals("(Ljava/lang/Object;)Z")) { 508 } else if (name.equals("equals") && sig.equals("(Ljava/lang/Object;)Z")) {
@@ -499,7 +528,7 @@ public class Deobfuscator {
499 } 528 }
500 529
501 // FIXME: HACK EVEN MORE HACK! 530 // FIXME: HACK EVEN MORE HACK!
502 if (hack && this.jarIndex.containsObfEntry(obfEntry.getClassEntry())) 531 if (hack && this.jarIndex.containsObfEntry(obfEntry.getOwnerClassEntry()))
503 return true; 532 return true;
504 } 533 }
505 534
@@ -515,27 +544,24 @@ public class Deobfuscator {
515 } 544 }
516 545
517 public boolean hasDeobfuscatedName(Entry obfEntry) { 546 public boolean hasDeobfuscatedName(Entry obfEntry) {
518 Translator translator = getTranslator(TranslationDirection.Deobfuscating); 547 Translator translator = getTranslator(TranslationDirection.DEOBFUSCATING);
519 if (obfEntry instanceof ClassEntry) { 548 if (obfEntry instanceof ClassEntry) {
520 ClassEntry obfClass = (ClassEntry) obfEntry; 549 ClassEntry obfClass = (ClassEntry) obfEntry;
521 List<ClassMapping> mappingChain = this.mappings.getClassMappingChain(obfClass); 550 List<ClassMapping> mappingChain = this.mappings.getClassMappingChain(obfClass);
522 ClassMapping classMapping = mappingChain.get(mappingChain.size() - 1); 551 ClassMapping classMapping = mappingChain.get(mappingChain.size() - 1);
523 return classMapping != null && classMapping.getDeobfName() != null; 552 return classMapping != null && classMapping.getDeobfName() != null;
524 } else if (obfEntry instanceof FieldEntry) { 553 } else if (obfEntry instanceof FieldEntry) {
525 return translator.translate((FieldEntry) obfEntry) != null; 554 return translator.hasFieldMapping((FieldEntry) obfEntry);
526 } else if (obfEntry instanceof MethodEntry) { 555 } else if (obfEntry instanceof MethodEntry) {
527 return translator.translate((MethodEntry) obfEntry) != null; 556 MethodEntry methodEntry = (MethodEntry) obfEntry;
528 } else if (obfEntry instanceof ConstructorEntry) { 557 if (methodEntry.isConstructor()) {
529 // constructors have no names 558 return false;
530 return false; 559 }
531 } else if (obfEntry instanceof ArgumentEntry) { 560 return translator.hasMethodMapping(methodEntry);
532 return translator.translate((ArgumentEntry) obfEntry) != null;
533 } else if (obfEntry instanceof LocalVariableEntry) { 561 } else if (obfEntry instanceof LocalVariableEntry) {
534 // TODO: Implement it 562 return translator.hasLocalVariableMapping((LocalVariableEntry) obfEntry);
535 //return translator.translate((LocalVariableEntry)obfEntry) != null;
536 return false;
537 } else { 563 } else {
538 throw new Error("Unknown entry type: " + obfEntry.getClass().getName()); 564 throw new Error("Unknown entry desc: " + obfEntry.getClass().getName());
539 } 565 }
540 } 566 }
541 567
@@ -547,19 +573,18 @@ public class Deobfuscator {
547 573
548 public void rename(Entry obfEntry, String newName, boolean clearCache) { 574 public void rename(Entry obfEntry, String newName, boolean clearCache) {
549 if (obfEntry instanceof ClassEntry) { 575 if (obfEntry instanceof ClassEntry) {
550 this.renamer.setClassName((ClassEntry) obfEntry, Descriptor.toJvmName(newName)); 576 this.renamer.setClassName((ClassEntry) obfEntry, newName);
551 } else if (obfEntry instanceof FieldEntry) { 577 } else if (obfEntry instanceof FieldEntry) {
552 this.renamer.setFieldName((FieldEntry) obfEntry, newName); 578 this.renamer.setFieldName((FieldEntry) obfEntry, newName);
553 } else if (obfEntry instanceof MethodEntry) { 579 } else if (obfEntry instanceof MethodEntry) {
580 if (((MethodEntry) obfEntry).isConstructor()) {
581 throw new IllegalArgumentException("Cannot rename constructors");
582 }
554 this.renamer.setMethodTreeName((MethodEntry) obfEntry, newName); 583 this.renamer.setMethodTreeName((MethodEntry) obfEntry, newName);
555 } else if (obfEntry instanceof ConstructorEntry) {
556 throw new IllegalArgumentException("Cannot rename constructors");
557 } else if (obfEntry instanceof ArgumentEntry) {
558 this.renamer.setArgumentTreeName((ArgumentEntry) obfEntry, newName);
559 } else if (obfEntry instanceof LocalVariableEntry) { 584 } else if (obfEntry instanceof LocalVariableEntry) {
560 // TODO: Implement it 585 this.renamer.setLocalVariableTreeName((LocalVariableEntry) obfEntry, newName);
561 } else { 586 } else {
562 throw new Error("Unknown entry type: " + obfEntry.getClass().getName()); 587 throw new Error("Unknown entry desc: " + obfEntry.getClass().getName());
563 } 588 }
564 589
565 // clear caches 590 // clear caches
@@ -573,13 +598,14 @@ public class Deobfuscator {
573 } else if (obfEntry instanceof FieldEntry) { 598 } else if (obfEntry instanceof FieldEntry) {
574 this.renamer.removeFieldMapping((FieldEntry) obfEntry); 599 this.renamer.removeFieldMapping((FieldEntry) obfEntry);
575 } else if (obfEntry instanceof MethodEntry) { 600 } else if (obfEntry instanceof MethodEntry) {
601 if (((MethodEntry) obfEntry).isConstructor()) {
602 throw new IllegalArgumentException("Cannot rename constructors");
603 }
576 this.renamer.removeMethodTreeMapping((MethodEntry) obfEntry); 604 this.renamer.removeMethodTreeMapping((MethodEntry) obfEntry);
577 } else if (obfEntry instanceof ConstructorEntry) { 605 } else if (obfEntry instanceof LocalVariableEntry) {
578 throw new IllegalArgumentException("Cannot rename constructors"); 606 this.renamer.removeLocalVariableMapping((LocalVariableEntry) obfEntry);
579 } else if (obfEntry instanceof ArgumentEntry) {
580 this.renamer.removeArgumentMapping((ArgumentEntry) obfEntry);
581 } else { 607 } else {
582 throw new Error("Unknown entry type: " + obfEntry); 608 throw new Error("Unknown entry desc: " + obfEntry);
583 } 609 }
584 610
585 // clear caches 611 // clear caches
@@ -592,15 +618,15 @@ public class Deobfuscator {
592 } else if (obfEntry instanceof FieldEntry) { 618 } else if (obfEntry instanceof FieldEntry) {
593 this.renamer.markFieldAsDeobfuscated((FieldEntry) obfEntry); 619 this.renamer.markFieldAsDeobfuscated((FieldEntry) obfEntry);
594 } else if (obfEntry instanceof MethodEntry) { 620 } else if (obfEntry instanceof MethodEntry) {
595 this.renamer.markMethodTreeAsDeobfuscated((MethodEntry) obfEntry); 621 MethodEntry methodEntry = (MethodEntry) obfEntry;
596 } else if (obfEntry instanceof ConstructorEntry) { 622 if (methodEntry.isConstructor()) {
597 throw new IllegalArgumentException("Cannot rename constructors"); 623 throw new IllegalArgumentException("Cannot rename constructors");
598 } else if (obfEntry instanceof ArgumentEntry) { 624 }
599 this.renamer.markArgumentAsDeobfuscated((ArgumentEntry) obfEntry); 625 this.renamer.markMethodTreeAsDeobfuscated(methodEntry);
600 } else if (obfEntry instanceof LocalVariableEntry) { 626 } else if (obfEntry instanceof LocalVariableEntry) {
601 // TODO: Implement it 627 this.renamer.markArgumentAsDeobfuscated((LocalVariableEntry) obfEntry);
602 } else { 628 } else {
603 throw new Error("Unknown entry type: " + obfEntry); 629 throw new Error("Unknown entry desc: " + obfEntry);
604 } 630 }
605 631
606 // clear caches 632 // clear caches
@@ -613,17 +639,33 @@ public class Deobfuscator {
613 this.renamer.setClassModifier((ClassEntry) obfEntry, modifierEntry); 639 this.renamer.setClassModifier((ClassEntry) obfEntry, modifierEntry);
614 else if (obfEntry instanceof FieldEntry) 640 else if (obfEntry instanceof FieldEntry)
615 this.renamer.setFieldModifier((FieldEntry) obfEntry, modifierEntry); 641 this.renamer.setFieldModifier((FieldEntry) obfEntry, modifierEntry);
616 else if (obfEntry instanceof BehaviorEntry) 642 else if (obfEntry instanceof MethodEntry)
617 this.renamer.setMethodModifier((BehaviorEntry) obfEntry, modifierEntry); 643 this.renamer.setMethodModifier((MethodEntry) obfEntry, modifierEntry);
618 else 644 else
619 throw new Error("Unknown entry type: " + obfEntry); 645 throw new Error("Unknown entry desc: " + obfEntry);
620 } 646 }
621 647
622 public Mappings.EntryModifier getModifier(Entry obEntry) { 648 public Mappings.EntryModifier getModifier(Entry obfEntry) {
623 Entry entry = obfuscateEntry(obEntry); 649 Entry entry = obfuscateEntry(obfEntry);
624 if (entry != null) 650 if (entry != null)
625 obEntry = entry; 651 obfEntry = entry;
626 return getTranslator(TranslationDirection.Deobfuscating).getModifier(obEntry); 652 if (obfEntry instanceof ClassEntry)
653 return this.renamer.getClassModifier((ClassEntry) obfEntry);
654 else if (obfEntry instanceof FieldEntry)
655 return this.renamer.getFieldModifier((FieldEntry) obfEntry);
656 else if (obfEntry instanceof MethodEntry)
657 return this.renamer.getMethodModfifier((MethodEntry) obfEntry);
658 else
659 throw new Error("Unknown entry desc: " + obfEntry);
660 }
661
662 public static void runCustomTransforms(AstBuilder builder, DecompilerContext context){
663 List<IAstTransform> transformers = Arrays.asList(
664 new ObfuscatedEnumSwitchRewriterTransform(context)
665 );
666 for (IAstTransform transform : transformers){
667 transform.run(builder.getCompilationUnit());
668 }
627 } 669 }
628 670
629 public interface ProgressListener { 671 public interface ProgressListener {
@@ -633,6 +675,7 @@ public class Deobfuscator {
633 } 675 }
634 676
635 public interface ClassTransformer { 677 public interface ClassTransformer {
636 CtClass transform(CtClass c) throws Exception; 678 String transform(ClassNode node, ClassWriter writer);
637 } 679 }
680
638} 681}
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 @@
1package cuchaz.enigma;
2
3import com.strobel.assembler.metadata.ITypeLoader;
4import cuchaz.enigma.mapping.entry.ClassEntry;
5import org.objectweb.asm.ClassWriter;
6import org.objectweb.asm.tree.ClassNode;
7
8import java.util.List;
9
10/**
11 * For delegation of TranslatingTypeLoader without needing the subclass the whole thing
12 */
13public interface ITranslatingTypeLoader extends ITypeLoader {
14 List<String> getClassNamesToTry(String className);
15
16 List<String> getClassNamesToTry(ClassEntry obfClassEntry);
17
18 String transformInto(ClassNode node, ClassWriter writer);
19}
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 @@
1package cuchaz.enigma;
2
3import com.strobel.assembler.metadata.Buffer;
4import cuchaz.enigma.mapping.entry.ClassEntry;
5import org.objectweb.asm.ClassWriter;
6import org.objectweb.asm.tree.ClassNode;
7
8import java.util.List;
9
10/**
11 * Typeloader with synchronized tryLoadType method
12 */
13public class SynchronizedTypeLoader implements ITranslatingTypeLoader {
14 private final TranslatingTypeLoader delegate;
15
16 public SynchronizedTypeLoader(TranslatingTypeLoader delegate) {
17 this.delegate = delegate;
18 }
19
20 @Override
21 public List<String> getClassNamesToTry(String className) {
22 return delegate.getClassNamesToTry(className);
23 }
24
25 @Override
26 public List<String> getClassNamesToTry(ClassEntry obfClassEntry) {
27 return delegate.getClassNamesToTry(obfClassEntry);
28 }
29
30 @Override
31 public String transformInto(ClassNode node, ClassWriter writer) {
32 return delegate.transformInto(node, writer);
33 }
34
35 @Override
36 public synchronized boolean tryLoadType(String internalName, Buffer buffer) {
37 return delegate.tryLoadType(internalName, buffer);
38 }
39}
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 @@
12package cuchaz.enigma; 12package cuchaz.enigma;
13 13
14import com.google.common.collect.Lists; 14import com.google.common.collect.Lists;
15import com.google.common.collect.Maps;
16import com.strobel.assembler.metadata.Buffer; 15import com.strobel.assembler.metadata.Buffer;
17import com.strobel.assembler.metadata.ClasspathTypeLoader;
18import com.strobel.assembler.metadata.ITypeLoader; 16import com.strobel.assembler.metadata.ITypeLoader;
19import cuchaz.enigma.analysis.BridgeMarker;
20import cuchaz.enigma.analysis.JarIndex; 17import cuchaz.enigma.analysis.JarIndex;
21import cuchaz.enigma.bytecode.translators.ClassTranslator; 18import cuchaz.enigma.analysis.ParsedJar;
22import cuchaz.enigma.bytecode.translators.InnerClassWriter; 19import cuchaz.enigma.bytecode.translators.TranslationClassVisitor;
23import cuchaz.enigma.bytecode.translators.LocalVariableTranslator; 20import cuchaz.enigma.mapping.entry.ClassEntry;
24import cuchaz.enigma.bytecode.translators.MethodParameterTranslator; 21import cuchaz.enigma.mapping.entry.ReferencedEntryPool;
25import cuchaz.enigma.mapping.ClassEntry;
26import cuchaz.enigma.mapping.Translator; 22import cuchaz.enigma.mapping.Translator;
27import javassist.*; 23import org.objectweb.asm.ClassWriter;
28import javassist.bytecode.Descriptor; 24import org.objectweb.asm.Opcodes;
25import org.objectweb.asm.tree.ClassNode;
29 26
30import java.io.ByteArrayOutputStream;
31import java.io.IOException;
32import java.io.InputStream;
33import java.util.List; 27import java.util.List;
34import java.util.Map;
35import java.util.jar.JarEntry;
36import java.util.jar.JarFile;
37 28
38public class TranslatingTypeLoader implements ITypeLoader { 29public class TranslatingTypeLoader extends CachingTypeLoader implements ITranslatingTypeLoader {
30 //Store one instance as the classpath shouldnt change during load
31 private static final ITypeLoader defaultTypeLoader = new CachingClasspathTypeLoader();
39 32
40 private JarFile jar; 33 private final ParsedJar jar;
41 private JarIndex jarIndex; 34 private final JarIndex jarIndex;
42 private Translator obfuscatingTranslator; 35 private final ReferencedEntryPool entryPool;
43 private Translator deobfuscatingTranslator; 36 private final Translator obfuscatingTranslator;
44 private Map<String, byte[]> cache; 37 private final Translator deobfuscatingTranslator;
45 private ClasspathTypeLoader defaultTypeLoader;
46 38
47 public TranslatingTypeLoader(JarFile jar, JarIndex jarIndex, Translator obfuscatingTranslator, Translator deobfuscatingTranslator) { 39 public TranslatingTypeLoader(ParsedJar jar, JarIndex jarIndex, ReferencedEntryPool entryPool, Translator obfuscatingTranslator, Translator deobfuscatingTranslator) {
48 this.jar = jar; 40 this.jar = jar;
49 this.jarIndex = jarIndex; 41 this.jarIndex = jarIndex;
42 this.entryPool = entryPool;
50 this.obfuscatingTranslator = obfuscatingTranslator; 43 this.obfuscatingTranslator = obfuscatingTranslator;
51 this.deobfuscatingTranslator = deobfuscatingTranslator; 44 this.deobfuscatingTranslator = deobfuscatingTranslator;
52 this.cache = Maps.newHashMap();
53 this.defaultTypeLoader = new ClasspathTypeLoader();
54
55 }
56
57 public void clearCache() {
58 this.cache.clear();
59 }
60
61 @Override
62 public boolean tryLoadType(String className, Buffer out) {
63
64 // check the cache
65 byte[] data;
66 if (this.cache.containsKey(className)) {
67 data = this.cache.get(className);
68 } else {
69 data = loadType(className);
70 this.cache.put(className, data);
71 }
72
73 if (data == null) {
74 // chain to default type loader
75 return this.defaultTypeLoader.tryLoadType(className, out);
76 }
77
78 // send the class to the decompiler
79 out.reset(data.length);
80 System.arraycopy(data, 0, out.array(), out.position(), data.length);
81 out.position(0);
82 return true;
83 } 45 }
84 46
85 public CtClass loadClass(String deobfClassName) { 47 protected byte[] doLoad(String className){
86 48 byte[] data = loadType(className);
87 byte[] data = loadType(deobfClassName);
88 if (data == null) { 49 if (data == null) {
89 return null; 50 // chain to default desc loader
90 } 51 Buffer parentBuf = new Buffer();
91 52 if (defaultTypeLoader.tryLoadType(className, parentBuf)){
92 // return a javassist handle for the class 53 return parentBuf.array();
93 String javaClassFileName = Descriptor.toJavaName(deobfClassName); 54 }
94 ClassPool classPool = new ClassPool(); 55 return EMPTY_ARRAY;//need to return *something* as null means no store
95 classPool.insertClassPath(new ByteArrayClassPath(javaClassFileName, data));
96 try {
97 return classPool.get(javaClassFileName);
98 } catch (NotFoundException ex) {
99 throw new Error(ex);
100 } 56 }
57 return data;
101 } 58 }
102 59
103 private byte[] loadType(String className) { 60 private byte[] loadType(String className) {
104 61
105 // NOTE: don't know if class name is obf or deobf 62 // NOTE: don't know if class name is obf or deobf
106 ClassEntry classEntry = new ClassEntry(className); 63 ClassEntry classEntry = new ClassEntry(className);
107 ClassEntry obfClassEntry = this.obfuscatingTranslator.translateEntry(classEntry); 64 ClassEntry obfClassEntry = this.obfuscatingTranslator.getTranslatedClass(classEntry);
108 65
109 // is this an inner class referenced directly? (ie trying to load b instead of a$b) 66 // is this an inner class referenced directly? (ie trying to load b instead of a$b)
110 if (!obfClassEntry.isInnerClass()) { 67 if (!obfClassEntry.isInnerClass()) {
111 List<ClassEntry> classChain = this.jarIndex.getObfClassChain(obfClassEntry); 68 List<ClassEntry> classChain = this.jarIndex.getObfClassChain(obfClassEntry);
112 if (classChain.size() > 1) { 69 if (classChain.size() > 1) {
113 System.err.println(String.format("WARNING: no class %s after inner class reconstruction. Try %s", 70 System.err.println(String.format("WARNING: no class %s after inner class reconstruction. Try %s",
114 className, obfClassEntry.buildClassEntry(classChain) 71 className, obfClassEntry.buildClassEntry(classChain)
115 )); 72 ));
116 return null; 73 return null;
117 } 74 }
@@ -126,56 +83,26 @@ public class TranslatingTypeLoader implements ITypeLoader {
126 //System.out.println(String.format("Looking for %s (obf: %s)", classEntry.getName(), obfClassEntry.getName())); 83 //System.out.println(String.format("Looking for %s (obf: %s)", classEntry.getName(), obfClassEntry.getName()));
127 84
128 // find the class in the jar 85 // find the class in the jar
129 String classInJarName = findClassInJar(obfClassEntry); 86 ClassNode node = findClassInJar(obfClassEntry);
130 if (classInJarName == null) { 87 if (node == null) {
131 // couldn't find it 88 // couldn't find it
132 return null; 89 return null;
133 } 90 }
134 91
135 try { 92 ClassWriter writer = new ClassWriter(0);
136 // read the class file into a buffer 93 transformInto(node, writer);
137 ByteArrayOutputStream data = new ByteArrayOutputStream();
138 byte[] buf = new byte[1024 * 1024]; // 1 KiB
139 InputStream in = this.jar.getInputStream(this.jar.getJarEntry(classInJarName + ".class"));
140 while (true) {
141 int bytesRead = in.read(buf);
142 if (bytesRead <= 0) {
143 break;
144 }
145 data.write(buf, 0, bytesRead);
146 }
147 data.close();
148 in.close();
149 buf = data.toByteArray();
150
151 // load the javassist handle to the raw class
152 ClassPool classPool = new ClassPool();
153 String classInJarJavaName = Descriptor.toJavaName(classInJarName);
154 classPool.insertClassPath(new ByteArrayClassPath(classInJarJavaName, buf));
155 CtClass c = classPool.get(classInJarJavaName);
156
157 c = transformClass(c);
158
159 // sanity checking
160 assertClassName(c, classEntry);
161 94
162 // DEBUG 95 // we have a transformed class!
163 //Util.writeClass( c ); 96 return writer.toByteArray();
164
165 // we have a transformed class!
166 return c.toBytecode();
167 } catch (IOException | NotFoundException | CannotCompileException ex) {
168 throw new Error(ex);
169 }
170 } 97 }
171 98
172 private String findClassInJar(ClassEntry obfClassEntry) { 99 private ClassNode findClassInJar(ClassEntry obfClassEntry) {
173 100
174 // try to find the class in the jar 101 // try to find the class in the jar
175 for (String className : getClassNamesToTry(obfClassEntry)) { 102 for (String className : getClassNamesToTry(obfClassEntry)) {
176 JarEntry jarEntry = this.jar.getJarEntry(className + ".class"); 103 ClassNode node = this.jar.getClassNode(className);
177 if (jarEntry != null) { 104 if (node != null) {
178 return className; 105 return node;
179 } 106 }
180 } 107 }
181 108
@@ -183,10 +110,12 @@ public class TranslatingTypeLoader implements ITypeLoader {
183 return null; 110 return null;
184 } 111 }
185 112
113 @Override
186 public List<String> getClassNamesToTry(String className) { 114 public List<String> getClassNamesToTry(String className) {
187 return getClassNamesToTry(this.obfuscatingTranslator.translateEntry(new ClassEntry(className))); 115 return getClassNamesToTry(this.obfuscatingTranslator.getTranslatedClass(new ClassEntry(className)));
188 } 116 }
189 117
118 @Override
190 public List<String> getClassNamesToTry(ClassEntry obfClassEntry) { 119 public List<String> getClassNamesToTry(ClassEntry obfClassEntry) {
191 List<String> classNamesToTry = Lists.newArrayList(); 120 List<String> classNamesToTry = Lists.newArrayList();
192 classNamesToTry.add(obfClassEntry.getName()); 121 classNamesToTry.add(obfClassEntry.getName());
@@ -197,36 +126,10 @@ public class TranslatingTypeLoader implements ITypeLoader {
197 return classNamesToTry; 126 return classNamesToTry;
198 } 127 }
199 128
200 public CtClass transformClass(CtClass c) 129 @Override
201 throws IOException, NotFoundException, CannotCompileException { 130 public String transformInto(ClassNode node, ClassWriter writer) {
202 131 node.accept(new TranslationClassVisitor(deobfuscatingTranslator, jarIndex, entryPool, Opcodes.ASM5, writer));
203 // reconstruct inner classes 132 return deobfuscatingTranslator.getTranslatedClass(new ClassEntry(node.name)).getName();
204 InnerClassWriter.write(jarIndex, c);
205
206 // re-get the javassist handle since we changed class names
207 ClassEntry obfClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
208 String javaClassReconstructedName = Descriptor.toJavaName(obfClassEntry.getName());
209 ClassPool classPool = new ClassPool();
210 classPool.insertClassPath(new ByteArrayClassPath(javaClassReconstructedName, c.toBytecode()));
211 c = classPool.get(javaClassReconstructedName);
212
213 // check that the file is correct after inner class reconstruction (ie cause Javassist to fail fast if something is wrong)
214 assertClassName(c, obfClassEntry);
215
216 // do all kinds of deobfuscating transformations on the class
217 BridgeMarker.markBridges(this.jarIndex, c);
218 MethodParameterTranslator.translate(this.deobfuscatingTranslator, c);
219 LocalVariableTranslator.translate(this.deobfuscatingTranslator, c);
220 ClassTranslator.translate(this.deobfuscatingTranslator, c);
221
222 return c;
223 } 133 }
224 134
225 private void assertClassName(CtClass c, ClassEntry obfClassEntry) {
226 String name1 = Descriptor.toJvmName(c.getName());
227 assert (name1.equals(obfClassEntry.getName())) : String.format("Looking for %s, instead found %s", obfClassEntry.getName(), name1);
228
229 String name2 = Descriptor.toJvmName(c.getClassFile().getName());
230 assert (name2.equals(obfClassEntry.getName())) : String.format("Looking for %s, instead found %s", obfClassEntry.getName(), name2);
231 }
232} 135}
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 @@
11 11
12package cuchaz.enigma.analysis; 12package cuchaz.enigma.analysis;
13 13
14import javassist.CtBehavior; 14import cuchaz.enigma.bytecode.AccessFlags;
15import javassist.CtField;
16 15
17import java.lang.reflect.Modifier; 16import java.lang.reflect.Modifier;
18 17
@@ -20,12 +19,8 @@ public enum Access {
20 19
21 PUBLIC, PROTECTED, PACKAGE, PRIVATE; 20 PUBLIC, PROTECTED, PACKAGE, PRIVATE;
22 21
23 public static Access get(CtBehavior behavior) { 22 public static Access get(AccessFlags flags) {
24 return get(behavior.getModifiers()); 23 return get(flags.getFlags());
25 }
26
27 public static Access get(CtField field) {
28 return get(field.getModifiers());
29 } 24 }
30 25
31 public static Access get(int modifiers) { 26 public static Access get(int modifiers) {
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.analysis;
13
14import cuchaz.enigma.mapping.EntryFactory;
15import cuchaz.enigma.mapping.MethodEntry;
16import javassist.CtClass;
17import javassist.CtMethod;
18import javassist.bytecode.AccessFlag;
19
20public class BridgeMarker {
21
22 public static void markBridges(JarIndex jarIndex, CtClass c) {
23
24 for (CtMethod method : c.getDeclaredMethods()) {
25 MethodEntry methodEntry = EntryFactory.getMethodEntry(method);
26
27 // is this a bridge method?
28 MethodEntry bridgedMethodEntry = jarIndex.getBridgedMethod(methodEntry);
29 if (bridgedMethodEntry != null) {
30
31 // it's a bridge method! add the bridge flag
32 int flags = method.getMethodInfo().getAccessFlags();
33 flags |= AccessFlag.BRIDGE;
34 method.getMethodInfo().setAccessFlags(flags);
35 }
36 }
37 }
38}
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 @@
12package cuchaz.enigma.analysis; 12package cuchaz.enigma.analysis;
13 13
14import com.google.common.collect.Lists; 14import com.google.common.collect.Lists;
15import cuchaz.enigma.mapping.ClassEntry; 15import cuchaz.enigma.mapping.entry.ClassEntry;
16import cuchaz.enigma.mapping.MethodEntry; 16import cuchaz.enigma.mapping.entry.MethodEntry;
17import cuchaz.enigma.mapping.Translator; 17import cuchaz.enigma.mapping.Translator;
18 18
19import javax.swing.tree.DefaultMutableTreeNode; 19import javax.swing.tree.DefaultMutableTreeNode;
@@ -21,8 +21,8 @@ import java.util.List;
21 21
22public class ClassImplementationsTreeNode extends DefaultMutableTreeNode { 22public class ClassImplementationsTreeNode extends DefaultMutableTreeNode {
23 23
24 private Translator deobfuscatingTranslator; 24 private final Translator deobfuscatingTranslator;
25 private ClassEntry entry; 25 private final ClassEntry entry;
26 26
27 public ClassImplementationsTreeNode(Translator deobfuscatingTranslator, ClassEntry entry) { 27 public ClassImplementationsTreeNode(Translator deobfuscatingTranslator, ClassEntry entry) {
28 this.deobfuscatingTranslator = deobfuscatingTranslator; 28 this.deobfuscatingTranslator = deobfuscatingTranslator;
@@ -31,7 +31,7 @@ public class ClassImplementationsTreeNode extends DefaultMutableTreeNode {
31 31
32 public static ClassImplementationsTreeNode findNode(ClassImplementationsTreeNode node, MethodEntry entry) { 32 public static ClassImplementationsTreeNode findNode(ClassImplementationsTreeNode node, MethodEntry entry) {
33 // is this the node? 33 // is this the node?
34 if (node.entry.equals(entry.getClassEntry())) { 34 if (node.entry.equals(entry.getOwnerClassEntry())) {
35 return node; 35 return node;
36 } 36 }
37 37
@@ -50,7 +50,7 @@ public class ClassImplementationsTreeNode extends DefaultMutableTreeNode {
50 } 50 }
51 51
52 public String getDeobfClassName() { 52 public String getDeobfClassName() {
53 return this.deobfuscatingTranslator.translateClass(this.entry.getClassName()); 53 return this.deobfuscatingTranslator.getTranslatedClass(entry).getClassName();
54 } 54 }
55 55
56 @Override 56 @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 @@
12package cuchaz.enigma.analysis; 12package cuchaz.enigma.analysis;
13 13
14import com.google.common.collect.Lists; 14import com.google.common.collect.Lists;
15import cuchaz.enigma.mapping.ClassEntry; 15import cuchaz.enigma.mapping.entry.ClassEntry;
16import cuchaz.enigma.mapping.Translator; 16import cuchaz.enigma.mapping.Translator;
17 17
18import javax.swing.tree.DefaultMutableTreeNode; 18import javax.swing.tree.DefaultMutableTreeNode;
@@ -20,12 +20,12 @@ import java.util.List;
20 20
21public class ClassInheritanceTreeNode extends DefaultMutableTreeNode { 21public class ClassInheritanceTreeNode extends DefaultMutableTreeNode {
22 22
23 private Translator deobfuscatingTranslator; 23 private final Translator deobfuscatingTranslator;
24 private String obfClassName; 24 private final ClassEntry obfClassEntry;
25 25
26 public ClassInheritanceTreeNode(Translator deobfuscatingTranslator, String obfClassName) { 26 public ClassInheritanceTreeNode(Translator deobfuscatingTranslator, String obfClassName) {
27 this.deobfuscatingTranslator = deobfuscatingTranslator; 27 this.deobfuscatingTranslator = deobfuscatingTranslator;
28 this.obfClassName = obfClassName; 28 this.obfClassEntry = new ClassEntry(obfClassName);
29 } 29 }
30 30
31 public static ClassInheritanceTreeNode findNode(ClassInheritanceTreeNode node, ClassEntry entry) { 31 public static ClassInheritanceTreeNode findNode(ClassInheritanceTreeNode node, ClassEntry entry) {
@@ -45,11 +45,11 @@ public class ClassInheritanceTreeNode extends DefaultMutableTreeNode {
45 } 45 }
46 46
47 public String getObfClassName() { 47 public String getObfClassName() {
48 return this.obfClassName; 48 return this.obfClassEntry.getClassName();
49 } 49 }
50 50
51 public String getDeobfClassName() { 51 public String getDeobfClassName() {
52 return this.deobfuscatingTranslator.translateClass(this.obfClassName); 52 return this.deobfuscatingTranslator.getTranslatedClass(this.obfClassEntry).getClassName();
53 } 53 }
54 54
55 @Override 55 @Override
@@ -58,13 +58,13 @@ public class ClassInheritanceTreeNode extends DefaultMutableTreeNode {
58 if (deobfClassName != null) { 58 if (deobfClassName != null) {
59 return deobfClassName; 59 return deobfClassName;
60 } 60 }
61 return this.obfClassName; 61 return this.obfClassEntry.getName();
62 } 62 }
63 63
64 public void load(TranslationIndex ancestries, boolean recurse) { 64 public void load(TranslationIndex ancestries, boolean recurse) {
65 // get all the child nodes 65 // get all the child nodes
66 List<ClassInheritanceTreeNode> nodes = Lists.newArrayList(); 66 List<ClassInheritanceTreeNode> nodes = Lists.newArrayList();
67 for (ClassEntry subclassEntry : ancestries.getSubclass(new ClassEntry(this.obfClassName))) { 67 for (ClassEntry subclassEntry : ancestries.getSubclass(this.obfClassEntry)) {
68 nodes.add(new ClassInheritanceTreeNode(this.deobfuscatingTranslator, subclassEntry.getName())); 68 nodes.add(new ClassInheritanceTreeNode(this.deobfuscatingTranslator, subclassEntry.getName()));
69 } 69 }
70 70
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 @@
11 11
12package cuchaz.enigma.analysis; 12package cuchaz.enigma.analysis;
13 13
14import cuchaz.enigma.mapping.ClassEntry; 14import cuchaz.enigma.mapping.entry.ClassEntry;
15import cuchaz.enigma.mapping.ConstructorEntry; 15import cuchaz.enigma.mapping.entry.Entry;
16import cuchaz.enigma.mapping.Entry; 16import cuchaz.enigma.mapping.entry.MethodEntry;
17import cuchaz.enigma.utils.Utils; 17import cuchaz.enigma.utils.Utils;
18 18
19import java.util.Arrays; 19import java.util.Arrays;
@@ -21,7 +21,7 @@ import java.util.List;
21 21
22public class EntryReference<E extends Entry, C extends Entry> { 22public class EntryReference<E extends Entry, C extends Entry> {
23 23
24 private static final List<String> ConstructorNonNames = Arrays.asList("this", "super", "static"); 24 private static final List<String> CONSTRUCTOR_NON_NAMES = Arrays.asList("this", "super", "static");
25 public E entry; 25 public E entry;
26 public C context; 26 public C context;
27 27
@@ -40,7 +40,7 @@ public class EntryReference<E extends Entry, C extends Entry> {
40 this.context = context; 40 this.context = context;
41 41
42 this.sourceName = sourceName != null && !sourceName.isEmpty(); 42 this.sourceName = sourceName != null && !sourceName.isEmpty();
43 if (entry instanceof ConstructorEntry && ConstructorNonNames.contains(sourceName)) { 43 if (entry instanceof MethodEntry && ((MethodEntry) entry).isConstructor() && CONSTRUCTOR_NON_NAMES.contains(sourceName)) {
44 this.sourceName = false; 44 this.sourceName = false;
45 } 45 }
46 } 46 }
@@ -53,9 +53,9 @@ public class EntryReference<E extends Entry, C extends Entry> {
53 53
54 public ClassEntry getLocationClassEntry() { 54 public ClassEntry getLocationClassEntry() {
55 if (context != null) { 55 if (context != null) {
56 return context.getClassEntry(); 56 return context.getOwnerClassEntry();
57 } 57 }
58 return entry.getClassEntry(); 58 return entry.getOwnerClassEntry();
59 } 59 }
60 60
61 public boolean isNamed() { 61 public boolean isNamed() {
@@ -63,9 +63,9 @@ public class EntryReference<E extends Entry, C extends Entry> {
63 } 63 }
64 64
65 public Entry getNameableEntry() { 65 public Entry getNameableEntry() {
66 if (entry instanceof ConstructorEntry) { 66 if (entry instanceof MethodEntry && ((MethodEntry) entry).isConstructor()) {
67 // renaming a constructor really means renaming the class 67 // renaming a constructor really means renaming the class
68 return entry.getClassEntry(); 68 return entry.getOwnerClassEntry();
69 } 69 }
70 return entry; 70 return entry;
71 } 71 }
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;
15import com.google.common.collect.Multimap; 15import com.google.common.collect.Multimap;
16import com.google.common.collect.Sets; 16import com.google.common.collect.Sets;
17import cuchaz.enigma.mapping.*; 17import cuchaz.enigma.mapping.*;
18import cuchaz.enigma.mapping.entry.*;
18 19
19import java.util.AbstractMap; 20import java.util.AbstractMap;
20import java.util.List; 21import java.util.List;
@@ -87,18 +88,18 @@ public class EntryRenamer {
87 MethodEntry newMethodEntry = renames.get(methodEntry); 88 MethodEntry newMethodEntry = renames.get(methodEntry);
88 if (newMethodEntry != null) { 89 if (newMethodEntry != null) {
89 return (T) new MethodEntry( 90 return (T) new MethodEntry(
90 methodEntry.getClassEntry(), 91 methodEntry.getOwnerClassEntry(),
91 newMethodEntry.getName(), 92 newMethodEntry.getName(),
92 methodEntry.getSignature() 93 methodEntry.getDesc()
93 ); 94 );
94 } 95 }
95 return thing; 96 return thing;
96 } else if (thing instanceof ArgumentEntry) { 97 } else if (thing instanceof LocalVariableEntry) {
97 ArgumentEntry argumentEntry = (ArgumentEntry) thing; 98 LocalVariableEntry variableEntry = (LocalVariableEntry) thing;
98 return (T) new ArgumentEntry( 99 return (T) new LocalVariableEntry(
99 renameMethodsInThing(renames, argumentEntry.getBehaviorEntry()), 100 renameMethodsInThing(renames, variableEntry.getOwnerEntry()),
100 argumentEntry.getIndex(), 101 variableEntry.getIndex(),
101 argumentEntry.getName() 102 variableEntry.getName()
102 ); 103 );
103 } else if (thing instanceof EntryReference) { 104 } else if (thing instanceof EntryReference) {
104 EntryReference<Entry, Entry> reference = (EntryReference<Entry, Entry>) thing; 105 EntryReference<Entry, Entry> reference = (EntryReference<Entry, Entry>) thing;
@@ -119,27 +120,45 @@ public class EntryRenamer {
119 } else if (thing instanceof ClassEntry) { 120 } else if (thing instanceof ClassEntry) {
120 ClassEntry classEntry = (ClassEntry) thing; 121 ClassEntry classEntry = (ClassEntry) thing;
121 return (T) new ClassEntry(renameClassesInThing(renames, classEntry.getClassName())); 122 return (T) new ClassEntry(renameClassesInThing(renames, classEntry.getClassName()));
122 } else if (thing instanceof FieldEntry) { 123 } else if (thing instanceof FieldDefEntry) {
123 FieldEntry fieldEntry = (FieldEntry) thing; 124 FieldDefEntry fieldEntry = (FieldDefEntry) thing;
124 return (T) new FieldEntry(renameClassesInThing(renames, fieldEntry.getClassEntry()), fieldEntry.getName(), renameClassesInThing(renames, fieldEntry.getType())); 125 return (T) new FieldDefEntry(
125 } else if (thing instanceof ConstructorEntry) { 126 renameClassesInThing(renames, fieldEntry.getOwnerClassEntry()),
126 ConstructorEntry constructorEntry = (ConstructorEntry) thing; 127 fieldEntry.getName(),
127 return (T) new ConstructorEntry(renameClassesInThing(renames, constructorEntry.getClassEntry()), renameClassesInThing(renames, constructorEntry.getSignature())); 128 renameClassesInThing(renames, fieldEntry.getDesc()),
129 renameClassesInThing(renames, fieldEntry.getSignature()),
130 fieldEntry.getAccess()
131 );
132 } else if (thing instanceof MethodDefEntry) {
133 MethodDefEntry methodEntry = (MethodDefEntry) thing;
134 return (T) new MethodDefEntry(
135 renameClassesInThing(renames, methodEntry.getOwnerClassEntry()),
136 methodEntry.getName(),
137 renameClassesInThing(renames, methodEntry.getDesc()),
138 renameClassesInThing(renames, methodEntry.getSignature()),
139 methodEntry.getAccess()
140 );
128 } else if (thing instanceof MethodEntry) { 141 } else if (thing instanceof MethodEntry) {
129 MethodEntry methodEntry = (MethodEntry) thing; 142 MethodEntry methodEntry = (MethodEntry) thing;
130 return (T) new MethodEntry(renameClassesInThing(renames, methodEntry.getClassEntry()), methodEntry.getName(), renameClassesInThing(renames, methodEntry.getSignature())); 143 return (T) new MethodEntry(
131 } else if (thing instanceof ArgumentEntry) { 144 renameClassesInThing(renames, methodEntry.getOwnerClassEntry()),
132 ArgumentEntry argumentEntry = (ArgumentEntry) thing; 145 methodEntry.getName(),
133 return (T) new ArgumentEntry(renameClassesInThing(renames, argumentEntry.getBehaviorEntry()), argumentEntry.getIndex(), argumentEntry.getName()); 146 renameClassesInThing(renames, methodEntry.getDesc())
147 );
148 } else if (thing instanceof LocalVariableEntry) {
149 LocalVariableEntry argumentEntry = (LocalVariableEntry) thing;
150 return (T) new LocalVariableEntry(renameClassesInThing(renames, argumentEntry.getOwnerEntry()), argumentEntry.getIndex(), argumentEntry.getName());
134 } else if (thing instanceof EntryReference) { 151 } else if (thing instanceof EntryReference) {
135 EntryReference<Entry, Entry> reference = (EntryReference<Entry, Entry>) thing; 152 EntryReference<Entry, Entry> reference = (EntryReference<Entry, Entry>) thing;
136 reference.entry = renameClassesInThing(renames, reference.entry); 153 reference.entry = renameClassesInThing(renames, reference.entry);
137 reference.context = renameClassesInThing(renames, reference.context); 154 reference.context = renameClassesInThing(renames, reference.context);
138 return thing; 155 return thing;
156 } else if (thing instanceof MethodDescriptor) {
157 return (T) ((MethodDescriptor) thing).remap(className -> renameClassesInThing(renames, className));
158 } else if (thing instanceof TypeDescriptor) {
159 return (T) ((TypeDescriptor) thing).remap(className -> renameClassesInThing(renames, className));
139 } else if (thing instanceof Signature) { 160 } else if (thing instanceof Signature) {
140 return (T) new Signature((Signature) thing, className -> renameClassesInThing(renames, className)); 161 return (T) ((Signature) thing).remap(className -> renameClassesInThing(renames, className));
141 } else if (thing instanceof Type) {
142 return (T) new Type((Type) thing, className -> renameClassesInThing(renames, className));
143 } 162 }
144 163
145 return thing; 164 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 @@
11 11
12package cuchaz.enigma.analysis; 12package cuchaz.enigma.analysis;
13 13
14import cuchaz.enigma.mapping.BehaviorEntry; 14import cuchaz.enigma.mapping.*;
15import cuchaz.enigma.mapping.FieldEntry; 15import cuchaz.enigma.mapping.entry.FieldEntry;
16import cuchaz.enigma.mapping.Translator; 16import cuchaz.enigma.mapping.entry.MethodDefEntry;
17import cuchaz.enigma.mapping.entry.MethodEntry;
17 18
18import javax.swing.tree.DefaultMutableTreeNode; 19import javax.swing.tree.DefaultMutableTreeNode;
19 20
20public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode<FieldEntry, BehaviorEntry> { 21public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode<FieldEntry, MethodDefEntry> {
21 22
22 private Translator deobfuscatingTranslator; 23 private Translator deobfuscatingTranslator;
23 private FieldEntry entry; 24 private FieldEntry entry;
24 private EntryReference<FieldEntry, BehaviorEntry> reference; 25 private EntryReference<FieldEntry, MethodDefEntry> reference;
25 private Access access; 26 private Access access;
26 27
27 public FieldReferenceTreeNode(Translator deobfuscatingTranslator, FieldEntry entry) { 28 public FieldReferenceTreeNode(Translator deobfuscatingTranslator, FieldEntry entry) {
@@ -30,7 +31,7 @@ public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements Re
30 this.reference = null; 31 this.reference = null;
31 } 32 }
32 33
33 private FieldReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference<FieldEntry, BehaviorEntry> reference, Access access) { 34 private FieldReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference<FieldEntry, MethodDefEntry> reference, Access access) {
34 this.deobfuscatingTranslator = deobfuscatingTranslator; 35 this.deobfuscatingTranslator = deobfuscatingTranslator;
35 this.entry = reference.entry; 36 this.entry = reference.entry;
36 this.reference = reference; 37 this.reference = reference;
@@ -43,34 +44,34 @@ public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements Re
43 } 44 }
44 45
45 @Override 46 @Override
46 public EntryReference<FieldEntry, BehaviorEntry> getReference() { 47 public EntryReference<FieldEntry, MethodDefEntry> getReference() {
47 return this.reference; 48 return this.reference;
48 } 49 }
49 50
50 @Override 51 @Override
51 public String toString() { 52 public String toString() {
52 if (this.reference != null) { 53 if (this.reference != null) {
53 return String.format("%s (%s)", this.deobfuscatingTranslator.translateEntry(this.reference.context), this.access); 54 return String.format("%s (%s)", this.deobfuscatingTranslator.getTranslatedMethodDef(this.reference.context), this.access);
54 } 55 }
55 return this.deobfuscatingTranslator.translateEntry(this.entry).toString(); 56 return deobfuscatingTranslator.getTranslatedField(entry).getName();
56 } 57 }
57 58
58 public void load(JarIndex index, boolean recurse) { 59 public void load(JarIndex index, boolean recurse) {
59 // get all the child nodes 60 // get all the child nodes
60 if (this.reference == null) { 61 if (this.reference == null) {
61 for (EntryReference<FieldEntry, BehaviorEntry> reference : index.getFieldReferences(this.entry)) { 62 for (EntryReference<FieldEntry, MethodDefEntry> reference : index.getFieldReferences(this.entry)) {
62 add(new FieldReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccess(this.entry))); 63 add(new FieldReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccess(this.entry)));
63 } 64 }
64 } else { 65 } else {
65 for (EntryReference<BehaviorEntry, BehaviorEntry> reference : index.getBehaviorReferences(this.reference.context)) { 66 for (EntryReference<MethodEntry, MethodDefEntry> reference : index.getMethodsReferencing(this.reference.context)) {
66 add(new BehaviorReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccess(this.reference.context))); 67 add(new MethodReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccess(this.reference.context)));
67 } 68 }
68 } 69 }
69 70
70 if (recurse && children != null) { 71 if (recurse && children != null) {
71 for (Object node : children) { 72 for (Object node : children) {
72 if (node instanceof BehaviorReferenceTreeNode) { 73 if (node instanceof MethodReferenceTreeNode) {
73 ((BehaviorReferenceTreeNode) node).load(index, true); 74 ((MethodReferenceTreeNode) node).load(index, true);
74 } else if (node instanceof FieldReferenceTreeNode) { 75 } else if (node instanceof FieldReferenceTreeNode) {
75 ((FieldReferenceTreeNode) node).load(index, true); 76 ((FieldReferenceTreeNode) node).load(index, true);
76 } 77 }
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 @@
1package cuchaz.enigma.analysis;
2
3import cuchaz.enigma.mapping.entry.ClassDefEntry;
4import org.objectweb.asm.ClassVisitor;
5import org.objectweb.asm.FieldVisitor;
6import org.objectweb.asm.MethodVisitor;
7
8public class IndexClassVisitor extends ClassVisitor {
9 private final JarIndex index;
10 private ClassDefEntry classEntry;
11
12 public IndexClassVisitor(JarIndex index, int api) {
13 super(api);
14 this.index = index;
15 }
16
17 @Override
18 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
19 this.classEntry = this.index.indexClass(access, name, signature, superName, interfaces);
20 super.visit(version, access, name, signature, superName, interfaces);
21 }
22
23 @Override
24 public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
25 if (this.classEntry != null) {
26 this.index.indexField(this.classEntry, access, name, desc, signature);
27 }
28 return super.visitField(access, name, desc, signature, value);
29 }
30
31 @Override
32 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
33 if (this.classEntry != null) {
34 this.index.indexMethod(this.classEntry, access, name, desc, signature);
35 }
36 return super.visitMethod(access, name, desc, signature, exceptions);
37 }
38}
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 @@
1package cuchaz.enigma.analysis;
2
3import cuchaz.enigma.mapping.entry.ClassEntry;
4import org.objectweb.asm.ClassVisitor;
5
6public class IndexInnerClassVisitor extends ClassVisitor {
7 private final JarIndex index;
8
9 public IndexInnerClassVisitor(JarIndex index, int api) {
10 super(api);
11 this.index = index;
12 }
13
14 @Override
15 public void visitInnerClass(String name, String outerName, String innerName, int access) {
16 ClassEntry entry = new ClassEntry(name);
17 // Ignore anonymous classes
18 if (innerName != null && outerName != null) {
19 ClassEntry outerEntry = new ClassEntry(outerName);
20 index.indexInnerClass(entry, outerEntry);
21 }
22 }
23}
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 @@
1package cuchaz.enigma.analysis;
2
3import cuchaz.enigma.bytecode.AccessFlags;
4import cuchaz.enigma.mapping.MethodDescriptor;
5import cuchaz.enigma.mapping.Signature;
6import cuchaz.enigma.mapping.entry.ClassEntry;
7import cuchaz.enigma.mapping.entry.MethodDefEntry;
8import org.objectweb.asm.ClassVisitor;
9import org.objectweb.asm.Handle;
10import org.objectweb.asm.MethodVisitor;
11import org.objectweb.asm.Opcodes;
12
13public class IndexReferenceVisitor extends ClassVisitor {
14 private final JarIndex index;
15 private ClassEntry classEntry;
16
17 public IndexReferenceVisitor(JarIndex index, int api) {
18 super(api);
19 this.index = index;
20 }
21
22 @Override
23 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
24 this.classEntry = new ClassEntry(name);
25 }
26
27 @Override
28 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
29 MethodDefEntry entry = new MethodDefEntry(classEntry, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access));
30 return new Method(this.index, entry, this.api);
31 }
32
33 private class Method extends MethodVisitor {
34 private final JarIndex index;
35 private final MethodDefEntry callerEntry;
36
37 public Method(JarIndex index, MethodDefEntry callerEntry, int api) {
38 super(api);
39 this.index = index;
40 this.callerEntry = callerEntry;
41 }
42
43 @Override
44 public void visitFieldInsn(int opcode, String owner, String name, String desc) {
45 this.index.indexFieldAccess(callerEntry, owner, name, desc);
46 }
47
48 @Override
49 public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
50 this.index.indexMethodCall(callerEntry, owner, name, desc);
51 }
52
53 @Override
54 public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
55 for (Object bsmArg : bsmArgs){
56 if (bsmArg instanceof Handle){
57 Handle handle = (Handle)bsmArg;
58 switch (handle.getTag()){
59 case Opcodes.H_GETFIELD:
60 case Opcodes.H_GETSTATIC:
61 case Opcodes.H_PUTFIELD:
62 case Opcodes.H_PUTSTATIC:
63 this.index.indexFieldAccess(callerEntry, handle.getOwner(), handle.getName(), handle.getDesc());
64 break;
65 case Opcodes.H_INVOKEINTERFACE:
66 case Opcodes.H_INVOKESPECIAL:
67 case Opcodes.H_INVOKESTATIC:
68 case Opcodes.H_INVOKEVIRTUAL:
69 case Opcodes.H_NEWINVOKESPECIAL:
70 this.index.indexMethodCall(callerEntry, handle.getOwner(), handle.getName(), handle.getDesc());
71 break;
72 }
73 }
74 }
75 }
76 }
77}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.analysis;
13
14import com.google.common.collect.Lists;
15import cuchaz.enigma.Constants;
16import cuchaz.enigma.mapping.ClassEntry;
17import javassist.ByteArrayClassPath;
18import javassist.ClassPool;
19import javassist.CtClass;
20import javassist.NotFoundException;
21import javassist.bytecode.Descriptor;
22
23import java.io.ByteArrayOutputStream;
24import java.io.IOException;
25import java.io.InputStream;
26import java.util.Enumeration;
27import java.util.Iterator;
28import java.util.List;
29import java.util.jar.JarEntry;
30import java.util.jar.JarFile;
31
32public class JarClassIterator implements Iterator<CtClass> {
33
34 private JarFile jar;
35 private Iterator<JarEntry> iter;
36
37 public JarClassIterator(JarFile jar) {
38 this.jar = jar;
39
40 // get the jar entries that correspond to classes
41 List<JarEntry> classEntries = Lists.newArrayList();
42 Enumeration<JarEntry> entries = this.jar.entries();
43 while (entries.hasMoreElements()) {
44 JarEntry entry = entries.nextElement();
45
46 // is this a class file?
47 if (entry.getName().endsWith(".class")) {
48 classEntries.add(entry);
49 }
50 }
51 this.iter = classEntries.iterator();
52 }
53
54 public static List<ClassEntry> getClassEntries(JarFile jar) {
55 List<ClassEntry> classEntries = Lists.newArrayList();
56 Enumeration<JarEntry> entries = jar.entries();
57 while (entries.hasMoreElements()) {
58 JarEntry entry = entries.nextElement();
59
60 // is this a class file?
61 if (!entry.isDirectory() && entry.getName().endsWith(".class")) {
62 classEntries.add(getClassEntry(entry));
63 }
64 }
65 return classEntries;
66 }
67
68 public static Iterable<CtClass> classes(final JarFile jar) {
69 return () -> new JarClassIterator(jar);
70 }
71
72 private static CtClass getClass(JarFile jar, JarEntry entry) throws IOException, NotFoundException {
73 // read the class into a buffer
74 ByteArrayOutputStream bos = new ByteArrayOutputStream();
75 byte[] buf = new byte[Constants.KiB];
76 int totalNumBytesRead = 0;
77 InputStream in = jar.getInputStream(entry);
78 while (in.available() > 0) {
79 int numBytesRead = in.read(buf);
80 if (numBytesRead < 0) {
81 break;
82 }
83 bos.write(buf, 0, numBytesRead);
84
85 // sanity checking
86 totalNumBytesRead += numBytesRead;
87 if (totalNumBytesRead > Constants.MiB) {
88 throw new Error("Class file " + entry.getName() + " larger than 1 MiB! Something is wrong!");
89 }
90 }
91
92 // get a javassist handle for the class
93 String className = Descriptor.toJavaName(getClassEntry(entry).getName());
94 ClassPool classPool = new ClassPool();
95 classPool.appendSystemPath();
96 classPool.insertClassPath(new ByteArrayClassPath(className, bos.toByteArray()));
97 return classPool.get(className);
98 }
99
100 private static ClassEntry getClassEntry(JarEntry entry) {
101 return new ClassEntry(entry.getName().substring(0, entry.getName().length() - ".class".length()));
102 }
103
104 @Override
105 public boolean hasNext() {
106 return this.iter.hasNext();
107 }
108
109 @Override
110 public CtClass next() {
111 JarEntry entry = this.iter.next();
112 try {
113 return getClass(this.jar, entry);
114 } catch (IOException | NotFoundException ex) {
115 throw new Error("Unable to load class: " + entry.getName());
116 }
117 }
118
119 @Override
120 public void remove() {
121 throw new UnsupportedOperationException();
122 }
123}
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 @@
12package cuchaz.enigma.analysis; 12package cuchaz.enigma.analysis;
13 13
14import com.google.common.collect.*; 14import com.google.common.collect.*;
15import cuchaz.enigma.bytecode.AccessFlags;
15import cuchaz.enigma.mapping.*; 16import cuchaz.enigma.mapping.*;
16import cuchaz.enigma.mapping.Translator; 17import cuchaz.enigma.mapping.entry.*;
17import javassist.*; 18import org.objectweb.asm.Opcodes;
18import javassist.bytecode.*;
19import javassist.expr.*;
20 19
21import java.lang.reflect.Modifier;
22import java.util.*; 20import java.util.*;
23import java.util.jar.JarFile;
24 21
25public class JarIndex { 22public class JarIndex {
26 23
24 private final ReferencedEntryPool entryPool;
25
27 private Set<ClassEntry> obfClassEntries; 26 private Set<ClassEntry> obfClassEntries;
28 private TranslationIndex translationIndex; 27 private TranslationIndex translationIndex;
29 private Map<Entry, Access> access; 28 private Map<Entry, Access> access;
30 private Multimap<ClassEntry, FieldEntry> fields; 29 private Multimap<ClassEntry, FieldDefEntry> fields;
31 private Multimap<ClassEntry, BehaviorEntry> behaviors; 30 private Multimap<ClassEntry, MethodDefEntry> methods;
32 private Multimap<String, MethodEntry> methodImplementations; 31 private Multimap<String, MethodDefEntry> methodImplementations;
33 private Multimap<BehaviorEntry, EntryReference<BehaviorEntry, BehaviorEntry>> behaviorReferences; 32 private Multimap<MethodEntry, EntryReference<MethodEntry, MethodDefEntry>> methodsReferencing;
34 private Multimap<FieldEntry, EntryReference<FieldEntry, BehaviorEntry>> fieldReferences; 33 private Multimap<MethodEntry, MethodEntry> methodReferences;
34 private Multimap<FieldEntry, EntryReference<FieldEntry, MethodDefEntry>> fieldReferences;
35 private Multimap<ClassEntry, ClassEntry> innerClassesByOuter; 35 private Multimap<ClassEntry, ClassEntry> innerClassesByOuter;
36 private Map<ClassEntry, ClassEntry> outerClassesByInner; 36 private Map<ClassEntry, ClassEntry> outerClassesByInner;
37 private Map<ClassEntry, BehaviorEntry> anonymousClasses;
38 private Map<MethodEntry, MethodEntry> bridgedMethods; 37 private Map<MethodEntry, MethodEntry> bridgedMethods;
39 private Set<MethodEntry> syntheticMethods; 38 private Set<MethodEntry> syntheticMethods;
40 39
41 public JarIndex() { 40 public JarIndex(ReferencedEntryPool entryPool) {
41 this.entryPool = entryPool;
42 this.obfClassEntries = Sets.newHashSet(); 42 this.obfClassEntries = Sets.newHashSet();
43 this.translationIndex = new TranslationIndex(); 43 this.translationIndex = new TranslationIndex(entryPool);
44 this.access = Maps.newHashMap(); 44 this.access = Maps.newHashMap();
45 this.fields = HashMultimap.create(); 45 this.fields = HashMultimap.create();
46 this.behaviors = HashMultimap.create(); 46 this.methods = HashMultimap.create();
47 this.methodImplementations = HashMultimap.create(); 47 this.methodImplementations = HashMultimap.create();
48 this.behaviorReferences = HashMultimap.create(); 48 this.methodsReferencing = HashMultimap.create();
49 this.methodReferences = HashMultimap.create();
49 this.fieldReferences = HashMultimap.create(); 50 this.fieldReferences = HashMultimap.create();
50 this.innerClassesByOuter = HashMultimap.create(); 51 this.innerClassesByOuter = HashMultimap.create();
51 this.outerClassesByInner = Maps.newHashMap(); 52 this.outerClassesByInner = Maps.newHashMap();
52 this.anonymousClasses = Maps.newHashMap();
53 this.bridgedMethods = Maps.newHashMap(); 53 this.bridgedMethods = Maps.newHashMap();
54 this.syntheticMethods = Sets.newHashSet(); 54 this.syntheticMethods = Sets.newHashSet();
55 } 55 }
56 56
57 public void indexJar(JarFile jar, boolean buildInnerClasses) { 57 public void indexJar(ParsedJar jar, boolean buildInnerClasses) {
58 58
59 // step 1: read the class names 59 // step 1: read the class names
60 this.obfClassEntries.addAll(JarClassIterator.getClassEntries(jar)); 60 obfClassEntries.addAll(jar.getClassEntries());
61
62 // step 2: index field/method/constructor access
63 for (CtClass c : JarClassIterator.classes(jar)) {
64 for (CtField field : c.getDeclaredFields()) {
65 FieldEntry fieldEntry = EntryFactory.getFieldEntry(field);
66 this.access.put(fieldEntry, Access.get(field));
67 this.fields.put(fieldEntry.getClassEntry(), fieldEntry);
68 }
69 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
70 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
71 this.access.put(behaviorEntry, Access.get(behavior));
72 this.behaviors.put(behaviorEntry.getClassEntry(), behaviorEntry);
73 }
74 }
75 61
76 // step 3: index extends, implements, fields, and methods 62 // step 2: index classes, fields, methods, interfaces
77 for (CtClass c : JarClassIterator.classes(jar)) { 63 jar.visit(node -> node.accept(new IndexClassVisitor(this, Opcodes.ASM5)));
78 this.translationIndex.indexClass(c);
79 String className = Descriptor.toJvmName(c.getName());
80 for (String interfaceName : c.getClassFile().getInterfaces()) {
81 className = Descriptor.toJvmName(className);
82 interfaceName = Descriptor.toJvmName(interfaceName);
83 if (className.equals(interfaceName)) {
84 throw new IllegalArgumentException("Class cannot be its own interface! " + className);
85 }
86 }
87 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
88 indexBehavior(behavior);
89 }
90 }
91 64
92 // step 4: index field, method, constructor references 65 // step 3: index field, method, constructor references
93 for (CtClass c : JarClassIterator.classes(jar)) { 66 jar.visit(node -> node.accept(new IndexReferenceVisitor(this, Opcodes.ASM5)));
94 for (CtBehavior behavior : c.getDeclaredBehaviors()) { 67
95 indexBehaviorReferences(behavior); 68 // step 4: index access and bridged methods
69 for (MethodDefEntry methodEntry : methods.values()) {
70 // look for access and bridged methods
71 MethodEntry accessedMethod = findAccessMethod(methodEntry);
72 if (accessedMethod != null) {
73 if (isBridgedMethod(accessedMethod, methodEntry)) {
74 this.bridgedMethods.put(methodEntry, accessedMethod);
75 }
96 } 76 }
97 } 77 }
98 78
99 if (buildInnerClasses) { 79 if (buildInnerClasses) {
100
101 // step 5: index inner classes and anonymous classes 80 // step 5: index inner classes and anonymous classes
102 for (CtClass c : JarClassIterator.classes(jar)) { 81 jar.visit(node -> node.accept(new IndexInnerClassVisitor(this, Opcodes.ASM5)));
103 ClassEntry innerClassEntry = EntryFactory.getClassEntry(c);
104 ClassEntry outerClassEntry = findOuterClass(c);
105 if (outerClassEntry != null) {
106 this.innerClassesByOuter.put(outerClassEntry, innerClassEntry);
107 boolean innerWasAdded = this.outerClassesByInner.put(innerClassEntry, outerClassEntry) == null;
108 assert (innerWasAdded);
109
110 BehaviorEntry enclosingBehavior = isAnonymousClass(c, outerClassEntry);
111 if (enclosingBehavior != null) {
112 this.anonymousClasses.put(innerClassEntry, enclosingBehavior);
113
114 // DEBUG
115 //System.out.println("ANONYMOUS: " + outerClassEntry.getName() + "$" + innerClassEntry.getSimpleName());
116 }/* else {
117 // DEBUG
118 //System.out.println("INNER: " + outerClassEntry.getName() + "$" + innerClassEntry.getSimpleName());
119 }*/
120 }
121 }
122 82
123 // step 6: update other indices with inner class info 83 // step 6: update other indices with inner class info
124 Map<String, String> renames = Maps.newHashMap(); 84 Map<String, String> renames = Maps.newHashMap();
@@ -133,385 +93,138 @@ public class JarIndex {
133 EntryRenamer.renameClassesInSet(renames, this.obfClassEntries); 93 EntryRenamer.renameClassesInSet(renames, this.obfClassEntries);
134 this.translationIndex.renameClasses(renames); 94 this.translationIndex.renameClasses(renames);
135 EntryRenamer.renameClassesInMultimap(renames, this.methodImplementations); 95 EntryRenamer.renameClassesInMultimap(renames, this.methodImplementations);
136 EntryRenamer.renameClassesInMultimap(renames, this.behaviorReferences); 96 EntryRenamer.renameClassesInMultimap(renames, this.methodsReferencing);
97 EntryRenamer.renameClassesInMultimap(renames, this.methodReferences);
137 EntryRenamer.renameClassesInMultimap(renames, this.fieldReferences); 98 EntryRenamer.renameClassesInMultimap(renames, this.fieldReferences);
138 EntryRenamer.renameClassesInMap(renames, this.access); 99 EntryRenamer.renameClassesInMap(renames, this.access);
139 } 100 }
140 } 101 }
141 102
142 private void indexBehavior(CtBehavior behavior) { 103 protected ClassDefEntry indexClass(int access, String name, String signature, String superName, String[] interfaces) {
143 // get the behavior entry 104 for (String interfaceName : interfaces) {
144 final BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); 105 if (name.equals(interfaceName)) {
145 if (behaviorEntry instanceof MethodEntry) { 106 throw new IllegalArgumentException("Class cannot be its own interface! " + name);
146 MethodEntry methodEntry = (MethodEntry) behaviorEntry;
147
148 // is synthetic
149 if ((behavior.getModifiers() & AccessFlag.SYNTHETIC) != 0) {
150 syntheticMethods.add(methodEntry);
151 } 107 }
152
153 // index implementation
154 this.methodImplementations.put(behaviorEntry.getClassName(), methodEntry);
155
156 // look for bridge and bridged methods
157 CtMethod bridgedMethod = getBridgedMethod((CtMethod) behavior);
158 if (bridgedMethod != null) {
159 this.bridgedMethods.put(methodEntry, EntryFactory.getMethodEntry(bridgedMethod));
160 }
161 }
162 // looks like we don't care about constructors here
163 }
164
165 private void indexBehaviorReferences(CtBehavior behavior) {
166 // index method calls
167 final BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
168 try {
169 behavior.instrument(new ExprEditor() {
170 @Override
171 public void edit(MethodCall call) {
172 MethodEntry calledMethodEntry = EntryFactory.getMethodEntry(call);
173 ClassEntry resolvedClassEntry = translationIndex.resolveEntryClass(calledMethodEntry);
174 if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledMethodEntry.getClassEntry())) {
175 calledMethodEntry = new MethodEntry(
176 resolvedClassEntry,
177 calledMethodEntry.getName(),
178 calledMethodEntry.getSignature()
179 );
180 }
181 EntryReference<BehaviorEntry, BehaviorEntry> reference = new EntryReference<>(
182 calledMethodEntry,
183 call.getMethodName(),
184 behaviorEntry
185 );
186 behaviorReferences.put(calledMethodEntry, reference);
187 }
188
189 @Override
190 public void edit(FieldAccess call) {
191 FieldEntry calledFieldEntry = EntryFactory.getFieldEntry(call);
192 ClassEntry resolvedClassEntry = translationIndex.resolveEntryClass(calledFieldEntry);
193 if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledFieldEntry.getClassEntry())) {
194 calledFieldEntry = new FieldEntry(calledFieldEntry, resolvedClassEntry);
195 }
196 EntryReference<FieldEntry, BehaviorEntry> reference = new EntryReference<>(
197 calledFieldEntry,
198 call.getFieldName(),
199 behaviorEntry
200 );
201 fieldReferences.put(calledFieldEntry, reference);
202 }
203
204 @Override
205 public void edit(ConstructorCall call) {
206 ConstructorEntry calledConstructorEntry = EntryFactory.getConstructorEntry(call);
207 EntryReference<BehaviorEntry, BehaviorEntry> reference = new EntryReference<>(
208 calledConstructorEntry,
209 call.getMethodName(),
210 behaviorEntry
211 );
212 behaviorReferences.put(calledConstructorEntry, reference);
213 }
214
215 @Override
216 public void edit(NewExpr call) {
217 ConstructorEntry calledConstructorEntry = EntryFactory.getConstructorEntry(call);
218 EntryReference<BehaviorEntry, BehaviorEntry> reference = new EntryReference<>(
219 calledConstructorEntry,
220 call.getClassName(),
221 behaviorEntry
222 );
223 behaviorReferences.put(calledConstructorEntry, reference);
224 }
225 });
226 } catch (CannotCompileException ex) {
227 throw new Error(ex);
228 } 108 }
109 return this.translationIndex.indexClass(access, name, signature, superName, interfaces);
229 } 110 }
230 111
231 private CtMethod getBridgedMethod(CtMethod method) { 112 protected void indexField(ClassDefEntry owner, int access, String name, String desc, String signature) {
232 113 FieldDefEntry fieldEntry = new FieldDefEntry(owner, name, new TypeDescriptor(desc), Signature.createTypedSignature(signature), new AccessFlags(access));
233 // bridge methods just call another method, cast it to the return type, and return the result 114 this.translationIndex.indexField(fieldEntry);
234 // let's see if we can detect this scenario 115 this.access.put(fieldEntry, Access.get(access));
235 116 this.fields.put(fieldEntry.getOwnerClassEntry(), fieldEntry);
236 // skip non-synthetic methods 117 }
237 if ((method.getModifiers() & AccessFlag.SYNTHETIC) == 0) {
238 return null;
239 }
240 118
241 // get all the called methods 119 protected void indexMethod(ClassDefEntry owner, int access, String name, String desc, String signature) {
242 final List<MethodCall> methodCalls = Lists.newArrayList(); 120 MethodDefEntry methodEntry = new MethodDefEntry(owner, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access));
243 try { 121 this.translationIndex.indexMethod(methodEntry);
244 method.instrument(new ExprEditor() { 122 this.access.put(methodEntry, Access.get(access));
245 @Override 123 this.methods.put(methodEntry.getOwnerClassEntry(), methodEntry);
246 public void edit(MethodCall call) {
247 methodCalls.add(call);
248 }
249 });
250 } catch (CannotCompileException ex) {
251 // this is stupid... we're not even compiling anything
252 throw new Error(ex);
253 }
254 124
255 // is there just one? 125 if (new AccessFlags(access).isSynthetic()) {
256 if (methodCalls.size() != 1) { 126 syntheticMethods.add(methodEntry);
257 return null;
258 } 127 }
259 MethodCall call = methodCalls.get(0);
260 128
261 try { 129 // we don't care about constructors here
262 // we have a bridge method! 130 if (!methodEntry.isConstructor()) {
263 return call.getMethod(); 131 // index implementation
264 } catch (NotFoundException ex) { 132 this.methodImplementations.put(methodEntry.getClassName(), methodEntry);
265 // can't find the type? not a bridge method
266 return null;
267 } 133 }
268 } 134 }
269 135
270 private ClassEntry findOuterClass(CtClass c) { 136 protected void indexMethodCall(MethodDefEntry callerEntry, String owner, String name, String desc) {
271 137 MethodEntry referencedMethod = new MethodEntry(entryPool.getClass(owner), name, new MethodDescriptor(desc));
272 ClassEntry classEntry = EntryFactory.getClassEntry(c); 138 ClassEntry resolvedClassEntry = translationIndex.resolveEntryOwner(referencedMethod);
273 139 if (resolvedClassEntry != null && !resolvedClassEntry.equals(referencedMethod.getOwnerClassEntry())) {
274 // does this class already have an outer class? 140 referencedMethod = referencedMethod.updateOwnership(resolvedClassEntry);
275 if (classEntry.isInnerClass()) {
276 return classEntry.getOuterClassEntry();
277 } 141 }
278 142 methodsReferencing.put(referencedMethod, new EntryReference<>(referencedMethod, referencedMethod.getName(), callerEntry));
279 // inner classes: 143 methodReferences.put(callerEntry, referencedMethod);
280 // have constructors that can (illegally) set synthetic fields
281 // the outer class is the only class that calls constructors
282
283 // use the synthetic fields to find the synthetic constructors
284 for (CtConstructor constructor : c.getDeclaredConstructors()) {
285 Set<String> syntheticFieldTypes = Sets.newHashSet();
286 if (!isIllegalConstructor(syntheticFieldTypes, constructor)) {
287 continue;
288 }
289
290 ConstructorEntry constructorEntry = EntryFactory.getConstructorEntry(constructor);
291
292 // gather the classes from the illegally-set synthetic fields
293 Set<ClassEntry> illegallySetClasses = Sets.newHashSet();
294 for (String type : syntheticFieldTypes) {
295 if (type.startsWith("L")) {
296 ClassEntry outerClassEntry = new ClassEntry(type.substring(1, type.length() - 1));
297 if (isSaneOuterClass(outerClassEntry, classEntry)) {
298 illegallySetClasses.add(outerClassEntry);
299 }
300 }
301 }
302
303 // who calls this constructor?
304 Set<ClassEntry> callerClasses = Sets.newHashSet();
305 for (EntryReference<BehaviorEntry, BehaviorEntry> reference : getBehaviorReferences(constructorEntry)) {
306
307 // make sure it's not a call to super
308 if (reference.entry instanceof ConstructorEntry && reference.context instanceof ConstructorEntry) {
309
310 // is the entry a superclass of the context?
311 ClassEntry calledClassEntry = reference.entry.getClassEntry();
312 ClassEntry superclassEntry = this.translationIndex.getSuperclass(reference.context.getClassEntry());
313 if (superclassEntry != null && superclassEntry.equals(calledClassEntry)) {
314 // it's a super call, skip
315 continue;
316 }
317 }
318
319 if (isSaneOuterClass(reference.context.getClassEntry(), classEntry)) {
320 callerClasses.add(reference.context.getClassEntry());
321 }
322 }
323
324 // do we have an answer yet?
325 if (callerClasses.isEmpty()) {
326 if (illegallySetClasses.size() == 1) {
327 return illegallySetClasses.iterator().next();
328 } else {
329 System.out.println(String.format("WARNING: Unable to find outer class for %s. No caller and no illegally set field classes.", classEntry));
330 }
331 } else {
332 if (callerClasses.size() == 1) {
333 return callerClasses.iterator().next();
334 } else {
335 // multiple callers, do the illegally set classes narrow it down?
336 Set<ClassEntry> intersection = Sets.newHashSet(callerClasses);
337 intersection.retainAll(illegallySetClasses);
338 if (intersection.size() == 1) {
339 return intersection.iterator().next();
340 } else {
341 System.out.println(String.format("WARNING: Unable to choose outer class for %s among options: %s", classEntry, callerClasses));
342 }
343 }
344 }
345 }
346
347 return null;
348 } 144 }
349 145
350 private boolean isSaneOuterClass(ClassEntry outerClassEntry, ClassEntry innerClassEntry) { 146 protected void indexFieldAccess(MethodDefEntry callerEntry, String owner, String name, String desc) {
351 147 FieldEntry referencedField = new FieldEntry(entryPool.getClass(owner), name, new TypeDescriptor(desc));
352 // clearly this would be silly 148 ClassEntry resolvedClassEntry = translationIndex.resolveEntryOwner(referencedField);
353 if (outerClassEntry.equals(innerClassEntry)) { 149 if (resolvedClassEntry != null && !resolvedClassEntry.equals(referencedField.getOwnerClassEntry())) {
354 return false; 150 referencedField = referencedField.updateOwnership(resolvedClassEntry);
355 } 151 }
356 152 fieldReferences.put(referencedField, new EntryReference<>(referencedField, referencedField.getName(), callerEntry));
357 // is the outer class in the jar?
358 return this.obfClassEntries.contains(outerClassEntry);
359
360 } 153 }
361 154
362 @SuppressWarnings("unchecked") 155 public void indexInnerClass(ClassEntry innerEntry, ClassEntry outerEntry) {
363 private boolean isIllegalConstructor(Set<String> syntheticFieldTypes, CtConstructor constructor) { 156 this.innerClassesByOuter.put(outerEntry, innerEntry);
157 this.outerClassesByInner.putIfAbsent(innerEntry, outerEntry);
158 }
364 159
365 // illegal constructors only set synthetic member fields, then call super() 160 private MethodEntry findAccessMethod(MethodDefEntry method) {
366 String className = constructor.getDeclaringClass().getName();
367 161
368 // collect all the field accesses, constructor calls, and method calls 162 // we want to find all compiler-added methods that directly call another with no processing
369 final List<FieldAccess> illegalFieldWrites = Lists.newArrayList();
370 final List<ConstructorCall> constructorCalls = Lists.newArrayList();
371 try {
372 constructor.instrument(new ExprEditor() {
373 @Override
374 public void edit(FieldAccess fieldAccess) {
375 if (fieldAccess.isWriter() && constructorCalls.isEmpty()) {
376 illegalFieldWrites.add(fieldAccess);
377 }
378 }
379 163
380 @Override 164 // skip non-synthetic methods
381 public void edit(ConstructorCall constructorCall) { 165 if (!method.getAccess().isSynthetic()) {
382 constructorCalls.add(constructorCall); 166 return null;
383 }
384 });
385 } catch (CannotCompileException ex) {
386 // we're not compiling anything... this is stupid
387 throw new Error(ex);
388 }
389
390 // are there any illegal field writes?
391 if (illegalFieldWrites.isEmpty()) {
392 return false;
393 } 167 }
394 168
395 // are all the writes to synthetic fields? 169 // get all the methods that we call
396 for (FieldAccess fieldWrite : illegalFieldWrites) { 170 final Collection<MethodEntry> referencedMethods = methodReferences.get(method);
397
398 // all illegal writes have to be to the local class
399 if (!fieldWrite.getClassName().equals(className)) {
400 System.err.println(String.format("WARNING: illegal write to non-member field %s.%s", fieldWrite.getClassName(), fieldWrite.getFieldName()));
401 return false;
402 }
403
404 // find the field
405 FieldInfo fieldInfo = null;
406 for (FieldInfo info : (List<FieldInfo>) constructor.getDeclaringClass().getClassFile().getFields()) {
407 if (info.getName().equals(fieldWrite.getFieldName()) && info.getDescriptor().equals(fieldWrite.getSignature())) {
408 fieldInfo = info;
409 break;
410 }
411 }
412 if (fieldInfo == null) {
413 // field is in a superclass or something, can't be a local synthetic member
414 return false;
415 }
416 171
417 // is this field synthetic? 172 // is there just one?
418 boolean isSynthetic = (fieldInfo.getAccessFlags() & AccessFlag.SYNTHETIC) != 0; 173 if (referencedMethods.size() != 1) {
419 if (isSynthetic) { 174 return null;
420 syntheticFieldTypes.add(fieldInfo.getDescriptor());
421 } else {
422 System.err.println(String.format("WARNING: illegal write to non synthetic field %s %s.%s", fieldInfo.getDescriptor(), className, fieldInfo.getName()));
423 return false;
424 }
425 } 175 }
426 176
427 // we passed all the tests! 177 return referencedMethods.stream().findFirst().orElse(null);
428 return true;
429 } 178 }
430 179
431 private BehaviorEntry isAnonymousClass(CtClass c, ClassEntry outerClassEntry) { 180 private boolean isBridgedMethod(MethodEntry called, MethodEntry access) {
432 181 // Bridged methods will always have the same name as the method they are calling
433 // is this class already marked anonymous? 182 // They will also have the same amount of parameters (though equal descriptors cannot be guaranteed)
434 EnclosingMethodAttribute enclosingMethodAttribute = (EnclosingMethodAttribute) c.getClassFile().getAttribute(EnclosingMethodAttribute.tag); 183 if (!called.getName().equals(access.getName()) || called.getDesc().getArgumentDescs().size() != access.getDesc().getArgumentDescs().size()) {
435 if (enclosingMethodAttribute != null) { 184 return false;
436 if (enclosingMethodAttribute.methodIndex() > 0) {
437 return EntryFactory.getBehaviorEntry(
438 Descriptor.toJvmName(enclosingMethodAttribute.className()),
439 enclosingMethodAttribute.methodName(),
440 enclosingMethodAttribute.methodDescriptor()
441 );
442 } else {
443 // an attribute but no method? assume not anonymous
444 return null;
445 }
446 }
447
448 // if there's an inner class attribute, but not an enclosing method attribute, then it's not anonymous
449 InnerClassesAttribute innerClassesAttribute = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag);
450 if (innerClassesAttribute != null) {
451 return null;
452 } 185 }
453 186
454 ClassEntry innerClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); 187 TypeDescriptor accessReturn = access.getDesc().getReturnDesc();
455 188 TypeDescriptor calledReturn = called.getDesc().getReturnDesc();
456 // anonymous classes: 189 if (calledReturn.isVoid() || calledReturn.isPrimitive() || accessReturn.isVoid() || accessReturn.isPrimitive()) {
457 // can't be abstract 190 return false;
458 // have only one constructor
459 // it's called exactly once by the outer class
460 // the type the instance is assigned to can't be this type
461
462 // is abstract?
463 if (Modifier.isAbstract(c.getModifiers())) {
464 return null;
465 } 191 }
466 192
467 // is there exactly one constructor? 193 // Bridged methods will never have the same type as what they are calling
468 if (c.getDeclaredConstructors().length != 1) { 194 if (accessReturn.equals(calledReturn)) {
469 return null; 195 return false;
470 } 196 }
471 CtConstructor constructor = c.getDeclaredConstructors()[0];
472 197
473 // is this constructor called exactly once? 198 String accessType = accessReturn.toString();
474 ConstructorEntry constructorEntry = EntryFactory.getConstructorEntry(constructor);
475 Collection<EntryReference<BehaviorEntry, BehaviorEntry>> references = getBehaviorReferences(constructorEntry);
476 if (references.size() != 1) {
477 return null;
478 }
479 199
480 // does the caller use this type? 200 // If we're casting down from generic type to type-erased Object we're a bridge method
481 BehaviorEntry caller = references.iterator().next().context; 201 if (accessType.equals("Ljava/lang/Object;")) {
482 for (FieldEntry fieldEntry : getReferencedFields(caller)) { 202 return true;
483 if (fieldEntry.getType().hasClass() && fieldEntry.getType().getClassEntry().equals(innerClassEntry)) {
484 // caller references this type, so it can't be anonymous
485 return null;
486 }
487 }
488 for (BehaviorEntry behaviorEntry : getReferencedBehaviors(caller)) {
489 if (behaviorEntry.getSignature().hasClass(innerClassEntry)) {
490 return null;
491 }
492 } 203 }
493 204
494 return caller; 205 // Now we need to detect cases where we are being casted down to a higher type bound
206 List<ClassEntry> calledAncestry = translationIndex.getAncestry(calledReturn.getTypeEntry());
207 return calledAncestry.contains(accessReturn.getTypeEntry());
495 } 208 }
496 209
497 public Set<ClassEntry> getObfClassEntries() { 210 public Set<ClassEntry> getObfClassEntries() {
498 return this.obfClassEntries; 211 return this.obfClassEntries;
499 } 212 }
500 213
501 public Collection<FieldEntry> getObfFieldEntries() { 214 public Collection<FieldDefEntry> getObfFieldEntries() {
502 return this.fields.values(); 215 return this.fields.values();
503 } 216 }
504 217
505 public Collection<FieldEntry> getObfFieldEntries(ClassEntry classEntry) { 218 public Collection<FieldDefEntry> getObfFieldEntries(ClassEntry classEntry) {
506 return this.fields.get(classEntry); 219 return this.fields.get(classEntry);
507 } 220 }
508 221
509 public Collection<BehaviorEntry> getObfBehaviorEntries() { 222 public Collection<MethodDefEntry> getObfBehaviorEntries() {
510 return this.behaviors.values(); 223 return this.methods.values();
511 } 224 }
512 225
513 public Collection<BehaviorEntry> getObfBehaviorEntries(ClassEntry classEntry) { 226 public Collection<MethodDefEntry> getObfBehaviorEntries(ClassEntry classEntry) {
514 return this.behaviors.get(classEntry); 227 return this.methods.get(classEntry);
515 } 228 }
516 229
517 public TranslationIndex getTranslationIndex() { 230 public TranslationIndex getTranslationIndex() {
@@ -533,8 +246,8 @@ public class JarIndex {
533 } 246 }
534 } 247 }
535 ClassInheritanceTreeNode rootNode = new ClassInheritanceTreeNode( 248 ClassInheritanceTreeNode rootNode = new ClassInheritanceTreeNode(
536 deobfuscatingTranslator, 249 deobfuscatingTranslator,
537 ancestry.get(ancestry.size() - 1) 250 ancestry.get(ancestry.size() - 1)
538 ); 251 );
539 252
540 // expand all children recursively 253 // expand all children recursively
@@ -557,28 +270,20 @@ public class JarIndex {
557 public MethodInheritanceTreeNode getMethodInheritance(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) { 270 public MethodInheritanceTreeNode getMethodInheritance(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) {
558 271
559 // travel to the ancestor implementation 272 // travel to the ancestor implementation
560 ClassEntry baseImplementationClassEntry = obfMethodEntry.getClassEntry(); 273 ClassEntry baseImplementationClassEntry = obfMethodEntry.getOwnerClassEntry();
561 for (ClassEntry ancestorClassEntry : this.translationIndex.getAncestry(obfMethodEntry.getClassEntry())) { 274 for (ClassEntry ancestorClassEntry : this.translationIndex.getAncestry(obfMethodEntry.getOwnerClassEntry())) {
562 MethodEntry ancestorMethodEntry = new MethodEntry( 275 MethodEntry ancestorMethodEntry = entryPool.getMethod(ancestorClassEntry, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString());
563 new ClassEntry(ancestorClassEntry), 276 if (ancestorMethodEntry != null && containsObfMethod(ancestorMethodEntry)) {
564 obfMethodEntry.getName(),
565 obfMethodEntry.getSignature()
566 );
567 if (containsObfBehavior(ancestorMethodEntry)) {
568 baseImplementationClassEntry = ancestorClassEntry; 277 baseImplementationClassEntry = ancestorClassEntry;
569 } 278 }
570 } 279 }
571 280
572 // make a root node at the base 281 // make a root node at the base
573 MethodEntry methodEntry = new MethodEntry( 282 MethodEntry methodEntry = entryPool.getMethod(baseImplementationClassEntry, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString());
574 baseImplementationClassEntry,
575 obfMethodEntry.getName(),
576 obfMethodEntry.getSignature()
577 );
578 MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode( 283 MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode(
579 deobfuscatingTranslator, 284 deobfuscatingTranslator,
580 methodEntry, 285 methodEntry,
581 containsObfBehavior(methodEntry) 286 containsObfMethod(methodEntry)
582 ); 287 );
583 288
584 // expand the full tree 289 // expand the full tree
@@ -599,12 +304,8 @@ public class JarIndex {
599 for (ClassEntry interfaceEntry : getInterfaces(obfMethodEntry.getClassName())) { 304 for (ClassEntry interfaceEntry : getInterfaces(obfMethodEntry.getClassName())) {
600 305
601 // is this method defined in this interface? 306 // is this method defined in this interface?
602 MethodEntry methodInterface = new MethodEntry( 307 MethodEntry methodInterface = entryPool.getMethod(interfaceEntry, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString());
603 interfaceEntry, 308 if (methodInterface != null && containsObfMethod(methodInterface)) {
604 obfMethodEntry.getName(),
605 obfMethodEntry.getSignature()
606 );
607 if (containsObfBehavior(methodInterface)) {
608 interfaceMethodEntries.add(methodInterface); 309 interfaceMethodEntries.add(methodInterface);
609 } 310 }
610 } 311 }
@@ -623,27 +324,30 @@ public class JarIndex {
623 324
624 public Set<MethodEntry> getRelatedMethodImplementations(MethodEntry obfMethodEntry) { 325 public Set<MethodEntry> getRelatedMethodImplementations(MethodEntry obfMethodEntry) {
625 Set<MethodEntry> methodEntries = Sets.newHashSet(); 326 Set<MethodEntry> methodEntries = Sets.newHashSet();
626 getRelatedMethodImplementations(methodEntries, getMethodInheritance(new Translator(), obfMethodEntry)); 327 getRelatedMethodImplementations(methodEntries, getMethodInheritance(new DirectionalTranslator(entryPool), obfMethodEntry));
627 return methodEntries; 328 return methodEntries;
628 } 329 }
629 330
630 private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodInheritanceTreeNode node) { 331 private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodInheritanceTreeNode node) {
631 MethodEntry methodEntry = node.getMethodEntry(); 332 MethodEntry methodEntry = node.getMethodEntry();
333 if (methodEntries.contains(methodEntry)) {
334 return;
335 }
632 336
633 if (containsObfBehavior(methodEntry)) { 337 if (containsObfMethod(methodEntry)) {
634 // collect the entry 338 // collect the entry
635 methodEntries.add(methodEntry); 339 methodEntries.add(methodEntry);
636 } 340 }
637 341
638 // look at bridged methods! 342 // look at bridge methods!
639 MethodEntry bridgedEntry = getBridgedMethod(methodEntry); 343 MethodEntry bridgedMethod = getBridgedMethod(methodEntry);
640 while (bridgedEntry != null) { 344 while (bridgedMethod != null) {
641 methodEntries.addAll(getRelatedMethodImplementations(bridgedEntry)); 345 methodEntries.addAll(getRelatedMethodImplementations(bridgedMethod));
642 bridgedEntry = getBridgedMethod(bridgedEntry); 346 bridgedMethod = getBridgedMethod(bridgedMethod);
643 } 347 }
644 348
645 // look at interface methods too 349 // look at interface methods too
646 for (MethodImplementationsTreeNode implementationsNode : getMethodImplementations(new Translator(), methodEntry)) { 350 for (MethodImplementationsTreeNode implementationsNode : getMethodImplementations(new DirectionalTranslator(entryPool), methodEntry)) {
647 getRelatedMethodImplementations(methodEntries, implementationsNode); 351 getRelatedMethodImplementations(methodEntries, implementationsNode);
648 } 352 }
649 353
@@ -655,16 +359,16 @@ public class JarIndex {
655 359
656 private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodImplementationsTreeNode node) { 360 private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodImplementationsTreeNode node) {
657 MethodEntry methodEntry = node.getMethodEntry(); 361 MethodEntry methodEntry = node.getMethodEntry();
658 if (containsObfBehavior(methodEntry)) { 362 if (containsObfMethod(methodEntry)) {
659 // collect the entry 363 // collect the entry
660 methodEntries.add(methodEntry); 364 methodEntries.add(methodEntry);
661 } 365 }
662 366
663 // look at bridged methods! 367 // look at bridge methods!
664 MethodEntry bridgedEntry = getBridgedMethod(methodEntry); 368 MethodEntry bridgedMethod = getBridgedMethod(methodEntry);
665 while (bridgedEntry != null) { 369 while (bridgedMethod != null) {
666 methodEntries.addAll(getRelatedMethodImplementations(bridgedEntry)); 370 methodEntries.addAll(getRelatedMethodImplementations(bridgedMethod));
667 bridgedEntry = getBridgedMethod(bridgedEntry); 371 bridgedMethod = getBridgedMethod(bridgedMethod);
668 } 372 }
669 373
670 // recurse 374 // recurse
@@ -673,34 +377,27 @@ public class JarIndex {
673 } 377 }
674 } 378 }
675 379
676 public Collection<EntryReference<FieldEntry, BehaviorEntry>> getFieldReferences(FieldEntry fieldEntry) { 380 public Collection<EntryReference<FieldEntry, MethodDefEntry>> getFieldReferences(FieldEntry fieldEntry) {
677 return this.fieldReferences.get(fieldEntry); 381 return this.fieldReferences.get(fieldEntry);
678 } 382 }
679 383
680 public Collection<FieldEntry> getReferencedFields(BehaviorEntry behaviorEntry) { 384 public Collection<FieldEntry> getReferencedFields(MethodDefEntry methodEntry) {
681 // linear search is fast enough for now 385 // linear search is fast enough for now
682 Set<FieldEntry> fieldEntries = Sets.newHashSet(); 386 Set<FieldEntry> fieldEntries = Sets.newHashSet();
683 for (EntryReference<FieldEntry, BehaviorEntry> reference : this.fieldReferences.values()) { 387 for (EntryReference<FieldEntry, MethodDefEntry> reference : this.fieldReferences.values()) {
684 if (reference.context == behaviorEntry) { 388 if (reference.context == methodEntry) {
685 fieldEntries.add(reference.entry); 389 fieldEntries.add(reference.entry);
686 } 390 }
687 } 391 }
688 return fieldEntries; 392 return fieldEntries;
689 } 393 }
690 394
691 public Collection<EntryReference<BehaviorEntry, BehaviorEntry>> getBehaviorReferences(BehaviorEntry behaviorEntry) { 395 public Collection<EntryReference<MethodEntry, MethodDefEntry>> getMethodsReferencing(MethodEntry methodEntry) {
692 return this.behaviorReferences.get(behaviorEntry); 396 return this.methodsReferencing.get(methodEntry);
693 } 397 }
694 398
695 public Collection<BehaviorEntry> getReferencedBehaviors(BehaviorEntry behaviorEntry) { 399 public Collection<MethodEntry> getReferencedMethods(MethodDefEntry methodEntry) {
696 // linear search is fast enough for now 400 return this.methodReferences.get(methodEntry);
697 Set<BehaviorEntry> behaviorEntries = Sets.newHashSet();
698 for (EntryReference<BehaviorEntry, BehaviorEntry> reference : this.behaviorReferences.values()) {
699 if (reference.context == behaviorEntry) {
700 behaviorEntries.add(reference.entry);
701 }
702 }
703 return behaviorEntries;
704 } 401 }
705 402
706 public Collection<ClassEntry> getInnerClasses(ClassEntry obfOuterClassEntry) { 403 public Collection<ClassEntry> getInnerClasses(ClassEntry obfOuterClassEntry) {
@@ -711,22 +408,13 @@ public class JarIndex {
711 return this.outerClassesByInner.get(obfInnerClassEntry); 408 return this.outerClassesByInner.get(obfInnerClassEntry);
712 } 409 }
713 410
714 public boolean isAnonymousClass(ClassEntry obfInnerClassEntry) {
715 return this.anonymousClasses.containsKey(obfInnerClassEntry);
716 }
717
718 public boolean isSyntheticMethod(MethodEntry methodEntry) { 411 public boolean isSyntheticMethod(MethodEntry methodEntry) {
719 return this.syntheticMethods.contains(methodEntry); 412 return this.syntheticMethods.contains(methodEntry);
720 } 413 }
721 414
722 public BehaviorEntry getAnonymousClassCaller(ClassEntry obfInnerClassName) {
723 return this.anonymousClasses.get(obfInnerClassName);
724 }
725
726 public Set<ClassEntry> getInterfaces(String className) { 415 public Set<ClassEntry> getInterfaces(String className) {
727 ClassEntry classEntry = new ClassEntry(className); 416 ClassEntry classEntry = entryPool.getClass(className);
728 Set<ClassEntry> interfaces = new HashSet<>(); 417 Set<ClassEntry> interfaces = new HashSet<>(this.translationIndex.getInterfaces(classEntry));
729 interfaces.addAll(this.translationIndex.getInterfaces(classEntry));
730 for (ClassEntry ancestor : this.translationIndex.getAncestry(classEntry)) { 418 for (ClassEntry ancestor : this.translationIndex.getAncestry(classEntry)) {
731 interfaces.addAll(this.translationIndex.getInterfaces(ancestor)); 419 interfaces.addAll(this.translationIndex.getInterfaces(ancestor));
732 } 420 }
@@ -754,7 +442,7 @@ public class JarIndex {
754 } 442 }
755 443
756 public boolean isInterface(String className) { 444 public boolean isInterface(String className) {
757 return this.translationIndex.isInterface(new ClassEntry(className)); 445 return this.translationIndex.isInterface(entryPool.getClass(className));
758 } 446 }
759 447
760 public boolean containsObfClass(ClassEntry obfClassEntry) { 448 public boolean containsObfClass(ClassEntry obfClassEntry) {
@@ -765,8 +453,8 @@ public class JarIndex {
765 return this.access.containsKey(obfFieldEntry); 453 return this.access.containsKey(obfFieldEntry);
766 } 454 }
767 455
768 public boolean containsObfBehavior(BehaviorEntry obfBehaviorEntry) { 456 public boolean containsObfMethod(MethodEntry obfMethodEntry) {
769 return this.access.containsKey(obfBehaviorEntry); 457 return this.access.containsKey(obfMethodEntry);
770 } 458 }
771 459
772 public boolean containsEntryWithSameName(Entry entry) { 460 public boolean containsEntryWithSameName(Entry entry) {
@@ -776,15 +464,13 @@ public class JarIndex {
776 return false; 464 return false;
777 } 465 }
778 466
779 public boolean containsObfArgument(ArgumentEntry obfArgumentEntry) { 467 public boolean containsObfVariable(LocalVariableEntry obfVariableEntry) {
780 // check the behavior 468 // check the behavior
781 if (!containsObfBehavior(obfArgumentEntry.getBehaviorEntry())) { 469 if (!containsObfMethod(obfVariableEntry.getOwnerEntry())) {
782 return false; 470 return false;
783 } 471 }
784 472
785 // check the argument 473 return true;
786 return obfArgumentEntry.getIndex() < obfArgumentEntry.getBehaviorEntry().getSignature().getArgumentTypes().size();
787
788 } 474 }
789 475
790 public boolean containsObfEntry(Entry obfEntry) { 476 public boolean containsObfEntry(Entry obfEntry) {
@@ -792,15 +478,12 @@ public class JarIndex {
792 return containsObfClass((ClassEntry) obfEntry); 478 return containsObfClass((ClassEntry) obfEntry);
793 } else if (obfEntry instanceof FieldEntry) { 479 } else if (obfEntry instanceof FieldEntry) {
794 return containsObfField((FieldEntry) obfEntry); 480 return containsObfField((FieldEntry) obfEntry);
795 } else if (obfEntry instanceof BehaviorEntry) { 481 } else if (obfEntry instanceof MethodEntry) {
796 return containsObfBehavior((BehaviorEntry) obfEntry); 482 return containsObfMethod((MethodEntry) obfEntry);
797 } else if (obfEntry instanceof ArgumentEntry) {
798 return containsObfArgument((ArgumentEntry) obfEntry);
799 } else if (obfEntry instanceof LocalVariableEntry) { 483 } else if (obfEntry instanceof LocalVariableEntry) {
800 // TODO: Implement it 484 return containsObfVariable((LocalVariableEntry) obfEntry);
801 return false;
802 } else { 485 } else {
803 throw new Error("Entry type not supported: " + obfEntry.getClass().getName()); 486 throw new Error("Entry desc not supported: " + obfEntry.getClass().getName());
804 } 487 }
805 } 488 }
806 489
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 @@
12package cuchaz.enigma.analysis; 12package cuchaz.enigma.analysis;
13 13
14import com.google.common.collect.Lists; 14import com.google.common.collect.Lists;
15import cuchaz.enigma.mapping.ClassEntry; 15import cuchaz.enigma.mapping.entry.ClassEntry;
16import cuchaz.enigma.mapping.MethodEntry; 16import cuchaz.enigma.mapping.entry.MethodEntry;
17import cuchaz.enigma.mapping.Translator; 17import cuchaz.enigma.mapping.Translator;
18 18
19import javax.swing.tree.DefaultMutableTreeNode; 19import javax.swing.tree.DefaultMutableTreeNode;
@@ -54,11 +54,11 @@ public class MethodImplementationsTreeNode extends DefaultMutableTreeNode {
54 } 54 }
55 55
56 public String getDeobfClassName() { 56 public String getDeobfClassName() {
57 return this.deobfuscatingTranslator.translateClass(this.entry.getClassName()); 57 return this.deobfuscatingTranslator.getTranslatedClass(this.entry.getOwnerClassEntry()).getClassName();
58 } 58 }
59 59
60 public String getDeobfMethodName() { 60 public String getDeobfMethodName() {
61 return this.deobfuscatingTranslator.translate(this.entry); 61 return this.deobfuscatingTranslator.getTranslatedMethod(this.entry).getName();
62 } 62 }
63 63
64 @Override 64 @Override
@@ -80,9 +80,8 @@ public class MethodImplementationsTreeNode extends DefaultMutableTreeNode {
80 // get all method implementations 80 // get all method implementations
81 List<MethodImplementationsTreeNode> nodes = Lists.newArrayList(); 81 List<MethodImplementationsTreeNode> nodes = Lists.newArrayList();
82 for (String implementingClassName : index.getImplementingClasses(this.entry.getClassName())) { 82 for (String implementingClassName : index.getImplementingClasses(this.entry.getClassName())) {
83 MethodEntry methodEntry = new MethodEntry(new ClassEntry(implementingClassName), this.entry.getName(), this.entry.getSignature() 83 MethodEntry methodEntry = new MethodEntry(new ClassEntry(implementingClassName), this.entry.getName(), this.entry.getDesc());
84 ); 84 if (index.containsObfMethod(methodEntry)) {
85 if (index.containsObfBehavior(methodEntry)) {
86 nodes.add(new MethodImplementationsTreeNode(this.deobfuscatingTranslator, methodEntry)); 85 nodes.add(new MethodImplementationsTreeNode(this.deobfuscatingTranslator, methodEntry));
87 } 86 }
88 } 87 }
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 @@
12package cuchaz.enigma.analysis; 12package cuchaz.enigma.analysis;
13 13
14import com.google.common.collect.Lists; 14import com.google.common.collect.Lists;
15import cuchaz.enigma.mapping.ClassEntry; 15import cuchaz.enigma.mapping.entry.ClassEntry;
16import cuchaz.enigma.mapping.MethodEntry; 16import cuchaz.enigma.mapping.entry.MethodEntry;
17import cuchaz.enigma.mapping.Translator; 17import cuchaz.enigma.mapping.Translator;
18 18
19import javax.swing.tree.DefaultMutableTreeNode; 19import javax.swing.tree.DefaultMutableTreeNode;
@@ -52,11 +52,11 @@ public class MethodInheritanceTreeNode extends DefaultMutableTreeNode {
52 } 52 }
53 53
54 public String getDeobfClassName() { 54 public String getDeobfClassName() {
55 return this.deobfuscatingTranslator.translateClass(this.entry.getClassName()); 55 return this.deobfuscatingTranslator.getTranslatedClass(this.entry.getOwnerClassEntry()).getName();
56 } 56 }
57 57
58 public String getDeobfMethodName() { 58 public String getDeobfMethodName() {
59 return this.deobfuscatingTranslator.translate(this.entry); 59 return this.deobfuscatingTranslator.getTranslatedMethod(this.entry).getName();
60 } 60 }
61 61
62 public boolean isImplemented() { 62 public boolean isImplemented() {
@@ -84,11 +84,9 @@ public class MethodInheritanceTreeNode extends DefaultMutableTreeNode {
84 public void load(JarIndex index, boolean recurse) { 84 public void load(JarIndex index, boolean recurse) {
85 // get all the child nodes 85 // get all the child nodes
86 List<MethodInheritanceTreeNode> nodes = Lists.newArrayList(); 86 List<MethodInheritanceTreeNode> nodes = Lists.newArrayList();
87 for (ClassEntry subclassEntry : index.getTranslationIndex().getSubclass(this.entry.getClassEntry())) { 87 for (ClassEntry subclassEntry : index.getTranslationIndex().getSubclass(this.entry.getOwnerClassEntry())) {
88 MethodEntry methodEntry = new MethodEntry(subclassEntry, this.entry.getName(), this.entry.getSignature() 88 MethodEntry methodEntry = new MethodEntry(subclassEntry, this.entry.getName(), this.entry.getDesc());
89 ); 89 nodes.add(new MethodInheritanceTreeNode(this.deobfuscatingTranslator, methodEntry, index.containsObfMethod(methodEntry)));
90 nodes.add(new MethodInheritanceTreeNode(this.deobfuscatingTranslator, methodEntry, index.containsObfBehavior(methodEntry)
91 ));
92 } 90 }
93 91
94 // add them to this node 92 // add them to this node
diff --git a/src/main/java/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java
index 6556b2cf..76c73c15 100644
--- a/src/main/java/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java
+++ b/src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java
@@ -12,30 +12,31 @@
12package cuchaz.enigma.analysis; 12package cuchaz.enigma.analysis;
13 13
14import com.google.common.collect.Sets; 14import com.google.common.collect.Sets;
15import cuchaz.enigma.mapping.BehaviorEntry; 15import cuchaz.enigma.mapping.*;
16import cuchaz.enigma.mapping.Entry; 16import cuchaz.enigma.mapping.entry.Entry;
17import cuchaz.enigma.mapping.Translator; 17import cuchaz.enigma.mapping.entry.MethodDefEntry;
18import cuchaz.enigma.mapping.entry.MethodEntry;
18 19
19import javax.swing.tree.DefaultMutableTreeNode; 20import javax.swing.tree.DefaultMutableTreeNode;
20import javax.swing.tree.TreeNode; 21import javax.swing.tree.TreeNode;
21import java.util.Set; 22import java.util.Set;
22 23
23public class BehaviorReferenceTreeNode extends DefaultMutableTreeNode 24public class MethodReferenceTreeNode extends DefaultMutableTreeNode
24 implements ReferenceTreeNode<BehaviorEntry, BehaviorEntry> { 25 implements ReferenceTreeNode<MethodEntry, MethodDefEntry> {
25 26
26 private Translator deobfuscatingTranslator; 27 private Translator deobfuscatingTranslator;
27 private BehaviorEntry entry; 28 private MethodEntry entry;
28 private EntryReference<BehaviorEntry, BehaviorEntry> reference; 29 private EntryReference<MethodEntry, MethodDefEntry> reference;
29 private Access access; 30 private Access access;
30 31
31 public BehaviorReferenceTreeNode(Translator deobfuscatingTranslator, BehaviorEntry entry) { 32 public MethodReferenceTreeNode(Translator deobfuscatingTranslator, MethodEntry entry) {
32 this.deobfuscatingTranslator = deobfuscatingTranslator; 33 this.deobfuscatingTranslator = deobfuscatingTranslator;
33 this.entry = entry; 34 this.entry = entry;
34 this.reference = null; 35 this.reference = null;
35 } 36 }
36 37
37 public BehaviorReferenceTreeNode(Translator deobfuscatingTranslator, 38 public MethodReferenceTreeNode(Translator deobfuscatingTranslator,
38 EntryReference<BehaviorEntry, BehaviorEntry> reference, Access access) { 39 EntryReference<MethodEntry, MethodDefEntry> reference, Access access) {
39 this.deobfuscatingTranslator = deobfuscatingTranslator; 40 this.deobfuscatingTranslator = deobfuscatingTranslator;
40 this.entry = reference.entry; 41 this.entry = reference.entry;
41 this.reference = reference; 42 this.reference = reference;
@@ -43,42 +44,42 @@ public class BehaviorReferenceTreeNode extends DefaultMutableTreeNode
43 } 44 }
44 45
45 @Override 46 @Override
46 public BehaviorEntry getEntry() { 47 public MethodEntry getEntry() {
47 return this.entry; 48 return this.entry;
48 } 49 }
49 50
50 @Override 51 @Override
51 public EntryReference<BehaviorEntry, BehaviorEntry> getReference() { 52 public EntryReference<MethodEntry, MethodDefEntry> getReference() {
52 return this.reference; 53 return this.reference;
53 } 54 }
54 55
55 @Override 56 @Override
56 public String toString() { 57 public String toString() {
57 if (this.reference != null) { 58 if (this.reference != null) {
58 return String.format("%s (%s)", this.deobfuscatingTranslator.translateEntry(this.reference.context), 59 return String.format("%s (%s)", this.deobfuscatingTranslator.getTranslatedMethodDef(this.reference.context),
59 this.access); 60 this.access);
60 } 61 }
61 return this.deobfuscatingTranslator.translateEntry(this.entry).toString(); 62 return this.deobfuscatingTranslator.getTranslatedMethod(this.entry).getName();
62 } 63 }
63 64
64 public void load(JarIndex index, boolean recurse) { 65 public void load(JarIndex index, boolean recurse) {
65 // get all the child nodes 66 // get all the child nodes
66 for (EntryReference<BehaviorEntry, BehaviorEntry> reference : index.getBehaviorReferences(this.entry)) { 67 for (EntryReference<MethodEntry, MethodDefEntry> reference : index.getMethodsReferencing(this.entry)) {
67 add(new BehaviorReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccess(this.entry))); 68 add(new MethodReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccess(this.entry)));
68 } 69 }
69 70
70 if (recurse && this.children != null) { 71 if (recurse && this.children != null) {
71 for (Object child : this.children) { 72 for (Object child : this.children) {
72 if (child instanceof BehaviorReferenceTreeNode) { 73 if (child instanceof MethodReferenceTreeNode) {
73 BehaviorReferenceTreeNode node = (BehaviorReferenceTreeNode) child; 74 MethodReferenceTreeNode node = (MethodReferenceTreeNode) child;
74 75
75 // don't recurse into ancestor 76 // don't recurse into ancestor
76 Set<Entry> ancestors = Sets.newHashSet(); 77 Set<Entry> ancestors = Sets.newHashSet();
77 TreeNode n = node; 78 TreeNode n = node;
78 while (n.getParent() != null) { 79 while (n.getParent() != null) {
79 n = n.getParent(); 80 n = n.getParent();
80 if (n instanceof BehaviorReferenceTreeNode) { 81 if (n instanceof MethodReferenceTreeNode) {
81 ancestors.add(((BehaviorReferenceTreeNode) n).getEntry()); 82 ancestors.add(((MethodReferenceTreeNode) n).getEntry());
82 } 83 }
83 } 84 }
84 if (ancestors.contains(node.getEntry())) { 85 if (ancestors.contains(node.getEntry())) {
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.analysis;
13
14import cuchaz.enigma.mapping.entry.ClassEntry;
15import org.objectweb.asm.ClassReader;
16import org.objectweb.asm.tree.ClassNode;
17
18import java.io.BufferedInputStream;
19import java.io.IOException;
20import java.io.InputStream;
21import java.util.*;
22import java.util.function.Consumer;
23import java.util.jar.JarEntry;
24import java.util.jar.JarFile;
25import java.util.jar.JarInputStream;
26
27public class ParsedJar {
28 private final Map<String, ClassNode> nodes = new LinkedHashMap<>();
29
30 public ParsedJar(JarFile jar) throws IOException {
31 try {
32 // get the jar entries that correspond to classes
33 Enumeration<JarEntry> entries = jar.entries();
34 while (entries.hasMoreElements()) {
35 JarEntry entry = entries.nextElement();
36 // is this a class file?
37 if (entry.getName().endsWith(".class")) {
38 try (InputStream input = new BufferedInputStream(jar.getInputStream(entry))) {
39 // read the ClassNode from the jar
40 ClassReader reader = new ClassReader(input);
41 ClassNode node = new ClassNode();
42 reader.accept(node, 0);
43 String path = entry.getName().substring(0, entry.getName().length() - ".class".length());
44 nodes.put(path, node);
45 }
46 }
47 }
48 } finally {
49 jar.close();
50 }
51 }
52
53 public ParsedJar(JarInputStream jar) throws IOException {
54 try {
55 // get the jar entries that correspond to classes
56 JarEntry entry;
57 while ((entry = jar.getNextJarEntry()) != null) {
58 // is this a class file?
59 if (entry.getName().endsWith(".class")) {
60 // read the ClassNode from the jar
61 ClassReader reader = new ClassReader(jar);
62 ClassNode node = new ClassNode();
63 reader.accept(node, 0);
64 String path = entry.getName().substring(0, entry.getName().length() - ".class".length());
65 nodes.put(path, node);
66 jar.closeEntry();
67 }
68 }
69 } finally {
70 jar.close();
71 }
72 }
73
74 public void visit(Consumer<ClassNode> visitor) {
75 for (ClassNode node : nodes.values()) {
76 visitor.accept(node);
77 }
78 }
79
80 public int getClassCount() {
81 return nodes.size();
82 }
83
84 public List<ClassEntry> getClassEntries() {
85 List<ClassEntry> entries = new ArrayList<>(nodes.size());
86 for (ClassNode node : nodes.values()) {
87 entries.add(new ClassEntry(node.name));
88 }
89 return entries;
90 }
91
92 public ClassNode getClassNode(String name) {
93 return nodes.get(name);
94 }
95}
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 @@
11 11
12package cuchaz.enigma.analysis; 12package cuchaz.enigma.analysis;
13 13
14import cuchaz.enigma.mapping.Entry; 14import cuchaz.enigma.mapping.entry.Entry;
15 15
16public interface ReferenceTreeNode<E extends Entry, C extends Entry> { 16public interface ReferenceTreeNode<E extends Entry, C extends Entry> {
17 E getEntry(); 17 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;
18import com.strobel.decompiler.languages.Region; 18import com.strobel.decompiler.languages.Region;
19import com.strobel.decompiler.languages.java.ast.AstNode; 19import com.strobel.decompiler.languages.java.ast.AstNode;
20import com.strobel.decompiler.languages.java.ast.Identifier; 20import com.strobel.decompiler.languages.java.ast.Identifier;
21import cuchaz.enigma.mapping.Entry; 21import cuchaz.enigma.mapping.entry.Entry;
22 22
23import java.util.Collection; 23import java.util.Collection;
24import java.util.List; 24import java.util.List;
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;
17import com.strobel.assembler.metadata.TypeReference; 17import com.strobel.assembler.metadata.TypeReference;
18import com.strobel.decompiler.languages.TextLocation; 18import com.strobel.decompiler.languages.TextLocation;
19import com.strobel.decompiler.languages.java.ast.*; 19import com.strobel.decompiler.languages.java.ast.*;
20import cuchaz.enigma.mapping.*; 20import cuchaz.enigma.bytecode.AccessFlags;
21import cuchaz.enigma.mapping.Signature;
22import cuchaz.enigma.mapping.entry.*;
21 23
22public class SourceIndexClassVisitor extends SourceIndexVisitor { 24public class SourceIndexClassVisitor extends SourceIndexVisitor {
25 private final ReferencedEntryPool entryPool;
26 private final ProcyonEntryFactory entryFactory;
23 27
24 private ClassEntry classEntry; 28 private ClassDefEntry classEntry;
25 private boolean isEnum; 29 private boolean isEnum;
26 30
27 public SourceIndexClassVisitor(ClassEntry classEntry) { 31 public SourceIndexClassVisitor(ReferencedEntryPool entryPool, ClassDefEntry classEntry) {
32 super(entryPool);
33 this.entryPool = entryPool;
34 this.entryFactory = new ProcyonEntryFactory(entryPool);
28 this.classEntry = classEntry; 35 this.classEntry = classEntry;
29 } 36 }
30 37
@@ -32,11 +39,11 @@ public class SourceIndexClassVisitor extends SourceIndexVisitor {
32 public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) { 39 public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) {
33 // is this this class, or a subtype? 40 // is this this class, or a subtype?
34 TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION); 41 TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION);
35 ClassEntry classEntry = new ClassEntry(def.getInternalName()); 42 ClassDefEntry classEntry = new ClassDefEntry(def.getInternalName(), Signature.createSignature(def.getSignature()), new AccessFlags(def.getModifiers()));
36 if (!classEntry.equals(this.classEntry)) { 43 if (!classEntry.equals(this.classEntry)) {
37 // it's a sub-type, recurse 44 // it's a subtype, recurse
38 index.addDeclaration(node.getNameToken(), classEntry); 45 index.addDeclaration(node.getNameToken(), classEntry);
39 return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index); 46 return node.acceptVisitor(new SourceIndexClassVisitor(entryPool, classEntry), index);
40 } 47 }
41 48
42 return recurse(node, index); 49 return recurse(node, index);
@@ -56,31 +63,28 @@ public class SourceIndexClassVisitor extends SourceIndexVisitor {
56 @Override 63 @Override
57 public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) { 64 public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) {
58 MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION); 65 MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION);
59 BehaviorEntry behaviorEntry = ProcyonEntryFactory.getBehaviorEntry(def); 66 MethodDefEntry methodEntry = entryFactory.getMethodDefEntry(def);
60 AstNode tokenNode = node.getNameToken(); 67 AstNode tokenNode = node.getNameToken();
61 if (behaviorEntry instanceof ConstructorEntry) { 68 if (methodEntry.isConstructor() && methodEntry.getName().equals("<clinit>")) {
62 ConstructorEntry constructorEntry = (ConstructorEntry) behaviorEntry; 69 // for static initializers, check elsewhere for the token node
63 if (constructorEntry.isStatic()) { 70 tokenNode = node.getModifiers().firstOrNullObject();
64 // for static initializers, check elsewhere for the token node
65 tokenNode = node.getModifiers().firstOrNullObject();
66 }
67 } 71 }
68 index.addDeclaration(tokenNode, behaviorEntry); 72 index.addDeclaration(tokenNode, methodEntry);
69 return node.acceptVisitor(new SourceIndexBehaviorVisitor(behaviorEntry, false), index); 73 return node.acceptVisitor(new SourceIndexMethodVisitor(entryPool, classEntry, methodEntry), index);
70 } 74 }
71 75
72 @Override 76 @Override
73 public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) { 77 public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) {
74 MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION); 78 MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION);
75 ConstructorEntry constructorEntry = ProcyonEntryFactory.getConstructorEntry(def); 79 MethodDefEntry methodEntry = entryFactory.getMethodDefEntry(def);
76 index.addDeclaration(node.getNameToken(), constructorEntry); 80 index.addDeclaration(node.getNameToken(), methodEntry);
77 return node.acceptVisitor(new SourceIndexBehaviorVisitor(constructorEntry, isEnum), index); 81 return node.acceptVisitor(new SourceIndexMethodVisitor(entryPool, classEntry, methodEntry), index);
78 } 82 }
79 83
80 @Override 84 @Override
81 public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) { 85 public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) {
82 FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); 86 FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION);
83 FieldEntry fieldEntry = ProcyonEntryFactory.getFieldEntry(def); 87 FieldDefEntry fieldEntry = entryFactory.getFieldDefEntry(def);
84 assert (node.getVariables().size() == 1); 88 assert (node.getVariables().size() == 1);
85 VariableInitializer variable = node.getVariables().firstOrNullObject(); 89 VariableInitializer variable = node.getVariables().firstOrNullObject();
86 index.addDeclaration(variable.getNameToken(), fieldEntry); 90 index.addDeclaration(variable.getNameToken(), fieldEntry);
@@ -92,7 +96,7 @@ public class SourceIndexClassVisitor extends SourceIndexVisitor {
92 public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) { 96 public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) {
93 // treat enum declarations as field declarations 97 // treat enum declarations as field declarations
94 FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); 98 FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION);
95 FieldEntry fieldEntry = ProcyonEntryFactory.getFieldEntry(def); 99 FieldDefEntry fieldEntry = entryFactory.getFieldDefEntry(def);
96 index.addDeclaration(node.getNameToken(), fieldEntry); 100 index.addDeclaration(node.getNameToken(), fieldEntry);
97 this.isEnum = true; 101 this.isEnum = true;
98 return recurse(node, index); 102 return recurse(node, index);
diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java b/src/main/java/cuchaz/enigma/analysis/SourceIndexMethodVisitor.java
index 1b619164..83fe296c 100644
--- a/src/main/java/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java
+++ b/src/main/java/cuchaz/enigma/analysis/SourceIndexMethodVisitor.java
@@ -13,31 +13,33 @@ package cuchaz.enigma.analysis;
13 13
14import com.google.common.collect.HashMultimap; 14import com.google.common.collect.HashMultimap;
15import com.google.common.collect.Multimap; 15import com.google.common.collect.Multimap;
16import com.strobel.assembler.metadata.MemberReference; 16import com.strobel.assembler.metadata.*;
17import com.strobel.assembler.metadata.MethodReference; 17import com.strobel.decompiler.ast.Variable;
18import com.strobel.assembler.metadata.ParameterDefinition;
19import com.strobel.assembler.metadata.TypeReference;
20import com.strobel.decompiler.languages.TextLocation; 18import com.strobel.decompiler.languages.TextLocation;
21import com.strobel.decompiler.languages.java.ast.*; 19import com.strobel.decompiler.languages.java.ast.*;
22import cuchaz.enigma.mapping.*; 20import cuchaz.enigma.mapping.TypeDescriptor;
23import javassist.bytecode.Descriptor; 21import cuchaz.enigma.mapping.entry.*;
24 22
23import java.lang.Error;
25import java.util.HashMap; 24import java.util.HashMap;
26import java.util.Map; 25import java.util.Map;
27 26
28public class SourceIndexBehaviorVisitor extends SourceIndexVisitor { 27public class SourceIndexMethodVisitor extends SourceIndexVisitor {
29 private BehaviorEntry behaviorEntry; 28 private final ReferencedEntryPool entryPool;
29 private final ProcyonEntryFactory entryFactory;
30
31 private final ClassDefEntry ownerEntry;
32 private final MethodDefEntry methodEntry;
30 33
31 // TODO: Really fix Procyon index problem with inner classes
32 private int argumentPosition;
33 private int localsPosition;
34 private Multimap<String, Identifier> unmatchedIdentifier = HashMultimap.create(); 34 private Multimap<String, Identifier> unmatchedIdentifier = HashMultimap.create();
35 private Map<String, Entry> identifierEntryCache = new HashMap<>(); 35 private Map<String, Entry> identifierEntryCache = new HashMap<>();
36 36
37 public SourceIndexBehaviorVisitor(BehaviorEntry behaviorEntry, boolean isEnum) { 37 public SourceIndexMethodVisitor(ReferencedEntryPool entryPool, ClassDefEntry ownerEntry, MethodDefEntry methodEntry) {
38 this.behaviorEntry = behaviorEntry; 38 super(entryPool);
39 this.argumentPosition = isEnum ? 2 : 0; 39 this.entryPool = entryPool;
40 this.localsPosition = 0; 40 this.entryFactory = new ProcyonEntryFactory(entryPool);
41 this.ownerEntry = ownerEntry;
42 this.methodEntry = methodEntry;
41 } 43 }
42 44
43 @Override 45 @Override
@@ -45,19 +47,12 @@ public class SourceIndexBehaviorVisitor extends SourceIndexVisitor {
45 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); 47 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
46 48
47 // get the behavior entry 49 // get the behavior entry
48 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); 50 ClassEntry classEntry = entryPool.getClass(ref.getDeclaringType().getInternalName());
49 BehaviorEntry behaviorEntry = null; 51 MethodEntry methodEntry = null;
50 if (ref instanceof MethodReference) { 52 if (ref instanceof MethodReference) {
51 MethodReference methodRef = (MethodReference) ref; 53 methodEntry = entryPool.getMethod(classEntry, ref.getName(), ref.getErasedSignature());
52 if (methodRef.isConstructor()) {
53 behaviorEntry = new ConstructorEntry(classEntry, new Signature(ref.getErasedSignature()));
54 } else if (methodRef.isTypeInitializer()) {
55 behaviorEntry = new ConstructorEntry(classEntry);
56 } else {
57 behaviorEntry = new MethodEntry(classEntry, ref.getName(), new Signature(ref.getErasedSignature()));
58 }
59 } 54 }
60 if (behaviorEntry != null) { 55 if (methodEntry != null) {
61 // get the node for the token 56 // get the node for the token
62 AstNode tokenNode = null; 57 AstNode tokenNode = null;
63 if (node.getTarget() instanceof MemberReferenceExpression) { 58 if (node.getTarget() instanceof MemberReferenceExpression) {
@@ -68,13 +63,13 @@ public class SourceIndexBehaviorVisitor extends SourceIndexVisitor {
68 tokenNode = node.getTarget(); 63 tokenNode = node.getTarget();
69 } 64 }
70 if (tokenNode != null) { 65 if (tokenNode != null) {
71 index.addReference(tokenNode, behaviorEntry, this.behaviorEntry); 66 index.addReference(tokenNode, methodEntry, this.methodEntry);
72 } 67 }
73 } 68 }
74 69
75 // Check for identifier 70 // Check for identifier
76 node.getArguments().stream().filter(expression -> expression instanceof IdentifierExpression) 71 node.getArguments().stream().filter(expression -> expression instanceof IdentifierExpression)
77 .forEach(expression -> this.checkIdentifier((IdentifierExpression) expression, index)); 72 .forEach(expression -> this.checkIdentifier((IdentifierExpression) expression, index));
78 return recurse(node, index); 73 return recurse(node, index);
79 } 74 }
80 75
@@ -83,13 +78,17 @@ public class SourceIndexBehaviorVisitor extends SourceIndexVisitor {
83 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); 78 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
84 if (ref != null) { 79 if (ref != null) {
85 // make sure this is actually a field 80 // make sure this is actually a field
86 if (ref.getErasedSignature().indexOf('(') >= 0) { 81 String erasedSignature = ref.getErasedSignature();
82 if (erasedSignature.indexOf('(') >= 0) {
87 throw new Error("Expected a field here! got " + ref); 83 throw new Error("Expected a field here! got " + ref);
88 } 84 }
89 85
90 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); 86 ClassEntry classEntry = entryPool.getClass(ref.getDeclaringType().getInternalName());
91 FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new Type(ref.getErasedSignature())); 87 FieldEntry fieldEntry = entryPool.getField(classEntry, ref.getName(), new TypeDescriptor(erasedSignature));
92 index.addReference(node.getMemberNameToken(), fieldEntry, this.behaviorEntry); 88 if (fieldEntry == null) {
89 throw new Error("Failed to find field " + ref.getName() + " on " + classEntry.getName());
90 }
91 index.addReference(node.getMemberNameToken(), fieldEntry, this.methodEntry);
93 } 92 }
94 93
95 return recurse(node, index); 94 return recurse(node, index);
@@ -99,8 +98,8 @@ public class SourceIndexBehaviorVisitor extends SourceIndexVisitor {
99 public Void visitSimpleType(SimpleType node, SourceIndex index) { 98 public Void visitSimpleType(SimpleType node, SourceIndex index) {
100 TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE); 99 TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE);
101 if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) { 100 if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) {
102 ClassEntry classEntry = new ClassEntry(ref.getInternalName()); 101 ClassEntry classEntry = entryPool.getClass(ref.getInternalName());
103 index.addReference(node.getIdentifierToken(), classEntry, this.behaviorEntry); 102 index.addReference(node.getIdentifierToken(), classEntry, this.methodEntry);
104 } 103 }
105 104
106 return recurse(node, index); 105 return recurse(node, index);
@@ -109,13 +108,16 @@ public class SourceIndexBehaviorVisitor extends SourceIndexVisitor {
109 @Override 108 @Override
110 public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) { 109 public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) {
111 ParameterDefinition def = node.getUserData(Keys.PARAMETER_DEFINITION); 110 ParameterDefinition def = node.getUserData(Keys.PARAMETER_DEFINITION);
112 if (def.getMethod() instanceof MemberReference && def.getMethod() instanceof MethodReference) { 111
113 ArgumentEntry argumentEntry = new ArgumentEntry(ProcyonEntryFactory.getBehaviorEntry((MethodReference) def.getMethod()), 112 int variableOffset = this.methodEntry.getVariableOffset(ownerEntry);
114 argumentPosition++, node.getName()); 113 int parameterIndex = def.getSlot() - variableOffset;
114
115 if (parameterIndex >= 0) {
116 LocalVariableEntry localVariableEntry = new LocalVariableEntry(methodEntry, parameterIndex, node.getName());
115 Identifier identifier = node.getNameToken(); 117 Identifier identifier = node.getNameToken();
116 // cache the argument entry and the identifier 118 // cache the argument entry and the identifier
117 identifierEntryCache.put(identifier.getName(), argumentEntry); 119 identifierEntryCache.put(identifier.getName(), localVariableEntry);
118 index.addDeclaration(identifier, argumentEntry); 120 index.addDeclaration(identifier, localVariableEntry);
119 } 121 }
120 122
121 return recurse(node, index); 123 return recurse(node, index);
@@ -125,9 +127,12 @@ public class SourceIndexBehaviorVisitor extends SourceIndexVisitor {
125 public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) { 127 public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) {
126 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); 128 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
127 if (ref != null) { 129 if (ref != null) {
128 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); 130 ClassEntry classEntry = entryPool.getClass(ref.getDeclaringType().getInternalName());
129 FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new Type(ref.getErasedSignature())); 131 FieldEntry fieldEntry = entryPool.getField(classEntry, ref.getName(), new TypeDescriptor(ref.getErasedSignature()));
130 index.addReference(node.getIdentifierToken(), fieldEntry, this.behaviorEntry); 132 if (fieldEntry == null) {
133 throw new Error("Failed to find field " + ref.getName() + " on " + classEntry.getName());
134 }
135 index.addReference(node.getIdentifierToken(), fieldEntry, this.methodEntry);
131 } else 136 } else
132 this.checkIdentifier(node, index); 137 this.checkIdentifier(node, index);
133 return recurse(node, index); 138 return recurse(node, index);
@@ -155,11 +160,11 @@ public class SourceIndexBehaviorVisitor extends SourceIndexVisitor {
155 public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) { 160 public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) {
156 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); 161 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
157 if (ref != null) { 162 if (ref != null) {
158 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); 163 ClassEntry classEntry = entryPool.getClass(ref.getDeclaringType().getInternalName());
159 ConstructorEntry constructorEntry = new ConstructorEntry(classEntry, new Signature(ref.getErasedSignature())); 164 MethodEntry constructorEntry = entryPool.getMethod(classEntry, "<init>", ref.getErasedSignature());
160 if (node.getType() instanceof SimpleType) { 165 if (node.getType() instanceof SimpleType) {
161 SimpleType simpleTypeNode = (SimpleType) node.getType(); 166 SimpleType simpleTypeNode = (SimpleType) node.getType();
162 index.addReference(simpleTypeNode.getIdentifierToken(), constructorEntry, this.behaviorEntry); 167 index.addReference(simpleTypeNode.getIdentifierToken(), constructorEntry, this.methodEntry);
163 } 168 }
164 } 169 }
165 170
@@ -167,21 +172,6 @@ public class SourceIndexBehaviorVisitor extends SourceIndexVisitor {
167 } 172 }
168 173
169 @Override 174 @Override
170 public Void visitForEachStatement(ForEachStatement node, SourceIndex index) {
171 if (node.getVariableType() instanceof SimpleType) {
172 SimpleType type = (SimpleType) node.getVariableType();
173 TypeReference typeReference = type.getUserData(Keys.TYPE_REFERENCE);
174 Identifier identifier = node.getVariableNameToken();
175 String signature = Descriptor.of(typeReference.getErasedDescription());
176 LocalVariableEntry localVariableEntry = new LocalVariableEntry(behaviorEntry, argumentPosition + localsPosition++, identifier.getName(), new Type(signature));
177 identifierEntryCache.put(identifier.getName(), localVariableEntry);
178 addDeclarationToUnmatched(identifier.getName(), index);
179 index.addDeclaration(identifier, localVariableEntry);
180 }
181 return recurse(node, index);
182 }
183
184 @Override
185 public Void visitVariableDeclaration(VariableDeclarationStatement node, SourceIndex index) { 175 public Void visitVariableDeclaration(VariableDeclarationStatement node, SourceIndex index) {
186 AstNodeCollection<VariableInitializer> variables = node.getVariables(); 176 AstNodeCollection<VariableInitializer> variables = node.getVariables();
187 177
@@ -189,16 +179,46 @@ public class SourceIndexBehaviorVisitor extends SourceIndexVisitor {
189 if (variables.size() == 1) { 179 if (variables.size() == 1) {
190 VariableInitializer initializer = variables.firstOrNullObject(); 180 VariableInitializer initializer = variables.firstOrNullObject();
191 if (initializer != null && node.getType() instanceof SimpleType) { 181 if (initializer != null && node.getType() instanceof SimpleType) {
192 SimpleType type = (SimpleType) node.getType();
193 TypeReference typeReference = type.getUserData(Keys.TYPE_REFERENCE);
194 String signature = Descriptor.of(typeReference.getErasedDescription());
195 Identifier identifier = initializer.getNameToken(); 182 Identifier identifier = initializer.getNameToken();
196 LocalVariableEntry localVariableEntry = new LocalVariableEntry(behaviorEntry, argumentPosition + localsPosition++, initializer.getName(), new Type(signature)); 183 Variable variable = initializer.getUserData(Keys.VARIABLE);
197 identifierEntryCache.put(identifier.getName(), localVariableEntry); 184 if (variable != null) {
198 addDeclarationToUnmatched(identifier.getName(), index); 185 VariableDefinition originalVariable = variable.getOriginalVariable();
199 index.addDeclaration(identifier, localVariableEntry); 186 if (originalVariable != null) {
187 int variableOffset = methodEntry.getVariableOffset(ownerEntry);
188 int variableIndex = originalVariable.getSlot() - variableOffset;
189 if (variableIndex >= 0) {
190 LocalVariableEntry localVariableEntry = new LocalVariableEntry(methodEntry, variableIndex, initializer.getName());
191 identifierEntryCache.put(identifier.getName(), localVariableEntry);
192 addDeclarationToUnmatched(identifier.getName(), index);
193 index.addDeclaration(identifier, localVariableEntry);
194 }
195 }
196 }
200 } 197 }
201 } 198 }
202 return recurse(node, index); 199 return recurse(node, index);
203 } 200 }
201
202 @Override
203 public Void visitMethodGroupExpression(MethodGroupExpression node, SourceIndex index) {
204 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
205
206 if (ref instanceof MethodReference) {
207 // get the behavior entry
208 ClassEntry classEntry = entryPool.getClass(ref.getDeclaringType().getInternalName());
209 MethodEntry methodEntry = null;
210
211 methodEntry = entryPool.getMethod(classEntry, ref.getName(), ref.getErasedSignature());
212 // get the node for the token
213 AstNode tokenNode = node.getMethodNameToken();
214 if (tokenNode == null || (tokenNode.getRegion().getBeginLine() == 0 || tokenNode.getRegion().getEndLine() == 0)){
215 tokenNode = node.getTarget();
216 }
217 if (tokenNode != null) {
218 index.addReference(tokenNode, methodEntry, this.methodEntry);
219 }
220 }
221
222 return recurse(node, index);
223 }
204} 224}
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;
14import com.strobel.assembler.metadata.TypeDefinition; 14import com.strobel.assembler.metadata.TypeDefinition;
15import com.strobel.decompiler.languages.java.ast.*; 15import com.strobel.decompiler.languages.java.ast.*;
16import com.strobel.decompiler.patterns.Pattern; 16import com.strobel.decompiler.patterns.Pattern;
17import cuchaz.enigma.mapping.ClassEntry; 17import cuchaz.enigma.bytecode.AccessFlags;
18import cuchaz.enigma.mapping.Signature;
19import cuchaz.enigma.mapping.entry.ClassDefEntry;
20import cuchaz.enigma.mapping.entry.ReferencedEntryPool;
18 21
19public class SourceIndexVisitor implements IAstVisitor<SourceIndex, Void> { 22public class SourceIndexVisitor implements IAstVisitor<SourceIndex, Void> {
23 private final ReferencedEntryPool entryPool;
24
25 public SourceIndexVisitor(ReferencedEntryPool entryPool) {
26 this.entryPool = entryPool;
27 }
20 28
21 @Override 29 @Override
22 public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) { 30 public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) {
23 TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION); 31 TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION);
24 ClassEntry classEntry = new ClassEntry(def.getInternalName()); 32 ClassDefEntry classEntry = new ClassDefEntry(def.getInternalName(), Signature.createSignature(def.getSignature()), new AccessFlags(def.getModifiers()));
25 index.addDeclaration(node.getNameToken(), classEntry); 33 index.addDeclaration(node.getNameToken(), classEntry);
26 34
27 return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index); 35 return node.acceptVisitor(new SourceIndexClassVisitor(entryPool, classEntry), index);
28 } 36 }
29 37
30 protected Void recurse(AstNode node, SourceIndex index) { 38 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;
15import com.google.common.collect.Lists; 15import com.google.common.collect.Lists;
16import com.google.common.collect.Maps; 16import com.google.common.collect.Maps;
17import com.google.common.collect.Multimap; 17import com.google.common.collect.Multimap;
18import cuchaz.enigma.bytecode.AccessFlags;
18import cuchaz.enigma.mapping.*; 19import cuchaz.enigma.mapping.*;
19import javassist.CtBehavior; 20import cuchaz.enigma.mapping.entry.*;
20import javassist.CtClass;
21import javassist.CtField;
22import javassist.bytecode.Descriptor;
23 21
24import java.util.Collection; 22import java.util.Collection;
25import java.util.List; 23import java.util.List;
@@ -28,96 +26,91 @@ import java.util.Set;
28 26
29public class TranslationIndex { 27public class TranslationIndex {
30 28
29 private final ReferencedEntryPool entryPool;
31 private Map<ClassEntry, ClassEntry> superclasses; 30 private Map<ClassEntry, ClassEntry> superclasses;
32 private Multimap<ClassEntry, FieldEntry> fieldEntries; 31 private Multimap<ClassEntry, FieldDefEntry> fieldEntries;
33 private Multimap<ClassEntry, BehaviorEntry> behaviorEntries; 32 private Multimap<ClassEntry, MethodDefEntry> methodEntries;
34 private Multimap<ClassEntry, ClassEntry> interfaces; 33 private Multimap<ClassEntry, ClassEntry> interfaces;
35 34
36 public TranslationIndex() { 35 public TranslationIndex(ReferencedEntryPool entryPool) {
36 this.entryPool = entryPool;
37 this.superclasses = Maps.newHashMap(); 37 this.superclasses = Maps.newHashMap();
38 this.fieldEntries = HashMultimap.create(); 38 this.fieldEntries = HashMultimap.create();
39 this.behaviorEntries = HashMultimap.create(); 39 this.methodEntries = HashMultimap.create();
40 this.interfaces = HashMultimap.create(); 40 this.interfaces = HashMultimap.create();
41 } 41 }
42 42
43 public TranslationIndex(TranslationIndex other, Translator translator) { 43 public TranslationIndex(TranslationIndex other, Translator translator) {
44 this.entryPool = other.entryPool;
45
44 // translate the superclasses 46 // translate the superclasses
45 this.superclasses = Maps.newHashMap(); 47 this.superclasses = Maps.newHashMap();
46 for (Map.Entry<ClassEntry, ClassEntry> mapEntry : other.superclasses.entrySet()) { 48 for (Map.Entry<ClassEntry, ClassEntry> mapEntry : other.superclasses.entrySet()) {
47 this.superclasses.put(translator.translateEntry(mapEntry.getKey()), translator.translateEntry(mapEntry.getValue())); 49 this.superclasses.put(translator.getTranslatedClass(mapEntry.getKey()), translator.getTranslatedClass(mapEntry.getValue()));
48 } 50 }
49 51
50 // translate the interfaces 52 // translate the interfaces
51 this.interfaces = HashMultimap.create(); 53 this.interfaces = HashMultimap.create();
52 for (Map.Entry<ClassEntry, ClassEntry> mapEntry : other.interfaces.entries()) { 54 for (Map.Entry<ClassEntry, ClassEntry> mapEntry : other.interfaces.entries()) {
53 this.interfaces.put( 55 this.interfaces.put(
54 translator.translateEntry(mapEntry.getKey()), 56 translator.getTranslatedClass(mapEntry.getKey()),
55 translator.translateEntry(mapEntry.getValue()) 57 translator.getTranslatedClass(mapEntry.getValue())
56 ); 58 );
57 } 59 }
58 60
59 // translate the fields 61 // translate the fields
60 this.fieldEntries = HashMultimap.create(); 62 this.fieldEntries = HashMultimap.create();
61 for (Map.Entry<ClassEntry, FieldEntry> mapEntry : other.fieldEntries.entries()) { 63 for (Map.Entry<ClassEntry, FieldDefEntry> mapEntry : other.fieldEntries.entries()) {
62 this.fieldEntries.put( 64 this.fieldEntries.put(
63 translator.translateEntry(mapEntry.getKey()), 65 translator.getTranslatedClass(mapEntry.getKey()),
64 translator.translateEntry(mapEntry.getValue()) 66 translator.getTranslatedFieldDef(mapEntry.getValue())
65 ); 67 );
66 } 68 }
67 69
68 this.behaviorEntries = HashMultimap.create(); 70 this.methodEntries = HashMultimap.create();
69 for (Map.Entry<ClassEntry, BehaviorEntry> mapEntry : other.behaviorEntries.entries()) { 71 for (Map.Entry<ClassEntry, MethodDefEntry> mapEntry : other.methodEntries.entries()) {
70 this.behaviorEntries.put( 72 this.methodEntries.put(
71 translator.translateEntry(mapEntry.getKey()), 73 translator.getTranslatedClass(mapEntry.getKey()),
72 translator.translateEntry(mapEntry.getValue()) 74 translator.getTranslatedMethodDef(mapEntry.getValue())
73 ); 75 );
74 } 76 }
75 } 77 }
76 78
77 public void indexClass(CtClass c) { 79 protected ClassDefEntry indexClass(int access, String name, String signature, String superName, String[] interfaces) {
78 indexClass(c, true); 80 ClassDefEntry classEntry = new ClassDefEntry(name, Signature.createSignature(signature), new AccessFlags(access));
79 }
80
81 public void indexClass(CtClass c, boolean indexMembers) {
82 ClassEntry classEntry = EntryFactory.getClassEntry(c);
83 if (isJre(classEntry)) { 81 if (isJre(classEntry)) {
84 return; 82 return null;
85 } 83 }
86 84
87 // add the superclass 85 // add the superclass
88 ClassEntry superclassEntry = EntryFactory.getSuperclassEntry(c); 86 ClassEntry superclassEntry = entryPool.getClass(superName);
89 if (superclassEntry != null) { 87 if (superclassEntry != null) {
90 this.superclasses.put(classEntry, superclassEntry); 88 this.superclasses.put(classEntry, superclassEntry);
91 } 89 }
92 90
93 // add the interfaces 91 // add the interfaces
94 for (String interfaceClassName : c.getClassFile().getInterfaces()) { 92 for (String interfaceClassName : interfaces) {
95 ClassEntry interfaceClassEntry = new ClassEntry(Descriptor.toJvmName(interfaceClassName)); 93 ClassEntry interfaceClassEntry = entryPool.getClass(interfaceClassName);
96 if (!isJre(interfaceClassEntry)) { 94 if (!isJre(interfaceClassEntry)) {
97
98 this.interfaces.put(classEntry, interfaceClassEntry); 95 this.interfaces.put(classEntry, interfaceClassEntry);
99 } 96 }
100 } 97 }
101 98
102 if (indexMembers) { 99 return classEntry;
103 // add fields 100 }
104 for (CtField field : c.getDeclaredFields()) {
105 FieldEntry fieldEntry = EntryFactory.getFieldEntry(field);
106 this.fieldEntries.put(fieldEntry.getClassEntry(), fieldEntry);
107 }
108 101
109 // add behaviors 102 protected void indexField(FieldDefEntry fieldEntry) {
110 for (CtBehavior behavior : c.getDeclaredBehaviors()) { 103 this.fieldEntries.put(fieldEntry.getOwnerClassEntry(), fieldEntry);
111 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); 104 }
112 this.behaviorEntries.put(behaviorEntry.getClassEntry(), behaviorEntry); 105
113 } 106 protected void indexMethod(MethodDefEntry methodEntry) {
114 } 107 this.methodEntries.put(methodEntry.getOwnerClassEntry(), methodEntry);
115 } 108 }
116 109
117 public void renameClasses(Map<String, String> renames) { 110 public void renameClasses(Map<String, String> renames) {
118 EntryRenamer.renameClassesInMap(renames, this.superclasses); 111 EntryRenamer.renameClassesInMap(renames, this.superclasses);
119 EntryRenamer.renameClassesInMultimap(renames, this.fieldEntries); 112 EntryRenamer.renameClassesInMultimap(renames, this.fieldEntries);
120 EntryRenamer.renameClassesInMultimap(renames, this.behaviorEntries); 113 EntryRenamer.renameClassesInMultimap(renames, this.methodEntries);
121 } 114 }
122 115
123 public ClassEntry getSuperclass(ClassEntry classEntry) { 116 public ClassEntry getSuperclass(ClassEntry classEntry) {
@@ -175,31 +168,32 @@ public class TranslationIndex {
175 } 168 }
176 169
177 public boolean entryExists(Entry entry) { 170 public boolean entryExists(Entry entry) {
171 if (entry == null) {
172 return false;
173 }
178 if (entry instanceof FieldEntry) { 174 if (entry instanceof FieldEntry) {
179 return fieldExists((FieldEntry) entry); 175 return fieldExists((FieldEntry) entry);
180 } else if (entry instanceof BehaviorEntry) { 176 } else if (entry instanceof MethodEntry) {
181 return behaviorExists((BehaviorEntry) entry); 177 return methodExists((MethodEntry) entry);
182 } else if (entry instanceof ArgumentEntry) {
183 return behaviorExists(((ArgumentEntry) entry).getBehaviorEntry());
184 } else if (entry instanceof LocalVariableEntry) { 178 } else if (entry instanceof LocalVariableEntry) {
185 return behaviorExists(((LocalVariableEntry) entry).getBehaviorEntry()); 179 return methodExists(((LocalVariableEntry) entry).getOwnerEntry());
186 } 180 }
187 throw new IllegalArgumentException("Cannot check existence for " + entry.getClass()); 181 throw new IllegalArgumentException("Cannot check existence for " + entry.getClass());
188 } 182 }
189 183
190 public boolean fieldExists(FieldEntry fieldEntry) { 184 public boolean fieldExists(FieldEntry fieldEntry) {
191 return this.fieldEntries.containsEntry(fieldEntry.getClassEntry(), fieldEntry); 185 return this.fieldEntries.containsEntry(fieldEntry.getOwnerClassEntry(), fieldEntry);
192 } 186 }
193 187
194 public boolean behaviorExists(BehaviorEntry behaviorEntry) { 188 public boolean methodExists(MethodEntry methodEntry) {
195 return this.behaviorEntries.containsEntry(behaviorEntry.getClassEntry(), behaviorEntry); 189 return this.methodEntries.containsEntry(methodEntry.getOwnerClassEntry(), methodEntry);
196 } 190 }
197 191
198 public ClassEntry resolveEntryClass(Entry entry) { 192 public ClassEntry resolveEntryOwner(Entry entry) {
199 return resolveEntryClass(entry, false); 193 return resolveEntryOwner(entry, false);
200 } 194 }
201 195
202 public ClassEntry resolveEntryClass(Entry entry, boolean checkSuperclassBeforeChild) { 196 public ClassEntry resolveEntryOwner(Entry entry, boolean checkSuperclassBeforeChild) {
203 if (entry instanceof ClassEntry) { 197 if (entry instanceof ClassEntry) {
204 return (ClassEntry) entry; 198 return (ClassEntry) entry;
205 } 199 }
@@ -227,12 +221,12 @@ public class TranslationIndex {
227 Entry originalEntry = entry; 221 Entry originalEntry = entry;
228 222
229 // Get all possible superclasses and reverse the list 223 // Get all possible superclasses and reverse the list
230 List<ClassEntry> superclasses = Lists.reverse(getAncestry(originalEntry.getClassEntry())); 224 List<ClassEntry> superclasses = Lists.reverse(getAncestry(originalEntry.getOwnerClassEntry()));
231 225
232 boolean existInEntry = false; 226 boolean existInEntry = false;
233 227
234 for (ClassEntry classEntry : superclasses) { 228 for (ClassEntry classEntry : superclasses) {
235 entry = entry.cloneToNewClass(classEntry); 229 entry = entry.updateOwnership(classEntry);
236 existInEntry = entryExists(entry); 230 existInEntry = entryExists(entry);
237 231
238 // Check for possible entry in interfaces of superclasses 232 // Check for possible entry in interfaces of superclasses
@@ -245,9 +239,9 @@ public class TranslationIndex {
245 239
246 // Doesn't exists in superclasses? check the child or return null 240 // Doesn't exists in superclasses? check the child or return null
247 if (!existInEntry) 241 if (!existInEntry)
248 return !entryExists(originalEntry) ? null : originalEntry.getClassEntry(); 242 return !entryExists(originalEntry) ? null : originalEntry.getOwnerClassEntry();
249 243
250 return entry.getClassEntry(); 244 return entry.getOwnerClassEntry();
251 } 245 }
252 246
253 public ClassEntry resolveSuperclass(Entry entry) { 247 public ClassEntry resolveSuperclass(Entry entry) {
@@ -256,7 +250,7 @@ public class TranslationIndex {
256 250
257 while (!entryExists(entry)) { 251 while (!entryExists(entry)) {
258 // is there a parent class? 252 // is there a parent class?
259 ClassEntry superclassEntry = getSuperclass(entry.getClassEntry()); 253 ClassEntry superclassEntry = getSuperclass(entry.getOwnerClassEntry());
260 if (superclassEntry == null) { 254 if (superclassEntry == null) {
261 // this is probably a method from a class in a library 255 // this is probably a method from a class in a library
262 // we can't trace the implementation up any higher unless we index the library 256 // we can't trace the implementation up any higher unless we index the library
@@ -264,23 +258,23 @@ public class TranslationIndex {
264 } 258 }
265 259
266 // move up to the parent class 260 // move up to the parent class
267 entry = entry.cloneToNewClass(superclassEntry); 261 entry = entry.updateOwnership(superclassEntry);
268 } 262 }
269 return entry.getClassEntry(); 263 return entry.getOwnerClassEntry();
270 } 264 }
271 265
272 public ClassEntry resolveInterface(Entry entry) { 266 public ClassEntry resolveInterface(Entry entry) {
273 // the interfaces for any class is a forest 267 // the interfaces for any class is a forest
274 // so let's look at all the trees 268 // so let's look at all the trees
275 269
276 for (ClassEntry interfaceEntry : this.interfaces.get(entry.getClassEntry())) { 270 for (ClassEntry interfaceEntry : this.interfaces.get(entry.getOwnerClassEntry())) {
277 Collection<ClassEntry> subInterface = this.interfaces.get(interfaceEntry); 271 Collection<ClassEntry> subInterface = this.interfaces.get(interfaceEntry);
278 if (subInterface != null && !subInterface.isEmpty()) { 272 if (subInterface != null && !subInterface.isEmpty()) {
279 ClassEntry result = resolveInterface(entry.cloneToNewClass(interfaceEntry)); 273 ClassEntry result = resolveInterface(entry.updateOwnership(interfaceEntry));
280 if (result != null) 274 if (result != null)
281 return result; 275 return result;
282 } 276 }
283 ClassEntry resolvedClassEntry = resolveSuperclass(entry.cloneToNewClass(interfaceEntry)); 277 ClassEntry resolvedClassEntry = resolveSuperclass(entry.updateOwnership(interfaceEntry));
284 if (resolvedClassEntry != null) { 278 if (resolvedClassEntry != null) {
285 return resolvedClassEntry; 279 return resolvedClassEntry;
286 } 280 }
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 @@
1package cuchaz.enigma.bytecode;
2
3import org.objectweb.asm.Opcodes;
4
5import java.lang.reflect.Modifier;
6
7public class AccessFlags {
8 private int flags;
9
10 public AccessFlags(int flags) {
11 this.flags = flags;
12 }
13
14 public boolean isPrivate() {
15 return Modifier.isPrivate(this.flags);
16 }
17
18 public boolean isProtected() {
19 return Modifier.isProtected(this.flags);
20 }
21
22 public boolean isPublic() {
23 return Modifier.isPublic(this.flags);
24 }
25
26 public boolean isSynthetic() {
27 return (this.flags & Opcodes.ACC_SYNTHETIC) != 0;
28 }
29
30 public boolean isStatic() {
31 return Modifier.isStatic(this.flags);
32 }
33
34 public boolean isEnum() {
35 return (flags & Opcodes.ACC_ENUM) != 0;
36 }
37
38 public AccessFlags setPrivate() {
39 this.setVisibility(Opcodes.ACC_PRIVATE);
40 return this;
41 }
42
43 public AccessFlags setProtected() {
44 this.setVisibility(Opcodes.ACC_PROTECTED);
45 return this;
46 }
47
48 public AccessFlags setPublic() {
49 this.setVisibility(Opcodes.ACC_PUBLIC);
50 return this;
51 }
52
53 public AccessFlags setBridged() {
54 this.setVisibility(Opcodes.ACC_BRIDGE);
55 return this;
56 }
57
58 public void setVisibility(int visibility) {
59 this.resetVisibility();
60 this.flags |= visibility;
61 }
62
63 private void resetVisibility() {
64 this.flags &= ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED | Opcodes.ACC_PUBLIC);
65 }
66
67 public int getFlags() {
68 return this.flags;
69 }
70
71 @Override
72 public boolean equals(Object obj) {
73 return obj instanceof AccessFlags && ((AccessFlags) obj).flags == flags;
74 }
75
76 @Override
77 public int hashCode() {
78 return flags;
79 }
80}
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 @@
11 11
12package cuchaz.enigma.bytecode; 12package cuchaz.enigma.bytecode;
13 13
14import javassist.CtBehavior; 14import org.objectweb.asm.ClassVisitor;
15import javassist.CtClass; 15import org.objectweb.asm.FieldVisitor;
16import javassist.CtField; 16import org.objectweb.asm.MethodVisitor;
17import javassist.bytecode.AccessFlag;
18import javassist.bytecode.InnerClassesAttribute;
19 17
20public class ClassProtectifier { 18public class ClassProtectifier extends ClassVisitor {
21 19
22 public static CtClass protectify(CtClass c) { 20 public ClassProtectifier(int api, ClassVisitor cv) {
23 21 super(api, cv);
24 // protectify all the fields 22 }
25 for (CtField field : c.getDeclaredFields()) {
26 field.setModifiers(protectify(field.getModifiers()));
27 }
28 23
29 // protectify all the methods and constructors 24 @Override
30 for (CtBehavior behavior : c.getDeclaredBehaviors()) { 25 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
31 behavior.setModifiers(protectify(behavior.getModifiers())); 26 access = protectify(access);
32 } 27 return super.visitMethod(access, name, desc, signature, exceptions);
28 }
33 29
34 // protectify all the inner classes 30 @Override
35 InnerClassesAttribute attr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag); 31 public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
36 if (attr != null) { 32 access = protectify(access);
37 for (int i = 0; i < attr.tableLength(); i++) { 33 return super.visitField(access, name, desc, signature, value);
38 attr.setAccessFlags(i, protectify(attr.accessFlags(i))); 34 }
39 }
40 }
41 35
42 return c; 36 @Override
37 public void visitInnerClass(String name, String outerName, String innerName, int access) {
38 access = protectify(access);
39 super.visitInnerClass(name, outerName, innerName, access);
43 } 40 }
44 41
45 private static int protectify(int flags) { 42 private static int protectify(int access) {
46 if (AccessFlag.isPrivate(flags)) { 43 AccessFlags accessFlags = new AccessFlags(access);
47 flags = AccessFlag.setProtected(flags); 44 if (accessFlags.isPrivate()) {
45 accessFlags.setProtected();
48 } 46 }
49 return flags; 47 return accessFlags.getFlags();
50 } 48 }
51} 49}
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 @@
11 11
12package cuchaz.enigma.bytecode; 12package cuchaz.enigma.bytecode;
13 13
14import javassist.CtBehavior; 14import org.objectweb.asm.ClassVisitor;
15import javassist.CtClass; 15import org.objectweb.asm.FieldVisitor;
16import javassist.CtField; 16import org.objectweb.asm.MethodVisitor;
17import javassist.bytecode.AccessFlag;
18import javassist.bytecode.InnerClassesAttribute;
19 17
20public class ClassPublifier { 18public class ClassPublifier extends ClassVisitor {
21 19
22 public static CtClass publify(CtClass c) { 20 public ClassPublifier(int api, ClassVisitor cv) {
21 super(api, cv);
22 }
23 23
24 // publify all the fields 24 @Override
25 for (CtField field : c.getDeclaredFields()) { 25 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
26 field.setModifiers(publify(field.getModifiers())); 26 access = publify(access);
27 } 27 super.visit(version, access, name, signature, superName, interfaces);
28 }
28 29
29 // publify all the methods and constructors 30 @Override
30 for (CtBehavior behavior : c.getDeclaredBehaviors()) { 31 public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
31 behavior.setModifiers(publify(behavior.getModifiers())); 32 access = publify(access);
32 } 33 return super.visitField(access, name, desc, signature, value);
34 }
33 35
34 // publify all the inner classes 36 @Override
35 InnerClassesAttribute attr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag); 37 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
36 if (attr != null) { 38 access = publify(access);
37 for (int i = 0; i < attr.tableLength(); i++) { 39 return super.visitMethod(access, name, desc, signature, exceptions);
38 attr.setAccessFlags(i, publify(attr.accessFlags(i))); 40 }
39 }
40 }
41 41
42 return c; 42 @Override
43 public void visitInnerClass(String name, String outerName, String innerName, int access) {
44 access = publify(access);
45 super.visitInnerClass(name, outerName, innerName, access);
43 } 46 }
44 47
45 private static int publify(int flags) { 48 private static int publify(int access) {
46 if (!AccessFlag.isPublic(flags)) { 49 AccessFlags accessFlags = new AccessFlags(access);
47 flags = AccessFlag.setPublic(flags); 50 if (!accessFlags.isPublic()) {
51 accessFlags.setPublic();
48 } 52 }
49 return flags; 53 return accessFlags.getFlags();
50 } 54 }
51} 55}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.bytecode;
13
14import cuchaz.enigma.mapping.ClassEntry;
15import cuchaz.enigma.mapping.ClassNameReplacer;
16import cuchaz.enigma.mapping.Mappings;
17import cuchaz.enigma.mapping.Translator;
18import javassist.CtBehavior;
19import javassist.CtClass;
20import javassist.CtField;
21import javassist.Modifier;
22import javassist.bytecode.*;
23import javassist.bytecode.SignatureAttribute.*;
24
25import java.lang.reflect.InvocationTargetException;
26import java.lang.reflect.Method;
27import java.util.Arrays;
28import java.util.HashMap;
29import java.util.List;
30import java.util.Map;
31
32public class ClassRenamer {
33
34 public static void applyModifier(Object obj, Mappings.EntryModifier modifier) {
35 int mod = -1;
36 if (obj instanceof CtField)
37 mod = ((CtField) obj).getModifiers();
38 else if (obj instanceof CtBehavior)
39 mod = ((CtBehavior) obj).getModifiers();
40 else if (obj instanceof CtClass)
41 mod = ((CtClass) obj).getModifiers();
42
43 if (mod != -1) {
44 switch (modifier) {
45 case PRIVATE:
46 mod = Modifier.setPrivate(mod);
47 break;
48 case PROTECTED:
49 mod = Modifier.setProtected(mod);
50 break;
51 case PUBLIC:
52 mod = Modifier.setPublic(mod);
53 break;
54 default:
55 break;
56 }
57 if (obj instanceof CtField)
58 ((CtField) obj).setModifiers(mod);
59 else if (obj instanceof CtBehavior)
60 ((CtBehavior) obj).setModifiers(mod);
61 else
62 ((CtClass) obj).setModifiers(mod);
63 }
64 }
65
66 public static void renameClasses(CtClass c, final Translator translator) {
67 renameClasses(c, className -> {
68 ClassEntry entry = translator.translateEntry(new ClassEntry(className));
69 if (entry != null) {
70 return entry.getName();
71 }
72 return null;
73 });
74 }
75
76 public static void moveAllClassesOutOfDefaultPackage(CtClass c, final String newPackageName) {
77 renameClasses(c, className -> {
78 ClassEntry entry = new ClassEntry(className);
79 if (entry.isInDefaultPackage()) {
80 return newPackageName + "/" + entry.getName();
81 }
82 return null;
83 });
84 }
85
86 public static void moveAllClassesIntoDefaultPackage(CtClass c, final String oldPackageName) {
87 renameClasses(c, className -> {
88 ClassEntry entry = new ClassEntry(className);
89 if (entry.getPackageName().equals(oldPackageName)) {
90 return entry.getSimpleName();
91 }
92 return null;
93 });
94 }
95
96 @SuppressWarnings("unchecked")
97 public static void renameClasses(CtClass c, ClassNameReplacer replacer) {
98
99 // sadly, we can't use CtClass.renameClass() because SignatureAttribute.renameClass() is extremely buggy =(
100
101 ReplacerClassMap map = new ReplacerClassMap(replacer);
102 ClassFile classFile = c.getClassFile();
103
104 // rename the constant pool (covers ClassInfo, MethodTypeInfo, and NameAndTypeInfo)
105 ConstPool constPool = c.getClassFile().getConstPool();
106 constPool.renameClass(map);
107
108 // rename class attributes
109 renameAttributes(classFile.getAttributes(), map, SignatureType.Class);
110
111 // rename methods
112 for (MethodInfo methodInfo : (List<MethodInfo>) classFile.getMethods()) {
113 methodInfo.setDescriptor(Descriptor.rename(methodInfo.getDescriptor(), map));
114 renameAttributes(methodInfo.getAttributes(), map, SignatureType.Method);
115 }
116
117 // rename fields
118 for (FieldInfo fieldInfo : (List<FieldInfo>) classFile.getFields()) {
119 fieldInfo.setDescriptor(Descriptor.rename(fieldInfo.getDescriptor(), map));
120 renameAttributes(fieldInfo.getAttributes(), map, SignatureType.Field);
121 }
122
123 // rename the class name itself last
124 // NOTE: don't use the map here, because setName() calls the buggy SignatureAttribute.renameClass()
125 // we only want to replace exactly this class name
126 String newName = renameClassName(c.getName(), map);
127 if (newName != null) {
128 c.setName(newName);
129 }
130
131 // replace simple names in the InnerClasses attribute too
132 InnerClassesAttribute attr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag);
133 if (attr != null) {
134 for (int i = 0; i < attr.tableLength(); i++) {
135
136 String innerName = attr.innerClass(i);
137 // get the inner class full name (which has already been translated)
138 ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(innerName));
139
140 if (attr.innerNameIndex(i) != 0) {
141 // update the inner name
142 attr.setInnerNameIndex(i, constPool.addUtf8Info(classEntry.getInnermostClassName()));
143 }
144
145 /* DEBUG
146 System.out.println(String.format("\tDEOBF: %s-> ATTR: %s,%s,%s", classEntry, attr.outerClass(i), attr.innerClass(i), attr.innerName(i)));
147 */
148 }
149 }
150 }
151
152 @SuppressWarnings("unchecked")
153 private static void renameAttributes(List<AttributeInfo> attributes, ReplacerClassMap map, SignatureType type) {
154 try {
155
156 // make the rename class method accessible
157 Method renameClassMethod = AttributeInfo.class.getDeclaredMethod("renameClass", Map.class);
158 renameClassMethod.setAccessible(true);
159
160 for (AttributeInfo attribute : attributes) {
161 if (attribute instanceof SignatureAttribute) {
162 // this has to be handled specially because SignatureAttribute.renameClass() is buggy as hell
163 SignatureAttribute signatureAttribute = (SignatureAttribute) attribute;
164 String newSignature = type.rename(signatureAttribute.getSignature(), map);
165 if (newSignature != null) {
166 signatureAttribute.setSignature(newSignature);
167 }
168 } else if (attribute instanceof CodeAttribute) {
169 // code attributes have signature attributes too (indirectly)
170 CodeAttribute codeAttribute = (CodeAttribute) attribute;
171 renameAttributes(codeAttribute.getAttributes(), map, type);
172 } else if (attribute instanceof LocalVariableTypeAttribute) {
173 // lvt attributes have signature attributes too
174 LocalVariableTypeAttribute localVariableAttribute = (LocalVariableTypeAttribute) attribute;
175 renameLocalVariableTypeAttribute(localVariableAttribute, map);
176 } else {
177 renameClassMethod.invoke(attribute, map);
178 }
179 }
180
181 } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
182 throw new Error("Unable to call javassist methods by reflection!", ex);
183 }
184 }
185
186 private static void renameLocalVariableTypeAttribute(LocalVariableTypeAttribute attribute, ReplacerClassMap map) {
187
188 // adapted from LocalVariableAttribute.renameClass()
189 ConstPool cp = attribute.getConstPool();
190 int n = attribute.tableLength();
191 byte[] info = attribute.get();
192 for (int i = 0; i < n; ++i) {
193 int pos = i * 10 + 2;
194 int index = ByteArray.readU16bit(info, pos + 6);
195 if (index != 0) {
196 String signature = cp.getUtf8Info(index);
197 String newSignature = renameLocalVariableSignature(signature, map);
198 if (newSignature != null) {
199 ByteArray.write16bit(cp.addUtf8Info(newSignature), info, pos + 6);
200 }
201 }
202 }
203 }
204
205 private static String renameLocalVariableSignature(String signature, ReplacerClassMap map) {
206
207 // for some reason, signatures with . in them don't count as field signatures
208 // looks like anonymous classes delimit with . in stead of $
209 // convert the . to $, but keep track of how many we replace
210 // we need to put them back after we translate
211 int start = signature.lastIndexOf('$') + 1;
212 int numConverted = 0;
213 StringBuilder buf = new StringBuilder(signature);
214 for (int i = buf.length() - 1; i >= start; i--) {
215 char c = buf.charAt(i);
216 if (c == '.') {
217 buf.setCharAt(i, '$');
218 numConverted++;
219 }
220 }
221 signature = buf.toString();
222
223 // translate
224 String newSignature = renameFieldSignature(signature, map);
225 if (newSignature != null) {
226
227 // put the delimiters back
228 buf = new StringBuilder(newSignature);
229 for (int i = buf.length() - 1; i >= 0 && numConverted > 0; i--) {
230 char c = buf.charAt(i);
231 if (c == '$') {
232 buf.setCharAt(i, '.');
233 numConverted--;
234 }
235 }
236 assert (numConverted == 0);
237 newSignature = buf.toString();
238
239 return newSignature;
240 }
241
242 return null;
243 }
244
245 private static String renameClassSignature(String signature, ReplacerClassMap map) {
246 try {
247 ClassSignature type = renameType(SignatureAttribute.toClassSignature(signature), map);
248 return type.encode();
249 } catch (BadBytecode ex) {
250 throw new Error("Can't parse field signature: " + signature);
251 }
252 }
253
254 private static String renameFieldSignature(String signature, ReplacerClassMap map) {
255 try {
256 ObjectType type = renameType(SignatureAttribute.toFieldSignature(signature), map);
257 if (type != null) {
258 return type.encode();
259 }
260 return null;
261 } catch (BadBytecode ex) {
262 throw new Error("Can't parse class signature: " + signature);
263 }
264 }
265
266 private static String renameMethodSignature(String signature, ReplacerClassMap map) {
267 try {
268 MethodSignature type = renameType(SignatureAttribute.toMethodSignature(signature), map);
269 return type.encode();
270 } catch (BadBytecode ex) {
271 throw new Error("Can't parse method signature: " + signature);
272 }
273 }
274
275 private static TypeParameter[] renameTypeParameter(TypeParameter[] typeParamTypes, ReplacerClassMap map) {
276 if (typeParamTypes != null) {
277 typeParamTypes = Arrays.copyOf(typeParamTypes, typeParamTypes.length);
278 for (int i = 0; i < typeParamTypes.length; i++) {
279 TypeParameter newParamType = renameType(typeParamTypes[i], map);
280 if (newParamType != null) {
281 typeParamTypes[i] = newParamType;
282 }
283 }
284 }
285 return typeParamTypes;
286 }
287
288 private static ClassSignature renameType(ClassSignature type, ReplacerClassMap map) {
289
290 TypeParameter[] typeParamTypes = renameTypeParameter(type.getParameters(), map);
291
292 ClassType superclassType = type.getSuperClass();
293 if (superclassType != ClassType.OBJECT) {
294 ClassType newSuperclassType = renameType(superclassType, map);
295 if (newSuperclassType != null) {
296 superclassType = newSuperclassType;
297 }
298 }
299
300 ClassType[] interfaceTypes = type.getInterfaces();
301 if (interfaceTypes != null) {
302 interfaceTypes = Arrays.copyOf(interfaceTypes, interfaceTypes.length);
303 for (int i = 0; i < interfaceTypes.length; i++) {
304 ClassType newInterfaceType = renameType(interfaceTypes[i], map);
305 if (newInterfaceType != null) {
306 interfaceTypes[i] = newInterfaceType;
307 }
308 }
309 }
310
311 return new ClassSignature(typeParamTypes, superclassType, interfaceTypes);
312 }
313
314 private static MethodSignature renameType(MethodSignature type, ReplacerClassMap map) {
315
316 TypeParameter[] typeParamTypes = renameTypeParameter(type.getTypeParameters(), map);
317
318 Type[] paramTypes = type.getParameterTypes();
319 if (paramTypes != null) {
320 paramTypes = Arrays.copyOf(paramTypes, paramTypes.length);
321 for (int i = 0; i < paramTypes.length; i++) {
322 Type newParamType = renameType(paramTypes[i], map);
323 if (newParamType != null) {
324 paramTypes[i] = newParamType;
325 }
326 }
327 }
328
329 Type returnType = type.getReturnType();
330 if (returnType != null) {
331 Type newReturnType = renameType(returnType, map);
332 if (newReturnType != null) {
333 returnType = newReturnType;
334 }
335 }
336
337 ObjectType[] exceptionTypes = type.getExceptionTypes();
338 if (exceptionTypes != null) {
339 exceptionTypes = Arrays.copyOf(exceptionTypes, exceptionTypes.length);
340 for (int i = 0; i < exceptionTypes.length; i++) {
341 ObjectType newExceptionType = renameType(exceptionTypes[i], map);
342 if (newExceptionType != null) {
343 exceptionTypes[i] = newExceptionType;
344 }
345 }
346 }
347
348 return new MethodSignature(typeParamTypes, paramTypes, returnType, exceptionTypes);
349 }
350
351 private static Type renameType(Type type, ReplacerClassMap map) {
352 if (type instanceof ObjectType) {
353 return renameType((ObjectType) type, map);
354 } else if (type instanceof BaseType) {
355 return renameType((BaseType) type, map);
356 } else {
357 throw new Error("Don't know how to rename type " + type.getClass());
358 }
359 }
360
361 private static ObjectType renameType(ObjectType type, ReplacerClassMap map) {
362 if (type instanceof ArrayType) {
363 return renameType((ArrayType) type, map);
364 } else if (type instanceof ClassType) {
365 return renameType((ClassType) type, map);
366 } else if (type instanceof TypeVariable) {
367 return renameType((TypeVariable) type, map);
368 } else {
369 throw new Error("Don't know how to rename type " + type.getClass());
370 }
371 }
372
373 private static BaseType renameType(BaseType type, ReplacerClassMap map) {
374 // don't have to rename primitives
375 return null;
376 }
377
378 private static TypeVariable renameType(TypeVariable type, ReplacerClassMap map) {
379 // don't have to rename template args
380 return null;
381 }
382
383 private static ClassType renameType(ClassType type, ReplacerClassMap map) {
384
385 // translate type args
386 TypeArgument[] args = type.getTypeArguments();
387 if (args != null) {
388 args = Arrays.copyOf(args, args.length);
389 for (int i = 0; i < args.length; i++) {
390 TypeArgument newType = renameType(args[i], map);
391 if (newType != null) {
392 args[i] = newType;
393 }
394 }
395 }
396
397 if (type instanceof NestedClassType) {
398 NestedClassType nestedType = (NestedClassType) type;
399
400 // translate the name
401 String name = getClassName(type);
402 String newName = map.get(name);
403 if (newName != null) {
404 name = new ClassEntry(newName).getInnermostClassName();
405 }
406
407 // translate the parent class too
408 ClassType parent = renameType(nestedType.getDeclaringClass(), map);
409 if (parent == null) {
410 parent = nestedType.getDeclaringClass();
411 }
412
413 return new NestedClassType(parent, name, args);
414 } else {
415
416 // translate the name
417 String name = type.getName();
418 String newName = renameClassName(name, map);
419 if (newName != null) {
420 name = newName;
421 }
422
423 return new ClassType(name, args);
424 }
425 }
426
427 private static String getClassName(ClassType type) {
428 if (type instanceof NestedClassType) {
429 NestedClassType nestedType = (NestedClassType) type;
430 return getClassName(nestedType.getDeclaringClass()) + "$" + Descriptor.toJvmName(type.getName().replace('.', '$'));
431 } else {
432 return Descriptor.toJvmName(type.getName());
433 }
434 }
435
436 private static String renameClassName(String name, ReplacerClassMap map) {
437 String newName = map.get(Descriptor.toJvmName(name));
438 if (newName != null) {
439 return Descriptor.toJavaName(newName);
440 }
441 return null;
442 }
443
444 private static TypeArgument renameType(TypeArgument type, ReplacerClassMap map) {
445 ObjectType subType = type.getType();
446 if (subType != null) {
447 ObjectType newSubType = renameType(subType, map);
448 if (newSubType != null) {
449 switch (type.getKind()) {
450 case ' ':
451 return new TypeArgument(newSubType);
452 case '+':
453 return TypeArgument.subclassOf(newSubType);
454 case '-':
455 return TypeArgument.superOf(newSubType);
456 default:
457 throw new Error("Unknown type kind: " + type.getKind());
458 }
459 }
460 }
461 return null;
462 }
463
464 private static ArrayType renameType(ArrayType type, ReplacerClassMap map) {
465 Type newSubType = renameType(type.getComponentType(), map);
466 if (newSubType != null) {
467 return new ArrayType(type.getDimension(), newSubType);
468 }
469 return null;
470 }
471
472 private static TypeParameter renameType(TypeParameter type, ReplacerClassMap map) {
473
474 ObjectType superclassType = type.getClassBound();
475 if (superclassType != null) {
476 ObjectType newSuperclassType = renameType(superclassType, map);
477 if (newSuperclassType != null) {
478 superclassType = newSuperclassType;
479 }
480 }
481
482 ObjectType[] interfaceTypes = type.getInterfaceBound();
483 if (interfaceTypes != null) {
484 interfaceTypes = Arrays.copyOf(interfaceTypes, interfaceTypes.length);
485 for (int i = 0; i < interfaceTypes.length; i++) {
486 ObjectType newInterfaceType = renameType(interfaceTypes[i], map);
487 if (newInterfaceType != null) {
488 interfaceTypes[i] = newInterfaceType;
489 }
490 }
491 }
492
493 return new TypeParameter(type.getName(), superclassType, interfaceTypes);
494 }
495
496 private enum SignatureType {
497 Class {
498 @Override
499 public String rename(String signature, ReplacerClassMap map) {
500 return renameClassSignature(signature, map);
501 }
502 },
503 Field {
504 @Override
505 public String rename(String signature, ReplacerClassMap map) {
506 return renameFieldSignature(signature, map);
507 }
508 },
509 Method {
510 @Override
511 public String rename(String signature, ReplacerClassMap map) {
512 return renameMethodSignature(signature, map);
513 }
514 };
515
516 public abstract String rename(String signature, ReplacerClassMap map);
517 }
518
519 private static class ReplacerClassMap extends HashMap<String, String> {
520
521 private ClassNameReplacer replacer;
522
523 public ReplacerClassMap(ClassNameReplacer replacer) {
524 this.replacer = replacer;
525 }
526
527 @Override
528 public String get(Object obj) {
529 if (obj instanceof String) {
530 return get((String) obj);
531 }
532 return null;
533 }
534
535 public String get(String className) {
536 return replacer.replace(className);
537 }
538 }
539}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.bytecode;
13
14import cuchaz.enigma.bytecode.accessors.ClassInfoAccessor;
15import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor;
16import cuchaz.enigma.bytecode.accessors.MemberRefInfoAccessor;
17import javassist.bytecode.ConstPool;
18import javassist.bytecode.Descriptor;
19
20import java.io.DataInputStream;
21import java.io.DataOutputStream;
22import java.lang.reflect.Constructor;
23import java.lang.reflect.Field;
24import java.lang.reflect.Method;
25import java.util.HashMap;
26
27public class ConstPoolEditor {
28
29 private static Method getItem;
30 private static Method addItem;
31 private static Method addItem0;
32 private static Field items;
33 private static Field cache;
34 private static Field numItems;
35 private static Field objects;
36 private static Field elements;
37 private static Method methodWritePool;
38 private static Constructor<ConstPool> constructorPool;
39
40 static {
41 try {
42 getItem = ConstPool.class.getDeclaredMethod("getItem", int.class);
43 getItem.setAccessible(true);
44
45 addItem = ConstPool.class.getDeclaredMethod("addItem", Class.forName("javassist.bytecode.ConstInfo"));
46 addItem.setAccessible(true);
47
48 addItem0 = ConstPool.class.getDeclaredMethod("addItem0", Class.forName("javassist.bytecode.ConstInfo"));
49 addItem0.setAccessible(true);
50
51 items = ConstPool.class.getDeclaredField("items");
52 items.setAccessible(true);
53
54 cache = ConstPool.class.getDeclaredField("itemsCache");
55 cache.setAccessible(true);
56
57 numItems = ConstPool.class.getDeclaredField("numOfItems");
58 numItems.setAccessible(true);
59
60 objects = Class.forName("javassist.bytecode.LongVector").getDeclaredField("objects");
61 objects.setAccessible(true);
62
63 elements = Class.forName("javassist.bytecode.LongVector").getDeclaredField("elements");
64 elements.setAccessible(true);
65
66 methodWritePool = ConstPool.class.getDeclaredMethod("write", DataOutputStream.class);
67 methodWritePool.setAccessible(true);
68
69 constructorPool = ConstPool.class.getDeclaredConstructor(DataInputStream.class);
70 constructorPool.setAccessible(true);
71 } catch (Exception ex) {
72 throw new Error(ex);
73 }
74 }
75
76 private ConstPool pool;
77
78 public ConstPoolEditor(ConstPool pool) {
79 this.pool = pool;
80 }
81
82 public static ConstPool readPool(DataInputStream in) {
83 try {
84 return constructorPool.newInstance(in);
85 } catch (Exception ex) {
86 throw new Error(ex);
87 }
88 }
89
90 public static ConstPool newConstPool() {
91 // const pool expects the name of a class to initialize itself
92 // but we want an empty pool
93 // so give it a bogus name, and then clear the entries afterwards
94 ConstPool pool = new ConstPool("a");
95
96 ConstPoolEditor editor = new ConstPoolEditor(pool);
97 int size = pool.getSize();
98 for (int i = 0; i < size - 1; i++) {
99 editor.removeLastItem();
100 }
101
102 // make sure the pool is actually empty
103 // although, in this case "empty" means one thing in it
104 // the JVM spec says index 0 should be reserved
105 assert (pool.getSize() == 1);
106 assert (editor.getItem(0) == null);
107 assert (editor.getItem(1) == null);
108 assert (editor.getItem(2) == null);
109 assert (editor.getItem(3) == null);
110
111 // also, clear the cache
112 editor.getCache().clear();
113
114 return pool;
115 }
116
117 public void writePool(DataOutputStream out) {
118 try {
119 methodWritePool.invoke(this.pool, out);
120 } catch (Exception ex) {
121 throw new Error(ex);
122 }
123 }
124
125 public String getMemberrefClassname(int memberrefIndex) {
126 return Descriptor.toJvmName(this.pool.getClassInfo(this.pool.getMemberClass(memberrefIndex)));
127 }
128
129 public String getMemberrefName(int memberrefIndex) {
130 return this.pool.getUtf8Info(this.pool.getNameAndTypeName(this.pool.getMemberNameAndType(memberrefIndex)));
131 }
132
133 public String getMemberrefType(int memberrefIndex) {
134 return this.pool.getUtf8Info(this.pool.getNameAndTypeDescriptor(this.pool.getMemberNameAndType(memberrefIndex)));
135 }
136
137 public ConstInfoAccessor getItem(int index) {
138 try {
139 Object entry = getItem.invoke(this.pool, index);
140 if (entry == null) {
141 return null;
142 }
143 return new ConstInfoAccessor(entry);
144 } catch (Exception ex) {
145 throw new Error(ex);
146 }
147 }
148
149 public int addItem(Object item) {
150 try {
151 return (Integer) addItem.invoke(this.pool, item);
152 } catch (Exception ex) {
153 throw new Error(ex);
154 }
155 }
156
157 public int addItemForceNew(Object item) {
158 try {
159 return (Integer) addItem0.invoke(this.pool, item);
160 } catch (Exception ex) {
161 throw new Error(ex);
162 }
163 }
164
165 @SuppressWarnings("rawtypes")
166 public void removeLastItem() {
167 try {
168 // remove the item from the cache
169 HashMap cache = getCache();
170 if (cache != null) {
171 Object item = getItem(this.pool.getSize() - 1);
172 cache.remove(item);
173 }
174
175 // remove the actual item
176 // based off of LongVector.addElement()
177 Object item = items.get(this.pool);
178 Object[][] object = (Object[][]) objects.get(items);
179 int numElements = (Integer) elements.get(items) - 1;
180 int nth = numElements >> 7;
181 int offset = numElements & (128 - 1);
182 object[nth][offset] = null;
183
184 // decrement the number of items
185 elements.set(item, numElements);
186 numItems.set(this.pool, (Integer) numItems.get(this.pool) - 1);
187 } catch (Exception ex) {
188 throw new Error(ex);
189 }
190 }
191
192 @SuppressWarnings("rawtypes")
193 public HashMap getCache() {
194 try {
195 return (HashMap) cache.get(this.pool);
196 } catch (Exception ex) {
197 throw new Error(ex);
198 }
199 }
200
201 @SuppressWarnings({ "rawtypes", "unchecked" })
202 public void changeMemberrefNameAndType(int memberrefIndex, String newName, String newType) {
203 // NOTE: when changing values, we always need to copy-on-write
204 try {
205 // get the memberref item
206 Object item = getItem(memberrefIndex).getItem();
207
208 // update the cache
209 HashMap cache = getCache();
210 if (cache != null) {
211 cache.remove(item);
212 }
213
214 new MemberRefInfoAccessor(item).setNameAndTypeIndex(this.pool.addNameAndTypeInfo(newName, newType));
215
216 // update the cache
217 if (cache != null) {
218 cache.put(item, item);
219 }
220 } catch (Exception ex) {
221 throw new Error(ex);
222 }
223
224 // make sure the change worked
225 assert (newName.equals(getMemberrefName(memberrefIndex)));
226 assert (newType.equals(getMemberrefType(memberrefIndex)));
227 }
228
229 @SuppressWarnings({ "rawtypes", "unchecked" })
230 public void changeClassName(int classNameIndex, String newName) {
231 // NOTE: when changing values, we always need to copy-on-write
232 try {
233 // get the class item
234 Object item = getItem(classNameIndex).getItem();
235
236 // update the cache
237 HashMap cache = getCache();
238 if (cache != null) {
239 cache.remove(item);
240 }
241
242 // add the new name and repoint the name-and-type to it
243 new ClassInfoAccessor(item).setNameIndex(this.pool.addUtf8Info(newName));
244
245 // update the cache
246 if (cache != null) {
247 cache.put(item, item);
248 }
249 } catch (Exception ex) {
250 throw new Error(ex);
251 }
252 }
253
254 public String dump() {
255 StringBuilder buf = new StringBuilder();
256 for (int i = 1; i < this.pool.getSize(); i++) {
257 buf.append(String.format("%4d", i));
258 buf.append(" ");
259 buf.append(getItem(i));
260 buf.append("\n");
261 }
262 return buf.toString();
263 }
264}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.bytecode;
13
14import com.google.common.collect.Maps;
15import cuchaz.enigma.bytecode.accessors.*;
16
17import java.util.Collection;
18import java.util.Map;
19
20public enum InfoType {
21
22 Utf8Info(1),
23 IntegerInfo(3),
24 FloatInfo(4),
25 LongInfo(5),
26 DoubleInfo(6),
27 ClassInfo(7) {
28 @Override
29 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
30 ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem());
31 gatherIndexTree(indices, editor, accessor.getNameIndex());
32 }
33
34 @Override
35 public void remapIndices(Map<Integer, Integer> map, ConstInfoAccessor entry) {
36 ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem());
37 accessor.setNameIndex(remapIndex(map, accessor.getNameIndex()));
38 }
39
40 @Override
41 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
42 ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem());
43 ConstInfoAccessor nameEntry = pool.getItem(accessor.getNameIndex());
44 return nameEntry != null && nameEntry.getTag() == Utf8Info.getTag();
45 }
46 },
47 StringInfo(8) {
48 @Override
49 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
50 StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem());
51 gatherIndexTree(indices, editor, accessor.getStringIndex());
52 }
53
54 @Override
55 public void remapIndices(Map<Integer, Integer> map, ConstInfoAccessor entry) {
56 StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem());
57 accessor.setStringIndex(remapIndex(map, accessor.getStringIndex()));
58 }
59
60 @Override
61 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
62 StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem());
63 ConstInfoAccessor stringEntry = pool.getItem(accessor.getStringIndex());
64 return stringEntry != null && stringEntry.getTag() == Utf8Info.getTag();
65 }
66 },
67 FieldRefInfo(9) {
68 @Override
69 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
70 MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem());
71 gatherIndexTree(indices, editor, accessor.getClassIndex());
72 gatherIndexTree(indices, editor, accessor.getNameAndTypeIndex());
73 }
74
75 @Override
76 public void remapIndices(Map<Integer, Integer> map, ConstInfoAccessor entry) {
77 MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem());
78 accessor.setClassIndex(remapIndex(map, accessor.getClassIndex()));
79 accessor.setNameAndTypeIndex(remapIndex(map, accessor.getNameAndTypeIndex()));
80 }
81
82 @Override
83 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
84 MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem());
85 ConstInfoAccessor classEntry = pool.getItem(accessor.getClassIndex());
86 ConstInfoAccessor nameAndTypeEntry = pool.getItem(accessor.getNameAndTypeIndex());
87 return classEntry != null && classEntry.getTag() == ClassInfo.getTag() && nameAndTypeEntry != null && nameAndTypeEntry.getTag() == NameAndTypeInfo.getTag();
88 }
89 },
90 // same as FieldRefInfo
91 MethodRefInfo(10) {
92 @Override
93 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
94 FieldRefInfo.gatherIndexTree(indices, editor, entry);
95 }
96
97 @Override
98 public void remapIndices(Map<Integer, Integer> map, ConstInfoAccessor entry) {
99 FieldRefInfo.remapIndices(map, entry);
100 }
101
102 @Override
103 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
104 return FieldRefInfo.subIndicesAreValid(entry, pool);
105 }
106 },
107 // same as FieldRefInfo
108 InterfaceMethodRefInfo(11) {
109 @Override
110 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
111 FieldRefInfo.gatherIndexTree(indices, editor, entry);
112 }
113
114 @Override
115 public void remapIndices(Map<Integer, Integer> map, ConstInfoAccessor entry) {
116 FieldRefInfo.remapIndices(map, entry);
117 }
118
119 @Override
120 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
121 return FieldRefInfo.subIndicesAreValid(entry, pool);
122 }
123 },
124 NameAndTypeInfo(12) {
125 @Override
126 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
127 NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem());
128 gatherIndexTree(indices, editor, accessor.getNameIndex());
129 gatherIndexTree(indices, editor, accessor.getTypeIndex());
130 }
131
132 @Override
133 public void remapIndices(Map<Integer, Integer> map, ConstInfoAccessor entry) {
134 NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem());
135 accessor.setNameIndex(remapIndex(map, accessor.getNameIndex()));
136 accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex()));
137 }
138
139 @Override
140 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
141 NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem());
142 ConstInfoAccessor nameEntry = pool.getItem(accessor.getNameIndex());
143 ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex());
144 return nameEntry != null && nameEntry.getTag() == Utf8Info.getTag() && typeEntry != null && typeEntry.getTag() == Utf8Info.getTag();
145 }
146 },
147 MethodHandleInfo(15) {
148 @Override
149 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
150 MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem());
151 gatherIndexTree(indices, editor, accessor.getTypeIndex());
152 gatherIndexTree(indices, editor, accessor.getMethodRefIndex());
153 }
154
155 @Override
156 public void remapIndices(Map<Integer, Integer> map, ConstInfoAccessor entry) {
157 MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem());
158 accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex()));
159 accessor.setMethodRefIndex(remapIndex(map, accessor.getMethodRefIndex()));
160 }
161
162 @Override
163 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
164 MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem());
165 ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex());
166 ConstInfoAccessor methodRefEntry = pool.getItem(accessor.getMethodRefIndex());
167 return typeEntry != null && typeEntry.getTag() == Utf8Info.getTag() && methodRefEntry != null && methodRefEntry.getTag() == MethodRefInfo.getTag();
168 }
169 },
170 MethodTypeInfo(16) {
171 @Override
172 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
173 MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem());
174 gatherIndexTree(indices, editor, accessor.getTypeIndex());
175 }
176
177 @Override
178 public void remapIndices(Map<Integer, Integer> map, ConstInfoAccessor entry) {
179 MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem());
180 accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex()));
181 }
182
183 @Override
184 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
185 MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem());
186 ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex());
187 return typeEntry != null && typeEntry.getTag() == Utf8Info.getTag();
188 }
189 },
190 InvokeDynamicInfo(18) {
191 @Override
192 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
193 InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem());
194 gatherIndexTree(indices, editor, accessor.getBootstrapIndex());
195 gatherIndexTree(indices, editor, accessor.getNameAndTypeIndex());
196 }
197
198 @Override
199 public void remapIndices(Map<Integer, Integer> map, ConstInfoAccessor entry) {
200 InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem());
201 accessor.setBootstrapIndex(remapIndex(map, accessor.getBootstrapIndex()));
202 accessor.setNameAndTypeIndex(remapIndex(map, accessor.getNameAndTypeIndex()));
203 }
204
205 @Override
206 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
207 InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem());
208 ConstInfoAccessor bootstrapEntry = pool.getItem(accessor.getBootstrapIndex());
209 ConstInfoAccessor nameAndTypeEntry = pool.getItem(accessor.getNameAndTypeIndex());
210 return bootstrapEntry != null && bootstrapEntry.getTag() == Utf8Info.getTag() && nameAndTypeEntry != null && nameAndTypeEntry.getTag() == NameAndTypeInfo.getTag();
211 }
212 };
213
214 private static Map<Integer, InfoType> types;
215
216 static {
217 types = Maps.newTreeMap();
218 for (InfoType type : values()) {
219 types.put(type.getTag(), type);
220 }
221 }
222
223 private int tag;
224
225 InfoType(int tag) {
226 this.tag = tag;
227 }
228
229 public static InfoType getByTag(int tag) {
230 return types.get(tag);
231 }
232
233 public static void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, int index) {
234 // add own index
235 indices.add(index);
236
237 // recurse
238 ConstInfoAccessor entry = editor.getItem(index);
239 entry.getType().gatherIndexTree(indices, editor, entry);
240 }
241
242 private static int remapIndex(Map<Integer, Integer> map, int index) {
243 Integer newIndex = map.get(index);
244 if (newIndex == null) {
245 newIndex = index;
246 }
247 return newIndex;
248 }
249
250 public int getTag() {
251 return this.tag;
252 }
253
254 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
255 // by default, do nothing
256 }
257
258 public void remapIndices(Map<Integer, Integer> map, ConstInfoAccessor entry) {
259 // by default, do nothing
260 }
261
262 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
263 // by default, everything is good
264 return true;
265 }
266}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.bytecode;
13
14import javassist.bytecode.AttributeInfo;
15import javassist.bytecode.ConstPool;
16import javassist.bytecode.MethodInfo;
17
18import java.io.ByteArrayOutputStream;
19import java.io.DataOutputStream;
20import java.io.IOException;
21import java.util.ArrayList;
22import java.util.List;
23
24public class MethodParametersAttribute extends AttributeInfo {
25
26 private MethodParametersAttribute(ConstPool pool, List<Integer> parameterNameIndices) {
27 super(pool, "MethodParameters", writeStruct(parameterNameIndices));
28 }
29
30 public static void updateClass(MethodInfo info, List<String> names) {
31
32 // add the names to the class const pool
33 ConstPool constPool = info.getConstPool();
34 List<Integer> parameterNameIndices = new ArrayList<>();
35 for (String name : names) {
36 if (name != null) {
37 parameterNameIndices.add(constPool.addUtf8Info(name));
38 }
39 }
40
41 // add the attribute to the method
42 info.addAttribute(new MethodParametersAttribute(constPool, parameterNameIndices));
43 }
44
45 private static byte[] writeStruct(List<Integer> parameterNameIndices) {
46 // JVM 8 Spec says the struct looks like this:
47 // http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.24
48 // uint8 num_params
49 // for each param:
50 // uint16 name_index -> points to UTF8 entry in constant pool, or 0 for no entry
51 // uint16 access_flags -> don't care, just set to 0
52
53 ByteArrayOutputStream buf = new ByteArrayOutputStream();
54 DataOutputStream out = new DataOutputStream(buf);
55
56 // NOTE: java hates unsigned integers, so we have to be careful here
57 // the writeShort(), writeByte() methods will read 16,8 low-order bits from the int argument
58 // as long as the int argument is in range of the unsigned short/byte type, it will be written as an unsigned short/byte
59 // if the int is out of range, the byte stream won't look the way we want and weird things will happen
60 final int SIZEOF_UINT8 = 1;
61 final int SIZEOF_UINT16 = 2;
62 final int MAX_UINT8 = (1 << 8) - 1;
63 final int MAX_UINT16 = (1 << 16) - 1;
64
65 try {
66 assert (parameterNameIndices.size() >= 0 && parameterNameIndices.size() <= MAX_UINT8);
67 out.writeByte(parameterNameIndices.size());
68
69 for (Integer index : parameterNameIndices) {
70 assert (index >= 0 && index <= MAX_UINT16);
71 out.writeShort(index);
72
73 // just write 0 for the access flags
74 out.writeShort(0);
75 }
76
77 out.close();
78 byte[] data = buf.toByteArray();
79 assert (data.length == SIZEOF_UINT8 + parameterNameIndices.size() * (SIZEOF_UINT16 + SIZEOF_UINT16));
80 return data;
81 } catch (IOException ex) {
82 throw new Error(ex);
83 }
84 }
85}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.bytecode.accessors;
13
14import java.lang.reflect.Field;
15
16public class ClassInfoAccessor {
17
18 private static Class<?> clazz;
19 private static Field nameIndex;
20
21 static {
22 try {
23 clazz = Class.forName("javassist.bytecode.ClassInfo");
24 nameIndex = clazz.getDeclaredField("name");
25 nameIndex.setAccessible(true);
26 } catch (Exception ex) {
27 throw new Error(ex);
28 }
29 }
30
31 private Object item;
32
33 public ClassInfoAccessor(Object item) {
34 this.item = item;
35 }
36
37 public static boolean isType(ConstInfoAccessor accessor) {
38 return clazz.isAssignableFrom(accessor.getItem().getClass());
39 }
40
41 public int getNameIndex() {
42 try {
43 return (Integer) nameIndex.get(this.item);
44 } catch (Exception ex) {
45 throw new Error(ex);
46 }
47 }
48
49 public void setNameIndex(int val) {
50 try {
51 nameIndex.set(this.item, val);
52 } catch (Exception ex) {
53 throw new Error(ex);
54 }
55 }
56}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.bytecode.accessors;
13
14import com.google.common.base.Charsets;
15import cuchaz.enigma.bytecode.InfoType;
16
17import java.io.*;
18import java.lang.reflect.Field;
19import java.lang.reflect.Method;
20
21public class ConstInfoAccessor {
22
23 private static Class<?> clazz;
24 private static Field index;
25 private static Method getTag;
26
27 static {
28 try {
29 clazz = Class.forName("javassist.bytecode.ConstInfo");
30 index = clazz.getDeclaredField("index");
31 index.setAccessible(true);
32 getTag = clazz.getMethod("getTag");
33 getTag.setAccessible(true);
34 } catch (Exception ex) {
35 throw new Error(ex);
36 }
37 }
38
39 private Object item;
40
41 public ConstInfoAccessor(Object item) {
42 if (item == null) {
43 throw new IllegalArgumentException("item cannot be null!");
44 }
45 this.item = item;
46 }
47
48 public Object getItem() {
49 return this.item;
50 }
51
52 public int getIndex() {
53 try {
54 return (Integer) index.get(this.item);
55 } catch (Exception ex) {
56 throw new Error(ex);
57 }
58 }
59
60 public int getTag() {
61 try {
62 return (Integer) getTag.invoke(this.item);
63 } catch (Exception ex) {
64 throw new Error(ex);
65 }
66 }
67
68 public ConstInfoAccessor copy() {
69 return new ConstInfoAccessor(copyItem());
70 }
71
72 public Object copyItem() {
73 // I don't know of a simpler way to copy one of these silly things...
74 try {
75 // serialize the item
76 ByteArrayOutputStream buf = new ByteArrayOutputStream();
77 DataOutputStream out = new DataOutputStream(buf);
78 write(out);
79
80 // deserialize the item
81 DataInputStream in = new DataInputStream(new ByteArrayInputStream(buf.toByteArray()));
82 Object item = new ConstInfoAccessor(in).getItem();
83 in.close();
84
85 return item;
86 } catch (Exception ex) {
87 throw new Error(ex);
88 }
89 }
90
91 public void write(DataOutputStream out) throws IOException {
92 try {
93 out.writeUTF(this.item.getClass().getName());
94 out.writeInt(getIndex());
95
96 Method method = this.item.getClass().getMethod("write", DataOutputStream.class);
97 method.setAccessible(true);
98 method.invoke(this.item, out);
99 } catch (IOException ex) {
100 throw ex;
101 } catch (Exception ex) {
102 throw new Error(ex);
103 }
104 }
105
106 @Override
107 public String toString() {
108 try {
109 ByteArrayOutputStream buf = new ByteArrayOutputStream();
110 PrintWriter out = new PrintWriter(new OutputStreamWriter(buf, Charsets.UTF_8));
111 Method print = this.item.getClass().getMethod("print", PrintWriter.class);
112 print.setAccessible(true);
113 print.invoke(this.item, out);
114 out.close();
115 return buf.toString("UTF-8").replace("\n", "");
116 } catch (Exception ex) {
117 throw new Error(ex);
118 }
119 }
120
121 public InfoType getType() {
122 return InfoType.getByTag(getTag());
123 }
124}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.bytecode.accessors;
13
14import java.lang.reflect.Field;
15
16public class InvokeDynamicInfoAccessor {
17
18 private static Class<?> clazz;
19 private static Field bootstrapIndex;
20 private static Field nameAndTypeIndex;
21
22 static {
23 try {
24 clazz = Class.forName("javassist.bytecode.InvokeDynamicInfo");
25 bootstrapIndex = clazz.getDeclaredField("bootstrap");
26 bootstrapIndex.setAccessible(true);
27 nameAndTypeIndex = clazz.getDeclaredField("nameAndType");
28 nameAndTypeIndex.setAccessible(true);
29 } catch (Exception ex) {
30 throw new Error(ex);
31 }
32 }
33
34 private Object item;
35
36 public InvokeDynamicInfoAccessor(Object item) {
37 this.item = item;
38 }
39
40 public static boolean isType(ConstInfoAccessor accessor) {
41 return clazz.isAssignableFrom(accessor.getItem().getClass());
42 }
43
44 public int getBootstrapIndex() {
45 try {
46 return (Integer) bootstrapIndex.get(this.item);
47 } catch (Exception ex) {
48 throw new Error(ex);
49 }
50 }
51
52 public void setBootstrapIndex(int val) {
53 try {
54 bootstrapIndex.set(this.item, val);
55 } catch (Exception ex) {
56 throw new Error(ex);
57 }
58 }
59
60 public int getNameAndTypeIndex() {
61 try {
62 return (Integer) nameAndTypeIndex.get(this.item);
63 } catch (Exception ex) {
64 throw new Error(ex);
65 }
66 }
67
68 public void setNameAndTypeIndex(int val) {
69 try {
70 nameAndTypeIndex.set(this.item, val);
71 } catch (Exception ex) {
72 throw new Error(ex);
73 }
74 }
75}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.bytecode.accessors;
13
14import java.lang.reflect.Field;
15
16public class MemberRefInfoAccessor {
17
18 private static Class<?> clazz;
19 private static Field classIndex;
20 private static Field nameAndTypeIndex;
21
22 static {
23 try {
24 clazz = Class.forName("javassist.bytecode.MemberrefInfo");
25 classIndex = clazz.getDeclaredField("classIndex");
26 classIndex.setAccessible(true);
27 nameAndTypeIndex = clazz.getDeclaredField("nameAndTypeIndex");
28 nameAndTypeIndex.setAccessible(true);
29 } catch (Exception ex) {
30 throw new Error(ex);
31 }
32 }
33
34 private Object item;
35
36 public MemberRefInfoAccessor(Object item) {
37 this.item = item;
38 }
39
40 public static boolean isType(ConstInfoAccessor accessor) {
41 return clazz.isAssignableFrom(accessor.getItem().getClass());
42 }
43
44 public int getClassIndex() {
45 try {
46 return (Integer) classIndex.get(this.item);
47 } catch (Exception ex) {
48 throw new Error(ex);
49 }
50 }
51
52 public void setClassIndex(int val) {
53 try {
54 classIndex.set(this.item, val);
55 } catch (Exception ex) {
56 throw new Error(ex);
57 }
58 }
59
60 public int getNameAndTypeIndex() {
61 try {
62 return (Integer) nameAndTypeIndex.get(this.item);
63 } catch (Exception ex) {
64 throw new Error(ex);
65 }
66 }
67
68 public void setNameAndTypeIndex(int val) {
69 try {
70 nameAndTypeIndex.set(this.item, val);
71 } catch (Exception ex) {
72 throw new Error(ex);
73 }
74 }
75}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.bytecode.accessors;
13
14import java.lang.reflect.Field;
15
16public class MethodHandleInfoAccessor {
17
18 private static Class<?> clazz;
19 private static Field kindIndex;
20 private static Field indexIndex;
21
22 static {
23 try {
24 clazz = Class.forName("javassist.bytecode.MethodHandleInfo");
25 kindIndex = clazz.getDeclaredField("refKind");
26 kindIndex.setAccessible(true);
27 indexIndex = clazz.getDeclaredField("refIndex");
28 indexIndex.setAccessible(true);
29 } catch (Exception ex) {
30 throw new Error(ex);
31 }
32 }
33
34 private Object item;
35
36 public MethodHandleInfoAccessor(Object item) {
37 this.item = item;
38 }
39
40 public static boolean isType(ConstInfoAccessor accessor) {
41 return clazz.isAssignableFrom(accessor.getItem().getClass());
42 }
43
44 public int getTypeIndex() {
45 try {
46 return (Integer) kindIndex.get(this.item);
47 } catch (Exception ex) {
48 throw new Error(ex);
49 }
50 }
51
52 public void setTypeIndex(int val) {
53 try {
54 kindIndex.set(this.item, val);
55 } catch (Exception ex) {
56 throw new Error(ex);
57 }
58 }
59
60 public int getMethodRefIndex() {
61 try {
62 return (Integer) indexIndex.get(this.item);
63 } catch (Exception ex) {
64 throw new Error(ex);
65 }
66 }
67
68 public void setMethodRefIndex(int val) {
69 try {
70 indexIndex.set(this.item, val);
71 } catch (Exception ex) {
72 throw new Error(ex);
73 }
74 }
75}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.bytecode.accessors;
13
14import java.lang.reflect.Field;
15
16public class MethodTypeInfoAccessor {
17
18 private static Class<?> clazz;
19 private static Field descriptorIndex;
20
21 static {
22 try {
23 clazz = Class.forName("javassist.bytecode.MethodTypeInfo");
24 descriptorIndex = clazz.getDeclaredField("descriptor");
25 descriptorIndex.setAccessible(true);
26 } catch (Exception ex) {
27 throw new Error(ex);
28 }
29 }
30
31 private Object item;
32
33 public MethodTypeInfoAccessor(Object item) {
34 this.item = item;
35 }
36
37 public static boolean isType(ConstInfoAccessor accessor) {
38 return clazz.isAssignableFrom(accessor.getItem().getClass());
39 }
40
41 public int getTypeIndex() {
42 try {
43 return (Integer) descriptorIndex.get(this.item);
44 } catch (Exception ex) {
45 throw new Error(ex);
46 }
47 }
48
49 public void setTypeIndex(int val) {
50 try {
51 descriptorIndex.set(this.item, val);
52 } catch (Exception ex) {
53 throw new Error(ex);
54 }
55 }
56
57}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.bytecode.accessors;
13
14import java.lang.reflect.Field;
15
16public class NameAndTypeInfoAccessor {
17
18 private static Class<?> clazz;
19 private static Field nameIndex;
20 private static Field typeIndex;
21
22 static {
23 try {
24 clazz = Class.forName("javassist.bytecode.NameAndTypeInfo");
25 nameIndex = clazz.getDeclaredField("memberName");
26 nameIndex.setAccessible(true);
27 typeIndex = clazz.getDeclaredField("typeDescriptor");
28 typeIndex.setAccessible(true);
29 } catch (Exception ex) {
30 throw new Error(ex);
31 }
32 }
33
34 private Object item;
35
36 public NameAndTypeInfoAccessor(Object item) {
37 this.item = item;
38 }
39
40 public static boolean isType(ConstInfoAccessor accessor) {
41 return clazz.isAssignableFrom(accessor.getItem().getClass());
42 }
43
44 public int getNameIndex() {
45 try {
46 return (Integer) nameIndex.get(this.item);
47 } catch (Exception ex) {
48 throw new Error(ex);
49 }
50 }
51
52 public void setNameIndex(int val) {
53 try {
54 nameIndex.set(this.item, val);
55 } catch (Exception ex) {
56 throw new Error(ex);
57 }
58 }
59
60 public int getTypeIndex() {
61 try {
62 return (Integer) typeIndex.get(this.item);
63 } catch (Exception ex) {
64 throw new Error(ex);
65 }
66 }
67
68 public void setTypeIndex(int val) {
69 try {
70 typeIndex.set(this.item, val);
71 } catch (Exception ex) {
72 throw new Error(ex);
73 }
74 }
75}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.bytecode.accessors;
13
14import java.lang.reflect.Field;
15
16public class StringInfoAccessor {
17
18 private static Class<?> clazz;
19 private static Field stringIndex;
20
21 static {
22 try {
23 clazz = Class.forName("javassist.bytecode.StringInfo");
24 stringIndex = clazz.getDeclaredField("string");
25 stringIndex.setAccessible(true);
26 } catch (Exception ex) {
27 throw new Error(ex);
28 }
29 }
30
31 private Object item;
32
33 public StringInfoAccessor(Object item) {
34 this.item = item;
35 }
36
37 public static boolean isType(ConstInfoAccessor accessor) {
38 return clazz.isAssignableFrom(accessor.getItem().getClass());
39 }
40
41 public int getStringIndex() {
42 try {
43 return (Integer) stringIndex.get(this.item);
44 } catch (Exception ex) {
45 throw new Error(ex);
46 }
47 }
48
49 public void setStringIndex(int val) {
50 try {
51 stringIndex.set(this.item, val);
52 } catch (Exception ex) {
53 throw new Error(ex);
54 }
55 }
56}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.bytecode.accessors;
13
14public class Utf8InfoAccessor {
15
16 private static Class<?> clazz;
17
18 static {
19 try {
20 clazz = Class.forName("javassist.bytecode.Utf8Info");
21 } catch (Exception ex) {
22 throw new Error(ex);
23 }
24 }
25
26 public static boolean isType(ConstInfoAccessor accessor) {
27 return clazz.isAssignableFrom(accessor.getItem().getClass());
28 }
29}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.bytecode.translators;
13
14import cuchaz.enigma.bytecode.ClassRenamer;
15import cuchaz.enigma.bytecode.ConstPoolEditor;
16import cuchaz.enigma.mapping.*;
17import javassist.CtBehavior;
18import javassist.CtClass;
19import javassist.CtField;
20import javassist.CtMethod;
21import javassist.bytecode.*;
22
23public class ClassTranslator {
24
25 public static void translate(Translator translator, CtClass c) {
26
27 // NOTE: the order of these translations is very important
28
29 // translate all the field and method references in the code by editing the constant pool
30 ConstPool constants = c.getClassFile().getConstPool();
31 ConstPoolEditor editor = new ConstPoolEditor(constants);
32 for (int i = 1; i < constants.getSize(); i++) {
33 switch (constants.getTag(i)) {
34
35 case ConstPool.CONST_Fieldref: {
36
37 // translate the name and type
38 FieldEntry entry = EntryFactory.getFieldEntry(
39 Descriptor.toJvmName(constants.getFieldrefClassName(i)),
40 constants.getFieldrefName(i),
41 constants.getFieldrefType(i)
42 );
43 FieldEntry translatedEntry = translator.translateEntry(entry);
44 if (!entry.equals(translatedEntry)) {
45 editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedEntry.getType().toString());
46 }
47 }
48 break;
49
50 case ConstPool.CONST_Methodref:
51 case ConstPool.CONST_InterfaceMethodref: {
52
53 // translate the name and type (ie signature)
54 BehaviorEntry entry = EntryFactory.getBehaviorEntry(
55 Descriptor.toJvmName(editor.getMemberrefClassname(i)),
56 editor.getMemberrefName(i),
57 editor.getMemberrefType(i)
58 );
59 BehaviorEntry translatedEntry = translator.translateEntry(entry);
60 if (!entry.equals(translatedEntry)) {
61 editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedEntry.getSignature().toString());
62 }
63 }
64 break;
65 default:
66 break;
67 }
68 }
69
70 ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
71 Mappings.EntryModifier modifier = translator.getModifier(classEntry);
72 if (modifier != null && modifier != Mappings.EntryModifier.UNCHANGED)
73 ClassRenamer.applyModifier(c, modifier);
74
75 // translate all the fields
76 for (CtField field : c.getDeclaredFields()) {
77
78 // translate the name
79 FieldEntry entry = EntryFactory.getFieldEntry(field);
80 String translatedName = translator.translate(entry);
81 modifier = translator.getModifier(entry);
82 if (modifier != null && modifier != Mappings.EntryModifier.UNCHANGED)
83 ClassRenamer.applyModifier(field, modifier);
84
85 if (translatedName != null) {
86 field.setName(translatedName);
87 }
88
89 // translate the type
90 Type translatedType = translator.translateType(entry.getType());
91 field.getFieldInfo().setDescriptor(translatedType.toString());
92 }
93
94 // translate all the methods and constructors
95 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
96
97 BehaviorEntry entry = EntryFactory.getBehaviorEntry(behavior);
98
99 modifier = translator.getModifier(entry);
100 if (modifier != null && modifier != Mappings.EntryModifier.UNCHANGED)
101 ClassRenamer.applyModifier(behavior, modifier);
102
103 if (behavior instanceof CtMethod) {
104 CtMethod method = (CtMethod) behavior;
105
106 // translate the name
107 String translatedName = translator.translate(entry);
108 if (translatedName != null) {
109 method.setName(translatedName);
110 }
111 }
112
113 if (entry.getSignature() != null) {
114 // translate the signature
115 Signature translatedSignature = translator.translateSignature(entry.getSignature());
116 behavior.getMethodInfo().setDescriptor(translatedSignature.toString());
117 }
118 }
119
120 // translate the EnclosingMethod attribute
121 EnclosingMethodAttribute enclosingMethodAttr = (EnclosingMethodAttribute) c.getClassFile().getAttribute(EnclosingMethodAttribute.tag);
122 if (enclosingMethodAttr != null) {
123
124 if (enclosingMethodAttr.methodIndex() == 0) {
125 BehaviorEntry obfBehaviorEntry = EntryFactory.getBehaviorEntry(Descriptor.toJvmName(enclosingMethodAttr.className()));
126 BehaviorEntry deobfBehaviorEntry = translator.translateEntry(obfBehaviorEntry);
127 c.getClassFile().addAttribute(new EnclosingMethodAttribute(
128 constants,
129 deobfBehaviorEntry.getClassName()
130 ));
131 } else {
132 BehaviorEntry obfBehaviorEntry = EntryFactory.getBehaviorEntry(
133 Descriptor.toJvmName(enclosingMethodAttr.className()),
134 enclosingMethodAttr.methodName(),
135 enclosingMethodAttr.methodDescriptor()
136 );
137 BehaviorEntry deobfBehaviorEntry = translator.translateEntry(obfBehaviorEntry);
138 c.getClassFile().addAttribute(new EnclosingMethodAttribute(
139 constants,
140 deobfBehaviorEntry.getClassName(),
141 deobfBehaviorEntry.getName(),
142 deobfBehaviorEntry.getSignature().toString()
143 ));
144 }
145 }
146
147 // translate all the class names referenced in the code
148 // the above code only changed method/field/reference names and types, but not the rest of the class references
149 ClassRenamer.renameClasses(c, translator);
150
151 // translate the source file attribute too
152 ClassEntry deobfClassEntry = translator.translateEntry(classEntry);
153 if (deobfClassEntry != null) {
154 String sourceFile = Descriptor.toJvmName(deobfClassEntry.getOutermostClassEntry().getSimpleName()) + ".java";
155 c.getClassFile().addAttribute(new SourceFileAttribute(constants, sourceFile));
156 }
157 InnerClassesAttribute attr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag);
158 if (attr != null)
159 InnerClassWriter.changeModifier(c, attr, translator);
160 }
161}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.bytecode.translators;
13
14import com.google.common.collect.Lists;
15import cuchaz.enigma.analysis.JarIndex;
16import cuchaz.enigma.bytecode.ClassRenamer;
17import cuchaz.enigma.mapping.*;
18import javassist.ClassPool;
19import javassist.CtClass;
20import javassist.NotFoundException;
21import javassist.bytecode.*;
22
23import java.util.Collection;
24import java.util.List;
25
26public class InnerClassWriter {
27
28 // FIXME: modifier is not applied to inner class
29 public static void changeModifier(CtClass c, InnerClassesAttribute attr, Translator translator) {
30 ClassPool pool = c.getClassPool();
31 for (int i = 0; i < attr.tableLength(); i++) {
32
33 String innerName = attr.innerClass(i);
34 // get the inner class full name (which has already been translated)
35 ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(innerName));
36 try {
37 CtClass innerClass = pool.get(innerName);
38 Mappings.EntryModifier modifier = translator.getModifier(classEntry);
39 if (modifier != null && modifier != Mappings.EntryModifier.UNCHANGED)
40 ClassRenamer.applyModifier(innerClass, modifier);
41 } catch (NotFoundException e) {
42 // This shouldn't be possible in theory
43 //e.printStackTrace();
44 }
45 }
46 }
47
48 public static void write(JarIndex index, CtClass c) {
49
50 // don't change anything if there's already an attribute there
51 InnerClassesAttribute oldAttr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag);
52 if (oldAttr != null) {
53 // bail!
54 return;
55 }
56
57 ClassEntry obfClassEntry = EntryFactory.getClassEntry(c);
58 List<ClassEntry> obfClassChain = index.getObfClassChain(obfClassEntry);
59
60 boolean isInnerClass = obfClassChain.size() > 1;
61 if (isInnerClass) {
62
63 // it's an inner class, rename it to the fully qualified name
64 c.setName(obfClassEntry.buildClassEntry(obfClassChain).getName());
65
66 BehaviorEntry caller = index.getAnonymousClassCaller(obfClassEntry);
67 if (caller != null) {
68
69 // write the enclosing method attribute
70 if (caller.getName().equals("<clinit>")) {
71 c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName()));
72 } else {
73 c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName(), caller.getName(), caller.getSignature().toString()));
74 }
75 }
76 }
77
78 // does this class have any inner classes?
79 Collection<ClassEntry> obfInnerClassEntries = index.getInnerClasses(obfClassEntry);
80
81 if (isInnerClass || !obfInnerClassEntries.isEmpty()) {
82
83 // create an inner class attribute
84 InnerClassesAttribute attr = new InnerClassesAttribute(c.getClassFile().getConstPool());
85 c.getClassFile().addAttribute(attr);
86
87 // write the ancestry, but not the outermost class
88 for (int i = 1; i < obfClassChain.size(); i++) {
89 ClassEntry obfInnerClassEntry = obfClassChain.get(i);
90 writeInnerClass(index, attr, obfClassChain, obfInnerClassEntry);
91
92 // update references to use the fully qualified inner class name
93 c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(obfClassChain).getName());
94 }
95
96 // write the inner classes
97 for (ClassEntry obfInnerClassEntry : obfInnerClassEntries) {
98
99 // extend the class chain
100 List<ClassEntry> extendedObfClassChain = Lists.newArrayList(obfClassChain);
101 extendedObfClassChain.add(obfInnerClassEntry);
102
103 writeInnerClass(index, attr, extendedObfClassChain, obfInnerClassEntry);
104
105 // update references to use the fully qualified inner class name
106 c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(extendedObfClassChain).getName());
107 }
108 }
109 }
110
111 private static void writeInnerClass(JarIndex index, InnerClassesAttribute attr, List<ClassEntry> obfClassChain, ClassEntry obfClassEntry) {
112
113 // get the new inner class name
114 ClassEntry obfInnerClassEntry = obfClassEntry.buildClassEntry(obfClassChain);
115 ClassEntry obfOuterClassEntry = obfInnerClassEntry.getOuterClassEntry();
116
117 // here's what the JVM spec says about the InnerClasses attribute
118 // append(inner, parent, 0 if anonymous else simple name, flags);
119
120 // update the attribute with this inner class
121 ConstPool constPool = attr.getConstPool();
122 int innerClassIndex = constPool.addClassInfo(obfInnerClassEntry.getName());
123 int parentClassIndex = constPool.addClassInfo(obfOuterClassEntry.getName());
124 int innerClassNameIndex = 0;
125 int accessFlags = AccessFlag.PUBLIC;
126 // TODO: need to figure out if we can put static or not
127 if (!index.isAnonymousClass(obfClassEntry)) {
128 innerClassNameIndex = constPool.addUtf8Info(obfInnerClassEntry.getInnermostClassName());
129 }
130
131 attr.append(innerClassIndex, parentClassIndex, innerClassNameIndex, accessFlags);
132
133 /* DEBUG
134 System.out.println(String.format("\tOBF: %s -> ATTR: %s,%s,%s (replace %s with %s)",
135 obfClassEntry,
136 attr.innerClass(attr.tableLength() - 1),
137 attr.outerClass(attr.tableLength() - 1),
138 attr.innerName(attr.tableLength() - 1),
139 Constants.NonePackage + "/" + obfInnerClassName,
140 obfClassEntry.getName()
141 ));
142 */
143 }
144}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.bytecode.translators;
13
14import cuchaz.enigma.mapping.*;
15import javassist.CtBehavior;
16import javassist.CtClass;
17import javassist.bytecode.*;
18
19public class LocalVariableTranslator {
20
21 public static void translate(Translator translator, CtClass c) {
22 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
23
24 // if there's a local variable table, just rename everything to v1, v2, v3, ... for now
25 CodeAttribute codeAttribute = behavior.getMethodInfo().getCodeAttribute();
26 if (codeAttribute == null) {
27 continue;
28 }
29
30 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
31 ConstPool constants = c.getClassFile().getConstPool();
32
33 LocalVariableAttribute table = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
34 if (table != null) {
35 renameLVT(translator, behaviorEntry, constants, table, c);
36 }
37
38 LocalVariableTypeAttribute typeTable = (LocalVariableTypeAttribute) codeAttribute.getAttribute(LocalVariableAttribute.typeTag);
39 if (typeTable != null) {
40 renameLVTT(typeTable, table);
41 }
42 }
43 }
44
45 // DEBUG
46 @SuppressWarnings("unused")
47 private static void dumpTable(LocalVariableAttribute table) {
48 for (int i = 0; i < table.tableLength(); i++) {
49 System.out.println(String.format("\t%d (%d): %s %s",
50 i, table.index(i), table.variableName(i), table.descriptor(i)
51 ));
52 }
53 }
54
55 private static void renameLVT(Translator translator, BehaviorEntry behaviorEntry, ConstPool constants, LocalVariableAttribute table, CtClass ctClass) {
56
57 // skip empty tables
58 if (table.tableLength() <= 0) {
59 return;
60 }
61
62 // where do we start counting variables?
63 int starti = 0;
64 if (table.variableName(0).equals("this")) {
65 // skip the "this" variable
66 starti++;
67 }
68
69 // rename method arguments first
70 int numArgs = 0;
71 if (behaviorEntry.getSignature() != null) {
72 numArgs = behaviorEntry.getSignature().getArgumentTypes().size();
73 boolean isNestedClassConstructor = false;
74
75 // If the behavior is a constructor and if it have more than one arg, it's probably from a nested!
76 if (behaviorEntry instanceof ConstructorEntry && behaviorEntry.getClassEntry() != null && behaviorEntry.getClassEntry().isInnerClass() && numArgs >= 1) {
77 // Get the first arg type
78 Type firstArg = behaviorEntry.getSignature().getArgumentTypes().get(0);
79
80 // 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
81 if (firstArg.isClass() && firstArg.getClassEntry().equals(behaviorEntry.getClassEntry().getOuterClassEntry())) {
82 isNestedClassConstructor = true;
83 numArgs--;
84 }
85 }
86
87 for (int i = starti; i < starti + numArgs && i < table.tableLength(); i++) {
88 int argi = i - starti;
89 if (ctClass.isEnum())
90 argi += 2;
91 String argName = translator.translate(new ArgumentEntry(behaviorEntry, argi, ""));
92 if (argName == null) {
93 int argIndex = isNestedClassConstructor ? argi + 1 : argi;
94 if (ctClass.isEnum())
95 argIndex -= 2;
96 Type argType = behaviorEntry.getSignature().getArgumentTypes().get(argIndex);
97 // Unfortunately each of these have different name getters, so they have different code paths
98 if (argType.isPrimitive()) {
99 Type.Primitive argCls = argType.getPrimitive();
100 argName = "a" + argCls.name() + (argIndex + 1);
101 } else if (argType.isArray()) {
102 // List types would require this whole block again, so just go with aListx
103 argName = "aList" + (argIndex + 1);
104 } else if (argType.isClass()) {
105 ClassEntry argClsTrans = translator.translateEntry(argType.getClassEntry());
106 argName = "a" + argClsTrans.getSimpleName().replace("$", "") + (argIndex + 1);
107 } else {
108 argName = "a" + (argIndex + 1);
109 }
110 }
111 renameVariable(table, i, constants.addUtf8Info(argName));
112 }
113 }
114
115 // then rename the rest of the args, if any
116 for (int i = starti + numArgs; i < table.tableLength(); i++) {
117 int firstIndex = Math.min(table.index(starti + numArgs), table.index(i));
118 renameVariable(table, i, constants.addUtf8Info("v" + (table.index(i) - firstIndex + 1)));
119 }
120 }
121
122 private static void renameLVTT(LocalVariableTypeAttribute typeTable, LocalVariableAttribute table) {
123 // rename args to the same names as in the LVT
124 for (int i = 0; i < typeTable.tableLength(); i++) {
125 renameVariable(typeTable, i, getNameIndex(table, typeTable.index(i)));
126 }
127 }
128
129 private static void renameVariable(LocalVariableAttribute table, int i, int stringId) {
130 // based off of LocalVariableAttribute.nameIndex()
131 ByteArray.write16bit(stringId, table.get(), i * 10 + 6);
132 }
133
134 private static int getNameIndex(LocalVariableAttribute table, int index) {
135 for (int i = 0; i < table.tableLength(); i++) {
136 if (table.index(i) == index) {
137 return table.nameIndex(i);
138 }
139 }
140 return 0;
141 }
142}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.bytecode.translators;
13
14import cuchaz.enigma.bytecode.MethodParametersAttribute;
15import cuchaz.enigma.mapping.*;
16import javassist.CtBehavior;
17import javassist.CtClass;
18import javassist.bytecode.CodeAttribute;
19import javassist.bytecode.LocalVariableAttribute;
20
21import java.util.ArrayList;
22import java.util.List;
23
24public class MethodParameterTranslator {
25
26 public static void translate(Translator translator, CtClass c) {
27
28 // Procyon will read method arguments from the "MethodParameters" attribute, so write those
29 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
30
31 // if there's a local variable table here, don't write a MethodParameters attribute
32 // let the local variable writer deal with it instead
33 // procyon starts doing really weird things if we give it both attributes
34 CodeAttribute codeAttribute = behavior.getMethodInfo().getCodeAttribute();
35 if (codeAttribute != null && codeAttribute.getAttribute(LocalVariableAttribute.tag) != null) {
36 continue;
37 }
38
39 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
40
41 // get the number of arguments
42 Signature signature = behaviorEntry.getSignature();
43 if (signature == null) {
44 // static initializers have no signatures, or arguments
45 continue;
46 }
47 int numParams = signature.getArgumentTypes().size();
48 if (numParams <= 0) {
49 continue;
50 }
51
52 // get the list of argument names
53 List<String> names = new ArrayList<>(numParams);
54 for (int i = 0; i < numParams; i++) {
55 names.add(translator.translate(new ArgumentEntry(behaviorEntry, i, "")));
56 }
57
58 // save the mappings to the class
59 MethodParametersAttribute.updateClass(behavior.getMethodInfo(), names);
60 }
61 }
62}
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 @@
1package cuchaz.enigma.bytecode.translators;
2
3import cuchaz.enigma.mapping.Translator;
4import cuchaz.enigma.mapping.TypeDescriptor;
5import cuchaz.enigma.mapping.entry.ClassEntry;
6import cuchaz.enigma.mapping.entry.FieldEntry;
7import org.objectweb.asm.AnnotationVisitor;
8
9public class TranslationAnnotationVisitor extends AnnotationVisitor {
10 private final Translator translator;
11 private final ClassEntry annotationEntry;
12
13 public TranslationAnnotationVisitor(Translator translator, ClassEntry annotationEntry, int api, AnnotationVisitor av) {
14 super(api, av);
15 this.translator = translator;
16 this.annotationEntry = annotationEntry;
17 }
18
19 @Override
20 public void visit(String name, Object value) {
21 super.visit(name, translator.getTranslatedValue(value));
22 }
23
24 @Override
25 public AnnotationVisitor visitArray(String name) {
26 return this;
27 }
28
29 @Override
30 public AnnotationVisitor visitAnnotation(String name, String desc) {
31 TypeDescriptor type = new TypeDescriptor(desc);
32 FieldEntry annotationField = translator.getTranslatedField(new FieldEntry(annotationEntry, name, type));
33 return super.visitAnnotation(annotationField.getName(), annotationField.getDesc().toString());
34 }
35
36 @Override
37 public void visitEnum(String name, String desc, String value) {
38 TypeDescriptor type = new TypeDescriptor(desc);
39 FieldEntry annotationField = translator.getTranslatedField(new FieldEntry(annotationEntry, name, type));
40 FieldEntry enumField = translator.getTranslatedField(new FieldEntry(type.getTypeEntry(), value, type));
41 super.visitEnum(annotationField.getName(), annotationField.getDesc().toString(), enumField.getName());
42 }
43}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.bytecode.translators;
13
14import cuchaz.enigma.analysis.JarIndex;
15import cuchaz.enigma.bytecode.AccessFlags;
16import cuchaz.enigma.mapping.MethodDescriptor;
17import cuchaz.enigma.mapping.Signature;
18import cuchaz.enigma.mapping.Translator;
19import cuchaz.enigma.mapping.TypeDescriptor;
20import cuchaz.enigma.mapping.entry.*;
21import org.objectweb.asm.*;
22
23public class TranslationClassVisitor extends ClassVisitor {
24 private final Translator translator;
25 private final JarIndex jarIndex;
26 private final ReferencedEntryPool entryPool;
27
28 private ClassDefEntry obfClassEntry;
29 private Signature obfSignature;
30
31 public TranslationClassVisitor(Translator translator, JarIndex jarIndex, ReferencedEntryPool entryPool, int api, ClassVisitor cv) {
32 super(api, cv);
33 this.translator = translator;
34 this.jarIndex = jarIndex;
35 this.entryPool = entryPool;
36 }
37
38 @Override
39 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
40 obfSignature = Signature.createSignature(signature);
41 obfClassEntry = new ClassDefEntry(name, obfSignature, new AccessFlags(access));
42 ClassDefEntry translatedEntry = translator.getTranslatedClassDef(obfClassEntry);
43 ClassEntry superEntry = translator.getTranslatedClass(entryPool.getClass(superName));
44 String[] translatedInterfaces = new String[interfaces.length];
45 for (int i = 0; i < interfaces.length; i++) {
46 translatedInterfaces[i] = translator.getTranslatedClass(entryPool.getClass(interfaces[i])).getName();
47 }
48 super.visit(version, translatedEntry.getAccess().getFlags(), translatedEntry.getName(), translatedEntry.getSignature().toString(), superEntry.getName(), translatedInterfaces);
49 }
50
51 @Override
52 public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
53 FieldDefEntry entry = new FieldDefEntry(obfClassEntry, name, new TypeDescriptor(desc), Signature.createTypedSignature(signature), new AccessFlags(access));
54 FieldDefEntry translatedEntry = translator.getTranslatedFieldDef(entry);
55 FieldVisitor fv = super.visitField(translatedEntry.getAccess().getFlags(), translatedEntry.getName(), translatedEntry.getDesc().toString(), translatedEntry.getSignature().toString(), value);
56 return new TranslationFieldVisitor(translator, translatedEntry, api, fv);
57 }
58
59 @Override
60 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
61 MethodDefEntry entry = new MethodDefEntry(obfClassEntry, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access));
62 MethodDefEntry translatedEntry = translator.getTranslatedMethodDef(entry);
63 if (jarIndex.getBridgedMethod(entry) != null) {
64 translatedEntry.getAccess().setBridged();
65 }
66 String[] translatedExceptions = new String[exceptions.length];
67 for (int i = 0; i < exceptions.length; i++) {
68 translatedExceptions[i] = translator.getTranslatedClass(entryPool.getClass(exceptions[i])).getName();
69 }
70 MethodVisitor mv = super.visitMethod(translatedEntry.getAccess().getFlags(), translatedEntry.getName(), translatedEntry.getDesc().toString(), translatedEntry.getSignature().toString(), translatedExceptions);
71 return new TranslationMethodVisitor(translator, obfClassEntry, entry, api, mv);
72 }
73
74 @Override
75 public void visitInnerClass(String name, String outerName, String innerName, int access) {
76 ClassDefEntry translatedEntry = translator.getTranslatedClassDef(new ClassDefEntry(name, obfSignature, new AccessFlags(access)));
77 String translatedName = translatedEntry.getName();
78 int separatorIndex = translatedName.lastIndexOf("$");
79 String parentName = translatedName.substring(0, separatorIndex);
80 String childName = translatedName.substring(separatorIndex + 1);
81
82 ClassEntry outerEntry = translator.getTranslatedClass(entryPool.getClass(parentName));
83
84 // 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
85 String translatedOuterName = outerName != null ? outerEntry.getName() : null;
86 String translatedInnerName = innerName != null ? childName : null;
87 super.visitInnerClass(translatedName, translatedOuterName, translatedInnerName, translatedEntry.getAccess().getFlags());
88 }
89
90 @Override
91 public void visitOuterClass(String owner, String name, String desc) {
92 if (desc != null) {
93 MethodEntry translatedEntry = translator.getTranslatedMethod(new MethodEntry(new ClassEntry(owner), name, new MethodDescriptor(desc)));
94 super.visitOuterClass(translatedEntry.getClassName(), translatedEntry.getName(), translatedEntry.getDesc().toString());
95 } else {
96 super.visitOuterClass(owner, name, desc);
97 }
98 }
99
100 @Override
101 public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
102 TypeDescriptor translatedDesc = translator.getTranslatedTypeDesc(new TypeDescriptor(desc));
103 AnnotationVisitor av = super.visitAnnotation(translatedDesc.toString(), visible);
104 return new TranslationAnnotationVisitor(translator, translatedDesc.getTypeEntry(), api, av);
105 }
106
107 @Override
108 public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {
109 TypeDescriptor translatedDesc = translator.getTranslatedTypeDesc(new TypeDescriptor(desc));
110 AnnotationVisitor av = super.visitTypeAnnotation(typeRef, typePath, translatedDesc.toString(), visible);
111 return new TranslationAnnotationVisitor(translator, translatedDesc.getTypeEntry(), api, av);
112 }
113}
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 @@
1package cuchaz.enigma.bytecode.translators;
2
3import cuchaz.enigma.mapping.Translator;
4import cuchaz.enigma.mapping.TypeDescriptor;
5import cuchaz.enigma.mapping.entry.FieldDefEntry;
6import org.objectweb.asm.AnnotationVisitor;
7import org.objectweb.asm.FieldVisitor;
8import org.objectweb.asm.TypePath;
9
10public class TranslationFieldVisitor extends FieldVisitor {
11 private final FieldDefEntry fieldEntry;
12 private final Translator translator;
13
14 public TranslationFieldVisitor(Translator translator, FieldDefEntry fieldEntry, int api, FieldVisitor fv) {
15 super(api, fv);
16 this.translator = translator;
17 this.fieldEntry = fieldEntry;
18 }
19
20 @Override
21 public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
22 TypeDescriptor typeDesc = translator.getTranslatedTypeDesc(new TypeDescriptor(desc));
23 AnnotationVisitor av = super.visitAnnotation(typeDesc.toString(), visible);
24 return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av);
25 }
26
27 @Override
28 public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {
29 TypeDescriptor typeDesc = translator.getTranslatedTypeDesc(new TypeDescriptor(desc));
30 AnnotationVisitor av = super.visitAnnotation(typeDesc.toString(), visible);
31 return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av);
32 }
33}
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 @@
1package cuchaz.enigma.bytecode.translators;
2
3import cuchaz.enigma.mapping.MethodDescriptor;
4import cuchaz.enigma.mapping.Signature;
5import cuchaz.enigma.mapping.Translator;
6import cuchaz.enigma.mapping.TypeDescriptor;
7import cuchaz.enigma.mapping.entry.*;
8import org.objectweb.asm.*;
9
10import java.util.List;
11import java.util.Locale;
12
13public class TranslationMethodVisitor extends MethodVisitor {
14 private final ClassDefEntry ownerEntry;
15 private final MethodDefEntry methodEntry;
16 private final Translator translator;
17
18 private boolean hasParameterMeta;
19
20 public TranslationMethodVisitor(Translator translator, ClassDefEntry ownerEntry, MethodDefEntry methodEntry, int api, MethodVisitor mv) {
21 super(api, mv);
22 this.translator = translator;
23 this.ownerEntry = ownerEntry;
24 this.methodEntry = methodEntry;
25 }
26
27 @Override
28 public void visitFieldInsn(int opcode, String owner, String name, String desc) {
29 FieldEntry entry = new FieldEntry(new ClassEntry(owner), name, new TypeDescriptor(desc));
30 FieldEntry translatedEntry = translator.getTranslatedField(entry);
31 super.visitFieldInsn(opcode, translatedEntry.getClassName(), translatedEntry.getName(), translatedEntry.getDesc().toString());
32 }
33
34 @Override
35 public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
36 MethodEntry entry = new MethodEntry(new ClassEntry(owner), name, new MethodDescriptor(desc));
37 MethodEntry translatedEntry = translator.getTranslatedMethod(entry);
38 super.visitMethodInsn(opcode, translatedEntry.getClassName(), translatedEntry.getName(), translatedEntry.getDesc().toString(), itf);
39 }
40
41 @Override
42 public void visitFrame(int type, int localCount, Object[] locals, int stackCount, Object[] stack) {
43 Object[] translatedLocals = this.getTranslatedFrame(locals, localCount);
44 Object[] translatedStack = this.getTranslatedFrame(stack, stackCount);
45 super.visitFrame(type, localCount, translatedLocals, stackCount, translatedStack);
46 }
47
48 private Object[] getTranslatedFrame(Object[] array, int count) {
49 if (array == null) {
50 return null;
51 }
52 for (int i = 0; i < count; i++) {
53 Object object = array[i];
54 if (object instanceof String) {
55 String type = (String) object;
56 array[i] = translator.getTranslatedClass(new ClassEntry(type)).getName();
57 }
58 }
59 return array;
60 }
61
62 @Override
63 public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
64 TypeDescriptor typeDesc = translator.getTranslatedTypeDesc(new TypeDescriptor(desc));
65 AnnotationVisitor av = super.visitAnnotation(typeDesc.toString(), visible);
66 return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av);
67 }
68
69 @Override
70 public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
71 TypeDescriptor typeDesc = translator.getTranslatedTypeDesc(new TypeDescriptor(desc));
72 AnnotationVisitor av = super.visitParameterAnnotation(parameter, typeDesc.toString(), visible);
73 return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av);
74 }
75
76 @Override
77 public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {
78 TypeDescriptor typeDesc = translator.getTranslatedTypeDesc(new TypeDescriptor(desc));
79 AnnotationVisitor av = super.visitTypeAnnotation(typeRef, typePath, typeDesc.toString(), visible);
80 return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av);
81 }
82
83 @Override
84 public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
85 hasParameterMeta = true;
86
87 String translatedSignature = translator.getTranslatedSignature(Signature.createTypedSignature(signature)).toString();
88
89 int offset = methodEntry.getVariableOffset(ownerEntry);
90
91 int offsetIndex = index - offset;
92 if (offsetIndex >= 0) {
93 LocalVariableDefEntry entry = new LocalVariableDefEntry(methodEntry, offsetIndex, name, new TypeDescriptor(desc));
94 LocalVariableDefEntry translatedEntry = translator.getTranslatedVariableDef(entry);
95 String translatedName = translatedEntry.getName();
96
97 // TODO: Better name inference
98 if (translatedName.equals(entry.getName())) {
99 boolean argument = offsetIndex < methodEntry.getDesc().getArgumentDescs().size();
100 translatedName = inferName(argument, offsetIndex, translatedEntry.getDesc());
101 }
102
103 super.visitLocalVariable(translatedName, translatedEntry.getDesc().toString(), translatedSignature, start, end, index);
104 } else {
105 // Handle "this" variable
106 TypeDescriptor translatedDesc = translator.getTranslatedTypeDesc(new TypeDescriptor(desc));
107 super.visitLocalVariable(name, translatedDesc.toString(), translatedSignature, start, end, index);
108 }
109 }
110
111 @Override
112 public void visitTypeInsn(int opcode, String type) {
113 ClassEntry translatedEntry = translator.getTranslatedClass(new ClassEntry(type));
114 super.visitTypeInsn(opcode, translatedEntry.getName());
115 }
116
117 @Override
118 public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
119 MethodDescriptor translatedMethodDesc = translator.getTranslatedMethodDesc(new MethodDescriptor(desc));
120 Object[] translatedBsmArgs = new Object[bsmArgs.length];
121 for (int i = 0; i < bsmArgs.length; i++) {
122 translatedBsmArgs[i] = translator.getTranslatedValue(bsmArgs[i]);
123 }
124 super.visitInvokeDynamicInsn(name, translatedMethodDesc.toString(), translator.getTranslatedHandle(bsm), translatedBsmArgs);
125 }
126
127 @Override
128 public void visitLdcInsn(Object cst) {
129 super.visitLdcInsn(translator.getTranslatedValue(cst));
130 }
131
132 @Override
133 public void visitMultiANewArrayInsn(String desc, int dims) {
134 super.visitMultiANewArrayInsn(translator.getTranslatedTypeDesc(new TypeDescriptor(desc)).toString(), dims);
135 }
136
137 @Override
138 public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
139 if (type != null) {
140 ClassEntry translatedEntry = translator.getTranslatedClass(new ClassEntry(type));
141 super.visitTryCatchBlock(start, end, handler, translatedEntry.getName());
142 } else {
143 super.visitTryCatchBlock(start, end, handler, type);
144 }
145 }
146
147 @Override
148 public void visitEnd() {
149 // If we didn't receive any parameter metadata, generate it
150 if (!hasParameterMeta) {
151 List<TypeDescriptor> arguments = methodEntry.getDesc().getArgumentDescs();
152 for (int index = 0; index < arguments.size(); index++) {
153 LocalVariableEntry entry = new LocalVariableEntry(methodEntry, index, "");
154 LocalVariableEntry translatedEntry = translator.getTranslatedVariable(entry);
155 String translatedName = translatedEntry.getName();
156 if (translatedName.equals(entry.getName())) {
157 super.visitParameter(inferName(true, index, arguments.get(index)), 0);
158 } else {
159 super.visitParameter(translatedName, 0);
160 }
161 }
162 }
163 super.visitEnd();
164 }
165
166 private String inferName(boolean argument, int argumentIndex, TypeDescriptor desc) {
167 String translatedName;
168 int nameIndex = argumentIndex + 1;
169 StringBuilder nameBuilder = new StringBuilder(argument ? "a" : "v");
170 // Unfortunately each of these have different name getters, so they have different code paths
171 if (desc.isPrimitive()) {
172 TypeDescriptor.Primitive argCls = desc.getPrimitive();
173 nameBuilder.append(argCls.name());
174 } else if (desc.isArray()) {
175 // List types would require this whole block again, so just go with aListx
176 nameBuilder.append("Arr");
177 } else if (desc.isType()) {
178 String typeName = desc.getTypeEntry().getSimpleName().replace("$", "");
179 typeName = typeName.substring(0, 1).toUpperCase(Locale.ROOT) + typeName.substring(1);
180 nameBuilder.append(typeName);
181 } else {
182 System.err.println("Encountered invalid argument type descriptor " + desc.toString());
183 nameBuilder.append("Unk");
184 }
185 if (!argument || methodEntry.getDesc().getArgumentDescs().size() > 1) {
186 nameBuilder.append(nameIndex);
187 }
188 translatedName = nameBuilder.toString();
189 return translatedName;
190 }
191}
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 @@
1package cuchaz.enigma.bytecode.translators;
2
3import org.objectweb.asm.Opcodes;
4import org.objectweb.asm.signature.SignatureVisitor;
5
6import java.util.Stack;
7import java.util.function.Function;
8
9public class TranslationSignatureVisitor extends SignatureVisitor {
10 private final Function<String, String> remapper;
11
12 private final SignatureVisitor sv;
13 private final Stack<String> classStack = new Stack<>();
14
15 public TranslationSignatureVisitor(Function<String, String> remapper, SignatureVisitor sv) {
16 super(Opcodes.ASM5);
17 this.remapper = remapper;
18 this.sv = sv;
19 }
20
21 @Override
22 public void visitClassType(String name) {
23 classStack.push(name);
24 String translatedEntry = this.remapper.apply(name);
25 this.sv.visitClassType(translatedEntry);
26 }
27
28 @Override
29 public void visitInnerClassType(String name) {
30 String lastClass = classStack.pop();
31 if (!name.startsWith(lastClass+"$")){//todo see if there's a way to base this on whether there were type params or not
32 name = lastClass+"$"+name;
33 }
34 String translatedEntry = this.remapper.apply(name);
35 if (translatedEntry.contains("/")){
36 translatedEntry = translatedEntry.substring(translatedEntry.lastIndexOf("/")+1);
37 }
38 if (translatedEntry.contains("$")){
39 translatedEntry = translatedEntry.substring(translatedEntry.lastIndexOf("$")+1);
40 }
41 this.sv.visitInnerClassType(translatedEntry);
42 }
43
44 @Override
45 public void visitFormalTypeParameter(String name) {
46 this.sv.visitFormalTypeParameter(name);
47 }
48
49 @Override
50 public void visitTypeVariable(String name) {
51 this.sv.visitTypeVariable(name);
52 }
53
54 @Override
55 public SignatureVisitor visitArrayType() {
56 this.sv.visitArrayType();
57 return this;
58 }
59
60 @Override
61 public void visitBaseType(char descriptor) {
62 this.sv.visitBaseType(descriptor);
63 }
64
65 @Override
66 public SignatureVisitor visitClassBound() {
67 this.sv.visitClassBound();
68 return this;
69 }
70
71 @Override
72 public SignatureVisitor visitExceptionType() {
73 this.sv.visitExceptionType();
74 return this;
75 }
76
77 @Override
78 public SignatureVisitor visitInterface() {
79 this.sv.visitInterface();
80 return this;
81 }
82
83 @Override
84 public SignatureVisitor visitInterfaceBound() {
85 this.sv.visitInterfaceBound();
86 return this;
87 }
88
89 @Override
90 public SignatureVisitor visitParameterType() {
91 this.sv.visitParameterType();
92 return this;
93 }
94
95 @Override
96 public SignatureVisitor visitReturnType() {
97 this.sv.visitReturnType();
98 return this;
99 }
100
101 @Override
102 public SignatureVisitor visitSuperclass() {
103 this.sv.visitSuperclass();
104 return this;
105 }
106
107 @Override
108 public void visitTypeArgument() {
109 this.sv.visitTypeArgument();
110 }
111
112 @Override
113 public SignatureVisitor visitTypeArgument(char wildcard) {
114 this.sv.visitTypeArgument(wildcard);
115 return this;
116 }
117
118 @Override
119 public void visitEnd() {
120 this.sv.visitEnd();
121 if (!classStack.empty())
122 classStack.pop();
123 }
124
125 @Override
126 public String toString() {
127 return this.sv.toString();
128 }
129}
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;
17import com.google.common.collect.Multimap; 17import com.google.common.collect.Multimap;
18import cuchaz.enigma.gui.node.ClassSelectorClassNode; 18import cuchaz.enigma.gui.node.ClassSelectorClassNode;
19import cuchaz.enigma.gui.node.ClassSelectorPackageNode; 19import cuchaz.enigma.gui.node.ClassSelectorPackageNode;
20import cuchaz.enigma.mapping.ClassEntry; 20import cuchaz.enigma.mapping.entry.ClassEntry;
21import cuchaz.enigma.throwables.IllegalNameException; 21import cuchaz.enigma.throwables.IllegalNameException;
22 22
23import javax.swing.*; 23import javax.swing.*;
@@ -328,8 +328,12 @@ public class ClassSelector extends JTree {
328 } 328 }
329 329
330 public ClassSelectorPackageNode getPackageNode(ClassEntry entry) { 330 public ClassSelectorPackageNode getPackageNode(ClassEntry entry) {
331 String packageName = entry.getPackageName();
332 if (packageName == null){
333 packageName = "(none)";
334 }
331 for (ClassSelectorPackageNode packageNode : packageNodes()) { 335 for (ClassSelectorPackageNode packageNode : packageNodes()) {
332 if (packageNode.getPackageName().equals(entry.getPackageName())) { 336 if (packageNode.getPackageName().equals(packageName)) {
333 return packageNode; 337 return packageNode;
334 } 338 }
335 } 339 }
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;
17import cuchaz.enigma.analysis.SourceIndex; 17import cuchaz.enigma.analysis.SourceIndex;
18import cuchaz.enigma.analysis.Token; 18import cuchaz.enigma.analysis.Token;
19import cuchaz.enigma.gui.highlight.SelectionHighlightPainter; 19import cuchaz.enigma.gui.highlight.SelectionHighlightPainter;
20import cuchaz.enigma.mapping.ClassEntry; 20import cuchaz.enigma.mapping.entry.ClassEntry;
21import cuchaz.enigma.mapping.Entry; 21import cuchaz.enigma.mapping.entry.Entry;
22import de.sciss.syntaxpane.DefaultSyntaxKit; 22import de.sciss.syntaxpane.DefaultSyntaxKit;
23 23
24import javax.swing.*; 24import javax.swing.*;
@@ -162,7 +162,7 @@ public class CodeReader extends JEditorPane {
162 // couldn't find the class declaration token, might be an anonymous class 162 // couldn't find the class declaration token, might be an anonymous class
163 // look for any declaration in that class instead 163 // look for any declaration in that class instead
164 for (Entry entry : sourceIndex.declarations()) { 164 for (Entry entry : sourceIndex.declarations()) {
165 if (entry.getClassEntry().equals(classEntry)) { 165 if (entry.getOwnerClassEntry().equals(classEntry)) {
166 token = sourceIndex.getDeclarationToken(entry); 166 token = sourceIndex.getDeclarationToken(entry);
167 break; 167 break;
168 } 168 }
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;
32import cuchaz.enigma.gui.panels.PanelIdentifier; 32import cuchaz.enigma.gui.panels.PanelIdentifier;
33import cuchaz.enigma.gui.panels.PanelObf; 33import cuchaz.enigma.gui.panels.PanelObf;
34import cuchaz.enigma.mapping.*; 34import cuchaz.enigma.mapping.*;
35import cuchaz.enigma.mapping.entry.*;
35import cuchaz.enigma.throwables.IllegalNameException; 36import cuchaz.enigma.throwables.IllegalNameException;
36import cuchaz.enigma.utils.Utils; 37import cuchaz.enigma.utils.Utils;
37import de.sciss.syntaxpane.DefaultSyntaxKit; 38import de.sciss.syntaxpane.DefaultSyntaxKit;
38import javassist.bytecode.Descriptor;
39 39
40import javax.swing.*; 40import javax.swing.*;
41import javax.swing.text.BadLocationException; 41import javax.swing.text.BadLocationException;
@@ -48,8 +48,10 @@ import java.awt.*;
48import java.awt.event.*; 48import java.awt.event.*;
49import java.io.File; 49import java.io.File;
50import java.io.IOException; 50import java.io.IOException;
51import java.util.*; 51import java.util.Collection;
52import java.util.Collections;
52import java.util.List; 53import java.util.List;
54import java.util.Vector;
53import java.util.function.Function; 55import java.util.function.Function;
54 56
55public class Gui { 57public class Gui {
@@ -438,14 +440,10 @@ public class Gui {
438 showFieldEntry((FieldEntry) this.reference.entry); 440 showFieldEntry((FieldEntry) this.reference.entry);
439 } else if (this.reference.entry instanceof MethodEntry) { 441 } else if (this.reference.entry instanceof MethodEntry) {
440 showMethodEntry((MethodEntry) this.reference.entry); 442 showMethodEntry((MethodEntry) this.reference.entry);
441 } else if (this.reference.entry instanceof ConstructorEntry) {
442 showConstructorEntry((ConstructorEntry) this.reference.entry);
443 } else if (this.reference.entry instanceof ArgumentEntry) {
444 showArgumentEntry((ArgumentEntry) this.reference.entry);
445 } else if (this.reference.entry instanceof LocalVariableEntry) { 443 } else if (this.reference.entry instanceof LocalVariableEntry) {
446 showLocalVariableEntry((LocalVariableEntry) this.reference.entry); 444 showLocalVariableEntry((LocalVariableEntry) this.reference.entry);
447 } else { 445 } else {
448 throw new Error("Unknown entry type: " + this.reference.entry.getClass().getName()); 446 throw new Error("Unknown entry desc: " + this.reference.entry.getClass().getName());
449 } 447 }
450 448
451 redraw(); 449 redraw();
@@ -453,10 +451,9 @@ public class Gui {
453 451
454 private void showLocalVariableEntry(LocalVariableEntry entry) { 452 private void showLocalVariableEntry(LocalVariableEntry entry) {
455 addNameValue(infoPanel, "Variable", entry.getName()); 453 addNameValue(infoPanel, "Variable", entry.getName());
456 addNameValue(infoPanel, "Class", entry.getClassEntry().getName()); 454 addNameValue(infoPanel, "Class", entry.getOwnerClassEntry().getName());
457 addNameValue(infoPanel, "Method", entry.getBehaviorEntry().getName()); 455 addNameValue(infoPanel, "Method", entry.getOwnerEntry().getName());
458 addNameValue(infoPanel, "Index", Integer.toString(entry.getIndex())); 456 addNameValue(infoPanel, "Index", Integer.toString(entry.getIndex()));
459 addNameValue(infoPanel, "Type", entry.getType().toString());
460 } 457 }
461 458
462 private void showClassEntry(ClassEntry entry) { 459 private void showClassEntry(ClassEntry entry) {
@@ -466,32 +463,20 @@ public class Gui {
466 463
467 private void showFieldEntry(FieldEntry entry) { 464 private void showFieldEntry(FieldEntry entry) {
468 addNameValue(infoPanel, "Field", entry.getName()); 465 addNameValue(infoPanel, "Field", entry.getName());
469 addNameValue(infoPanel, "Class", entry.getClassEntry().getName()); 466 addNameValue(infoPanel, "Class", entry.getOwnerClassEntry().getName());
470 addNameValue(infoPanel, "Type", entry.getType().toString()); 467 addNameValue(infoPanel, "TypeDescriptor", entry.getDesc().toString());
471 addModifierComboBox(infoPanel, "Modifier", entry); 468 addModifierComboBox(infoPanel, "Modifier", entry);
472 } 469 }
473 470
474 private void showMethodEntry(MethodEntry entry) { 471 private void showMethodEntry(MethodEntry entry) {
475 addNameValue(infoPanel, "Method", entry.getName()); 472 if (entry.isConstructor()) {
476 addNameValue(infoPanel, "Class", entry.getClassEntry().getName()); 473 addNameValue(infoPanel, "Constructor", entry.getOwnerClassEntry().getName());
477 addNameValue(infoPanel, "Signature", entry.getSignature().toString()); 474 } else {
478 addModifierComboBox(infoPanel, "Modifier", entry); 475 addNameValue(infoPanel, "Method", entry.getName());
479 476 addNameValue(infoPanel, "Class", entry.getOwnerClassEntry().getName());
480 }
481
482 private void showConstructorEntry(ConstructorEntry entry) {
483 addNameValue(infoPanel, "Constructor", entry.getClassEntry().getName());
484 if (!entry.isStatic()) {
485 addNameValue(infoPanel, "Signature", entry.getSignature().toString());
486 addModifierComboBox(infoPanel, "Modifier", entry);
487 } 477 }
488 } 478 addNameValue(infoPanel, "MethodDescriptor", entry.getDesc().toString());
489 479 addModifierComboBox(infoPanel, "Modifier", entry);
490 private void showArgumentEntry(ArgumentEntry entry) {
491 addNameValue(infoPanel, "Argument", entry.getName());
492 addNameValue(infoPanel, "Class", entry.getClassEntry().getName());
493 addNameValue(infoPanel, "Method", entry.getBehaviorEntry().getName());
494 addNameValue(infoPanel, "Index", Integer.toString(entry.getIndex()));
495 } 480 }
496 481
497 private void addNameValue(JPanel container, String name, String value) { 482 private void addNameValue(JPanel container, String name, String value) {
@@ -532,8 +517,8 @@ public class Gui {
532 reference = this.controller.getDeobfReference(token); 517 reference = this.controller.getDeobfReference(token);
533 boolean isClassEntry = isToken && reference.entry instanceof ClassEntry; 518 boolean isClassEntry = isToken && reference.entry instanceof ClassEntry;
534 boolean isFieldEntry = isToken && reference.entry instanceof FieldEntry; 519 boolean isFieldEntry = isToken && reference.entry instanceof FieldEntry;
535 boolean isMethodEntry = isToken && reference.entry instanceof MethodEntry; 520 boolean isMethodEntry = isToken && reference.entry instanceof MethodEntry && !((MethodEntry) reference.entry).isConstructor();
536 boolean isConstructorEntry = isToken && reference.entry instanceof ConstructorEntry; 521 boolean isConstructorEntry = isToken && reference.entry instanceof MethodEntry && ((MethodEntry) reference.entry).isConstructor();
537 boolean isInJar = isToken && this.controller.entryIsInJar(reference.entry); 522 boolean isInJar = isToken && this.controller.entryIsInJar(reference.entry);
538 boolean isRenameable = isToken && this.controller.referenceIsRenameable(reference); 523 boolean isRenameable = isToken && this.controller.referenceIsRenameable(reference);
539 524
@@ -710,16 +695,13 @@ public class Gui {
710 if (reference.entry instanceof ClassEntry) { 695 if (reference.entry instanceof ClassEntry) {
711 // look for calls to the default constructor 696 // look for calls to the default constructor
712 // TODO: get a list of all the constructors and find calls to all of them 697 // TODO: get a list of all the constructors and find calls to all of them
713 BehaviorReferenceTreeNode node = this.controller.getMethodReferences(new ConstructorEntry((ClassEntry) reference.entry, new Signature("()V"))); 698 MethodReferenceTreeNode node = this.controller.getMethodReferences(new MethodEntry((ClassEntry) reference.entry, "<init>", new MethodDescriptor("()V")));
714 callsTree.setModel(new DefaultTreeModel(node)); 699 callsTree.setModel(new DefaultTreeModel(node));
715 } else if (reference.entry instanceof FieldEntry) { 700 } else if (reference.entry instanceof FieldEntry) {
716 FieldReferenceTreeNode node = this.controller.getFieldReferences((FieldEntry) reference.entry); 701 FieldReferenceTreeNode node = this.controller.getFieldReferences((FieldEntry) reference.entry);
717 callsTree.setModel(new DefaultTreeModel(node)); 702 callsTree.setModel(new DefaultTreeModel(node));
718 } else if (reference.entry instanceof MethodEntry) { 703 } else if (reference.entry instanceof MethodEntry) {
719 BehaviorReferenceTreeNode node = this.controller.getMethodReferences((MethodEntry) reference.entry); 704 MethodReferenceTreeNode node = this.controller.getMethodReferences((MethodEntry) reference.entry);
720 callsTree.setModel(new DefaultTreeModel(node));
721 } else if (reference.entry instanceof ConstructorEntry) {
722 BehaviorReferenceTreeNode node = this.controller.getMethodReferences((ConstructorEntry) reference.entry);
723 callsTree.setModel(new DefaultTreeModel(node)); 705 callsTree.setModel(new DefaultTreeModel(node));
724 } 706 }
725 707
@@ -790,7 +772,6 @@ public class Gui {
790 // package rename 772 // package rename
791 if (data instanceof String) { 773 if (data instanceof String) {
792 for (int i = 0; i < node.getChildCount(); i++) { 774 for (int i = 0; i < node.getChildCount(); i++) {
793 data = Descriptor.toJvmName((String) data);
794 DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) node.getChildAt(i); 775 DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) node.getChildAt(i);
795 ClassEntry prevDataChild = (ClassEntry) childNode.getUserObject(); 776 ClassEntry prevDataChild = (ClassEntry) childNode.getUserObject();
796 ClassEntry dataChild = new ClassEntry(data + "/" + prevDataChild.getSimpleName()); 777 ClassEntry dataChild = new ClassEntry(data + "/" + prevDataChild.getSimpleName());
@@ -807,15 +788,15 @@ public class Gui {
807 } 788 }
808 789
809 public void moveClassTree(EntryReference<Entry, Entry> deobfReference, String newName) { 790 public void moveClassTree(EntryReference<Entry, Entry> deobfReference, String newName) {
810 String oldEntry = deobfReference.entry.getClassEntry().getPackageName(); 791 String oldEntry = deobfReference.entry.getOwnerClassEntry().getPackageName();
811 String newEntry = new ClassEntry(Descriptor.toJvmName(newName)).getPackageName(); 792 String newEntry = new ClassEntry(newName).getPackageName();
812 moveClassTree(deobfReference, newName, oldEntry == null, 793 moveClassTree(deobfReference, newName, oldEntry == null,
813 newEntry == null); 794 newEntry == null);
814 } 795 }
815 796
816 public void moveClassTree(EntryReference<Entry, Entry> deobfReference, String newName, boolean isOldOb, boolean isNewOb) { 797 public void moveClassTree(EntryReference<Entry, Entry> deobfReference, String newName, boolean isOldOb, boolean isNewOb) {
817 ClassEntry oldEntry = deobfReference.entry.getClassEntry(); 798 ClassEntry oldEntry = deobfReference.entry.getOwnerClassEntry();
818 ClassEntry newEntry = new ClassEntry(Descriptor.toJvmName(newName)); 799 ClassEntry newEntry = new ClassEntry(newName);
819 800
820 // Ob -> deob 801 // Ob -> deob
821 if (isOldOb && !isNewOb) { 802 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;
18import cuchaz.enigma.analysis.*; 18import cuchaz.enigma.analysis.*;
19import cuchaz.enigma.gui.dialog.ProgressDialog; 19import cuchaz.enigma.gui.dialog.ProgressDialog;
20import cuchaz.enigma.mapping.*; 20import cuchaz.enigma.mapping.*;
21import cuchaz.enigma.mapping.entry.ClassEntry;
22import cuchaz.enigma.mapping.entry.Entry;
23import cuchaz.enigma.mapping.entry.FieldEntry;
24import cuchaz.enigma.mapping.entry.MethodEntry;
21import cuchaz.enigma.throwables.MappingParseException; 25import cuchaz.enigma.throwables.MappingParseException;
22import cuchaz.enigma.utils.ReadableToken; 26import cuchaz.enigma.utils.ReadableToken;
23 27
@@ -51,10 +55,10 @@ public class GuiController {
51 return this.isDirty; 55 return this.isDirty;
52 } 56 }
53 57
54 public void openJar(final JarFile jar) { 58 public void openJar(final JarFile jar) throws IOException {
55 this.gui.onStartOpenJar(); 59 this.gui.onStartOpenJar();
56 this.deobfuscator = new Deobfuscator(jar); 60 this.deobfuscator = new Deobfuscator(jar);
57 this.gui.onFinishOpenJar(this.deobfuscator.getJarName()); 61 this.gui.onFinishOpenJar(jar.getName());
58 refreshClasses(); 62 refreshClasses();
59 } 63 }
60 64
@@ -161,24 +165,24 @@ public class GuiController {
161 165
162 public ClassInheritanceTreeNode getClassInheritance(ClassEntry deobfClassEntry) { 166 public ClassInheritanceTreeNode getClassInheritance(ClassEntry deobfClassEntry) {
163 ClassEntry obfClassEntry = this.deobfuscator.obfuscateEntry(deobfClassEntry); 167 ClassEntry obfClassEntry = this.deobfuscator.obfuscateEntry(deobfClassEntry);
164 ClassInheritanceTreeNode rootNode = this.deobfuscator.getJarIndex().getClassInheritance(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfClassEntry); 168 ClassInheritanceTreeNode rootNode = this.deobfuscator.getJarIndex().getClassInheritance(this.deobfuscator.getTranslator(TranslationDirection.DEOBFUSCATING), obfClassEntry);
165 return ClassInheritanceTreeNode.findNode(rootNode, obfClassEntry); 169 return ClassInheritanceTreeNode.findNode(rootNode, obfClassEntry);
166 } 170 }
167 171
168 public ClassImplementationsTreeNode getClassImplementations(ClassEntry deobfClassEntry) { 172 public ClassImplementationsTreeNode getClassImplementations(ClassEntry deobfClassEntry) {
169 ClassEntry obfClassEntry = this.deobfuscator.obfuscateEntry(deobfClassEntry); 173 ClassEntry obfClassEntry = this.deobfuscator.obfuscateEntry(deobfClassEntry);
170 return this.deobfuscator.getJarIndex().getClassImplementations(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfClassEntry); 174 return this.deobfuscator.getJarIndex().getClassImplementations(this.deobfuscator.getTranslator(TranslationDirection.DEOBFUSCATING), obfClassEntry);
171 } 175 }
172 176
173 public MethodInheritanceTreeNode getMethodInheritance(MethodEntry deobfMethodEntry) { 177 public MethodInheritanceTreeNode getMethodInheritance(MethodEntry deobfMethodEntry) {
174 MethodEntry obfMethodEntry = this.deobfuscator.obfuscateEntry(deobfMethodEntry); 178 MethodEntry obfMethodEntry = this.deobfuscator.obfuscateEntry(deobfMethodEntry);
175 MethodInheritanceTreeNode rootNode = this.deobfuscator.getJarIndex().getMethodInheritance(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfMethodEntry); 179 MethodInheritanceTreeNode rootNode = this.deobfuscator.getJarIndex().getMethodInheritance(this.deobfuscator.getTranslator(TranslationDirection.DEOBFUSCATING), obfMethodEntry);
176 return MethodInheritanceTreeNode.findNode(rootNode, obfMethodEntry); 180 return MethodInheritanceTreeNode.findNode(rootNode, obfMethodEntry);
177 } 181 }
178 182
179 public MethodImplementationsTreeNode getMethodImplementations(MethodEntry deobfMethodEntry) { 183 public MethodImplementationsTreeNode getMethodImplementations(MethodEntry deobfMethodEntry) {
180 MethodEntry obfMethodEntry = this.deobfuscator.obfuscateEntry(deobfMethodEntry); 184 MethodEntry obfMethodEntry = this.deobfuscator.obfuscateEntry(deobfMethodEntry);
181 List<MethodImplementationsTreeNode> rootNodes = this.deobfuscator.getJarIndex().getMethodImplementations(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfMethodEntry); 185 List<MethodImplementationsTreeNode> rootNodes = this.deobfuscator.getJarIndex().getMethodImplementations(this.deobfuscator.getTranslator(TranslationDirection.DEOBFUSCATING), obfMethodEntry);
182 if (rootNodes.isEmpty()) { 186 if (rootNodes.isEmpty()) {
183 return null; 187 return null;
184 } 188 }
@@ -190,14 +194,14 @@ public class GuiController {
190 194
191 public FieldReferenceTreeNode getFieldReferences(FieldEntry deobfFieldEntry) { 195 public FieldReferenceTreeNode getFieldReferences(FieldEntry deobfFieldEntry) {
192 FieldEntry obfFieldEntry = this.deobfuscator.obfuscateEntry(deobfFieldEntry); 196 FieldEntry obfFieldEntry = this.deobfuscator.obfuscateEntry(deobfFieldEntry);
193 FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfFieldEntry); 197 FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(this.deobfuscator.getTranslator(TranslationDirection.DEOBFUSCATING), obfFieldEntry);
194 rootNode.load(this.deobfuscator.getJarIndex(), true); 198 rootNode.load(this.deobfuscator.getJarIndex(), true);
195 return rootNode; 199 return rootNode;
196 } 200 }
197 201
198 public BehaviorReferenceTreeNode getMethodReferences(BehaviorEntry deobfBehaviorEntry) { 202 public MethodReferenceTreeNode getMethodReferences(MethodEntry deobfMethodEntry) {
199 BehaviorEntry obfBehaviorEntry = this.deobfuscator.obfuscateEntry(deobfBehaviorEntry); 203 MethodEntry obfMethodEntry = this.deobfuscator.obfuscateEntry(deobfMethodEntry);
200 BehaviorReferenceTreeNode rootNode = new BehaviorReferenceTreeNode(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfBehaviorEntry); 204 MethodReferenceTreeNode rootNode = new MethodReferenceTreeNode(this.deobfuscator.getTranslator(TranslationDirection.DEOBFUSCATING), obfMethodEntry);
201 rootNode.load(this.deobfuscator.getJarIndex(), true); 205 rootNode.load(this.deobfuscator.getJarIndex(), true);
202 return rootNode; 206 return rootNode;
203 } 207 }
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;
14import javax.swing.*; 14import javax.swing.*;
15import java.awt.*; 15import java.awt.*;
16import java.awt.event.ActionListener; 16import java.awt.event.ActionListener;
17import java.util.Arrays;
18 17
19public class GuiTricks { 18public class GuiTricks {
20 19
@@ -27,7 +26,7 @@ public class GuiTricks {
27 public static void deactivateButton(JButton button) { 26 public static void deactivateButton(JButton button) {
28 button.setEnabled(false); 27 button.setEnabled(false);
29 button.setText(""); 28 button.setText("");
30 for (ActionListener listener : Arrays.asList(button.getActionListeners())) { 29 for (ActionListener listener : button.getActionListeners()) {
31 button.removeActionListener(listener); 30 button.removeActionListener(listener);
32 } 31 }
33 } 32 }
@@ -35,7 +34,7 @@ public class GuiTricks {
35 public static void activateButton(JButton button, String text, ActionListener newListener) { 34 public static void activateButton(JButton button, String text, ActionListener newListener) {
36 button.setText(text); 35 button.setText(text);
37 button.setEnabled(true); 36 button.setEnabled(true);
38 for (ActionListener listener : Arrays.asList(button.getActionListeners())) { 37 for (ActionListener listener : button.getActionListeners()) {
39 button.removeActionListener(listener); 38 button.removeActionListener(listener);
40 } 39 }
41 button.addActionListener(newListener); 40 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 @@
11 11
12package cuchaz.enigma.gui; 12package cuchaz.enigma.gui;
13 13
14import cuchaz.enigma.mapping.ClassEntry; 14import cuchaz.enigma.mapping.entry.ClassEntry;
15 15
16public class ScoredClassEntry extends ClassEntry { 16public class ScoredClassEntry extends ClassEntry {
17 17
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 @@
11 11
12package cuchaz.enigma.gui.node; 12package cuchaz.enigma.gui.node;
13 13
14import cuchaz.enigma.mapping.ClassEntry; 14import cuchaz.enigma.mapping.entry.ClassEntry;
15 15
16import javax.swing.tree.DefaultMutableTreeNode; 16import javax.swing.tree.DefaultMutableTreeNode;
17 17
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 @@
11 11
12package cuchaz.enigma.gui.node; 12package cuchaz.enigma.gui.node;
13 13
14import javassist.bytecode.Descriptor;
15
16import javax.swing.tree.DefaultMutableTreeNode; 14import javax.swing.tree.DefaultMutableTreeNode;
17 15
18public class ClassSelectorPackageNode extends DefaultMutableTreeNode { 16public class ClassSelectorPackageNode extends DefaultMutableTreeNode {
@@ -41,7 +39,7 @@ public class ClassSelectorPackageNode extends DefaultMutableTreeNode {
41 39
42 @Override 40 @Override
43 public String toString() { 41 public String toString() {
44 return !packageName.equals("(none)") ? Descriptor.toJavaName(this.packageName) : "(none)"; 42 return !packageName.equals("(none)") ? this.packageName : "(none)";
45 } 43 }
46 44
47 @Override 45 @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;
2 2
3import cuchaz.enigma.gui.ClassSelector; 3import cuchaz.enigma.gui.ClassSelector;
4import cuchaz.enigma.gui.Gui; 4import cuchaz.enigma.gui.Gui;
5import cuchaz.enigma.mapping.ClassEntry; 5import cuchaz.enigma.mapping.entry.ClassEntry;
6 6
7import javax.swing.*; 7import javax.swing.*;
8import java.awt.*; 8import 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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.mapping;
13
14import cuchaz.enigma.utils.Utils;
15
16public class ArgumentEntry implements Entry {
17
18 private BehaviorEntry behaviorEntry;
19 private int index;
20 private String name;
21
22 public ArgumentEntry(BehaviorEntry behaviorEntry, int index, String name) {
23 if (behaviorEntry == null) {
24 throw new IllegalArgumentException("Behavior cannot be null!");
25 }
26 if (index < 0) {
27 throw new IllegalArgumentException("Index must be non-negative!");
28 }
29 if (name == null) {
30 throw new IllegalArgumentException("Argument name cannot be null!");
31 }
32
33 this.behaviorEntry = behaviorEntry;
34 this.index = index;
35 this.name = name;
36 }
37
38 public ArgumentEntry(ArgumentEntry other) {
39 this.behaviorEntry = other.getBehaviorEntry();
40 this.index = other.index;
41 this.name = other.name;
42 }
43
44 public ArgumentEntry(ArgumentEntry other, String newClassName) {
45 this.behaviorEntry = (BehaviorEntry) other.behaviorEntry.cloneToNewClass(new ClassEntry(newClassName));
46 this.index = other.index;
47 this.name = other.name;
48 }
49
50 public ArgumentEntry(ArgumentEntry other, BehaviorEntry entry) {
51 this.behaviorEntry = entry;
52 this.index = other.index;
53 this.name = other.name;
54 }
55
56 public BehaviorEntry getBehaviorEntry() {
57 return this.behaviorEntry;
58 }
59
60 public int getIndex() {
61 return this.index;
62 }
63
64 @Override
65 public String getName() {
66 return this.name;
67 }
68
69 @Override
70 public ClassEntry getClassEntry() {
71 return this.behaviorEntry.getClassEntry();
72 }
73
74 @Override
75 public String getClassName() {
76 return this.behaviorEntry.getClassName();
77 }
78
79 @Override
80 public ArgumentEntry cloneToNewClass(ClassEntry classEntry) {
81 return new ArgumentEntry(this, classEntry.getName());
82 }
83
84 public String getMethodName() {
85 return this.behaviorEntry.getName();
86 }
87
88 public Signature getMethodSignature() {
89 return this.behaviorEntry.getSignature();
90 }
91
92 @Override
93 public int hashCode() {
94 return Utils.combineHashesOrdered(this.behaviorEntry, Integer.valueOf(this.index).hashCode(), this.name.hashCode());
95 }
96
97 @Override
98 public boolean equals(Object other) {
99 return other instanceof ArgumentEntry && equals((ArgumentEntry) other);
100 }
101
102 public boolean equals(ArgumentEntry other) {
103 return this.behaviorEntry.equals(other.behaviorEntry) && this.index == other.index && this.name.equals(other.name);
104 }
105
106 @Override
107 public String toString() {
108 return this.behaviorEntry + "(" + this.index + ":" + this.name + ")";
109 }
110}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.mapping;
13
14public interface BehaviorEntry extends Entry {
15 Signature getSignature();
16}
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 @@
12package cuchaz.enigma.mapping; 12package cuchaz.enigma.mapping;
13 13
14import com.google.common.collect.Maps; 14import com.google.common.collect.Maps;
15import cuchaz.enigma.mapping.entry.ClassEntry;
16import cuchaz.enigma.mapping.entry.FieldEntry;
17import cuchaz.enigma.mapping.entry.MethodEntry;
15import cuchaz.enigma.throwables.MappingConflict; 18import cuchaz.enigma.throwables.MappingConflict;
16 19
17import java.util.ArrayList; 20import java.util.ArrayList;
@@ -34,7 +37,6 @@ public class ClassMapping implements Comparable<ClassMapping> {
34 private Map<String, MethodMapping> methodsByDeobf; 37 private Map<String, MethodMapping> methodsByDeobf;
35 private boolean isDirty; 38 private boolean isDirty;
36 private Mappings.EntryModifier modifier; 39 private Mappings.EntryModifier modifier;
37 private boolean deobfInner;
38 40
39 public ClassMapping(String obfFullName) { 41 public ClassMapping(String obfFullName) {
40 this(obfFullName, null, Mappings.EntryModifier.UNCHANGED); 42 this(obfFullName, null, Mappings.EntryModifier.UNCHANGED);
@@ -81,6 +83,10 @@ public class ClassMapping implements Comparable<ClassMapping> {
81 return deobfName; 83 return deobfName;
82 } 84 }
83 85
86 public String getTranslatedName(TranslationDirection direction) {
87 return direction.choose(deobfName, obfFullName);
88 }
89
84 //// INNER CLASSES //////// 90 //// INNER CLASSES ////////
85 91
86 public void setDeobfName(String val) { 92 public void setDeobfName(String val) {
@@ -191,21 +197,21 @@ public class ClassMapping implements Comparable<ClassMapping> {
191 return fieldsByObf.values(); 197 return fieldsByObf.values();
192 } 198 }
193 199
194 public boolean containsObfField(String obfName, Type obfType) { 200 public boolean containsObfField(String obfName, TypeDescriptor obfDesc) {
195 return fieldsByObf.containsKey(getFieldKey(obfName, obfType)); 201 return fieldsByObf.containsKey(getFieldKey(obfName, obfDesc));
196 } 202 }
197 203
198 public boolean containsDeobfField(String deobfName, Type deobfType) { 204 public boolean containsDeobfField(String deobfName, TypeDescriptor deobfDesc) {
199 return fieldsByDeobf.containsKey(getFieldKey(deobfName, deobfType)); 205 return fieldsByDeobf.containsKey(getFieldKey(deobfName, deobfDesc));
200 } 206 }
201 207
202 public void addFieldMapping(FieldMapping fieldMapping) { 208 public void addFieldMapping(FieldMapping fieldMapping) {
203 String obfKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType()); 209 String obfKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfDesc());
204 if (fieldsByObf.containsKey(obfKey)) { 210 if (fieldsByObf.containsKey(obfKey)) {
205 throw new Error("Already have mapping for " + obfFullName + "." + obfKey); 211 throw new Error("Already have mapping for " + obfFullName + "." + obfKey);
206 } 212 }
207 if (fieldMapping.getDeobfName() != null) { 213 if (fieldMapping.getDeobfName() != null) {
208 String deobfKey = getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfType()); 214 String deobfKey = getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfDesc());
209 if (fieldsByDeobf.containsKey(deobfKey)) { 215 if (fieldsByDeobf.containsKey(deobfKey)) {
210 throw new Error("Already have mapping for " + deobfName + "." + deobfKey); 216 throw new Error("Already have mapping for " + deobfName + "." + deobfKey);
211 } 217 }
@@ -218,63 +224,67 @@ public class ClassMapping implements Comparable<ClassMapping> {
218 } 224 }
219 225
220 public void removeFieldMapping(FieldMapping fieldMapping) { 226 public void removeFieldMapping(FieldMapping fieldMapping) {
221 boolean obfWasRemoved = fieldsByObf.remove(getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType())) != null; 227 boolean obfWasRemoved = fieldsByObf.remove(getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfDesc())) != null;
222 assert (obfWasRemoved); 228 assert (obfWasRemoved);
223 if (fieldMapping.getDeobfName() != null) { 229 if (fieldMapping.getDeobfName() != null) {
224 boolean deobfWasRemoved = fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfType())) != null; 230 boolean deobfWasRemoved = fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfDesc())) != null;
225 assert (deobfWasRemoved); 231 assert (deobfWasRemoved);
226 } 232 }
227 this.isDirty = true; 233 this.isDirty = true;
228 } 234 }
229 235
230 public FieldMapping getFieldByObf(String obfName, Type obfType) { 236 public FieldMapping getFieldByObf(String obfName, TypeDescriptor obfDesc) {
231 return fieldsByObf.get(getFieldKey(obfName, obfType)); 237 return fieldsByObf.get(getFieldKey(obfName, obfDesc));
238 }
239
240 public FieldMapping getFieldByObf(FieldEntry field) {
241 return getFieldByObf(field.getName(), field.getDesc());
232 } 242 }
233 243
234 public FieldMapping getFieldByDeobf(String deobfName, Type obfType) { 244 public FieldMapping getFieldByDeobf(String deobfName, TypeDescriptor obfDesc) {
235 return fieldsByDeobf.get(getFieldKey(deobfName, obfType)); 245 return fieldsByDeobf.get(getFieldKey(deobfName, obfDesc));
236 } 246 }
237 247
238 public String getObfFieldName(String deobfName, Type obfType) { 248 public String getObfFieldName(String deobfName, TypeDescriptor obfDesc) {
239 FieldMapping fieldMapping = fieldsByDeobf.get(getFieldKey(deobfName, obfType)); 249 FieldMapping fieldMapping = fieldsByDeobf.get(getFieldKey(deobfName, obfDesc));
240 if (fieldMapping != null) { 250 if (fieldMapping != null) {
241 return fieldMapping.getObfName(); 251 return fieldMapping.getObfName();
242 } 252 }
243 return null; 253 return null;
244 } 254 }
245 255
246 public String getDeobfFieldName(String obfName, Type obfType) { 256 public String getDeobfFieldName(String obfName, TypeDescriptor obfDesc) {
247 FieldMapping fieldMapping = fieldsByObf.get(getFieldKey(obfName, obfType)); 257 FieldMapping fieldMapping = fieldsByObf.get(getFieldKey(obfName, obfDesc));
248 if (fieldMapping != null) { 258 if (fieldMapping != null) {
249 return fieldMapping.getDeobfName(); 259 return fieldMapping.getDeobfName();
250 } 260 }
251 return null; 261 return null;
252 } 262 }
253 263
254 private String getFieldKey(String name, Type type) { 264 private String getFieldKey(String name, TypeDescriptor desc) {
255 if (name == null) { 265 if (name == null) {
256 throw new IllegalArgumentException("name cannot be null!"); 266 throw new IllegalArgumentException("name cannot be null!");
257 } 267 }
258 if (type == null) { 268 if (desc == null) {
259 throw new IllegalArgumentException("type cannot be null!"); 269 throw new IllegalArgumentException("desc cannot be null!");
260 } 270 }
261 return name + ":" + type; 271 return name + ":" + desc;
262 } 272 }
263 273
264 public void setFieldName(String obfName, Type obfType, String deobfName) { 274 public void setFieldName(String obfName, TypeDescriptor obfDesc, String deobfName) {
265 assert (deobfName != null); 275 assert (deobfName != null);
266 FieldMapping fieldMapping = fieldsByObf.get(getFieldKey(obfName, obfType)); 276 FieldMapping fieldMapping = fieldsByObf.get(getFieldKey(obfName, obfDesc));
267 if (fieldMapping == null) { 277 if (fieldMapping == null) {
268 fieldMapping = new FieldMapping(obfName, obfType, deobfName, Mappings.EntryModifier.UNCHANGED); 278 fieldMapping = new FieldMapping(obfName, obfDesc, deobfName, Mappings.EntryModifier.UNCHANGED);
269 boolean obfWasAdded = fieldsByObf.put(getFieldKey(obfName, obfType), fieldMapping) == null; 279 boolean obfWasAdded = fieldsByObf.put(getFieldKey(obfName, obfDesc), fieldMapping) == null;
270 assert (obfWasAdded); 280 assert (obfWasAdded);
271 } else { 281 } else {
272 boolean wasRemoved = fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), obfType)) != null; 282 boolean wasRemoved = fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), obfDesc)) != null;
273 assert (wasRemoved); 283 assert (wasRemoved);
274 } 284 }
275 fieldMapping.setDeobfName(deobfName); 285 fieldMapping.setDeobfName(deobfName);
276 if (deobfName != null) { 286 if (deobfName != null) {
277 boolean wasAdded = fieldsByDeobf.put(getFieldKey(deobfName, obfType), fieldMapping) == null; 287 boolean wasAdded = fieldsByDeobf.put(getFieldKey(deobfName, obfDesc), fieldMapping) == null;
278 assert (wasAdded); 288 assert (wasAdded);
279 } 289 }
280 this.isDirty = true; 290 this.isDirty = true;
@@ -282,13 +292,13 @@ public class ClassMapping implements Comparable<ClassMapping> {
282 292
283 //// METHODS //////// 293 //// METHODS ////////
284 294
285 public void setFieldObfNameAndType(String oldObfName, Type obfType, String newObfName, Type newObfType) { 295 public void setFieldObfNameAndType(String oldObfName, TypeDescriptor obfDesc, String newObfName, TypeDescriptor newObfDesc) {
286 assert (newObfName != null); 296 assert (newObfName != null);
287 FieldMapping fieldMapping = fieldsByObf.remove(getFieldKey(oldObfName, obfType)); 297 FieldMapping fieldMapping = fieldsByObf.remove(getFieldKey(oldObfName, obfDesc));
288 assert (fieldMapping != null); 298 assert (fieldMapping != null);
289 fieldMapping.setObfName(newObfName); 299 fieldMapping.setObfName(newObfName);
290 fieldMapping.setObfType(newObfType); 300 fieldMapping.setObfDesc(newObfDesc);
291 boolean obfWasAdded = fieldsByObf.put(getFieldKey(newObfName, newObfType), fieldMapping) == null; 301 boolean obfWasAdded = fieldsByObf.put(getFieldKey(newObfName, newObfDesc), fieldMapping) == null;
292 assert (obfWasAdded); 302 assert (obfWasAdded);
293 this.isDirty = true; 303 this.isDirty = true;
294 } 304 }
@@ -298,23 +308,23 @@ public class ClassMapping implements Comparable<ClassMapping> {
298 return methodsByObf.values(); 308 return methodsByObf.values();
299 } 309 }
300 310
301 public boolean containsObfMethod(String obfName, Signature obfSignature) { 311 public boolean containsObfMethod(String obfName, MethodDescriptor obfDescriptor) {
302 return methodsByObf.containsKey(getMethodKey(obfName, obfSignature)); 312 return methodsByObf.containsKey(getMethodKey(obfName, obfDescriptor));
303 } 313 }
304 314
305 public boolean containsDeobfMethod(String deobfName, Signature obfSignature) { 315 public boolean containsDeobfMethod(String deobfName, MethodDescriptor obfDescriptor) {
306 return methodsByDeobf.containsKey(getMethodKey(deobfName, obfSignature)); 316 return methodsByDeobf.containsKey(getMethodKey(deobfName, obfDescriptor));
307 } 317 }
308 318
309 public void addMethodMapping(MethodMapping methodMapping) { 319 public void addMethodMapping(MethodMapping methodMapping) {
310 String obfKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()); 320 String obfKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfDesc());
311 if (methodsByObf.containsKey(obfKey)) { 321 if (methodsByObf.containsKey(obfKey)) {
312 throw new Error("Already have mapping for " + obfFullName + "." + obfKey); 322 throw new Error("Already have mapping for " + obfFullName + "." + obfKey);
313 } 323 }
314 boolean wasAdded = methodsByObf.put(obfKey, methodMapping) == null; 324 boolean wasAdded = methodsByObf.put(obfKey, methodMapping) == null;
315 assert (wasAdded); 325 assert (wasAdded);
316 if (methodMapping.getDeobfName() != null) { 326 if (!methodMapping.isObfuscated()) {
317 String deobfKey = getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature()); 327 String deobfKey = getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfDesc());
318 if (methodsByDeobf.containsKey(deobfKey)) { 328 if (methodsByDeobf.containsKey(deobfKey)) {
319 throw new Error("Already have mapping for " + deobfName + "." + deobfKey); 329 throw new Error("Already have mapping for " + deobfName + "." + deobfKey);
320 } 330 }
@@ -326,44 +336,48 @@ public class ClassMapping implements Comparable<ClassMapping> {
326 } 336 }
327 337
328 public void removeMethodMapping(MethodMapping methodMapping) { 338 public void removeMethodMapping(MethodMapping methodMapping) {
329 boolean obfWasRemoved = methodsByObf.remove(getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature())) != null; 339 boolean obfWasRemoved = methodsByObf.remove(getMethodKey(methodMapping.getObfName(), methodMapping.getObfDesc())) != null;
330 assert (obfWasRemoved); 340 assert (obfWasRemoved);
331 if (methodMapping.getDeobfName() != null) { 341 if (!methodMapping.isObfuscated()) {
332 boolean deobfWasRemoved = methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature())) != null; 342 boolean deobfWasRemoved = methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfDesc())) != null;
333 assert (deobfWasRemoved); 343 assert (deobfWasRemoved);
334 } 344 }
335 this.isDirty = true; 345 this.isDirty = true;
336 } 346 }
337 347
338 public MethodMapping getMethodByObf(String obfName, Signature obfSignature) { 348 public MethodMapping getMethodByObf(String obfName, MethodDescriptor obfDescriptor) {
339 return methodsByObf.get(getMethodKey(obfName, obfSignature)); 349 return methodsByObf.get(getMethodKey(obfName, obfDescriptor));
350 }
351
352 public MethodMapping getMethodByObf(MethodEntry method) {
353 return getMethodByObf(method.getName(), method.getDesc());
340 } 354 }
341 355
342 public MethodMapping getMethodByDeobf(String deobfName, Signature obfSignature) { 356 public MethodMapping getMethodByDeobf(String deobfName, MethodDescriptor obfDescriptor) {
343 return methodsByDeobf.get(getMethodKey(deobfName, obfSignature)); 357 return methodsByDeobf.get(getMethodKey(deobfName, obfDescriptor));
344 } 358 }
345 359
346 private String getMethodKey(String name, Signature signature) { 360 private String getMethodKey(String name, MethodDescriptor descriptor) {
347 if (name == null) { 361 if (name == null) {
348 throw new IllegalArgumentException("name cannot be null!"); 362 throw new IllegalArgumentException("name cannot be null!");
349 } 363 }
350 if (signature == null) { 364 if (descriptor == null) {
351 throw new IllegalArgumentException("signature cannot be null!"); 365 throw new IllegalArgumentException("descriptor cannot be null!");
352 } 366 }
353 return name + signature; 367 return name + descriptor;
354 } 368 }
355 369
356 public void setMethodName(String obfName, Signature obfSignature, String deobfName) { 370 public void setMethodName(String obfName, MethodDescriptor obfDescriptor, String deobfName) {
357 MethodMapping methodMapping = methodsByObf.get(getMethodKey(obfName, obfSignature)); 371 MethodMapping methodMapping = methodsByObf.get(getMethodKey(obfName, obfDescriptor));
358 if (methodMapping == null) { 372 if (methodMapping == null) {
359 methodMapping = createMethodMapping(obfName, obfSignature); 373 methodMapping = createMethodMapping(obfName, obfDescriptor);
360 } else if (methodMapping.getDeobfName() != null) { 374 } else if (!methodMapping.isObfuscated()) {
361 boolean wasRemoved = methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature())) != null; 375 boolean wasRemoved = methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfDesc())) != null;
362 assert (wasRemoved); 376 assert (wasRemoved);
363 } 377 }
364 methodMapping.setDeobfName(deobfName); 378 methodMapping.setDeobfName(deobfName);
365 if (deobfName != null) { 379 if (deobfName != null) {
366 boolean wasAdded = methodsByDeobf.put(getMethodKey(deobfName, obfSignature), methodMapping) == null; 380 boolean wasAdded = methodsByDeobf.put(getMethodKey(deobfName, obfDescriptor), methodMapping) == null;
367 assert (wasAdded); 381 assert (wasAdded);
368 } 382 }
369 this.isDirty = true; 383 this.isDirty = true;
@@ -371,35 +385,35 @@ public class ClassMapping implements Comparable<ClassMapping> {
371 385
372 //// ARGUMENTS //////// 386 //// ARGUMENTS ////////
373 387
374 public void setMethodObfNameAndSignature(String oldObfName, Signature obfSignature, String newObfName, Signature newObfSignature) { 388 public void setMethodObfNameAndSignature(String oldObfName, MethodDescriptor obfDescriptor, String newObfName, MethodDescriptor newObfDescriptor) {
375 assert (newObfName != null); 389 assert (newObfName != null);
376 MethodMapping methodMapping = methodsByObf.remove(getMethodKey(oldObfName, obfSignature)); 390 MethodMapping methodMapping = methodsByObf.remove(getMethodKey(oldObfName, obfDescriptor));
377 assert (methodMapping != null); 391 assert (methodMapping != null);
378 methodMapping.setObfName(newObfName); 392 methodMapping.setObfName(newObfName);
379 methodMapping.setObfSignature(newObfSignature); 393 methodMapping.setObfDescriptor(newObfDescriptor);
380 boolean obfWasAdded = methodsByObf.put(getMethodKey(newObfName, newObfSignature), methodMapping) == null; 394 boolean obfWasAdded = methodsByObf.put(getMethodKey(newObfName, newObfDescriptor), methodMapping) == null;
381 assert (obfWasAdded); 395 assert (obfWasAdded);
382 this.isDirty = true; 396 this.isDirty = true;
383 } 397 }
384 398
385 public void setArgumentName(String obfMethodName, Signature obfMethodSignature, int argumentIndex, String argumentName) { 399 public void setArgumentName(String obfMethodName, MethodDescriptor obfMethodDescriptor, int argumentIndex, String argumentName) {
386 assert (argumentName != null); 400 assert (argumentName != null);
387 MethodMapping methodMapping = methodsByObf.get(getMethodKey(obfMethodName, obfMethodSignature)); 401 MethodMapping methodMapping = methodsByObf.get(getMethodKey(obfMethodName, obfMethodDescriptor));
388 if (methodMapping == null) { 402 if (methodMapping == null) {
389 methodMapping = createMethodMapping(obfMethodName, obfMethodSignature); 403 methodMapping = createMethodMapping(obfMethodName, obfMethodDescriptor);
390 } 404 }
391 methodMapping.setArgumentName(argumentIndex, argumentName); 405 methodMapping.setLocalVariableName(argumentIndex, argumentName);
392 this.isDirty = true; 406 this.isDirty = true;
393 } 407 }
394 408
395 public void removeArgumentName(String obfMethodName, Signature obfMethodSignature, int argumentIndex) { 409 public void removeArgumentName(String obfMethodName, MethodDescriptor obfMethodDescriptor, int argumentIndex) {
396 methodsByObf.get(getMethodKey(obfMethodName, obfMethodSignature)).removeArgumentName(argumentIndex); 410 methodsByObf.get(getMethodKey(obfMethodName, obfMethodDescriptor)).removeLocalVariableName(argumentIndex);
397 this.isDirty = true; 411 this.isDirty = true;
398 } 412 }
399 413
400 private MethodMapping createMethodMapping(String obfName, Signature obfSignature) { 414 private MethodMapping createMethodMapping(String obfName, MethodDescriptor obfDescriptor) {
401 MethodMapping methodMapping = new MethodMapping(obfName, obfSignature); 415 MethodMapping methodMapping = new MethodMapping(obfName, obfDescriptor);
402 boolean wasAdded = methodsByObf.put(getMethodKey(obfName, obfSignature), methodMapping) == null; 416 boolean wasAdded = methodsByObf.put(getMethodKey(obfName, obfDescriptor), methodMapping) == null;
403 assert (wasAdded); 417 assert (wasAdded);
404 this.isDirty = true; 418 this.isDirty = true;
405 return methodMapping; 419 return methodMapping;
@@ -459,24 +473,24 @@ public class ClassMapping implements Comparable<ClassMapping> {
459 473
460 // rename field types 474 // rename field types
461 for (FieldMapping fieldMapping : new ArrayList<>(fieldsByObf.values())) { 475 for (FieldMapping fieldMapping : new ArrayList<>(fieldsByObf.values())) {
462 String oldFieldKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType()); 476 String oldFieldKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfDesc());
463 if (fieldMapping.renameObfClass(oldObfClassName, newObfClassName)) { 477 if (fieldMapping.renameObfClass(oldObfClassName, newObfClassName)) {
464 boolean wasRemoved = fieldsByObf.remove(oldFieldKey) != null; 478 boolean wasRemoved = fieldsByObf.remove(oldFieldKey) != null;
465 assert (wasRemoved); 479 assert (wasRemoved);
466 boolean wasAdded = fieldsByObf 480 boolean wasAdded = fieldsByObf
467 .put(getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType()), fieldMapping) == null; 481 .put(getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfDesc()), fieldMapping) == null;
468 assert (wasAdded); 482 assert (wasAdded);
469 } 483 }
470 } 484 }
471 485
472 // rename method signatures 486 // rename method signatures
473 for (MethodMapping methodMapping : new ArrayList<>(methodsByObf.values())) { 487 for (MethodMapping methodMapping : new ArrayList<>(methodsByObf.values())) {
474 String oldMethodKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()); 488 String oldMethodKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfDesc());
475 if (methodMapping.renameObfClass(oldObfClassName, newObfClassName)) { 489 if (methodMapping.renameObfClass(oldObfClassName, newObfClassName)) {
476 boolean wasRemoved = methodsByObf.remove(oldMethodKey) != null; 490 boolean wasRemoved = methodsByObf.remove(oldMethodKey) != null;
477 assert (wasRemoved); 491 assert (wasRemoved);
478 boolean wasAdded = methodsByObf 492 boolean wasAdded = methodsByObf
479 .put(getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()), methodMapping) == null; 493 .put(getMethodKey(methodMapping.getObfName(), methodMapping.getObfDesc()), methodMapping) == null;
480 assert (wasAdded); 494 assert (wasAdded);
481 } 495 }
482 } 496 }
@@ -490,9 +504,9 @@ public class ClassMapping implements Comparable<ClassMapping> {
490 return false; 504 return false;
491 } 505 }
492 506
493 public boolean containsArgument(BehaviorEntry obfBehaviorEntry, String name) { 507 public boolean containsArgument(MethodEntry obfMethodEntry, String name) {
494 MethodMapping methodMapping = methodsByObf.get(getMethodKey(obfBehaviorEntry.getName(), obfBehaviorEntry.getSignature())); 508 MethodMapping methodMapping = methodsByObf.get(getMethodKey(obfMethodEntry.getName(), obfMethodEntry.getDesc()));
495 return methodMapping != null && methodMapping.containsArgument(name); 509 return methodMapping != null && methodMapping.containsLocalVariable(name);
496 } 510 }
497 511
498 public ClassEntry getObfEntry() { 512 public ClassEntry getObfEntry() {
@@ -503,6 +517,14 @@ public class ClassMapping implements Comparable<ClassMapping> {
503 return deobfFullName != null ? new ClassEntry(deobfFullName) : null; 517 return deobfFullName != null ? new ClassEntry(deobfFullName) : null;
504 } 518 }
505 519
520 public boolean isObfuscated() {
521 return this.deobfName == null || this.deobfName.equals(this.obfFullName);
522 }
523
524 public String getSaveName() {
525 return this.isObfuscated() ? this.obfFullName : this.deobfName;
526 }
527
506 public boolean isDirty() { 528 public boolean isDirty() {
507 return isDirty; 529 return isDirty;
508 } 530 }
@@ -511,6 +533,10 @@ public class ClassMapping implements Comparable<ClassMapping> {
511 this.isDirty = false; 533 this.isDirty = false;
512 } 534 }
513 535
536 public void markDirty() {
537 this.isDirty = true;
538 }
539
514 public Mappings.EntryModifier getModifier() { 540 public Mappings.EntryModifier getModifier() {
515 return modifier; 541 return modifier;
516 } 542 }
@@ -521,9 +547,9 @@ public class ClassMapping implements Comparable<ClassMapping> {
521 this.modifier = modifier; 547 this.modifier = modifier;
522 } 548 }
523 549
524 public void setFieldModifier(String obfName, Type obfType, Mappings.EntryModifier modifier) { 550 public void setFieldModifier(String obfName, TypeDescriptor obfDesc, Mappings.EntryModifier modifier) {
525 FieldMapping fieldMapping = fieldsByObf.computeIfAbsent(getFieldKey(obfName, obfType), 551 FieldMapping fieldMapping = fieldsByObf.computeIfAbsent(getFieldKey(obfName, obfDesc),
526 k -> new FieldMapping(obfName, obfType, null, Mappings.EntryModifier.UNCHANGED)); 552 k -> new FieldMapping(obfName, obfDesc, null, Mappings.EntryModifier.UNCHANGED));
527 553
528 if (fieldMapping.getModifier() != modifier) { 554 if (fieldMapping.getModifier() != modifier) {
529 fieldMapping.setModifier(modifier); 555 fieldMapping.setModifier(modifier);
@@ -531,7 +557,7 @@ public class ClassMapping implements Comparable<ClassMapping> {
531 } 557 }
532 } 558 }
533 559
534 public void setMethodModifier(String obfName, Signature sig, Mappings.EntryModifier modifier) { 560 public void setMethodModifier(String obfName, MethodDescriptor sig, Mappings.EntryModifier modifier) {
535 MethodMapping methodMapping = methodsByObf.computeIfAbsent(getMethodKey(obfName, sig), 561 MethodMapping methodMapping = methodsByObf.computeIfAbsent(getMethodKey(obfName, sig),
536 k -> new MethodMapping(obfName, sig, null, Mappings.EntryModifier.UNCHANGED)); 562 k -> new MethodMapping(obfName, sig, null, Mappings.EntryModifier.UNCHANGED));
537 563
@@ -546,4 +572,30 @@ public class ClassMapping implements Comparable<ClassMapping> {
546 this.deobfFullName = deobName; 572 this.deobfFullName = deobName;
547 return this; 573 return this;
548 } 574 }
575
576 public ClassMapping copy() {
577 ClassMapping copied = new ClassMapping(this.obfFullName);
578 copied.obfSimpleName= this.obfSimpleName;
579 copied.modifier = this.modifier;
580 copied.deobfFullName = this.deobfFullName;
581 copied.deobfName = this.deobfName;
582 copied.innerClassesByDeobf = this.innerClassesByDeobf;
583 copied.innerClassesByObfFull = this.innerClassesByObfFull;
584 copied.innerClassesByObfSimple = this.innerClassesByObfSimple;
585 copied.fieldsByObf = this.fieldsByObf;
586 copied.fieldsByDeobf = this.fieldsByDeobf;
587 copied.methodsByObf = this.methodsByObf;
588 copied.methodsByDeobf = this.methodsByDeobf;
589 return copied;
590 }
591
592 @Override
593 public int hashCode() {
594 return this.obfFullName.hashCode();
595 }
596
597 @Override
598 public boolean equals(Object obj) {
599 return obj instanceof ClassMapping && ((ClassMapping) obj).obfFullName.equals(this.obfFullName);
600 }
549} 601}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.mapping;
13
14public interface ClassNameReplacer {
15 String replace(String className);
16}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.mapping;
13
14import cuchaz.enigma.utils.Utils;
15
16public class ConstructorEntry implements BehaviorEntry {
17
18 private ClassEntry classEntry;
19 private Signature signature;
20
21 public ConstructorEntry(ClassEntry classEntry) {
22 this(classEntry, null);
23 }
24
25 public ConstructorEntry(ClassEntry classEntry, Signature signature) {
26 if (classEntry == null) {
27 throw new IllegalArgumentException("Class cannot be null!");
28 }
29
30 this.classEntry = classEntry;
31 this.signature = signature;
32 }
33
34 public ConstructorEntry(ConstructorEntry other, String newClassName) {
35 this.classEntry = new ClassEntry(newClassName);
36 this.signature = other.signature;
37 }
38
39 @Override
40 public ClassEntry getClassEntry() {
41 return this.classEntry;
42 }
43
44 @Override
45 public String getName() {
46 if (isStatic()) {
47 return "<clinit>";
48 }
49 return "<init>";
50 }
51
52 public boolean isStatic() {
53 return this.signature == null;
54 }
55
56 @Override
57 public Signature getSignature() {
58 return this.signature;
59 }
60
61 @Override
62 public String getClassName() {
63 return this.classEntry.getName();
64 }
65
66 @Override
67 public ConstructorEntry cloneToNewClass(ClassEntry classEntry) {
68 return new ConstructorEntry(this, classEntry.getName());
69 }
70
71 @Override
72 public int hashCode() {
73 if (isStatic()) {
74 return Utils.combineHashesOrdered(this.classEntry);
75 } else {
76 return Utils.combineHashesOrdered(this.classEntry, this.signature);
77 }
78 }
79
80 @Override
81 public boolean equals(Object other) {
82 return other instanceof ConstructorEntry && equals((ConstructorEntry) other);
83 }
84
85 public boolean equals(ConstructorEntry other) {
86 if (isStatic() != other.isStatic()) {
87 return false;
88 }
89
90 if (isStatic()) {
91 return this.classEntry.equals(other.classEntry);
92 } else {
93 return this.classEntry.equals(other.classEntry) && this.signature.equals(other.signature);
94 }
95 }
96
97 @Override
98 public String toString() {
99 if (isStatic()) {
100 return this.classEntry.getName() + "." + getName();
101 } else {
102 return this.classEntry.getName() + "." + getName() + this.signature;
103 }
104 }
105}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.mapping;
13
14import com.google.common.collect.Lists;
15import com.google.common.collect.Maps;
16import cuchaz.enigma.analysis.TranslationIndex;
17import cuchaz.enigma.bytecode.AccessFlags;
18import cuchaz.enigma.mapping.entry.*;
19
20import java.util.ArrayList;
21import java.util.List;
22import java.util.Map;
23
24public class DirectionalTranslator implements Translator {
25 private final TranslationDirection direction;
26 private final Map<String, ClassMapping> classes;
27 private final TranslationIndex index;
28
29 public DirectionalTranslator(ReferencedEntryPool entryPool) {
30 this.direction = null;
31 this.classes = Maps.newHashMap();
32 this.index = new TranslationIndex(entryPool);
33 }
34
35 public DirectionalTranslator(TranslationDirection direction, Map<String, ClassMapping> classes, TranslationIndex index) {
36 this.direction = direction;
37 this.classes = classes;
38 this.index = index;
39 }
40
41 public TranslationDirection getDirection() {
42 return direction;
43 }
44
45 public TranslationIndex getTranslationIndex() {
46 return index;
47 }
48
49 @Override
50 public ClassEntry getTranslatedClass(ClassEntry entry) {
51 String className;
52 if (entry.isArray()) {
53 className = this.getTranslatedTypeDesc(new TypeDescriptor(entry.getName())).toString();
54 } else {
55 className = entry.isInnerClass() ? translateInnerClassName(entry) : translateClassName(entry);
56 }
57 return new ClassEntry(className);
58 }
59
60 @Override
61 public ClassDefEntry getTranslatedClassDef(ClassDefEntry entry) {
62 String className;
63 if (entry.isArray()) {
64 className = this.getTranslatedTypeDesc(new TypeDescriptor(entry.getName())).toString();
65 } else {
66 className = entry.isInnerClass() ? translateInnerClassName(entry) : translateClassName(entry);
67 }
68 Signature translatedSignature = this.getTranslatedSignature(entry.getSignature());
69 return new ClassDefEntry(className, translatedSignature, getClassModifier(entry).transform(entry.getAccess()));
70 }
71
72 private String translateClassName(ClassEntry entry) {
73 // normal classes are easy
74 ClassMapping classMapping = this.classes.get(entry.getName());
75 if (classMapping == null) {
76 return entry.getName();
77 }
78 return classMapping.getTranslatedName(direction);
79 }
80
81 private String translateInnerClassName(ClassEntry entry) {
82 // translate as much of the class chain as we can
83 List<ClassMapping> mappingsChain = getClassMappingChain(entry);
84 String[] obfClassNames = entry.getName().split("\\$");
85 StringBuilder buf = new StringBuilder();
86 for (int i = 0; i < obfClassNames.length; i++) {
87 boolean isFirstClass = buf.length() == 0;
88 String className = null;
89 ClassMapping classMapping = mappingsChain.get(i);
90 if (classMapping != null) {
91 className = this.direction.choose(
92 classMapping.getDeobfName(),
93 isFirstClass ? classMapping.getObfFullName() : classMapping.getObfSimpleName()
94 );
95 }
96 if (className == null) {
97 className = obfClassNames[i];
98 }
99 if (!isFirstClass) {
100 buf.append("$");
101 }
102 buf.append(className);
103 }
104 return buf.toString();
105 }
106
107 @Override
108 public FieldDefEntry getTranslatedFieldDef(FieldDefEntry entry) {
109 String translatedName = translateFieldName(entry);
110 if (translatedName == null) {
111 translatedName = entry.getName();
112 }
113 ClassEntry translatedOwner = getTranslatedClass(entry.getOwnerClassEntry());
114 TypeDescriptor translatedDesc = getTranslatedTypeDesc(entry.getDesc());
115 Signature translatedSignature = getTranslatedSignature(entry.getSignature());
116 AccessFlags translatedAccess = getFieldModifier(entry).transform(entry.getAccess());
117 return new FieldDefEntry(translatedOwner, translatedName, translatedDesc, translatedSignature, translatedAccess);
118 }
119
120 @Override
121 public FieldEntry getTranslatedField(FieldEntry entry) {
122 String translatedName = translateFieldName(entry);
123 if (translatedName == null) {
124 translatedName = entry.getName();
125 }
126 ClassEntry translatedOwner = getTranslatedClass(entry.getOwnerClassEntry());
127 TypeDescriptor translatedDesc = getTranslatedTypeDesc(entry.getDesc());
128 return new FieldEntry(translatedOwner, translatedName, translatedDesc);
129 }
130
131 private String translateFieldName(FieldEntry entry) {
132 // resolve the class entry
133 ClassEntry resolvedClassEntry = this.index.resolveEntryOwner(entry, true);
134 if (resolvedClassEntry != null) {
135 // look for the class
136 ClassMapping classMapping = findClassMapping(resolvedClassEntry);
137 if (classMapping != null) {
138 // look for the field
139 FieldMapping mapping = this.direction.choose(
140 classMapping.getFieldByObf(entry.getName(), entry.getDesc()),
141 classMapping.getFieldByDeobf(entry.getName(), getTranslatedTypeDesc(entry.getDesc()))
142 );
143 if (mapping != null) {
144 return this.direction.choose(mapping.getDeobfName(), mapping.getObfName());
145 }
146 }
147 }
148 return null;
149 }
150
151 @Override
152 public MethodDefEntry getTranslatedMethodDef(MethodDefEntry entry) {
153 String translatedName = translateMethodName(entry);
154 if (translatedName == null) {
155 translatedName = entry.getName();
156 }
157 ClassEntry translatedOwner = getTranslatedClass(entry.getOwnerClassEntry());
158 MethodDescriptor translatedDesc = getTranslatedMethodDesc(entry.getDesc());
159 Signature translatedSignature = getTranslatedSignature(entry.getSignature());
160 AccessFlags access = getMethodModifier(entry).transform(entry.getAccess());
161 return new MethodDefEntry(translatedOwner, translatedName, translatedDesc, translatedSignature, access);
162 }
163
164 @Override
165 public MethodEntry getTranslatedMethod(MethodEntry entry) {
166 String translatedName = translateMethodName(entry);
167 if (translatedName == null) {
168 translatedName = entry.getName();
169 }
170 ClassEntry translatedOwner = getTranslatedClass(entry.getOwnerClassEntry());
171 MethodDescriptor translatedDesc = getTranslatedMethodDesc(entry.getDesc());
172 return new MethodEntry(translatedOwner, translatedName, translatedDesc);
173 }
174
175 private String translateMethodName(MethodEntry entry) {
176 // resolve the class entry
177 ClassEntry resolvedOwner = this.index.resolveEntryOwner(entry, true);
178 if (resolvedOwner != null) {
179 // look for class
180 ClassMapping classMapping = findClassMapping(resolvedOwner);
181 if (classMapping != null) {
182 // look for the method
183 MethodMapping mapping = this.direction.choose(
184 classMapping.getMethodByObf(entry.getName(), entry.getDesc()),
185 classMapping.getMethodByDeobf(entry.getName(), getTranslatedMethodDesc(entry.getDesc()))
186 );
187 if (mapping != null) {
188 return this.direction.choose(mapping.getDeobfName(), mapping.getObfName());
189 }
190 }
191 }
192 return null;
193 }
194
195 @Override
196 public LocalVariableEntry getTranslatedVariable(LocalVariableEntry entry) {
197 String translatedArgumentName = translateLocalVariableName(entry);
198 if (translatedArgumentName == null) {
199 translatedArgumentName = inheritLocalVariableName(entry);
200 }
201 if (translatedArgumentName == null) {
202 translatedArgumentName = entry.getName();
203 }
204 // TODO: Translating arguments calls method translation.. Can we refactor the code in such a way that we don't need this?
205 MethodEntry translatedOwner = getTranslatedMethod(entry.getOwnerEntry());
206 return new LocalVariableEntry(translatedOwner, entry.getIndex(), translatedArgumentName);
207 }
208
209 @Override
210 public LocalVariableDefEntry getTranslatedVariableDef(LocalVariableDefEntry entry) {
211 String translatedArgumentName = translateLocalVariableName(entry);
212 if (translatedArgumentName == null) {
213 translatedArgumentName = inheritLocalVariableName(entry);
214 }
215 // TODO: Translating arguments calls method translation.. Can we refactor the code in such a way that we don't need this?
216 MethodDefEntry translatedOwner = getTranslatedMethodDef(entry.getOwnerEntry());
217 TypeDescriptor translatedTypeDesc = getTranslatedTypeDesc(entry.getDesc());
218 return new LocalVariableDefEntry(translatedOwner, entry.getIndex(), translatedArgumentName != null ? translatedArgumentName : entry.getName(), translatedTypeDesc);
219 }
220
221 @Override
222 public boolean hasClassMapping(ClassEntry entry) {
223 return classes.containsKey(entry.getName());
224 }
225
226 @Override
227 public boolean hasFieldMapping(FieldEntry entry) {
228 return translateFieldName(entry) != null;
229 }
230
231 @Override
232 public boolean hasMethodMapping(MethodEntry entry) {
233 return translateMethodName(entry) != null;
234 }
235
236 @Override
237 public boolean hasLocalVariableMapping(LocalVariableEntry entry) {
238 return translateLocalVariableName(entry) != null || inheritLocalVariableName(entry) != null;
239 }
240
241 // TODO: support not identical behavior (specific to constructor)
242 private String translateLocalVariableName(LocalVariableEntry entry) {
243 // look for identical behavior in superclasses
244 ClassEntry ownerEntry = entry.getOwnerClassEntry();
245 if (ownerEntry != null) {
246 // look for the class
247 ClassMapping classMapping = findClassMapping(ownerEntry);
248 if (classMapping != null) {
249 // look for the method
250 MethodMapping methodMapping = this.direction.choose(
251 classMapping.getMethodByObf(entry.getMethodName(), entry.getMethodDesc()),
252 classMapping.getMethodByDeobf(entry.getMethodName(), getTranslatedMethodDesc(entry.getMethodDesc()))
253 );
254 if (methodMapping != null) {
255 int index = entry.getIndex();
256 return this.direction.choose(
257 methodMapping.getDeobfLocalVariableName(index),
258 methodMapping.getObfLocalVariableName(index)
259 );
260 }
261 }
262 }
263 return null;
264 }
265
266 private String inheritLocalVariableName(LocalVariableEntry entry) {
267 List<ClassEntry> ancestry = this.index.getAncestry(entry.getOwnerClassEntry());
268 // Check in mother class for the arg
269 for (ClassEntry ancestorEntry : ancestry) {
270 LocalVariableEntry motherArg = entry.updateOwnership(ancestorEntry);
271 if (this.index.entryExists(motherArg)) {
272 String result = translateLocalVariableName(motherArg);
273 if (result != null) {
274 return result;
275 }
276 }
277 }
278 return null;
279 }
280
281 @Override
282 public TypeDescriptor getTranslatedTypeDesc(TypeDescriptor desc) {
283 return desc.remap(this::remapClass);
284 }
285
286 @Override
287 public MethodDescriptor getTranslatedMethodDesc(MethodDescriptor descriptor) {
288 List<TypeDescriptor> arguments = descriptor.getArgumentDescs();
289 List<TypeDescriptor> translatedArguments = new ArrayList<>(arguments.size());
290 for (TypeDescriptor argument : arguments) {
291 translatedArguments.add(getTranslatedTypeDesc(argument));
292 }
293 return new MethodDescriptor(translatedArguments, getTranslatedTypeDesc(descriptor.getReturnDesc()));
294 }
295
296 @Override
297 public Signature getTranslatedSignature(Signature signature) {
298 if (signature == null) {
299 return null;
300 }
301 return signature.remap(this::remapClass);
302 }
303
304 private ClassMapping findClassMapping(ClassEntry entry) {
305 List<ClassMapping> mappingChain = getClassMappingChain(entry);
306 return mappingChain.get(mappingChain.size() - 1);
307 }
308
309 private List<ClassMapping> getClassMappingChain(ClassEntry entry) {
310
311 // get a list of all the classes in the hierarchy
312 String[] parts = entry.getName().split("\\$");
313 List<ClassMapping> mappingsChain = Lists.newArrayList();
314
315 // get mappings for the outer class
316 ClassMapping outerClassMapping = this.classes.get(parts[0]);
317 mappingsChain.add(outerClassMapping);
318
319 for (int i = 1; i < parts.length; i++) {
320
321 // get mappings for the inner class
322 ClassMapping innerClassMapping = null;
323 if (outerClassMapping != null) {
324 innerClassMapping = this.direction.choose(
325 outerClassMapping.getInnerClassByObfSimple(parts[i]),
326 outerClassMapping.getInnerClassByDeobfThenObfSimple(parts[i])
327 );
328 }
329 mappingsChain.add(innerClassMapping);
330 outerClassMapping = innerClassMapping;
331 }
332
333 assert (mappingsChain.size() == parts.length);
334 return mappingsChain;
335 }
336
337 private Mappings.EntryModifier getClassModifier(ClassEntry entry) {
338 ClassMapping classMapping = findClassMapping(entry);
339 if (classMapping != null) {
340 return classMapping.getModifier();
341 }
342 return Mappings.EntryModifier.UNCHANGED;
343 }
344
345 private Mappings.EntryModifier getFieldModifier(FieldEntry entry) {
346 ClassMapping classMapping = findClassMapping(entry.getOwnerClassEntry());
347 if (classMapping != null) {
348 FieldMapping fieldMapping = classMapping.getFieldByObf(entry);
349 if (fieldMapping != null) {
350 return fieldMapping.getModifier();
351 }
352 }
353 return Mappings.EntryModifier.UNCHANGED;
354 }
355
356 private Mappings.EntryModifier getMethodModifier(MethodEntry entry) {
357 ClassMapping classMapping = findClassMapping(entry.getOwnerClassEntry());
358 if (classMapping != null) {
359 MethodMapping methodMapping = classMapping.getMethodByObf(entry);
360 if (methodMapping != null) {
361 return methodMapping.getModifier();
362 }
363 }
364 return Mappings.EntryModifier.UNCHANGED;
365 }
366
367 private String remapClass(String name) {
368 return getTranslatedClass(new ClassEntry(name)).getName();
369 }
370}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.mapping;
13
14import cuchaz.enigma.analysis.JarIndex;
15import javassist.*;
16import javassist.bytecode.Descriptor;
17import javassist.expr.ConstructorCall;
18import javassist.expr.FieldAccess;
19import javassist.expr.MethodCall;
20import javassist.expr.NewExpr;
21
22public class EntryFactory {
23
24 public static ClassEntry getClassEntry(CtClass c) {
25 return new ClassEntry(Descriptor.toJvmName(c.getName()));
26 }
27
28 public static ClassEntry getObfClassEntry(JarIndex jarIndex, ClassMapping classMapping) {
29 ClassEntry obfClassEntry = new ClassEntry(classMapping.getObfFullName());
30 return obfClassEntry.buildClassEntry(jarIndex.getObfClassChain(obfClassEntry));
31 }
32
33 private static ClassEntry getObfClassEntry(ClassMapping classMapping) {
34 return new ClassEntry(classMapping.getObfFullName());
35 }
36
37 public static ClassEntry getDeobfClassEntry(ClassMapping classMapping) {
38 return new ClassEntry(classMapping.getDeobfName());
39 }
40
41 public static ClassEntry getSuperclassEntry(CtClass c) {
42 return new ClassEntry(Descriptor.toJvmName(c.getClassFile().getSuperclass()));
43 }
44
45 public static FieldEntry getFieldEntry(CtField field) {
46 return new FieldEntry(getClassEntry(field.getDeclaringClass()), field.getName(), new Type(field.getFieldInfo().getDescriptor()));
47 }
48
49 public static FieldEntry getFieldEntry(FieldAccess call) {
50 return new FieldEntry(new ClassEntry(Descriptor.toJvmName(call.getClassName())), call.getFieldName(), new Type(call.getSignature()));
51 }
52
53 public static FieldEntry getFieldEntry(String className, String name, String type) {
54 return new FieldEntry(new ClassEntry(className), name, new Type(type));
55 }
56
57 public static FieldEntry getObfFieldEntry(ClassMapping classMapping, FieldMapping fieldMapping) {
58 return new FieldEntry(getObfClassEntry(classMapping), fieldMapping.getObfName(), fieldMapping.getObfType());
59 }
60
61 public static MethodEntry getMethodEntry(CtMethod method) {
62 return new MethodEntry(getClassEntry(method.getDeclaringClass()), method.getName(), new Signature(method.getMethodInfo().getDescriptor()));
63 }
64
65 public static MethodEntry getMethodEntry(MethodCall call) {
66 return new MethodEntry(new ClassEntry(Descriptor.toJvmName(call.getClassName())), call.getMethodName(), new Signature(call.getSignature()));
67 }
68
69 public static ConstructorEntry getConstructorEntry(CtConstructor constructor) {
70 if (constructor.isClassInitializer()) {
71 return new ConstructorEntry(getClassEntry(constructor.getDeclaringClass()));
72 } else {
73 return new ConstructorEntry(getClassEntry(constructor.getDeclaringClass()), new Signature(constructor.getMethodInfo().getDescriptor()));
74 }
75 }
76
77 public static ConstructorEntry getConstructorEntry(ConstructorCall call) {
78 return new ConstructorEntry(new ClassEntry(Descriptor.toJvmName(call.getClassName())), new Signature(call.getSignature()));
79 }
80
81 public static ConstructorEntry getConstructorEntry(NewExpr call) {
82 return new ConstructorEntry(new ClassEntry(Descriptor.toJvmName(call.getClassName())), new Signature(call.getSignature()));
83 }
84
85 public static BehaviorEntry getBehaviorEntry(CtBehavior behavior) {
86 if (behavior instanceof CtMethod) {
87 return getMethodEntry((CtMethod) behavior);
88 } else if (behavior instanceof CtConstructor) {
89 return getConstructorEntry((CtConstructor) behavior);
90 }
91 throw new Error("behavior is neither Method nor Constructor!");
92 }
93
94 public static BehaviorEntry getBehaviorEntry(String className, String behaviorName, String behaviorSignature) {
95 return getBehaviorEntry(new ClassEntry(className), behaviorName, new Signature(behaviorSignature));
96 }
97
98 public static BehaviorEntry getBehaviorEntry(String className, String behaviorName) {
99 return getBehaviorEntry(new ClassEntry(className), behaviorName);
100 }
101
102 public static BehaviorEntry getBehaviorEntry(String className) {
103 return new ConstructorEntry(new ClassEntry(className));
104 }
105
106 public static BehaviorEntry getBehaviorEntry(ClassEntry classEntry, String behaviorName, Signature behaviorSignature) {
107 switch (behaviorName) {
108 case "<init>":
109 return new ConstructorEntry(classEntry, behaviorSignature);
110 case "<clinit>":
111 return new ConstructorEntry(classEntry);
112 default:
113 return new MethodEntry(classEntry, behaviorName, behaviorSignature);
114 }
115 }
116
117 public static BehaviorEntry getBehaviorEntry(ClassEntry classEntry, String behaviorName) {
118 if (behaviorName.equals("<clinit>")) {
119 return new ConstructorEntry(classEntry);
120 } else {
121 throw new IllegalArgumentException("Only class initializers don't have signatures");
122 }
123 }
124
125 public static BehaviorEntry getObfBehaviorEntry(ClassEntry classEntry, MethodMapping methodMapping) {
126 return getBehaviorEntry(classEntry, methodMapping.getObfName(), methodMapping.getObfSignature());
127 }
128
129 public static BehaviorEntry getObfBehaviorEntry(ClassMapping classMapping, MethodMapping methodMapping) {
130 return getObfBehaviorEntry(getObfClassEntry(classMapping), methodMapping);
131 }
132}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.mapping;
13
14import cuchaz.enigma.utils.Utils;
15
16public class FieldEntry implements Entry {
17
18 private ClassEntry classEntry;
19 private String name;
20 private Type type;
21
22 // NOTE: this argument order is important for the MethodReader/MethodWriter
23 public FieldEntry(ClassEntry classEntry, String name, Type type) {
24 if (classEntry == null) {
25 throw new IllegalArgumentException("Class cannot be null!");
26 }
27 if (name == null) {
28 throw new IllegalArgumentException("Field name cannot be null!");
29 }
30 if (type == null) {
31 throw new IllegalArgumentException("Field type cannot be null!");
32 }
33
34 this.classEntry = classEntry;
35 this.name = name;
36 this.type = type;
37 }
38
39 public FieldEntry(FieldEntry other, ClassEntry newClassEntry) {
40 this.classEntry = newClassEntry;
41 this.name = other.name;
42 this.type = other.type;
43 }
44
45 @Override
46 public ClassEntry getClassEntry() {
47 return this.classEntry;
48 }
49
50 @Override
51 public String getName() {
52 return this.name;
53 }
54
55 @Override
56 public String getClassName() {
57 return this.classEntry.getName();
58 }
59
60 public Type getType() {
61 return this.type;
62 }
63
64 @Override
65 public FieldEntry cloneToNewClass(ClassEntry classEntry) {
66 return new FieldEntry(this, classEntry);
67 }
68
69 @Override
70 public int hashCode() {
71 return Utils.combineHashesOrdered(this.classEntry, this.name, this.type);
72 }
73
74 @Override
75 public boolean equals(Object other) {
76 return other instanceof FieldEntry && equals((FieldEntry) other);
77 }
78
79 public boolean equals(FieldEntry other) {
80 return this.classEntry.equals(other.classEntry) && this.name.equals(other.name) && this.type.equals(other.type);
81 }
82
83 @Override
84 public String toString() {
85 return this.classEntry.getName() + "." + this.name + ":" + this.type;
86 }
87}
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 @@
11 11
12package cuchaz.enigma.mapping; 12package cuchaz.enigma.mapping;
13 13
14import cuchaz.enigma.mapping.entry.ClassEntry;
15import cuchaz.enigma.mapping.entry.FieldEntry;
14import cuchaz.enigma.throwables.IllegalNameException; 16import cuchaz.enigma.throwables.IllegalNameException;
15 17
16public class FieldMapping implements Comparable<FieldMapping>, MemberMapping<FieldEntry> { 18public class FieldMapping implements Comparable<FieldMapping>, MemberMapping<FieldEntry> {
17 19
18 private String obfName; 20 private String obfName;
19 private String deobfName; 21 private String deobfName;
20 private Type obfType; 22 private TypeDescriptor obfDesc;
21 private Mappings.EntryModifier modifier; 23 private Mappings.EntryModifier modifier;
22 24
23 public FieldMapping(String obfName, Type obfType, String deobfName, Mappings.EntryModifier modifier) { 25 public FieldMapping(String obfName, TypeDescriptor obfDesc, String deobfName, Mappings.EntryModifier modifier) {
24 this.obfName = obfName; 26 this.obfName = obfName;
25 this.deobfName = NameValidator.validateFieldName(deobfName); 27 this.deobfName = NameValidator.validateFieldName(deobfName);
26 this.obfType = obfType; 28 this.obfDesc = obfDesc;
27 this.modifier = modifier; 29 this.modifier = modifier;
28 } 30 }
29 31
30 public FieldMapping(FieldMapping other, ClassNameReplacer obfClassNameReplacer) {
31 this.obfName = other.obfName;
32 this.deobfName = other.deobfName;
33 this.modifier = other.modifier;
34 this.obfType = new Type(other.obfType, obfClassNameReplacer);
35 }
36
37 @Override 32 @Override
38 public FieldEntry getObfEntry(ClassEntry classEntry) { 33 public FieldEntry getObfEntry(ClassEntry classEntry) {
39 return new FieldEntry(classEntry, this.obfName, this.obfType); 34 return new FieldEntry(classEntry, this.obfName, this.obfDesc);
40 } 35 }
41 36
42 @Override 37 @Override
@@ -65,12 +60,12 @@ public class FieldMapping implements Comparable<FieldMapping>, MemberMapping<Fie
65 this.deobfName = NameValidator.validateFieldName(val); 60 this.deobfName = NameValidator.validateFieldName(val);
66 } 61 }
67 62
68 public Type getObfType() { 63 public TypeDescriptor getObfDesc() {
69 return this.obfType; 64 return this.obfDesc;
70 } 65 }
71 66
72 public void setObfType(Type val) { 67 public void setObfDesc(TypeDescriptor val) {
73 this.obfType = val; 68 this.obfDesc = val;
74 } 69 }
75 70
76 public Mappings.EntryModifier getModifier() { 71 public Mappings.EntryModifier getModifier() {
@@ -83,21 +78,20 @@ public class FieldMapping implements Comparable<FieldMapping>, MemberMapping<Fie
83 78
84 @Override 79 @Override
85 public int compareTo(FieldMapping other) { 80 public int compareTo(FieldMapping other) {
86 return (this.obfName + this.obfType).compareTo(other.obfName + other.obfType); 81 return (this.obfName + this.obfDesc).compareTo(other.obfName + other.obfDesc);
87 } 82 }
88 83
89 public boolean renameObfClass(final String oldObfClassName, final String newObfClassName) { 84 public boolean renameObfClass(final String oldObfClassName, final String newObfClassName) {
90 // rename obf classes in the type 85 // rename obf classes in the desc
91 Type newType = new Type(this.obfType, className -> 86 TypeDescriptor newDesc = this.obfDesc.remap(className -> {
92 {
93 if (className.equals(oldObfClassName)) { 87 if (className.equals(oldObfClassName)) {
94 return newObfClassName; 88 return newObfClassName;
95 } 89 }
96 return null; 90 return className;
97 }); 91 });
98 92
99 if (!newType.equals(this.obfType)) { 93 if (!newDesc.equals(this.obfDesc)) {
100 this.obfType = newType; 94 this.obfDesc = newDesc;
101 return true; 95 return true;
102 } 96 }
103 return false; 97 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 @@
1package cuchaz.enigma.mapping;
2
3import cuchaz.enigma.utils.Utils;
4
5/**
6 * Desc...
7 * Created by Thog
8 * 19/10/2016
9 */
10public class LocalVariableEntry implements Entry {
11
12 protected final BehaviorEntry behaviorEntry;
13 protected final String name;
14 protected final Type type;
15 protected final int index;
16
17 public LocalVariableEntry(BehaviorEntry behaviorEntry, int index, String name, Type type) {
18 if (behaviorEntry == null) {
19 throw new IllegalArgumentException("Behavior cannot be null!");
20 }
21 if (index < 0) {
22 throw new IllegalArgumentException("Index must be non-negative!");
23 }
24 if (name == null) {
25 throw new IllegalArgumentException("Variable name cannot be null!");
26 }
27 if (type == null) {
28 throw new IllegalArgumentException("Variable type cannot be null!");
29 }
30
31 this.behaviorEntry = behaviorEntry;
32 this.name = name;
33 this.type = type;
34 this.index = index;
35 }
36
37 public LocalVariableEntry(LocalVariableEntry other, ClassEntry newClassEntry) {
38 this.behaviorEntry = (BehaviorEntry) other.behaviorEntry.cloneToNewClass(newClassEntry);
39 this.name = other.name;
40 this.type = other.type;
41 this.index = other.index;
42 }
43
44 public BehaviorEntry getBehaviorEntry() {
45 return this.behaviorEntry;
46 }
47
48 public Type getType() {
49 return type;
50 }
51
52 public int getIndex() {
53 return index;
54 }
55
56 @Override
57 public String getName() {
58 return this.name;
59 }
60
61 @Override
62 public ClassEntry getClassEntry() {
63 return this.behaviorEntry.getClassEntry();
64 }
65
66 @Override
67 public String getClassName() {
68 return this.behaviorEntry.getClassName();
69 }
70
71 @Override
72 public LocalVariableEntry cloneToNewClass(ClassEntry classEntry) {
73 return new LocalVariableEntry(this, classEntry);
74 }
75
76 public String getMethodName() {
77 return this.behaviorEntry.getName();
78 }
79
80 public Signature getMethodSignature() {
81 return this.behaviorEntry.getSignature();
82 }
83
84 @Override
85 public int hashCode() {
86 return Utils.combineHashesOrdered(this.behaviorEntry, this.type.hashCode(), this.name.hashCode(), Integer.hashCode(this.index));
87 }
88
89 @Override
90 public boolean equals(Object other) {
91 return other instanceof LocalVariableEntry && equals((LocalVariableEntry) other);
92 }
93
94 public boolean equals(LocalVariableEntry other) {
95 return this.behaviorEntry.equals(other.behaviorEntry) && this.type.equals(other.type) && this.name.equals(other.name) && this.index == other.index;
96 }
97
98 @Override
99 public String toString() {
100 return this.behaviorEntry + "(" + this.index + ":" + this.name + ":" + this.type + ")";
101 }
102}
diff --git a/src/main/java/cuchaz/enigma/mapping/ArgumentMapping.java b/src/main/java/cuchaz/enigma/mapping/LocalVariableMapping.java
index 91ecd106..62dbcf31 100644
--- a/src/main/java/cuchaz/enigma/mapping/ArgumentMapping.java
+++ b/src/main/java/cuchaz/enigma/mapping/LocalVariableMapping.java
@@ -11,18 +11,21 @@
11 11
12package cuchaz.enigma.mapping; 12package cuchaz.enigma.mapping;
13 13
14public class ArgumentMapping implements Comparable<ArgumentMapping> { 14import cuchaz.enigma.mapping.entry.LocalVariableEntry;
15import cuchaz.enigma.mapping.entry.MethodEntry;
16
17public class LocalVariableMapping implements Comparable<LocalVariableMapping> {
15 18
16 private int index; 19 private int index;
17 private String name; 20 private String name;
18 21
19 // NOTE: this argument order is important for the MethodReader/MethodWriter 22 // NOTE: this argument order is important for the MethodReader/MethodWriter
20 public ArgumentMapping(int index, String name) { 23 public LocalVariableMapping(int index, String name) {
21 this.index = index; 24 this.index = index;
22 this.name = NameValidator.validateArgumentName(name); 25 this.name = NameValidator.validateArgumentName(name);
23 } 26 }
24 27
25 public ArgumentMapping(ArgumentMapping other) { 28 public LocalVariableMapping(LocalVariableMapping other) {
26 this.index = other.index; 29 this.index = other.index;
27 this.name = other.name; 30 this.name = other.name;
28 } 31 }
@@ -39,12 +42,12 @@ public class ArgumentMapping implements Comparable<ArgumentMapping> {
39 this.name = NameValidator.validateArgumentName(val); 42 this.name = NameValidator.validateArgumentName(val);
40 } 43 }
41 44
42 public ArgumentEntry getObfEntry(BehaviorEntry behaviorEntry) { 45 public LocalVariableEntry getObfEntry(MethodEntry methodEntry) {
43 return new ArgumentEntry(behaviorEntry, index, name); 46 return new LocalVariableEntry(methodEntry, index, name);
44 } 47 }
45 48
46 @Override 49 @Override
47 public int compareTo(ArgumentMapping other) { 50 public int compareTo(LocalVariableMapping other) {
48 return Integer.compare(this.index, other.index); 51 return Integer.compare(this.index, other.index);
49 } 52 }
50} 53}
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;
15import com.google.common.collect.Maps; 15import com.google.common.collect.Maps;
16import com.google.common.collect.Sets; 16import com.google.common.collect.Sets;
17import cuchaz.enigma.analysis.TranslationIndex; 17import cuchaz.enigma.analysis.TranslationIndex;
18import cuchaz.enigma.bytecode.AccessFlags;
19import cuchaz.enigma.mapping.entry.ClassEntry;
20import cuchaz.enigma.mapping.entry.MethodEntry;
18import cuchaz.enigma.throwables.MappingConflict; 21import cuchaz.enigma.throwables.MappingConflict;
19 22
20import java.io.File; 23import java.io.File;
21import java.io.IOException; 24import java.io.IOException;
22import java.util.*; 25import java.util.*;
26import java.util.stream.Collectors;
23 27
24public class Mappings { 28public class Mappings {
25 29
@@ -96,11 +100,11 @@ public class Mappings {
96 100
97 public Translator getTranslator(TranslationDirection direction, TranslationIndex index) { 101 public Translator getTranslator(TranslationDirection direction, TranslationIndex index) {
98 switch (direction) { 102 switch (direction) {
99 case Deobfuscating: 103 case DEOBFUSCATING:
100 104
101 return new Translator(direction, this.classesByObf, index); 105 return new DirectionalTranslator(direction, this.classesByObf, index);
102 106
103 case Obfuscating: 107 case OBFUSCATING:
104 108
105 // fill in the missing deobf class entries with obf entries 109 // fill in the missing deobf class entries with obf entries
106 Map<String, ClassMapping> classes = Maps.newHashMap(); 110 Map<String, ClassMapping> classes = Maps.newHashMap();
@@ -114,9 +118,9 @@ public class Mappings {
114 118
115 // translate the translation index 119 // translate the translation index
116 // NOTE: this isn't actually recursive 120 // NOTE: this isn't actually recursive
117 TranslationIndex deobfIndex = new TranslationIndex(index, getTranslator(TranslationDirection.Deobfuscating, index)); 121 TranslationIndex deobfIndex = new TranslationIndex(index, getTranslator(TranslationDirection.DEOBFUSCATING, index));
118 122
119 return new Translator(direction, classes, deobfIndex); 123 return new DirectionalTranslator(direction, classes, deobfIndex);
120 124
121 default: 125 default:
122 throw new Error("Invalid translation direction!"); 126 throw new Error("Invalid translation direction!");
@@ -151,9 +155,9 @@ public class Mappings {
151 155
152 // add classes from method signatures 156 // add classes from method signatures
153 for (MethodMapping methodMapping : classMapping.methods()) { 157 for (MethodMapping methodMapping : classMapping.methods()) {
154 for (Type type : methodMapping.getObfSignature().types()) { 158 for (TypeDescriptor desc : methodMapping.getObfDesc().types()) {
155 if (type.hasClass()) { 159 if (desc.containsType()) {
156 classNames.add(type.getClassEntry().getClassName()); 160 classNames.add(desc.getTypeEntry().getClassName());
157 } 161 }
158 } 162 }
159 } 163 }
@@ -165,9 +169,9 @@ public class Mappings {
165 return this.classesByDeobf.containsKey(deobfName); 169 return this.classesByDeobf.containsKey(deobfName);
166 } 170 }
167 171
168 public boolean containsDeobfField(ClassEntry obfClassEntry, String deobfName, Type obfType) { 172 public boolean containsDeobfField(ClassEntry obfClassEntry, String deobfName, TypeDescriptor obfDesc) {
169 ClassMapping classMapping = this.classesByObf.get(obfClassEntry.getName()); 173 ClassMapping classMapping = this.classesByObf.get(obfClassEntry.getName());
170 return classMapping != null && classMapping.containsDeobfField(deobfName, obfType); 174 return classMapping != null && classMapping.containsDeobfField(deobfName, obfDesc);
171 } 175 }
172 176
173 public boolean containsDeobfField(ClassEntry obfClassEntry, String deobfName) { 177 public boolean containsDeobfField(ClassEntry obfClassEntry, String deobfName) {
@@ -180,14 +184,14 @@ public class Mappings {
180 return false; 184 return false;
181 } 185 }
182 186
183 public boolean containsDeobfMethod(ClassEntry obfClassEntry, String deobfName, Signature obfSignature) { 187 public boolean containsDeobfMethod(ClassEntry obfClassEntry, String deobfName, MethodDescriptor obfDescriptor) {
184 ClassMapping classMapping = this.classesByObf.get(obfClassEntry.getName()); 188 ClassMapping classMapping = this.classesByObf.get(obfClassEntry.getName());
185 return classMapping != null && classMapping.containsDeobfMethod(deobfName, obfSignature); 189 return classMapping != null && classMapping.containsDeobfMethod(deobfName, obfDescriptor);
186 } 190 }
187 191
188 public boolean containsArgument(BehaviorEntry obfBehaviorEntry, String name) { 192 public boolean containsArgument(MethodEntry obfMethodEntry, String name) {
189 ClassMapping classMapping = this.classesByObf.get(obfBehaviorEntry.getClassName()); 193 ClassMapping classMapping = this.classesByObf.get(obfMethodEntry.getClassName());
190 return classMapping != null && classMapping.containsArgument(obfBehaviorEntry, name); 194 return classMapping != null && classMapping.containsArgument(obfMethodEntry, name);
191 } 195 }
192 196
193 public List<ClassMapping> getClassMappingChain(ClassEntry obfClass) { 197 public List<ClassMapping> getClassMappingChain(ClassEntry obfClass) {
@@ -210,8 +214,14 @@ public class Mappings {
210 214
211 public void savePreviousState() { 215 public void savePreviousState() {
212 this.previousState = new Mappings(this.originMapping); 216 this.previousState = new Mappings(this.originMapping);
213 this.previousState.classesByDeobf = Maps.newHashMap(this.classesByDeobf); 217 this.previousState.classesByDeobf = new HashMap<>();
214 this.previousState.classesByObf = Maps.newHashMap(this.classesByObf); 218 for (Map.Entry<String, ClassMapping> entry : this.classesByDeobf.entrySet()) {
219 this.previousState.classesByDeobf.put(entry.getKey(), entry.getValue().copy());
220 }
221 this.previousState.classesByObf = new HashMap<>();
222 for (Map.Entry<String, ClassMapping> entry : this.classesByObf.entrySet()) {
223 this.previousState.classesByObf.put(entry.getKey(), entry.getValue().copy());
224 }
215 classesByDeobf.values().forEach(ClassMapping::resetDirty); 225 classesByDeobf.values().forEach(ClassMapping::resetDirty);
216 classesByObf.values().forEach(ClassMapping::resetDirty); 226 classesByObf.values().forEach(ClassMapping::resetDirty);
217 } 227 }
@@ -239,5 +249,19 @@ public class Mappings {
239 public String getFormattedName() { 249 public String getFormattedName() {
240 return " ACC:" + super.toString(); 250 return " ACC:" + super.toString();
241 } 251 }
252
253 public AccessFlags transform(AccessFlags access) {
254 switch (this) {
255 case PUBLIC:
256 return access.setPublic();
257 case PROTECTED:
258 return access.setProtected();
259 case PRIVATE:
260 return access.setPrivate();
261 case UNCHANGED:
262 default:
263 return access;
264 }
265 }
242 } 266 }
243} 267}
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;
14import com.google.common.collect.Lists; 14import com.google.common.collect.Lists;
15import com.google.common.collect.Maps; 15import com.google.common.collect.Maps;
16import cuchaz.enigma.analysis.JarIndex; 16import cuchaz.enigma.analysis.JarIndex;
17import cuchaz.enigma.mapping.entry.ClassEntry;
18import cuchaz.enigma.mapping.entry.EntryFactory;
19import cuchaz.enigma.mapping.entry.FieldEntry;
20import cuchaz.enigma.mapping.entry.MethodEntry;
17 21
18import java.util.Map; 22import java.util.Map;
19 23
@@ -23,7 +27,7 @@ public class MappingsChecker {
23 private Map<ClassEntry, ClassMapping> droppedClassMappings; 27 private Map<ClassEntry, ClassMapping> droppedClassMappings;
24 private Map<ClassEntry, ClassMapping> droppedInnerClassMappings; 28 private Map<ClassEntry, ClassMapping> droppedInnerClassMappings;
25 private Map<FieldEntry, FieldMapping> droppedFieldMappings; 29 private Map<FieldEntry, FieldMapping> droppedFieldMappings;
26 private Map<BehaviorEntry, MethodMapping> droppedMethodMappings; 30 private Map<MethodEntry, MethodMapping> droppedMethodMappings;
27 31
28 public MappingsChecker(JarIndex index) { 32 public MappingsChecker(JarIndex index) {
29 this.index = index; 33 this.index = index;
@@ -45,7 +49,7 @@ public class MappingsChecker {
45 return this.droppedFieldMappings; 49 return this.droppedFieldMappings;
46 } 50 }
47 51
48 public Map<BehaviorEntry, MethodMapping> getDroppedMethodMappings() { 52 public Map<MethodEntry, MethodMapping> getDroppedMethodMappings() {
49 return this.droppedMethodMappings; 53 return this.droppedMethodMappings;
50 } 54 }
51 55
@@ -77,10 +81,10 @@ public class MappingsChecker {
77 81
78 // check methods 82 // check methods
79 for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) { 83 for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) {
80 BehaviorEntry obfBehaviorEntry = EntryFactory.getObfBehaviorEntry(classEntry, methodMapping); 84 MethodEntry obfMethodEntry = EntryFactory.getObfMethodEntry(classEntry, methodMapping);
81 if (!this.index.containsObfBehavior(obfBehaviorEntry)) { 85 if (!this.index.containsObfMethod(obfMethodEntry)) {
82 classMapping.removeMethodMapping(methodMapping); 86 classMapping.removeMethodMapping(methodMapping);
83 this.droppedMethodMappings.put(obfBehaviorEntry, methodMapping); 87 this.droppedMethodMappings.put(obfMethodEntry, methodMapping);
84 } 88 }
85 } 89 }
86 90
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;
5import cuchaz.enigma.throwables.MappingConflict; 5import cuchaz.enigma.throwables.MappingConflict;
6import cuchaz.enigma.throwables.MappingParseException; 6import cuchaz.enigma.throwables.MappingParseException;
7 7
8import java.io.*; 8import java.io.BufferedReader;
9import java.io.File;
10import java.io.FileInputStream;
11import java.io.IOException;
12import java.io.InputStream;
13import java.io.InputStreamReader;
9import java.util.Deque; 14import java.util.Deque;
15import java.util.function.Supplier;
10 16
11public class MappingsEnigmaReader { 17public class MappingsEnigmaReader {
12 18
@@ -39,92 +45,95 @@ public class MappingsEnigmaReader {
39 } 45 }
40 46
41 public Mappings readFile(Mappings mappings, File file) throws IOException, MappingParseException { 47 public Mappings readFile(Mappings mappings, File file) throws IOException, MappingParseException {
48 return readFileStream(mappings, new FileInputStream(file), file::getAbsolutePath);
49 }
42 50
43 BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), Charsets.UTF_8)); 51 public Mappings readFileStream(Mappings mappings, InputStream stream, Supplier<String> filenameSupplier) throws IOException, MappingParseException {
44 Deque<Object> mappingStack = Queues.newArrayDeque(); 52 try (BufferedReader in = new BufferedReader(new InputStreamReader(stream, Charsets.UTF_8))) {
53 Deque<Object> mappingStack = Queues.newArrayDeque();
45 54
46 int lineNumber = 0; 55 int lineNumber = 0;
47 String line; 56 String line;
48 while ((line = in.readLine()) != null) { 57 while ((line = in.readLine()) != null) {
49 lineNumber++; 58 lineNumber++;
50 59
51 // strip comments 60 // strip comments
52 int commentPos = line.indexOf('#'); 61 int commentPos = line.indexOf('#');
53 if (commentPos >= 0) { 62 if (commentPos >= 0) {
54 line = line.substring(0, commentPos); 63 line = line.substring(0, commentPos);
55 } 64 }
56 65
57 // skip blank lines 66 // skip blank lines
58 if (line.trim().length() <= 0) { 67 if (line.trim().length() <= 0) {
59 continue; 68 continue;
60 } 69 }
61 70
62 // get the indent of this line 71 // get the indent of this line
63 int indent = 0; 72 int indent = 0;
64 for (int i = 0; i < line.length(); i++) { 73 for (int i = 0; i < line.length(); i++) {
65 if (line.charAt(i) != '\t') { 74 if (line.charAt(i) != '\t') {
66 break; 75 break;
76 }
77 indent++;
67 } 78 }
68 indent++;
69 }
70 79
71 // handle stack pops 80 // handle stack pops
72 while (indent < mappingStack.size()) { 81 while (indent < mappingStack.size()) {
73 mappingStack.pop(); 82 mappingStack.pop();
74 } 83 }
75 84
76 String[] parts = line.trim().split("\\s"); 85 String[] parts = line.trim().split("\\s");
77 try { 86 try {
78 // read the first token 87 // read the first token
79 String token = parts[0]; 88 String token = parts[0];
80 89
81 if (token.equalsIgnoreCase("CLASS")) { 90 if (token.equalsIgnoreCase("CLASS")) {
82 ClassMapping classMapping; 91 ClassMapping classMapping;
83 if (indent <= 0) { 92 if (indent <= 0) {
84 // outer class 93 // outer class
85 classMapping = readClass(parts, false); 94 classMapping = readClass(parts, false);
86 mappings.addClassMapping(classMapping); 95 mappings.addClassMapping(classMapping);
87 } else { 96 } else {
88 97
89 // inner class 98 // inner class
90 if (!(mappingStack.peek() instanceof ClassMapping)) { 99 if (!(mappingStack.peek() instanceof ClassMapping)) {
91 throw new MappingParseException(file, lineNumber, "Unexpected CLASS entry here!"); 100 throw new MappingParseException(filenameSupplier, lineNumber, "Unexpected CLASS entry here!");
101 }
102
103 classMapping = readClass(parts, true);
104 ((ClassMapping) mappingStack.peek()).addInnerClassMapping(classMapping);
92 } 105 }
93 106 mappingStack.push(classMapping);
94 classMapping = readClass(parts, true); 107 } else if (token.equalsIgnoreCase("FIELD")) {
95 ((ClassMapping) mappingStack.peek()).addInnerClassMapping(classMapping); 108 if (mappingStack.isEmpty() || !(mappingStack.peek() instanceof ClassMapping)) {
96 } 109 throw new MappingParseException(filenameSupplier, lineNumber, "Unexpected FIELD entry here!");
97 mappingStack.push(classMapping); 110 }
98 } else if (token.equalsIgnoreCase("FIELD")) { 111 ((ClassMapping) mappingStack.peek()).addFieldMapping(readField(parts));
99 if (mappingStack.isEmpty() || !(mappingStack.peek() instanceof ClassMapping)) { 112 } else if (token.equalsIgnoreCase("METHOD")) {
100 throw new MappingParseException(file, lineNumber, "Unexpected FIELD entry here!"); 113 if (mappingStack.isEmpty() || !(mappingStack.peek() instanceof ClassMapping)) {
101 } 114 throw new MappingParseException(filenameSupplier, lineNumber, "Unexpected METHOD entry here!");
102 ((ClassMapping) mappingStack.peek()).addFieldMapping(readField(parts)); 115 }
103 } else if (token.equalsIgnoreCase("METHOD")) { 116 MethodMapping methodMapping = readMethod(parts);
104 if (mappingStack.isEmpty() || !(mappingStack.peek() instanceof ClassMapping)) { 117 ((ClassMapping) mappingStack.peek()).addMethodMapping(methodMapping);
105 throw new MappingParseException(file, lineNumber, "Unexpected METHOD entry here!"); 118 mappingStack.push(methodMapping);
106 } 119 } else if (token.equalsIgnoreCase("ARG")) {
107 MethodMapping methodMapping = readMethod(parts); 120 if (mappingStack.isEmpty() || !(mappingStack.peek() instanceof MethodMapping)) {
108 ((ClassMapping) mappingStack.peek()).addMethodMapping(methodMapping); 121 throw new MappingParseException(filenameSupplier, lineNumber, "Unexpected ARG entry here!");
109 mappingStack.push(methodMapping); 122 }
110 } else if (token.equalsIgnoreCase("ARG")) { 123 ((MethodMapping) mappingStack.peek()).addArgumentMapping(readArgument(parts));
111 if (mappingStack.isEmpty() || !(mappingStack.peek() instanceof MethodMapping)) {
112 throw new MappingParseException(file, lineNumber, "Unexpected ARG entry here!");
113 } 124 }
114 ((MethodMapping) mappingStack.peek()).addArgumentMapping(readArgument(parts)); 125 } catch (ArrayIndexOutOfBoundsException | IllegalArgumentException ex) {
126 throw new MappingParseException(filenameSupplier, lineNumber, "Malformed line:\n" + line);
127 } catch (MappingConflict e) {
128 throw new MappingParseException(filenameSupplier, lineNumber, e.getMessage());
115 } 129 }
116 } catch (ArrayIndexOutOfBoundsException | IllegalArgumentException ex) {
117 throw new MappingParseException(file, lineNumber, "Malformed line:\n" + line);
118 } catch (MappingConflict e) {
119 throw new MappingParseException(file, lineNumber, e.getMessage());
120 } 130 }
131 return mappings;
121 } 132 }
122 in.close();
123 return mappings;
124 } 133 }
125 134
126 private ArgumentMapping readArgument(String[] parts) { 135 private LocalVariableMapping readArgument(String[] parts) {
127 return new ArgumentMapping(Integer.parseInt(parts[1]), parts[2]); 136 return new LocalVariableMapping(Integer.parseInt(parts[1]), parts[2]);
128 } 137 }
129 138
130 private ClassMapping readClass(String[] parts, boolean makeSimple) { 139 private ClassMapping readClass(String[] parts, boolean makeSimple) {
@@ -150,27 +159,27 @@ public class MappingsEnigmaReader {
150 if (parts.length == 4) { 159 if (parts.length == 4) {
151 boolean access = parts[3].startsWith("ACC:"); 160 boolean access = parts[3].startsWith("ACC:");
152 if (access) 161 if (access)
153 mapping = new FieldMapping(parts[1], new Type(parts[2]), null, 162 mapping = new FieldMapping(parts[1], new TypeDescriptor(parts[2]), null,
154 Mappings.EntryModifier.valueOf(parts[3].substring(4))); 163 Mappings.EntryModifier.valueOf(parts[3].substring(4)));
155 else 164 else
156 mapping = new FieldMapping(parts[1], new Type(parts[3]), parts[2], Mappings.EntryModifier.UNCHANGED); 165 mapping = new FieldMapping(parts[1], new TypeDescriptor(parts[3]), parts[2], Mappings.EntryModifier.UNCHANGED);
157 } else if (parts.length == 5) 166 } else if (parts.length == 5)
158 mapping = new FieldMapping(parts[1], new Type(parts[3]), parts[2], Mappings.EntryModifier.valueOf(parts[4].substring(4))); 167 mapping = new FieldMapping(parts[1], new TypeDescriptor(parts[3]), parts[2], Mappings.EntryModifier.valueOf(parts[4].substring(4)));
159 return mapping; 168 return mapping;
160 } 169 }
161 170
162 private MethodMapping readMethod(String[] parts) { 171 private MethodMapping readMethod(String[] parts) {
163 MethodMapping mapping = null; 172 MethodMapping mapping = null;
164 if (parts.length == 3) 173 if (parts.length == 3)
165 mapping = new MethodMapping(parts[1], new Signature(parts[2])); 174 mapping = new MethodMapping(parts[1], new MethodDescriptor(parts[2]));
166 else if (parts.length == 4) { 175 else if (parts.length == 4) {
167 boolean access = parts[3].startsWith("ACC:"); 176 boolean access = parts[3].startsWith("ACC:");
168 if (access) 177 if (access)
169 mapping = new MethodMapping(parts[1], new Signature(parts[2]), null, Mappings.EntryModifier.valueOf(parts[3].substring(4))); 178 mapping = new MethodMapping(parts[1], new MethodDescriptor(parts[2]), null, Mappings.EntryModifier.valueOf(parts[3].substring(4)));
170 else 179 else
171 mapping = new MethodMapping(parts[1], new Signature(parts[3]), parts[2]); 180 mapping = new MethodMapping(parts[1], new MethodDescriptor(parts[3]), parts[2]);
172 } else if (parts.length == 5) 181 } else if (parts.length == 5)
173 mapping = new MethodMapping(parts[1], new Signature(parts[3]), parts[2], 182 mapping = new MethodMapping(parts[1], new MethodDescriptor(parts[3]), parts[2],
174 Mappings.EntryModifier.valueOf(parts[4].substring(4))); 183 Mappings.EntryModifier.valueOf(parts[4].substring(4)));
175 return mapping; 184 return mapping;
176 } 185 }
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;
14import com.google.common.base.Charsets; 14import com.google.common.base.Charsets;
15 15
16import java.io.*; 16import java.io.*;
17import java.util.ArrayList; 17import java.util.*;
18import java.util.Collections;
19import java.util.List;
20 18
21public class MappingsEnigmaWriter { 19public class MappingsEnigmaWriter {
22 20
@@ -33,83 +31,67 @@ public class MappingsEnigmaWriter {
33 if (!target.exists() && !target.mkdirs()) 31 if (!target.exists() && !target.mkdirs())
34 throw new IOException("Cannot create mapping directory!"); 32 throw new IOException("Cannot create mapping directory!");
35 33
34 Mappings previousState = mappings.getPreviousState();
36 for (ClassMapping classMapping : sorted(mappings.classes())) { 35 for (ClassMapping classMapping : sorted(mappings.classes())) {
37 if (!classMapping.isDirty()) 36 if (!classMapping.isDirty()) {
38 continue; 37 continue;
39 this.deletePreviousClassMapping(target, classMapping);
40 File obFile = new File(target, classMapping.getObfFullName() + ".mapping");
41 File result;
42 if (classMapping.getDeobfName() == null)
43 result = obFile;
44 else {
45 // Make sure that old version of the file doesn't exist
46 if (obFile.exists())
47 obFile.delete();
48 result = new File(target, classMapping.getDeobfName() + ".mapping");
49 } 38 }
50 39
51 if (!result.getParentFile().exists()) 40 if (previousState != null) {
52 result.getParentFile().mkdirs(); 41 ClassMapping previousClass = previousState.classesByObf.get(classMapping.getObfFullName());
42 File previousFile;
43 if (previousClass != null) {
44 previousFile = new File(target, previousClass.getSaveName() + ".mapping");
45 } else {
46 previousFile = new File(target, classMapping.getObfFullName() + ".mapping");
47 }
48 if (previousFile.exists() && !previousFile.delete()) {
49 System.err.println("Failed to delete old class mapping " + previousFile.getName());
50 }
51 }
52
53 File result = new File(target, classMapping.getSaveName() + ".mapping");
54
55 File packageFile = result.getParentFile();
56 if (!packageFile.exists()) {
57 packageFile.mkdirs();
58 }
53 result.createNewFile(); 59 result.createNewFile();
54 PrintWriter outputWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(result), Charsets.UTF_8)); 60
55 write(outputWriter, classMapping, 0); 61 try (PrintWriter outputWriter = new PrintWriter(new BufferedWriter(new FileWriter(result)))) {
56 outputWriter.close(); 62 write(outputWriter, classMapping, 0);
63 }
57 } 64 }
58 65
59 // Remove dropped mappings 66 // Remove dropped mappings
60 if (mappings.getPreviousState() != null) { 67 if (previousState != null) {
61 List<ClassMapping> droppedClassMappings = new ArrayList<>(mappings.getPreviousState().classes()); 68 Set<ClassMapping> droppedClassMappings = new HashSet<>(previousState.classes());
62 List<ClassMapping> classMappings = new ArrayList<>(mappings.classes()); 69 droppedClassMappings.removeAll(mappings.classes());
63 droppedClassMappings.removeAll(classMappings); 70 for (ClassMapping droppedMapping : droppedClassMappings) {
64 for (ClassMapping classMapping : droppedClassMappings) { 71 File result = new File(target, droppedMapping.getSaveName() + ".mapping");
65 File obFile = new File(target, classMapping.getObfFullName() + ".mapping"); 72 if (!result.exists()) {
66 File result; 73 continue;
67 if (classMapping.getDeobfName() == null) 74 }
68 result = obFile; 75 if (!result.delete()) {
69 else { 76 System.err.println("Failed to delete dropped class mapping " + result.getName());
70 // Make sure that old version of the file doesn't exist
71 if (obFile.exists())
72 obFile.delete();
73 result = new File(target, classMapping.getDeobfName() + ".mapping");
74 } 77 }
75 if (result.exists())
76 result.delete();
77 } 78 }
78 } 79 }
79 } 80 }
80 81
81 private void deletePreviousClassMapping(File target, ClassMapping classMapping) {
82 File prevFile = null;
83 // Deob rename
84 if (classMapping.getDeobfName() != null && classMapping.getPreviousDeobfName() != null && !classMapping.getPreviousDeobfName().equals(classMapping.getDeobfName())) {
85 prevFile = new File(target, classMapping.getPreviousDeobfName() + ".mapping");
86 }
87 // Deob to ob rename
88 else if (classMapping.getDeobfName() == null && classMapping.getPreviousDeobfName() != null) {
89 prevFile = new File(target, classMapping.getPreviousDeobfName() + ".mapping");
90 }
91 // Ob to Deob rename
92 else if (classMapping.getDeobfName() != null && classMapping.getPreviousDeobfName() == null) {
93 prevFile = new File(target, classMapping.getObfFullName() + ".mapping");
94 }
95
96 if (prevFile != null && prevFile.exists())
97 prevFile.delete();
98 }
99
100 public void write(PrintWriter out, Mappings mappings) throws IOException { 82 public void write(PrintWriter out, Mappings mappings) throws IOException {
101 for (ClassMapping classMapping : sorted(mappings.classes())) { 83 for (ClassMapping classMapping : sorted(mappings.classes())) {
102 write(out, classMapping, 0); 84 write(out, classMapping, 0);
103 } 85 }
104 } 86 }
105 87
106 private void write(PrintWriter out, ClassMapping classMapping, int depth) throws IOException { 88 protected void write(PrintWriter out, ClassMapping classMapping, int depth) throws IOException {
107 if (classMapping.getDeobfName() == null) { 89 if (classMapping.getDeobfName() == null) {
108 out.format("%sCLASS %s%s\n", getIndent(depth), classMapping.getObfFullName(), 90 out.format("%sCLASS %s%s\n", getIndent(depth), classMapping.getObfFullName(),
109 classMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : classMapping.getModifier().getFormattedName()); 91 classMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : classMapping.getModifier().getFormattedName());
110 } else { 92 } else {
111 out.format("%sCLASS %s %s%s\n", getIndent(depth), classMapping.getObfFullName(), classMapping.getDeobfName(), 93 out.format("%sCLASS %s %s%s\n", getIndent(depth), classMapping.getObfFullName(), classMapping.getDeobfName(),
112 classMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : classMapping.getModifier().getFormattedName()); 94 classMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : classMapping.getModifier().getFormattedName());
113 } 95 }
114 96
115 for (ClassMapping innerClassMapping : sorted(classMapping.innerClasses())) { 97 for (ClassMapping innerClassMapping : sorted(classMapping.innerClasses())) {
@@ -127,32 +109,32 @@ public class MappingsEnigmaWriter {
127 109
128 private void write(PrintWriter out, FieldMapping fieldMapping, int depth) { 110 private void write(PrintWriter out, FieldMapping fieldMapping, int depth) {
129 if (fieldMapping.getDeobfName() == null) 111 if (fieldMapping.getDeobfName() == null)
130 out.format("%sFIELD %s %s%s\n", getIndent(depth), fieldMapping.getObfName(), fieldMapping.getObfType().toString(), 112 out.format("%sFIELD %s %s%s\n", getIndent(depth), fieldMapping.getObfName(), fieldMapping.getObfDesc().toString(),
131 fieldMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : fieldMapping.getModifier().getFormattedName()); 113 fieldMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : fieldMapping.getModifier().getFormattedName());
132 else 114 else
133 out.format("%sFIELD %s %s %s%s\n", getIndent(depth), fieldMapping.getObfName(), fieldMapping.getDeobfName(), fieldMapping.getObfType().toString(), 115 out.format("%sFIELD %s %s %s%s\n", getIndent(depth), fieldMapping.getObfName(), fieldMapping.getDeobfName(), fieldMapping.getObfDesc().toString(),
134 fieldMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : fieldMapping.getModifier().getFormattedName()); 116 fieldMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : fieldMapping.getModifier().getFormattedName());
135 } 117 }
136 118
137 private void write(PrintWriter out, MethodMapping methodMapping, int depth) throws IOException { 119 private void write(PrintWriter out, MethodMapping methodMapping, int depth) throws IOException {
138 if (methodMapping.getDeobfName() == null) { 120 if (methodMapping.isObfuscated()) {
139 out.format("%sMETHOD %s %s%s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getObfSignature(), 121 out.format("%sMETHOD %s %s%s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getObfDesc(),
140 methodMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : methodMapping.getModifier().getFormattedName()); 122 methodMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : methodMapping.getModifier().getFormattedName());
141 } else { 123 } else {
142 out.format("%sMETHOD %s %s %s%s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getDeobfName(), methodMapping.getObfSignature(), 124 out.format("%sMETHOD %s %s %s%s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getDeobfName(), methodMapping.getObfDesc(),
143 methodMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : methodMapping.getModifier().getFormattedName()); 125 methodMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : methodMapping.getModifier().getFormattedName());
144 } 126 }
145 127
146 for (ArgumentMapping argumentMapping : sorted(methodMapping.arguments())) { 128 for (LocalVariableMapping localVariableMapping : sorted(methodMapping.arguments())) {
147 write(out, argumentMapping, depth + 1); 129 write(out, localVariableMapping, depth + 1);
148 } 130 }
149 } 131 }
150 132
151 private void write(PrintWriter out, ArgumentMapping argumentMapping, int depth) { 133 private void write(PrintWriter out, LocalVariableMapping localVariableMapping, int depth) {
152 out.format("%sARG %d %s\n", getIndent(depth), argumentMapping.getIndex(), argumentMapping.getName()); 134 out.format("%sARG %d %s\n", getIndent(depth), localVariableMapping.getIndex(), localVariableMapping.getName());
153 } 135 }
154 136
155 private <T extends Comparable<T>> List<T> sorted(Iterable<T> classes) { 137 protected <T extends Comparable<T>> List<T> sorted(Iterable<T> classes) {
156 List<T> out = new ArrayList<>(); 138 List<T> out = new ArrayList<>();
157 for (T t : classes) { 139 for (T t : classes) {
158 out.add(t); 140 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;
13 13
14import com.google.common.collect.Lists; 14import com.google.common.collect.Lists;
15import cuchaz.enigma.analysis.JarIndex; 15import cuchaz.enigma.analysis.JarIndex;
16import cuchaz.enigma.mapping.entry.*;
16import cuchaz.enigma.throwables.IllegalNameException; 17import cuchaz.enigma.throwables.IllegalNameException;
17import cuchaz.enigma.throwables.MappingConflict; 18import cuchaz.enigma.throwables.MappingConflict;
18 19
@@ -25,12 +26,14 @@ import java.util.zip.GZIPOutputStream;
25 26
26public class MappingsRenamer { 27public class MappingsRenamer {
27 28
28 private JarIndex index; 29 private final JarIndex index;
30 private final ReferencedEntryPool entryPool;
29 private Mappings mappings; 31 private Mappings mappings;
30 32
31 public MappingsRenamer(JarIndex index, Mappings mappings) { 33 public MappingsRenamer(JarIndex index, Mappings mappings, ReferencedEntryPool entryPool) {
32 this.index = index; 34 this.index = index;
33 this.mappings = mappings; 35 this.mappings = mappings;
36 this.entryPool = entryPool;
34 } 37 }
35 38
36 public void setMappings(Mappings mappings) { 39 public void setMappings(Mappings mappings) {
@@ -46,7 +49,7 @@ public class MappingsRenamer {
46 49
47 if (deobfName != null) { 50 if (deobfName != null) {
48 // make sure we don't rename to an existing obf or deobf class 51 // make sure we don't rename to an existing obf or deobf class
49 if (mappings.containsDeobfClass(deobfName) || index.containsObfClass(new ClassEntry(deobfName))) { 52 if (mappings.containsDeobfClass(deobfName) || index.containsObfClass(entryPool.getClass(deobfName))) {
50 throw new IllegalNameException(deobfName, "There is already a class with that name"); 53 throw new IllegalNameException(deobfName, "There is already a class with that name");
51 } 54 }
52 } 55 }
@@ -87,13 +90,13 @@ public class MappingsRenamer {
87 90
88 public void setFieldName(FieldEntry obf, String deobfName) { 91 public void setFieldName(FieldEntry obf, String deobfName) {
89 deobfName = NameValidator.validateFieldName(deobfName); 92 deobfName = NameValidator.validateFieldName(deobfName);
90 FieldEntry targetEntry = new FieldEntry(obf.getClassEntry(), deobfName, obf.getType()); 93 FieldEntry targetEntry = entryPool.getField(obf.getOwnerClassEntry(), deobfName, obf.getDesc());
91 ClassEntry definedClass = null; 94 ClassEntry definedClass = null;
92 if (mappings.containsDeobfField(obf.getClassEntry(), deobfName) || index.containsEntryWithSameName(targetEntry)) 95 if (mappings.containsDeobfField(obf.getOwnerClassEntry(), deobfName) || index.containsEntryWithSameName(targetEntry))
93 definedClass = obf.getClassEntry(); 96 definedClass = obf.getOwnerClassEntry();
94 else { 97 else {
95 for (ClassEntry ancestorEntry : this.index.getTranslationIndex().getAncestry(obf.getClassEntry())) { 98 for (ClassEntry ancestorEntry : this.index.getTranslationIndex().getAncestry(obf.getOwnerClassEntry())) {
96 if (mappings.containsDeobfField(ancestorEntry, deobfName) || index.containsEntryWithSameName(targetEntry.cloneToNewClass(ancestorEntry))) { 99 if (mappings.containsDeobfField(ancestorEntry, deobfName) || index.containsEntryWithSameName(targetEntry.updateOwnership(ancestorEntry))) {
97 definedClass = ancestorEntry; 100 definedClass = ancestorEntry;
98 break; 101 break;
99 } 102 }
@@ -101,42 +104,44 @@ public class MappingsRenamer {
101 } 104 }
102 105
103 if (definedClass != null) { 106 if (definedClass != null) {
104 String className = mappings.getTranslator(TranslationDirection.Deobfuscating, index.getTranslationIndex()).translateClass(definedClass.getClassName()); 107 Translator translator = mappings.getTranslator(TranslationDirection.DEOBFUSCATING, index.getTranslationIndex());
108 String className = translator.getTranslatedClass(entryPool.getClass(definedClass.getClassName())).getName();
105 if (className == null) 109 if (className == null)
106 className = definedClass.getClassName(); 110 className = definedClass.getClassName();
107 throw new IllegalNameException(deobfName, "There is already a field with that name in " + className); 111 throw new IllegalNameException(deobfName, "There is already a field with that name in " + className);
108 } 112 }
109 113
110 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); 114 ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry());
111 classMapping.setFieldName(obf.getName(), obf.getType(), deobfName); 115 classMapping.setFieldName(obf.getName(), obf.getDesc(), deobfName);
112 } 116 }
113 117
114 public void removeFieldMapping(FieldEntry obf) { 118 public void removeFieldMapping(FieldEntry obf) {
115 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); 119 ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry());
116 classMapping.removeFieldMapping(classMapping.getFieldByObf(obf.getName(), obf.getType())); 120 classMapping.removeFieldMapping(classMapping.getFieldByObf(obf.getName(), obf.getDesc()));
117 } 121 }
118 122
119 public void markFieldAsDeobfuscated(FieldEntry obf) { 123 public void markFieldAsDeobfuscated(FieldEntry obf) {
120 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); 124 ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry());
121 classMapping.setFieldName(obf.getName(), obf.getType(), obf.getName()); 125 classMapping.setFieldName(obf.getName(), obf.getDesc(), obf.getName());
122 } 126 }
123 127
124 private void validateMethodTreeName(MethodEntry entry, String deobfName) { 128 private void validateMethodTreeName(MethodEntry entry, String deobfName) {
125 MethodEntry targetEntry = new MethodEntry(entry.getClassEntry(), deobfName, entry.getSignature()); 129 MethodEntry targetEntry = entryPool.getMethod(entry.getOwnerClassEntry(), deobfName, entry.getDesc());
126 130
127 // TODO: Verify if I don't break things 131 // TODO: Verify if I don't break things
128 ClassMapping classMapping = mappings.getClassByObf(entry.getClassEntry()); 132 ClassMapping classMapping = mappings.getClassByObf(entry.getOwnerClassEntry());
129 if ((classMapping != null && classMapping.containsDeobfMethod(deobfName, entry.getSignature()) && classMapping.getMethodByObf(entry.getName(), entry.getSignature()) != classMapping.getMethodByDeobf(deobfName, entry.getSignature())) 133 if ((classMapping != null && classMapping.containsDeobfMethod(deobfName, entry.getDesc()) && classMapping.getMethodByObf(entry.getName(), entry.getDesc()) != classMapping.getMethodByDeobf(deobfName, entry.getDesc()))
130 || index.containsObfBehavior(targetEntry)) { 134 || index.containsObfMethod(targetEntry)) {
131 String deobfClassName = mappings.getTranslator(TranslationDirection.Deobfuscating, index.getTranslationIndex()).translateClass(entry.getClassName()); 135 Translator translator = mappings.getTranslator(TranslationDirection.DEOBFUSCATING, index.getTranslationIndex());
136 String deobfClassName = translator.getTranslatedClass(entryPool.getClass(entry.getClassName())).getClassName();
132 if (deobfClassName == null) { 137 if (deobfClassName == null) {
133 deobfClassName = entry.getClassName(); 138 deobfClassName = entry.getClassName();
134 } 139 }
135 throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName); 140 throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName);
136 } 141 }
137 142
138 for (ClassEntry child : index.getTranslationIndex().getSubclass(entry.getClassEntry())) { 143 for (ClassEntry child : index.getTranslationIndex().getSubclass(entry.getOwnerClassEntry())) {
139 validateMethodTreeName(entry.cloneToNewClass(child), deobfName); 144 validateMethodTreeName(entry.updateOwnership(child), deobfName);
140 } 145 }
141 } 146 }
142 147
@@ -155,20 +160,21 @@ public class MappingsRenamer {
155 160
156 public void setMethodName(MethodEntry obf, String deobfName) { 161 public void setMethodName(MethodEntry obf, String deobfName) {
157 deobfName = NameValidator.validateMethodName(deobfName); 162 deobfName = NameValidator.validateMethodName(deobfName);
158 MethodEntry targetEntry = new MethodEntry(obf.getClassEntry(), deobfName, obf.getSignature()); 163 MethodEntry targetEntry = entryPool.getMethod(obf.getOwnerClassEntry(), deobfName, obf.getDesc());
159 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); 164 ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry());
160 165
161 // TODO: Verify if I don't break things 166 // TODO: Verify if I don't break things
162 if ((mappings.containsDeobfMethod(obf.getClassEntry(), deobfName, obf.getSignature()) && classMapping.getMethodByObf(obf.getName(), obf.getSignature()) != classMapping.getMethodByDeobf(deobfName, obf.getSignature())) 167 if ((mappings.containsDeobfMethod(obf.getOwnerClassEntry(), deobfName, obf.getDesc()) && classMapping.getMethodByObf(obf.getName(), obf.getDesc()) != classMapping.getMethodByDeobf(deobfName, obf.getDesc()))
163 || index.containsObfBehavior(targetEntry)) { 168 || index.containsObfMethod(targetEntry)) {
164 String deobfClassName = mappings.getTranslator(TranslationDirection.Deobfuscating, index.getTranslationIndex()).translateClass(obf.getClassName()); 169 Translator translator = mappings.getTranslator(TranslationDirection.DEOBFUSCATING, index.getTranslationIndex());
170 String deobfClassName = translator.getTranslatedClass(entryPool.getClass(obf.getClassName())).getClassName();
165 if (deobfClassName == null) { 171 if (deobfClassName == null) {
166 deobfClassName = obf.getClassName(); 172 deobfClassName = obf.getClassName();
167 } 173 }
168 throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName); 174 throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName);
169 } 175 }
170 176
171 classMapping.setMethodName(obf.getName(), obf.getSignature(), deobfName); 177 classMapping.setMethodName(obf.getName(), obf.getDesc(), deobfName);
172 } 178 }
173 179
174 public void removeMethodTreeMapping(MethodEntry obf) { 180 public void removeMethodTreeMapping(MethodEntry obf) {
@@ -176,8 +182,8 @@ public class MappingsRenamer {
176 } 182 }
177 183
178 public void removeMethodMapping(MethodEntry obf) { 184 public void removeMethodMapping(MethodEntry obf) {
179 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); 185 ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry());
180 classMapping.setMethodName(obf.getName(), obf.getSignature(), null); 186 classMapping.setMethodName(obf.getName(), obf.getDesc(), null);
181 } 187 }
182 188
183 public void markMethodTreeAsDeobfuscated(MethodEntry obf) { 189 public void markMethodTreeAsDeobfuscated(MethodEntry obf) {
@@ -185,30 +191,25 @@ public class MappingsRenamer {
185 } 191 }
186 192
187 public void markMethodAsDeobfuscated(MethodEntry obf) { 193 public void markMethodAsDeobfuscated(MethodEntry obf) {
188 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); 194 ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry());
189 classMapping.setMethodName(obf.getName(), obf.getSignature(), obf.getName()); 195 classMapping.setMethodName(obf.getName(), obf.getDesc(), obf.getName());
190 } 196 }
191 197
192 public void setArgumentTreeName(ArgumentEntry obf, String deobfName) { 198 public void setLocalVariableTreeName(LocalVariableEntry obf, String deobfName) {
193 if (!(obf.getBehaviorEntry() instanceof MethodEntry)) { 199 MethodEntry obfMethod = obf.getOwnerEntry();
194 setArgumentName(obf, deobfName);
195 return;
196 }
197
198 MethodEntry obfMethod = (MethodEntry) obf.getBehaviorEntry();
199 200
200 Set<MethodEntry> implementations = index.getRelatedMethodImplementations(obfMethod); 201 Set<MethodEntry> implementations = index.getRelatedMethodImplementations(obfMethod);
201 for (MethodEntry entry : implementations) { 202 for (MethodEntry entry : implementations) {
202 ClassMapping classMapping = mappings.getClassByObf(entry.getClassEntry()); 203 ClassMapping classMapping = mappings.getClassByObf(entry.getOwnerClassEntry());
203 if (classMapping != null) { 204 if (classMapping != null) {
204 MethodMapping mapping = classMapping.getMethodByObf(entry.getName(), entry.getSignature()); 205 MethodMapping mapping = classMapping.getMethodByObf(entry.getName(), entry.getDesc());
205 // NOTE: don't need to check arguments for name collisions with names determined by Procyon 206 // NOTE: don't need to check arguments for name collisions with names determined by Procyon
206 // TODO: Verify if I don't break things 207 // TODO: Verify if I don't break things
207 if (mapping != null) { 208 if (mapping != null) {
208 for (ArgumentMapping argumentMapping : Lists.newArrayList(mapping.arguments())) { 209 for (LocalVariableMapping localVariableMapping : Lists.newArrayList(mapping.arguments())) {
209 if (argumentMapping.getIndex() != obf.getIndex()) { 210 if (localVariableMapping.getIndex() != obf.getIndex()) {
210 if (mapping.getDeobfArgumentName(argumentMapping.getIndex()).equals(deobfName) 211 if (mapping.getDeobfLocalVariableName(localVariableMapping.getIndex()).equals(deobfName)
211 || argumentMapping.getName().equals(deobfName)) { 212 || localVariableMapping.getName().equals(deobfName)) {
212 throw new IllegalNameException(deobfName, "There is already an argument with that name"); 213 throw new IllegalNameException(deobfName, "There is already an argument with that name");
213 } 214 }
214 } 215 }
@@ -218,45 +219,45 @@ public class MappingsRenamer {
218 } 219 }
219 220
220 for (MethodEntry entry : implementations) { 221 for (MethodEntry entry : implementations) {
221 setArgumentName(new ArgumentEntry(obf, entry), deobfName); 222 setLocalVariableName(new LocalVariableEntry(entry, obf.getIndex(), obf.getName()), deobfName);
222 } 223 }
223 } 224 }
224 225
225 public void setArgumentName(ArgumentEntry obf, String deobfName) { 226 public void setLocalVariableName(LocalVariableEntry obf, String deobfName) {
226 deobfName = NameValidator.validateArgumentName(deobfName); 227 deobfName = NameValidator.validateArgumentName(deobfName);
227 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); 228 ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry());
228 MethodMapping mapping = classMapping.getMethodByObf(obf.getMethodName(), obf.getMethodSignature()); 229 MethodMapping mapping = classMapping.getMethodByObf(obf.getMethodName(), obf.getMethodDesc());
229 // NOTE: don't need to check arguments for name collisions with names determined by Procyon 230 // NOTE: don't need to check arguments for name collisions with names determined by Procyon
230 // TODO: Verify if I don't break things 231 // TODO: Verify if I don't break things
231 if (mapping != null) { 232 if (mapping != null) {
232 for (ArgumentMapping argumentMapping : Lists.newArrayList(mapping.arguments())) { 233 for (LocalVariableMapping localVariableMapping : Lists.newArrayList(mapping.arguments())) {
233 if (argumentMapping.getIndex() != obf.getIndex()) { 234 if (localVariableMapping.getIndex() != obf.getIndex()) {
234 if (mapping.getDeobfArgumentName(argumentMapping.getIndex()).equals(deobfName) 235 if (mapping.getDeobfLocalVariableName(localVariableMapping.getIndex()).equals(deobfName)
235 || argumentMapping.getName().equals(deobfName)) { 236 || localVariableMapping.getName().equals(deobfName)) {
236 throw new IllegalNameException(deobfName, "There is already an argument with that name"); 237 throw new IllegalNameException(deobfName, "There is already an argument with that name");
237 } 238 }
238 } 239 }
239 } 240 }
240 } 241 }
241 242
242 classMapping.setArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex(), deobfName); 243 classMapping.setArgumentName(obf.getMethodName(), obf.getMethodDesc(), obf.getIndex(), deobfName);
243 } 244 }
244 245
245 public void removeArgumentMapping(ArgumentEntry obf) { 246 public void removeLocalVariableMapping(LocalVariableEntry obf) {
246 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); 247 ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry());
247 classMapping.removeArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex()); 248 classMapping.removeArgumentName(obf.getMethodName(), obf.getMethodDesc(), obf.getIndex());
248 } 249 }
249 250
250 public void markArgumentAsDeobfuscated(ArgumentEntry obf) { 251 public void markArgumentAsDeobfuscated(LocalVariableEntry obf) {
251 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); 252 ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry());
252 classMapping.setArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex(), obf.getName()); 253 classMapping.setArgumentName(obf.getMethodName(), obf.getMethodDesc(), obf.getIndex(), obf.getName());
253 } 254 }
254 255
255 public boolean moveFieldToObfClass(ClassMapping classMapping, FieldMapping fieldMapping, ClassEntry obfClass) { 256 public boolean moveFieldToObfClass(ClassMapping classMapping, FieldMapping fieldMapping, ClassEntry obfClass) {
256 classMapping.removeFieldMapping(fieldMapping); 257 classMapping.removeFieldMapping(fieldMapping);
257 ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass); 258 ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass);
258 if (!targetClassMapping.containsObfField(fieldMapping.getObfName(), fieldMapping.getObfType())) { 259 if (!targetClassMapping.containsObfField(fieldMapping.getObfName(), fieldMapping.getObfDesc())) {
259 if (!targetClassMapping.containsDeobfField(fieldMapping.getDeobfName(), fieldMapping.getObfType())) { 260 if (!targetClassMapping.containsDeobfField(fieldMapping.getDeobfName(), fieldMapping.getObfDesc())) {
260 targetClassMapping.addFieldMapping(fieldMapping); 261 targetClassMapping.addFieldMapping(fieldMapping);
261 return true; 262 return true;
262 } else { 263 } else {
@@ -269,12 +270,12 @@ public class MappingsRenamer {
269 public boolean moveMethodToObfClass(ClassMapping classMapping, MethodMapping methodMapping, ClassEntry obfClass) { 270 public boolean moveMethodToObfClass(ClassMapping classMapping, MethodMapping methodMapping, ClassEntry obfClass) {
270 classMapping.removeMethodMapping(methodMapping); 271 classMapping.removeMethodMapping(methodMapping);
271 ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass); 272 ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass);
272 if (!targetClassMapping.containsObfMethod(methodMapping.getObfName(), methodMapping.getObfSignature())) { 273 if (!targetClassMapping.containsObfMethod(methodMapping.getObfName(), methodMapping.getObfDesc())) {
273 if (!targetClassMapping.containsDeobfMethod(methodMapping.getDeobfName(), methodMapping.getObfSignature())) { 274 if (!targetClassMapping.containsDeobfMethod(methodMapping.getDeobfName(), methodMapping.getObfDesc())) {
274 targetClassMapping.addMethodMapping(methodMapping); 275 targetClassMapping.addMethodMapping(methodMapping);
275 return true; 276 return true;
276 } else { 277 } else {
277 System.err.println("WARNING: deobf method was already there: " + obfClass + "." + methodMapping.getDeobfName() + methodMapping.getObfSignature()); 278 System.err.println("WARNING: deobf method was already there: " + obfClass + "." + methodMapping.getDeobfName() + methodMapping.getObfDesc());
278 } 279 }
279 } 280 }
280 return false; 281 return false;
@@ -326,12 +327,35 @@ public class MappingsRenamer {
326 } 327 }
327 328
328 public void setFieldModifier(FieldEntry obEntry, Mappings.EntryModifier modifier) { 329 public void setFieldModifier(FieldEntry obEntry, Mappings.EntryModifier modifier) {
329 ClassMapping classMapping = getOrCreateClassMapping(obEntry.getClassEntry()); 330 ClassMapping classMapping = getOrCreateClassMapping(obEntry.getOwnerClassEntry());
330 classMapping.setFieldModifier(obEntry.getName(), obEntry.getType(), modifier); 331 classMapping.setFieldModifier(obEntry.getName(), obEntry.getDesc(), modifier);
332 }
333
334 public void setMethodModifier(MethodEntry obEntry, Mappings.EntryModifier modifier) {
335 ClassMapping classMapping = getOrCreateClassMapping(obEntry.getOwnerClassEntry());
336 classMapping.setMethodModifier(obEntry.getName(), obEntry.getDesc(), modifier);
337 }
338
339 public Mappings.EntryModifier getClassModifier(ClassEntry obfEntry) {
340 ClassMapping classMapping = getOrCreateClassMapping(obfEntry);
341 return classMapping.getModifier();
331 } 342 }
332 343
333 public void setMethodModifier(BehaviorEntry obEntry, Mappings.EntryModifier modifier) { 344 public Mappings.EntryModifier getFieldModifier(FieldEntry obfEntry) {
334 ClassMapping classMapping = getOrCreateClassMapping(obEntry.getClassEntry()); 345 ClassMapping classMapping = getOrCreateClassMapping(obfEntry.getOwnerClassEntry());
335 classMapping.setMethodModifier(obEntry.getName(), obEntry.getSignature(), modifier); 346 FieldMapping fieldMapping = classMapping.getFieldByObf(obfEntry);
347 if (fieldMapping == null) {
348 return Mappings.EntryModifier.UNCHANGED;
349 }
350 return fieldMapping.getModifier();
351 }
352
353 public Mappings.EntryModifier getMethodModfifier(MethodEntry obfEntry) {
354 ClassMapping classMapping = getOrCreateClassMapping(obfEntry.getOwnerClassEntry());
355 MethodMapping methodMapping = classMapping.getMethodByObf(obfEntry);
356 if (methodMapping == null) {
357 return Mappings.EntryModifier.UNCHANGED;
358 }
359 return methodMapping.getModifier();
336 } 360 }
337} 361}
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;
2 2
3import com.google.common.base.Charsets; 3import com.google.common.base.Charsets;
4import cuchaz.enigma.analysis.TranslationIndex; 4import cuchaz.enigma.analysis.TranslationIndex;
5import cuchaz.enigma.mapping.entry.ReferencedEntryPool;
5 6
6import java.io.*; 7import java.io.*;
7import java.util.ArrayList; 8import java.util.ArrayList;
@@ -19,7 +20,7 @@ public class MappingsSRGWriter {
19 } 20 }
20 file.createNewFile(); 21 file.createNewFile();
21 22
22 TranslationIndex index = new TranslationIndex(); 23 TranslationIndex index = new TranslationIndex(new ReferencedEntryPool());
23 24
24 PrintWriter writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(file), Charsets.UTF_8)); 25 PrintWriter writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(file), Charsets.UTF_8));
25 List<String> fieldMappings = new ArrayList<>(); 26 List<String> fieldMappings = new ArrayList<>();
@@ -43,7 +44,7 @@ public class MappingsSRGWriter {
43 } 44 }
44 45
45 for (MethodMapping methodMapping : sorted(innerClassMapping.methods())) { 46 for (MethodMapping methodMapping : sorted(innerClassMapping.methods())) {
46 methodMappings.add("MD: " + innerClassName + "/" + methodMapping.getObfName() + " " + methodMapping.getObfSignature() + " " + innerDeobfClassName + "/" + methodMapping.getDeobfName() + " " + mappings.getTranslator(TranslationDirection.Deobfuscating, index).translateSignature(methodMapping.getObfSignature())); 47 methodMappings.add("MD: " + innerClassName + "/" + methodMapping.getObfName() + " " + methodMapping.getObfDesc() + " " + innerDeobfClassName + "/" + methodMapping.getDeobfName() + " " + mappings.getTranslator(TranslationDirection.DEOBFUSCATING, index).getTranslatedMethodDesc(methodMapping.getObfDesc()));
47 } 48 }
48 } 49 }
49 50
@@ -52,7 +53,7 @@ public class MappingsSRGWriter {
52 } 53 }
53 54
54 for (MethodMapping methodMapping : sorted(classMapping.methods())) { 55 for (MethodMapping methodMapping : sorted(classMapping.methods())) {
55 methodMappings.add("MD: " + classMapping.getObfFullName() + "/" + methodMapping.getObfName() + " " + methodMapping.getObfSignature() + " " + classMapping.getDeobfName() + "/" + methodMapping.getDeobfName() + " " + mappings.getTranslator(TranslationDirection.Deobfuscating, index).translateSignature(methodMapping.getObfSignature())); 56 methodMappings.add("MD: " + classMapping.getObfFullName() + "/" + methodMapping.getObfName() + " " + methodMapping.getObfDesc() + " " + classMapping.getDeobfName() + "/" + methodMapping.getDeobfName() + " " + mappings.getTranslator(TranslationDirection.DEOBFUSCATING, index).getTranslatedMethodDesc(methodMapping.getObfDesc()));
56 } 57 }
57 } 58 }
58 for (String fd : fieldMappings) { 59 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;
2 2
3import com.google.common.base.Charsets; 3import com.google.common.base.Charsets;
4import com.google.common.collect.Maps; 4import com.google.common.collect.Maps;
5import cuchaz.enigma.mapping.entry.ClassEntry;
5import cuchaz.enigma.throwables.MappingConflict; 6import cuchaz.enigma.throwables.MappingConflict;
6import cuchaz.enigma.throwables.MappingParseException; 7import cuchaz.enigma.throwables.MappingParseException;
7 8
@@ -20,11 +21,11 @@ public class MappingsTinyReader {
20 } 21 }
21 22
22 public FieldMapping readField(String[] parts) { 23 public FieldMapping readField(String[] parts) {
23 return new FieldMapping(parts[3], new Type(parts[2]), parts[4], Mappings.EntryModifier.UNCHANGED); 24 return new FieldMapping(parts[3], new TypeDescriptor(parts[2]), parts[4], Mappings.EntryModifier.UNCHANGED);
24 } 25 }
25 26
26 public MethodMapping readMethod(String[] parts) { 27 public MethodMapping readMethod(String[] parts) {
27 return new MethodMapping(parts[3], new Signature(parts[2]), parts[4]); 28 return new MethodMapping(parts[3], new MethodDescriptor(parts[2]), parts[4]);
28 } 29 }
29 30
30 public Mappings read(File file) throws IOException, MappingParseException { 31 public Mappings read(File file) throws IOException, MappingParseException {
@@ -72,7 +73,7 @@ public class MappingsTinyReader {
72 break; 73 break;
73 case "MTH-ARG": 74 case "MTH-ARG":
74 classMapping = classMappingMap.computeIfAbsent(parts[1], k -> new ClassMapping(parts[1])); 75 classMapping = classMappingMap.computeIfAbsent(parts[1], k -> new ClassMapping(parts[1]));
75 classMapping.setArgumentName(parts[3], new Signature(parts[2]), Integer.parseInt(parts[4]), parts[5]); 76 classMapping.setArgumentName(parts[3], new MethodDescriptor(parts[2]), Integer.parseInt(parts[4]), parts[5]);
76 break; 77 break;
77 default: 78 default:
78 throw new MappingParseException(file, lineNumber, "Unknown token '" + token + "' !"); 79 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 @@
11 11
12package cuchaz.enigma.mapping; 12package cuchaz.enigma.mapping;
13 13
14import cuchaz.enigma.mapping.entry.ClassEntry;
15import cuchaz.enigma.mapping.entry.Entry;
16
14public interface MemberMapping<T extends Entry> { 17public interface MemberMapping<T extends Entry> {
15 T getObfEntry(ClassEntry classEntry); 18 T getObfEntry(ClassEntry classEntry);
16 19
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.mapping;
13
14import com.google.common.collect.Lists;
15import cuchaz.enigma.mapping.entry.ClassEntry;
16import cuchaz.enigma.utils.Utils;
17
18import java.util.ArrayList;
19import java.util.List;
20import java.util.function.Function;
21
22public class MethodDescriptor {
23
24 private List<TypeDescriptor> argumentDescs;
25 private TypeDescriptor returnDesc;
26
27 public MethodDescriptor(String desc) {
28 try {
29 this.argumentDescs = Lists.newArrayList();
30 int i = 0;
31 while (i < desc.length()) {
32 char c = desc.charAt(i);
33 if (c == '(') {
34 assert (this.argumentDescs.isEmpty());
35 assert (this.returnDesc == null);
36 i++;
37 } else if (c == ')') {
38 i++;
39 break;
40 } else {
41 String type = TypeDescriptor.parseFirst(desc.substring(i));
42 this.argumentDescs.add(new TypeDescriptor(type));
43 i += type.length();
44 }
45 }
46 this.returnDesc = new TypeDescriptor(TypeDescriptor.parseFirst(desc.substring(i)));
47 } catch (Exception ex) {
48 throw new IllegalArgumentException("Unable to parse method descriptor: " + desc, ex);
49 }
50 }
51
52 public MethodDescriptor(List<TypeDescriptor> argumentDescs, TypeDescriptor returnDesc) {
53 this.argumentDescs = argumentDescs;
54 this.returnDesc = returnDesc;
55 }
56
57 public List<TypeDescriptor> getArgumentDescs() {
58 return this.argumentDescs;
59 }
60
61 public TypeDescriptor getReturnDesc() {
62 return this.returnDesc;
63 }
64
65 @Override
66 public String toString() {
67 StringBuilder buf = new StringBuilder();
68 buf.append("(");
69 for (TypeDescriptor desc : this.argumentDescs) {
70 buf.append(desc);
71 }
72 buf.append(")");
73 buf.append(this.returnDesc);
74 return buf.toString();
75 }
76
77 public Iterable<TypeDescriptor> types() {
78 List<TypeDescriptor> descs = Lists.newArrayList();
79 descs.addAll(this.argumentDescs);
80 descs.add(this.returnDesc);
81 return descs;
82 }
83
84 @Override
85 public boolean equals(Object other) {
86 return other instanceof MethodDescriptor && equals((MethodDescriptor) other);
87 }
88
89 public boolean equals(MethodDescriptor other) {
90 return this.argumentDescs.equals(other.argumentDescs) && this.returnDesc.equals(other.returnDesc);
91 }
92
93 @Override
94 public int hashCode() {
95 return Utils.combineHashesOrdered(this.argumentDescs.hashCode(), this.returnDesc.hashCode());
96 }
97
98 public boolean hasClass(ClassEntry classEntry) {
99 for (TypeDescriptor desc : types()) {
100 if (desc.containsType() && desc.getTypeEntry().equals(classEntry)) {
101 return true;
102 }
103 }
104 return false;
105 }
106
107 public MethodDescriptor remap(Function<String, String> remapper) {
108 List<TypeDescriptor> argumentDescs = new ArrayList<>(this.argumentDescs.size());
109 for (TypeDescriptor desc : this.argumentDescs) {
110 argumentDescs.add(desc.remap(remapper));
111 }
112 return new MethodDescriptor(argumentDescs, returnDesc.remap(remapper));
113 }
114}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.mapping;
13
14import cuchaz.enigma.utils.Utils;
15
16public class MethodEntry implements BehaviorEntry {
17
18 private ClassEntry classEntry;
19 private String name;
20 private Signature signature;
21
22 public MethodEntry(ClassEntry classEntry, String name, Signature signature) {
23 if (classEntry == null) {
24 throw new IllegalArgumentException("Class cannot be null!");
25 }
26 if (name == null) {
27 throw new IllegalArgumentException("Method name cannot be null!");
28 }
29 if (signature == null) {
30 throw new IllegalArgumentException("Method signature cannot be null!");
31 }
32 if (name.startsWith("<")) {
33 throw new IllegalArgumentException("Don't use MethodEntry for a constructor!");
34 }
35
36 this.classEntry = classEntry;
37 this.name = name;
38 this.signature = signature;
39 }
40
41 public MethodEntry(MethodEntry other, String newClassName) {
42 this.classEntry = new ClassEntry(newClassName);
43 this.name = other.name;
44 this.signature = other.signature;
45 }
46
47 @Override
48 public ClassEntry getClassEntry() {
49 return this.classEntry;
50 }
51
52 @Override
53 public String getName() {
54 return this.name;
55 }
56
57 @Override
58 public Signature getSignature() {
59 return this.signature;
60 }
61
62 @Override
63 public String getClassName() {
64 return this.classEntry.getName();
65 }
66
67 @Override
68 public MethodEntry cloneToNewClass(ClassEntry classEntry) {
69 return new MethodEntry(this, classEntry.getName());
70 }
71
72 @Override
73 public int hashCode() {
74 return Utils.combineHashesOrdered(this.classEntry, this.name, this.signature);
75 }
76
77 @Override
78 public boolean equals(Object other) {
79 return other instanceof MethodEntry && equals((MethodEntry) other);
80 }
81
82 public boolean equals(MethodEntry other) {
83 return this.classEntry.equals(other.classEntry) && this.name.equals(other.name) && this.signature.equals(other.signature);
84 }
85
86 @Override
87 public String toString() {
88 return this.classEntry.getName() + "." + this.name + this.signature;
89 }
90}
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 @@
11 11
12package cuchaz.enigma.mapping; 12package cuchaz.enigma.mapping;
13 13
14import com.google.common.base.Preconditions;
14import com.google.common.collect.Maps; 15import com.google.common.collect.Maps;
16import cuchaz.enigma.mapping.entry.ClassEntry;
17import cuchaz.enigma.mapping.entry.MethodEntry;
15import cuchaz.enigma.throwables.IllegalNameException; 18import cuchaz.enigma.throwables.IllegalNameException;
16import cuchaz.enigma.throwables.MappingConflict; 19import cuchaz.enigma.throwables.MappingConflict;
17 20
18import java.util.Map; 21import java.util.Map;
19 22
20public class MethodMapping implements Comparable<MethodMapping>, MemberMapping<BehaviorEntry> { 23public class MethodMapping implements Comparable<MethodMapping>, MemberMapping<MethodEntry> {
21 24
22 private String obfName; 25 private String obfName;
23 private String deobfName; 26 private String deobfName;
24 private Signature obfSignature; 27 private MethodDescriptor obfDescriptor;
25 private Map<Integer, ArgumentMapping> arguments; 28 private Map<Integer, LocalVariableMapping> localVariables;
26 private Mappings.EntryModifier modifier; 29 private Mappings.EntryModifier modifier;
27 30
28 public MethodMapping(String obfName, Signature obfSignature) { 31 public MethodMapping(String obfName, MethodDescriptor obfDescriptor) {
29 this(obfName, obfSignature, null, Mappings.EntryModifier.UNCHANGED); 32 this(obfName, obfDescriptor, null, Mappings.EntryModifier.UNCHANGED);
30 } 33 }
31 34
32 public MethodMapping(String obfName, Signature obfSignature, String deobfName) { 35 public MethodMapping(String obfName, MethodDescriptor obfDescriptor, String deobfName) {
33 this(obfName, obfSignature, deobfName, Mappings.EntryModifier.UNCHANGED); 36 this(obfName, obfDescriptor, deobfName, Mappings.EntryModifier.UNCHANGED);
34 } 37 }
35 38
36 public MethodMapping(String obfName, Signature obfSignature, String deobfName, Mappings.EntryModifier modifier) { 39 public MethodMapping(String obfName, MethodDescriptor obfDescriptor, String deobfName, Mappings.EntryModifier modifier) {
37 if (obfName == null) { 40 Preconditions.checkNotNull(obfName, "Method obf name cannot be null");
38 throw new IllegalArgumentException("obf name cannot be null!"); 41 Preconditions.checkNotNull(obfDescriptor, "Method obf desc cannot be null");
39 }
40 if (obfSignature == null) {
41 throw new IllegalArgumentException("obf signature cannot be null!");
42 }
43 this.obfName = obfName; 42 this.obfName = obfName;
44 this.deobfName = NameValidator.validateMethodName(deobfName); 43 this.deobfName = NameValidator.validateMethodName(deobfName);
45 this.obfSignature = obfSignature; 44 this.obfDescriptor = obfDescriptor;
46 this.arguments = Maps.newTreeMap(); 45 this.localVariables = Maps.newTreeMap();
47 this.modifier = modifier; 46 this.modifier = modifier;
48 } 47 }
49 48
50 public MethodMapping(MethodMapping other, ClassNameReplacer obfClassNameReplacer) { 49 public MethodMapping(MethodMapping other, Translator translator) {
51 this.obfName = other.obfName; 50 this.obfName = other.obfName;
52 this.deobfName = other.deobfName; 51 this.deobfName = other.deobfName;
53 this.modifier = other.modifier; 52 this.modifier = other.modifier;
54 this.obfSignature = new Signature(other.obfSignature, obfClassNameReplacer); 53 this.obfDescriptor = translator.getTranslatedMethodDesc(other.obfDescriptor);
55 this.arguments = Maps.newTreeMap(); 54 this.localVariables = Maps.newTreeMap();
56 for (Map.Entry<Integer, ArgumentMapping> entry : other.arguments.entrySet()) { 55 for (Map.Entry<Integer, LocalVariableMapping> entry : other.localVariables.entrySet()) {
57 this.arguments.put(entry.getKey(), new ArgumentMapping(entry.getValue())); 56 this.localVariables.put(entry.getKey(), new LocalVariableMapping(entry.getValue()));
58 } 57 }
59 } 58 }
60 59
@@ -77,6 +76,9 @@ public class MethodMapping implements Comparable<MethodMapping>, MemberMapping<B
77 } 76 }
78 77
79 public String getDeobfName() { 78 public String getDeobfName() {
79 if (deobfName == null) {
80 return obfName;
81 }
80 return this.deobfName; 82 return this.deobfName;
81 } 83 }
82 84
@@ -84,56 +86,56 @@ public class MethodMapping implements Comparable<MethodMapping>, MemberMapping<B
84 this.deobfName = NameValidator.validateMethodName(val); 86 this.deobfName = NameValidator.validateMethodName(val);
85 } 87 }
86 88
87 public Signature getObfSignature() { 89 public MethodDescriptor getObfDesc() {
88 return this.obfSignature; 90 return this.obfDescriptor;
89 } 91 }
90 92
91 public void setObfSignature(Signature val) { 93 public void setObfDescriptor(MethodDescriptor val) {
92 this.obfSignature = val; 94 this.obfDescriptor = val;
93 } 95 }
94 96
95 public Iterable<ArgumentMapping> arguments() { 97 public Iterable<LocalVariableMapping> arguments() {
96 return this.arguments.values(); 98 return this.localVariables.values();
97 } 99 }
98 100
99 public void addArgumentMapping(ArgumentMapping argumentMapping) throws MappingConflict { 101 public void addArgumentMapping(LocalVariableMapping localVariableMapping) throws MappingConflict {
100 if (this.arguments.containsKey(argumentMapping.getIndex())) { 102 if (this.localVariables.containsKey(localVariableMapping.getIndex())) {
101 throw new MappingConflict("argument", argumentMapping.getName(), this.arguments.get(argumentMapping.getIndex()).getName()); 103 throw new MappingConflict("argument", localVariableMapping.getName(), this.localVariables.get(localVariableMapping.getIndex()).getName());
102 } 104 }
103 this.arguments.put(argumentMapping.getIndex(), argumentMapping); 105 this.localVariables.put(localVariableMapping.getIndex(), localVariableMapping);
104 } 106 }
105 107
106 public String getObfArgumentName(int index) { 108 public String getObfLocalVariableName(int index) {
107 ArgumentMapping argumentMapping = this.arguments.get(index); 109 LocalVariableMapping localVariableMapping = this.localVariables.get(index);
108 if (argumentMapping != null) { 110 if (localVariableMapping != null) {
109 return argumentMapping.getName(); 111 return localVariableMapping.getName();
110 } 112 }
111 113
112 return null; 114 return null;
113 } 115 }
114 116
115 public String getDeobfArgumentName(int index) { 117 public String getDeobfLocalVariableName(int index) {
116 ArgumentMapping argumentMapping = this.arguments.get(index); 118 LocalVariableMapping localVariableMapping = this.localVariables.get(index);
117 if (argumentMapping != null) { 119 if (localVariableMapping != null) {
118 return argumentMapping.getName(); 120 return localVariableMapping.getName();
119 } 121 }
120 122
121 return null; 123 return null;
122 } 124 }
123 125
124 public void setArgumentName(int index, String name) { 126 public void setLocalVariableName(int index, String name) {
125 ArgumentMapping argumentMapping = this.arguments.get(index); 127 LocalVariableMapping localVariableMapping = this.localVariables.get(index);
126 if (argumentMapping == null) { 128 if (localVariableMapping == null) {
127 argumentMapping = new ArgumentMapping(index, name); 129 localVariableMapping = new LocalVariableMapping(index, name);
128 boolean wasAdded = this.arguments.put(index, argumentMapping) == null; 130 boolean wasAdded = this.localVariables.put(index, localVariableMapping) == null;
129 assert (wasAdded); 131 assert (wasAdded);
130 } else { 132 } else {
131 argumentMapping.setName(name); 133 localVariableMapping.setName(name);
132 } 134 }
133 } 135 }
134 136
135 public void removeArgumentName(int index) { 137 public void removeLocalVariableName(int index) {
136 boolean wasRemoved = this.arguments.remove(index) != null; 138 boolean wasRemoved = this.localVariables.remove(index) != null;
137 assert (wasRemoved); 139 assert (wasRemoved);
138 } 140 }
139 141
@@ -146,14 +148,14 @@ public class MethodMapping implements Comparable<MethodMapping>, MemberMapping<B
146 buf.append(this.deobfName); 148 buf.append(this.deobfName);
147 buf.append("\n"); 149 buf.append("\n");
148 buf.append("\t"); 150 buf.append("\t");
149 buf.append(this.obfSignature); 151 buf.append(this.obfDescriptor);
150 buf.append("\n"); 152 buf.append("\n");
151 buf.append("\tArguments:\n"); 153 buf.append("\tLocal Variables:\n");
152 for (ArgumentMapping argumentMapping : this.arguments.values()) { 154 for (LocalVariableMapping localVariableMapping : this.localVariables.values()) {
153 buf.append("\t\t"); 155 buf.append("\t\t");
154 buf.append(argumentMapping.getIndex()); 156 buf.append(localVariableMapping.getIndex());
155 buf.append(" -> "); 157 buf.append(" -> ");
156 buf.append(argumentMapping.getName()); 158 buf.append(localVariableMapping.getName());
157 buf.append("\n"); 159 buf.append("\n");
158 } 160 }
159 return buf.toString(); 161 return buf.toString();
@@ -161,12 +163,12 @@ public class MethodMapping implements Comparable<MethodMapping>, MemberMapping<B
161 163
162 @Override 164 @Override
163 public int compareTo(MethodMapping other) { 165 public int compareTo(MethodMapping other) {
164 return (this.obfName + this.obfSignature).compareTo(other.obfName + other.obfSignature); 166 return (this.obfName + this.obfDescriptor).compareTo(other.obfName + other.obfDescriptor);
165 } 167 }
166 168
167 public boolean containsArgument(String name) { 169 public boolean containsLocalVariable(String name) {
168 for (ArgumentMapping argumentMapping : this.arguments.values()) { 170 for (LocalVariableMapping localVariableMapping : this.localVariables.values()) {
169 if (argumentMapping.getName().equals(name)) { 171 if (localVariableMapping.getName().equals(name)) {
170 return true; 172 return true;
171 } 173 }
172 } 174 }
@@ -175,32 +177,23 @@ public class MethodMapping implements Comparable<MethodMapping>, MemberMapping<B
175 177
176 public boolean renameObfClass(final String oldObfClassName, final String newObfClassName) { 178 public boolean renameObfClass(final String oldObfClassName, final String newObfClassName) {
177 // rename obf classes in the signature 179 // rename obf classes in the signature
178 Signature newSignature = new Signature(this.obfSignature, className -> 180 MethodDescriptor newDescriptor = obfDescriptor.remap(className -> {
179 {
180 if (className.equals(oldObfClassName)) { 181 if (className.equals(oldObfClassName)) {
181 return newObfClassName; 182 return newObfClassName;
182 } 183 }
183 return null; 184 return className;
184 }); 185 });
185 186
186 if (!newSignature.equals(this.obfSignature)) { 187 if (!newDescriptor.equals(this.obfDescriptor)) {
187 this.obfSignature = newSignature; 188 this.obfDescriptor = newDescriptor;
188 return true; 189 return true;
189 } 190 }
190 return false; 191 return false;
191 } 192 }
192 193
193 public boolean isConstructor() {
194 return this.obfName.startsWith("<");
195 }
196
197 @Override 194 @Override
198 public BehaviorEntry getObfEntry(ClassEntry classEntry) { 195 public MethodEntry getObfEntry(ClassEntry classEntry) {
199 if (isConstructor()) { 196 return new MethodEntry(classEntry, this.obfName, this.obfDescriptor);
200 return new ConstructorEntry(classEntry, this.obfSignature);
201 } else {
202 return new MethodEntry(classEntry, this.obfName, this.obfSignature);
203 }
204 } 197 }
205 198
206 public Mappings.EntryModifier getModifier() { 199 public Mappings.EntryModifier getModifier() {
@@ -210,4 +203,8 @@ public class MethodMapping implements Comparable<MethodMapping>, MemberMapping<B
210 public void setModifier(Mappings.EntryModifier modifier) { 203 public void setModifier(Mappings.EntryModifier modifier) {
211 this.modifier = modifier; 204 this.modifier = modifier;
212 } 205 }
206
207 public boolean isObfuscated() {
208 return deobfName == null || deobfName.equals(obfName);
209 }
213} 210}
diff --git a/src/main/java/cuchaz/enigma/mapping/NameValidator.java b/src/main/java/cuchaz/enigma/mapping/NameValidator.java
index aa3dc4de..9273c9bc 100644
--- a/src/main/java/cuchaz/enigma/mapping/NameValidator.java
+++ b/src/main/java/cuchaz/enigma/mapping/NameValidator.java
@@ -11,8 +11,8 @@
11 11
12package cuchaz.enigma.mapping; 12package cuchaz.enigma.mapping;
13 13
14import cuchaz.enigma.mapping.entry.ClassEntry;
14import cuchaz.enigma.throwables.IllegalNameException; 15import cuchaz.enigma.throwables.IllegalNameException;
15import javassist.bytecode.Descriptor;
16 16
17import java.util.Arrays; 17import java.util.Arrays;
18import java.util.List; 18import java.util.List;
@@ -23,11 +23,11 @@ public class NameValidator {
23 private static final Pattern IdentifierPattern; 23 private static final Pattern IdentifierPattern;
24 private static final Pattern ClassPattern; 24 private static final Pattern ClassPattern;
25 private static final List<String> ReservedWords = Arrays.asList( 25 private static final List<String> ReservedWords = Arrays.asList(
26 "abstract", "continue", "for", "new", "switch", "assert", "default", "goto", "package", "synchronized", 26 "abstract", "continue", "for", "new", "switch", "assert", "default", "goto", "package", "synchronized",
27 "boolean", "do", "if", "private", "this", "break", "double", "implements", "protected", "throw", "byte", 27 "boolean", "do", "if", "private", "this", "break", "double", "implements", "protected", "throw", "byte",
28 "else", "import", "public", "throws", "case", "enum", "instanceof", "return", "transient", "catch", 28 "else", "import", "public", "throws", "case", "enum", "instanceof", "return", "transient", "catch",
29 "extends", "int", "short", "try", "char", "final", "interface", "static", "void", "class", "finally", 29 "extends", "int", "short", "try", "char", "final", "interface", "static", "void", "class", "finally",
30 "long", "strictfp", "volatile", "const", "float", "native", "super", "while" 30 "long", "strictfp", "volatile", "const", "float", "native", "super", "while"
31 ); 31 );
32 32
33 static { 33 static {
@@ -43,10 +43,10 @@ public class NameValidator {
43 if (!ClassPattern.matcher(name).matches() || ReservedWords.contains(name)) { 43 if (!ClassPattern.matcher(name).matches() || ReservedWords.contains(name)) {
44 throw new IllegalNameException(name, "This doesn't look like a legal class name"); 44 throw new IllegalNameException(name, "This doesn't look like a legal class name");
45 } 45 }
46 if (packageRequired && new ClassEntry(name).getPackageName() == null) { 46 if (packageRequired && ClassEntry.getPackageName(name) == null) {
47 throw new IllegalNameException(name, "Class must be in a package"); 47 throw new IllegalNameException(name, "Class must be in a package");
48 } 48 }
49 return Descriptor.toJvmName(name); 49 return name;
50 } 50 }
51 51
52 public static String validateFieldName(String name) { 52 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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.mapping;
13
14import com.strobel.assembler.metadata.*;
15
16import java.util.List;
17
18public class ProcyonEntryFactory {
19
20 private static String getErasedSignature(MemberReference def) {
21 if (!(def instanceof MethodReference))
22 return def.getErasedSignature();
23 MethodReference methodReference = (MethodReference) def;
24 StringBuilder builder = new StringBuilder("(");
25 for (ParameterDefinition param : methodReference.getParameters()) {
26 TypeReference paramType = param.getParameterType();
27 if (paramType.getErasedSignature().equals("Ljava/lang/Object;") && paramType.hasExtendsBound() && paramType.getExtendsBound() instanceof CompoundTypeReference) {
28 List<TypeReference> interfaces = ((CompoundTypeReference) paramType.getExtendsBound()).getInterfaces();
29 interfaces.forEach((inter) -> builder.append(inter.getErasedSignature()));
30 } else
31 builder.append(paramType.getErasedSignature());
32 }
33 builder.append(")");
34
35 TypeReference returnType = methodReference.getReturnType();
36 if (returnType.getErasedSignature().equals("Ljava/lang/Object;") && returnType.hasExtendsBound() && returnType.getExtendsBound() instanceof CompoundTypeReference) {
37 List<TypeReference> interfaces = ((CompoundTypeReference) returnType.getExtendsBound()).getInterfaces();
38 interfaces.forEach((inter) -> builder.append(inter.getErasedSignature()));
39 } else
40 builder.append(returnType.getErasedSignature());
41 return builder.toString();
42 }
43
44 public static FieldEntry getFieldEntry(MemberReference def) {
45 return new FieldEntry(new ClassEntry(def.getDeclaringType().getInternalName()), def.getName(), new Type(def.getErasedSignature()));
46 }
47
48 public static MethodEntry getMethodEntry(MemberReference def) {
49 return new MethodEntry(new ClassEntry(def.getDeclaringType().getInternalName()), def.getName(), new Signature(getErasedSignature(def)));
50 }
51
52 public static ConstructorEntry getConstructorEntry(MethodReference def) {
53 if (def.isTypeInitializer()) {
54 return new ConstructorEntry(new ClassEntry(def.getDeclaringType().getInternalName()));
55 } else {
56 return new ConstructorEntry(new ClassEntry(def.getDeclaringType().getInternalName()), new Signature(def.getErasedSignature()));
57 }
58 }
59
60 public static BehaviorEntry getBehaviorEntry(MethodReference def) {
61 if (def.isConstructor() || def.isTypeInitializer()) {
62 return getConstructorEntry(def);
63 } else {
64 return getMethodEntry(def);
65 }
66 }
67}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.mapping; 1package cuchaz.enigma.mapping;
13 2
14import com.google.common.collect.Lists; 3import cuchaz.enigma.bytecode.translators.TranslationSignatureVisitor;
15import cuchaz.enigma.utils.Utils; 4import org.objectweb.asm.signature.SignatureReader;
5import org.objectweb.asm.signature.SignatureVisitor;
6import org.objectweb.asm.signature.SignatureWriter;
16 7
17import java.util.List; 8import java.util.function.Function;
9import java.util.regex.Pattern;
18 10
19public class Signature { 11public class Signature {
12 private static final Pattern OBJECT_PATTERN = Pattern.compile(".*:Ljava/lang/Object;:.*");
20 13
21 private List<Type> argumentTypes; 14 private final String signature;
22 private Type returnType; 15 private final boolean isType;
23 16
24 public Signature(String signature) { 17 private Signature(String signature, boolean isType) {
25 try { 18 if (signature != null && OBJECT_PATTERN.matcher(signature).matches()) {
26 this.argumentTypes = Lists.newArrayList(); 19 signature = signature.replaceAll(":Ljava/lang/Object;:", "::");
27 int i = 0;
28 while (i < signature.length()) {
29 char c = signature.charAt(i);
30 if (c == '(') {
31 assert (this.argumentTypes.isEmpty());
32 assert (this.returnType == null);
33 i++;
34 } else if (c == ')') {
35 i++;
36 break;
37 } else {
38 String type = Type.parseFirst(signature.substring(i));
39 this.argumentTypes.add(new Type(type));
40 i += type.length();
41 }
42 }
43 this.returnType = new Type(Type.parseFirst(signature.substring(i)));
44 } catch (Exception ex) {
45 throw new IllegalArgumentException("Unable to parse signature: " + signature, ex);
46 } 20 }
21
22 this.signature = signature;
23 this.isType = isType;
47 } 24 }
48 25
49 public Signature(Signature other, ClassNameReplacer replacer) { 26 public static Signature createTypedSignature(String signature) {
50 this.argumentTypes = Lists.newArrayList(other.argumentTypes); 27 if (signature != null && !signature.isEmpty()) {
51 for (int i = 0; i < this.argumentTypes.size(); i++) { 28 return new Signature(signature, true);
52 this.argumentTypes.set(i, new Type(this.argumentTypes.get(i), replacer));
53 } 29 }
54 this.returnType = new Type(other.returnType, replacer); 30 return new Signature(null, true);
55 } 31 }
56 32
57 public List<Type> getArgumentTypes() { 33 public static Signature createSignature(String signature) {
58 return this.argumentTypes; 34 if (signature != null && !signature.isEmpty()) {
35 return new Signature(signature, false);
36 }
37 return new Signature(null, false);
59 } 38 }
60 39
61 public Type getReturnType() { 40 public String getSignature() {
62 return this.returnType; 41 return signature;
63 } 42 }
64 43
65 @Override 44 public boolean isType() {
66 public String toString() { 45 return isType;
67 StringBuilder buf = new StringBuilder();
68 buf.append("(");
69 for (Type type : this.argumentTypes) {
70 buf.append(type);
71 }
72 buf.append(")");
73 buf.append(this.returnType);
74 return buf.toString();
75 } 46 }
76 47
77 public Iterable<Type> types() { 48 public Signature remap(Function<String, String> remapper) {
78 List<Type> types = Lists.newArrayList(); 49 if (signature == null) {
79 types.addAll(this.argumentTypes); 50 return this;
80 types.add(this.returnType); 51 }
81 return types; 52 SignatureWriter writer = new SignatureWriter();
53 SignatureVisitor visitor = new TranslationSignatureVisitor(remapper, writer);
54 if (isType) {
55 new SignatureReader(signature).acceptType(visitor);
56 } else {
57 new SignatureReader(signature).accept(visitor);
58 }
59 return new Signature(writer.toString(), isType);
82 } 60 }
83 61
84 @Override 62 @Override
85 public boolean equals(Object other) { 63 public boolean equals(Object obj) {
86 return other instanceof Signature && equals((Signature) other); 64 if (obj instanceof Signature) {
87 } 65 Signature other = (Signature) obj;
88 66 return (other.signature == null && signature == null || other.signature != null
89 public boolean equals(Signature other) { 67 && signature != null && other.signature.equals(signature))
90 return this.argumentTypes.equals(other.argumentTypes) && this.returnType.equals(other.returnType); 68 && other.isType == this.isType;
69 }
70 return false;
91 } 71 }
92 72
93 @Override 73 @Override
94 public int hashCode() { 74 public int hashCode() {
95 return Utils.combineHashesOrdered(this.argumentTypes.hashCode(), this.returnType.hashCode()); 75 return signature.hashCode() | (isType ? 1 : 0) << 16;
96 } 76 }
97 77
98 public boolean hasClass(ClassEntry classEntry) { 78 @Override
99 for (Type type : types()) { 79 public String toString() {
100 if (type.hasClass() && type.getClassEntry().equals(classEntry)) { 80 return signature;
101 return true;
102 }
103 }
104 return false;
105 } 81 }
106} 82}
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;
13 13
14public enum TranslationDirection { 14public enum TranslationDirection {
15 15
16 Deobfuscating { 16 DEOBFUSCATING {
17 @Override 17 @Override
18 public <T> T choose(T deobfChoice, T obfChoice) { 18 public <T> T choose(T deobfChoice, T obfChoice) {
19 if (deobfChoice == null) {
20 return obfChoice;
21 }
19 return deobfChoice; 22 return deobfChoice;
20 } 23 }
21 }, 24 },
22 Obfuscating { 25 OBFUSCATING {
23 @Override 26 @Override
24 public <T> T choose(T deobfChoice, T obfChoice) { 27 public <T> T choose(T deobfChoice, T obfChoice) {
28 if (obfChoice == null) {
29 return deobfChoice;
30 }
25 return obfChoice; 31 return obfChoice;
26 } 32 }
27 }; 33 };
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 @@
11 11
12package cuchaz.enigma.mapping; 12package cuchaz.enigma.mapping;
13 13
14import com.google.common.collect.Lists; 14import cuchaz.enigma.mapping.entry.*;
15import com.google.common.collect.Maps; 15import org.objectweb.asm.Handle;
16import cuchaz.enigma.analysis.TranslationIndex; 16import org.objectweb.asm.Type;
17 17
18import java.util.List; 18public interface Translator {
19import java.util.Map; 19 ClassEntry getTranslatedClass(ClassEntry entry);
20 20
21public class Translator { 21 ClassDefEntry getTranslatedClassDef(ClassDefEntry entry);
22 22
23 private TranslationDirection direction; 23 FieldEntry getTranslatedField(FieldEntry entry);
24 private Map<String, ClassMapping> classes;
25 private TranslationIndex index;
26 24
27 private ClassNameReplacer classNameReplacer = className -> translateEntry(new ClassEntry(className)).getName(); 25 FieldDefEntry getTranslatedFieldDef(FieldDefEntry entry);
28 26
29 public Translator() { 27 MethodEntry getTranslatedMethod(MethodEntry entry);
30 this.direction = null;
31 this.classes = Maps.newHashMap();
32 this.index = new TranslationIndex();
33 }
34 28
35 public Translator(TranslationDirection direction, Map<String, ClassMapping> classes, TranslationIndex index) { 29 MethodDefEntry getTranslatedMethodDef(MethodDefEntry entry);
36 this.direction = direction;
37 this.classes = classes;
38 this.index = index;
39 }
40 30
41 public TranslationDirection getDirection() { 31 LocalVariableEntry getTranslatedVariable(LocalVariableEntry entry);
42 return direction;
43 }
44 32
45 public TranslationIndex getTranslationIndex() { 33 LocalVariableDefEntry getTranslatedVariableDef(LocalVariableDefEntry entry);
46 return index;
47 }
48 34
49 @SuppressWarnings("unchecked") 35 boolean hasClassMapping(ClassEntry entry);
50 public <T extends Entry> T translateEntry(T entry) {
51 if (entry instanceof ClassEntry) {
52 return (T) translateEntry((ClassEntry) entry);
53 } else if (entry instanceof FieldEntry) {
54 return (T) translateEntry((FieldEntry) entry);
55 } else if (entry instanceof MethodEntry) {
56 return (T) translateEntry((MethodEntry) entry);
57 } else if (entry instanceof ConstructorEntry) {
58 return (T) translateEntry((ConstructorEntry) entry);
59 } else if (entry instanceof ArgumentEntry) {
60 return (T) translateEntry((ArgumentEntry) entry);
61 } else if (entry instanceof LocalVariableEntry) {
62 return (T) translateEntry((LocalVariableEntry) entry);
63 } else {
64 throw new Error("Unknown entry type: " + entry.getClass().getName());
65 }
66 }
67 36
68 public <T extends Entry> String translate(T entry) { 37 boolean hasFieldMapping(FieldEntry entry);
69 if (entry instanceof ClassEntry) {
70 return translate((ClassEntry) entry);
71 } else if (entry instanceof FieldEntry) {
72 return translate((FieldEntry) entry);
73 } else if (entry instanceof MethodEntry) {
74 return translate((MethodEntry) entry);
75 } else if (entry instanceof ConstructorEntry) {
76 return translate(entry);
77 } else if (entry instanceof ArgumentEntry) {
78 return translate((ArgumentEntry) entry);
79 } else if (entry instanceof LocalVariableEntry) {
80 return translate((LocalVariableEntry) entry);
81 } else {
82 throw new Error("Unknown entry type: " + entry.getClass().getName());
83 }
84 }
85 38
86 public String translate(LocalVariableEntry in) { 39 boolean hasMethodMapping(MethodEntry entry);
87 LocalVariableEntry translated = translateEntry(in);
88 if (translated.equals(in)) {
89 return null;
90 }
91 return translated.getName();
92 }
93 40
94 public LocalVariableEntry translateEntry(LocalVariableEntry in) { 41 boolean hasLocalVariableMapping(LocalVariableEntry entry);
95 // TODO: Implement it
96 return in;
97 }
98 42
99 public String translate(ClassEntry in) { 43 TypeDescriptor getTranslatedTypeDesc(TypeDescriptor desc);
100 ClassEntry translated = translateEntry(in);
101 if (translated.equals(in)) {
102 return null;
103 }
104 return translated.getName();
105 }
106 44
107 public String translateClass(String className) { 45 MethodDescriptor getTranslatedMethodDesc(MethodDescriptor descriptor);
108 return translate(new ClassEntry(className));
109 }
110
111 public ClassEntry translateEntry(ClassEntry in) {
112 46
113 if (in.isInnerClass()) { 47 Signature getTranslatedSignature(Signature signature);
114 48
115 // translate as much of the class chain as we can 49 default Type getTranslatedType(Type type) {
116 List<ClassMapping> mappingsChain = getClassMappingChain(in); 50 String descString = type.getDescriptor();
117 String[] obfClassNames = in.getName().split("\\$"); 51 switch (type.getSort()) {
118 StringBuilder buf = new StringBuilder(); 52 case Type.OBJECT: {
119 for (int i = 0; i < obfClassNames.length; i++) { 53 ClassEntry classEntry = new ClassEntry(type.getInternalName());
120 boolean isFirstClass = buf.length() == 0; 54 return Type.getObjectType(getTranslatedClass(classEntry).getName());
121 String className = null;
122 ClassMapping classMapping = mappingsChain.get(i);
123 if (classMapping != null) {
124 className = this.direction.choose(
125 classMapping.getDeobfName(),
126 isFirstClass ? classMapping.getObfFullName() : classMapping.getObfSimpleName()
127 );
128 }
129 if (className == null) {
130 className = obfClassNames[i];
131 }
132 if (!isFirstClass) {
133 buf.append("$");
134 }
135 buf.append(className);
136 } 55 }
137 return new ClassEntry(buf.toString()); 56 case Type.ARRAY: {
138 57 TypeDescriptor descriptor = new TypeDescriptor(descString);
139 } else { 58 return Type.getType(getTranslatedTypeDesc(descriptor).toString());
140
141 // normal classes are easy
142 ClassMapping classMapping = this.classes.get(in.getName());
143 if (classMapping == null) {
144 return in;
145 } 59 }
146 return this.direction.choose( 60 case Type.METHOD: {
147 classMapping.getDeobfName() != null ? new ClassEntry(classMapping.getDeobfName()) : in, 61 MethodDescriptor descriptor = new MethodDescriptor(descString);
148 new ClassEntry(classMapping.getObfFullName()) 62 return Type.getMethodType(getTranslatedMethodDesc(descriptor).toString());
149 );
150 }
151 }
152
153 public String translate(FieldEntry in) {
154
155 // resolve the class entry
156 ClassEntry resolvedClassEntry = this.index.resolveEntryClass(in);
157 if (resolvedClassEntry != null) {
158
159 // look for the class
160 ClassMapping classMapping = findClassMapping(resolvedClassEntry);
161 if (classMapping != null) {
162
163 // look for the field
164 String translatedName = this.direction.choose(
165 classMapping.getDeobfFieldName(in.getName(), in.getType()),
166 classMapping.getObfFieldName(in.getName(), translateType(in.getType()))
167 );
168 if (translatedName != null) {
169 return translatedName;
170 }
171 } 63 }
172 } 64 }
173 return null; 65 return type;
174 } 66 }
175 67
176 public FieldEntry translateEntry(FieldEntry in) { 68 default Handle getTranslatedHandle(Handle handle) {
177 String name = translate(in); 69 MethodEntry entry = new MethodEntry(new ClassEntry(handle.getOwner()), handle.getName(), new MethodDescriptor(handle.getDesc()));
178 if (name == null) { 70 MethodEntry translatedMethod = getTranslatedMethod(entry);
179 name = in.getName(); 71 ClassEntry ownerClass = translatedMethod.getOwnerClassEntry();
180 } 72 return new Handle(handle.getTag(), ownerClass.getName(), translatedMethod.getName(), translatedMethod.getDesc().toString(), handle.isInterface());
181 return new FieldEntry(translateEntry(in.getClassEntry()), name, translateType(in.getType()));
182 } 73 }
183 74
184 public String translate(MethodEntry in) { 75 default Object getTranslatedValue(Object value) {
185 // resolve the class entry 76 if (value instanceof Type) {
186 ClassEntry resolvedClassEntry = this.index.resolveEntryClass(in, true); 77 return this.getTranslatedType((Type) value);
187 if (resolvedClassEntry != null) { 78 } else if (value instanceof Handle) {
188 79 return getTranslatedHandle((Handle) value);
189 // look for class
190 ClassMapping classMapping = findClassMapping(resolvedClassEntry);
191 if (classMapping != null) {
192
193 // look for the method
194 MethodMapping methodMapping = this.direction.choose(
195 classMapping.getMethodByObf(in.getName(), in.getSignature()),
196 classMapping.getMethodByDeobf(in.getName(), translateSignature(in.getSignature()))
197 );
198 if (methodMapping != null) {
199 return this.direction.choose(methodMapping.getDeobfName(), methodMapping.getObfName());
200 }
201 }
202 } 80 }
203 return null; 81 return value;
204 } 82 }
205 83
206 public MethodEntry translateEntry(MethodEntry in) { 84 @SuppressWarnings("unchecked")
207 String name = translate(in); 85 default <T extends Entry> T getTranslatedEntry(T entry) {
208 if (name == null) { 86 if (entry instanceof ClassDefEntry) {
209 name = in.getName(); 87 return (T) getTranslatedClassDef((ClassDefEntry) entry);
210 } 88 } else if (entry instanceof ClassEntry) {
211 return new MethodEntry(translateEntry(in.getClassEntry()), name, translateSignature(in.getSignature())); 89 return (T) getTranslatedClass((ClassEntry) entry);
212 } 90 } else if (entry instanceof FieldDefEntry) {
213 91 return (T) getTranslatedFieldDef((FieldDefEntry) entry);
214 public ConstructorEntry translateEntry(ConstructorEntry in) { 92 } else if (entry instanceof MethodDefEntry) {
215 if (in.isStatic()) { 93 return (T) getTranslatedMethodDef((MethodDefEntry) entry);
216 return new ConstructorEntry(translateEntry(in.getClassEntry())); 94 } else if (entry instanceof FieldEntry) {
217 } else { 95 return (T) getTranslatedField((FieldEntry) entry);
218 return new ConstructorEntry(translateEntry(in.getClassEntry()), translateSignature(in.getSignature())); 96 } else if (entry instanceof MethodEntry) {
219 } 97 return (T) getTranslatedMethod((MethodEntry) entry);
220 } 98 } else if (entry instanceof LocalVariableDefEntry) {
221 99 return (T) getTranslatedVariableDef((LocalVariableDefEntry) entry);
222 public BehaviorEntry translateEntry(BehaviorEntry in) { 100 } else if (entry instanceof LocalVariableEntry) {
223 if (in instanceof MethodEntry) { 101 return (T) getTranslatedVariable((LocalVariableEntry) entry);
224 return translateEntry((MethodEntry) in); 102 } else if (entry instanceof TypeDescriptor) {
225 } else if (in instanceof ConstructorEntry) { 103 return (T) getTranslatedTypeDesc((TypeDescriptor) entry);
226 return translateEntry((ConstructorEntry) in); 104 } else if (entry instanceof MethodDescriptor) {
227 } 105 return (T) getTranslatedMethodDesc((MethodDescriptor) entry);
228 throw new Error("Wrong entry type!");
229 }
230
231 // TODO: support not identical behavior (specific to constructor)
232 public String translate(ArgumentEntry in) {
233 String classTranslate = translateArgument(in);
234
235 // Not found in this class
236 if (classTranslate == null) {
237 List<ClassEntry> ancestry = this.index.getAncestry(in.getClassEntry());
238
239 // Check in mother class for the arg
240 for (ClassEntry entry : ancestry) {
241 ArgumentEntry motherArg = in.cloneToNewClass(entry);
242 if (this.index.entryExists(motherArg)) {
243 String result = translateArgument(motherArg);
244 if (result != null)
245 return result;
246 }
247 }
248 }
249 return classTranslate;
250 }
251
252 public String translateArgument(ArgumentEntry in) {
253 // look for identical behavior in superclasses
254 ClassEntry entry = in.getClassEntry();
255
256 if (entry != null) {
257 // look for the class
258 ClassMapping classMapping = findClassMapping(entry);
259 if (classMapping != null) {
260
261 // look for the method
262 MethodMapping methodMapping = this.direction.choose(
263 classMapping.getMethodByObf(in.getMethodName(), in.getMethodSignature()),
264 classMapping.getMethodByDeobf(in.getMethodName(), translateSignature(in.getMethodSignature()))
265 );
266 if (methodMapping != null) {
267 return this.direction.choose(
268 methodMapping.getDeobfArgumentName(in.getIndex()),
269 methodMapping.getObfArgumentName(in.getIndex())
270 );
271 }
272 }
273 }
274 return null;
275 }
276
277 public ArgumentEntry translateEntry(ArgumentEntry in) {
278 String name = translate(in);
279 if (name == null) {
280 name = in.getName();
281 }
282 return new ArgumentEntry(translateEntry(in.getBehaviorEntry()), in.getIndex(), name);
283 }
284
285 public Type translateType(Type type) {
286 return new Type(type, this.classNameReplacer);
287 }
288
289 public Signature translateSignature(Signature signature) {
290 return new Signature(signature, this.classNameReplacer);
291 }
292
293 private ClassMapping findClassMapping(ClassEntry in) {
294 List<ClassMapping> mappingChain = getClassMappingChain(in);
295 return mappingChain.get(mappingChain.size() - 1);
296 }
297
298 private List<ClassMapping> getClassMappingChain(ClassEntry in) {
299
300 // get a list of all the classes in the hierarchy
301 String[] parts = in.getName().split("\\$");
302 List<ClassMapping> mappingsChain = Lists.newArrayList();
303
304 // get mappings for the outer class
305 ClassMapping outerClassMapping = this.classes.get(parts[0]);
306 mappingsChain.add(outerClassMapping);
307
308 for (int i = 1; i < parts.length; i++) {
309
310 // get mappings for the inner class
311 ClassMapping innerClassMapping = null;
312 if (outerClassMapping != null) {
313 innerClassMapping = this.direction.choose(
314 outerClassMapping.getInnerClassByObfSimple(parts[i]),
315 outerClassMapping.getInnerClassByDeobfThenObfSimple(parts[i])
316 );
317 }
318 mappingsChain.add(innerClassMapping);
319 outerClassMapping = innerClassMapping;
320 }
321
322 assert (mappingsChain.size() == parts.length);
323 return mappingsChain;
324 }
325
326 public Mappings.EntryModifier getModifier(Entry entry) {
327 ClassMapping classMapping = findClassMapping(entry.getClassEntry());
328 if (classMapping != null && !entry.getName().equals("<clinit>")) {
329 if (entry instanceof ClassEntry)
330 return classMapping.getModifier();
331 else if (entry instanceof FieldEntry) {
332 FieldMapping fieldMapping = classMapping.getFieldByObf(entry.getName(), ((FieldEntry) entry).getType());
333 return fieldMapping != null ? fieldMapping.getModifier() : Mappings.EntryModifier.UNCHANGED;
334 } else if (entry instanceof BehaviorEntry) {
335 MethodMapping methodMapping = classMapping.getMethodByObf(entry.getName(), ((BehaviorEntry) entry).getSignature());
336 return methodMapping != null ? methodMapping.getModifier() : Mappings.EntryModifier.UNCHANGED;
337 } else
338 throw new Error("Unknown entry type: " + entry.getClass().getName());
339 } 106 }
340 return Mappings.EntryModifier.UNCHANGED; 107 throw new IllegalArgumentException("Cannot translate unknown entry type");
341 } 108 }
342} 109}
diff --git a/src/main/java/cuchaz/enigma/mapping/Type.java b/src/main/java/cuchaz/enigma/mapping/TypeDescriptor.java
index 609bd64e..b7b1255a 100644
--- a/src/main/java/cuchaz/enigma/mapping/Type.java
+++ b/src/main/java/cuchaz/enigma/mapping/TypeDescriptor.java
@@ -11,47 +11,36 @@
11 11
12package cuchaz.enigma.mapping; 12package cuchaz.enigma.mapping;
13 13
14import com.google.common.base.Preconditions;
14import com.google.common.collect.Maps; 15import com.google.common.collect.Maps;
16import cuchaz.enigma.mapping.entry.ClassEntry;
15 17
16import java.util.Map; 18import java.util.Map;
19import java.util.function.Function;
17 20
18public class Type { 21public class TypeDescriptor {
19 22
20 protected String name; 23 protected final String desc;
21 24
22 public Type(String name) { 25 public TypeDescriptor(String desc) {
26 Preconditions.checkNotNull(desc, "Desc cannot be null");
23 27
24 // don't deal with generics 28 // don't deal with generics
25 // this is just for raw jvm types 29 // this is just for raw jvm types
26 if (name.charAt(0) == 'T' || name.indexOf('<') >= 0 || name.indexOf('>') >= 0) { 30 if (desc.charAt(0) == 'T' || desc.indexOf('<') >= 0 || desc.indexOf('>') >= 0) {
27 throw new IllegalArgumentException("don't use with generic types or templates: " + name); 31 throw new IllegalArgumentException("don't use with generic types or templates: " + desc);
28 } 32 }
29 33
30 this.name = name; 34 this.desc = desc;
31 }
32
33 public Type(Type other, ClassNameReplacer replacer) {
34 this.name = other.name;
35 if (other.isClass()) {
36 String replacedName = replacer.replace(other.getClassEntry().getClassName());
37 if (replacedName != null) {
38 this.name = "L" + replacedName + ";";
39 }
40 } else if (other.isArray() && other.hasClass()) {
41 String replacedName = replacer.replace(other.getClassEntry().getClassName());
42 if (replacedName != null) {
43 this.name = Type.getArrayPrefix(other.getArrayDimension()) + "L" + replacedName + ";";
44 }
45 }
46 } 35 }
47 36
48 public static String parseFirst(String in) { 37 public static String parseFirst(String in) {
49 38
50 if (in == null || in.length() <= 0) { 39 if (in == null || in.length() <= 0) {
51 throw new IllegalArgumentException("No type to parse, input is empty!"); 40 throw new IllegalArgumentException("No desc to parse, input is empty!");
52 } 41 }
53 42
54 // read one type from the input 43 // read one desc from the input
55 44
56 char c = in.charAt(0); 45 char c = in.charAt(0);
57 46
@@ -79,21 +68,13 @@ public class Type {
79 // then check for arrays 68 // then check for arrays
80 int dim = countArrayDimension(in); 69 int dim = countArrayDimension(in);
81 if (dim > 0) { 70 if (dim > 0) {
82 String arrayType = Type.parseFirst(in.substring(dim)); 71 String arrayType = TypeDescriptor.parseFirst(in.substring(dim));
83 return in.substring(0, dim + arrayType.length()); 72 return in.substring(0, dim + arrayType.length());
84 } 73 }
85 74
86 throw new IllegalArgumentException("don't know how to parse: " + in); 75 throw new IllegalArgumentException("don't know how to parse: " + in);
87 } 76 }
88 77
89 private static String getArrayPrefix(int dimension) {
90 StringBuilder buf = new StringBuilder();
91 for (int i = 0; i < dimension; i++) {
92 buf.append("[");
93 }
94 return buf.toString();
95 }
96
97 private static int countArrayDimension(String in) { 78 private static int countArrayDimension(String in) {
98 int i = 0; 79 int i = 0;
99 while (i < in.length() && in.charAt(i) == '[') 80 while (i < in.length() && in.charAt(i) == '[')
@@ -121,33 +102,37 @@ public class Type {
121 return null; 102 return null;
122 } 103 }
123 104
105 public static TypeDescriptor of(String name) {
106 return new TypeDescriptor("L" + name + ";");
107 }
108
124 @Override 109 @Override
125 public String toString() { 110 public String toString() {
126 return this.name; 111 return this.desc;
127 } 112 }
128 113
129 public boolean isVoid() { 114 public boolean isVoid() {
130 return this.name.length() == 1 && this.name.charAt(0) == 'V'; 115 return this.desc.length() == 1 && this.desc.charAt(0) == 'V';
131 } 116 }
132 117
133 public boolean isPrimitive() { 118 public boolean isPrimitive() {
134 return this.name.length() == 1 && Primitive.get(this.name.charAt(0)) != null; 119 return this.desc.length() == 1 && Primitive.get(this.desc.charAt(0)) != null;
135 } 120 }
136 121
137 public Primitive getPrimitive() { 122 public Primitive getPrimitive() {
138 if (!isPrimitive()) { 123 if (!isPrimitive()) {
139 throw new IllegalStateException("not a primitive"); 124 throw new IllegalStateException("not a primitive");
140 } 125 }
141 return Primitive.get(this.name.charAt(0)); 126 return Primitive.get(this.desc.charAt(0));
142 } 127 }
143 128
144 public boolean isClass() { 129 public boolean isType() {
145 return this.name.charAt(0) == 'L' && this.name.charAt(this.name.length() - 1) == ';'; 130 return this.desc.charAt(0) == 'L' && this.desc.charAt(this.desc.length() - 1) == ';';
146 } 131 }
147 132
148 public ClassEntry getClassEntry() { 133 public ClassEntry getTypeEntry() {
149 if (isClass()) { 134 if (isType()) {
150 String name = this.name.substring(1, this.name.length() - 1); 135 String name = this.desc.substring(1, this.desc.length() - 1);
151 136
152 int pos = name.indexOf('<'); 137 int pos = name.indexOf('<');
153 if (pos >= 0) { 138 if (pos >= 0) {
@@ -157,46 +142,70 @@ public class Type {
157 142
158 return new ClassEntry(name); 143 return new ClassEntry(name);
159 144
160 } else if (isArray() && getArrayType().isClass()) { 145 } else if (isArray() && getArrayType().isType()) {
161 return getArrayType().getClassEntry(); 146 return getArrayType().getTypeEntry();
162 } else { 147 } else {
163 throw new IllegalStateException("type doesn't have a class"); 148 throw new IllegalStateException("desc doesn't have a class");
164 } 149 }
165 } 150 }
166 151
167 public boolean isArray() { 152 public boolean isArray() {
168 return this.name.charAt(0) == '['; 153 return this.desc.charAt(0) == '[';
169 } 154 }
170 155
171 public int getArrayDimension() { 156 public int getArrayDimension() {
172 if (!isArray()) { 157 if (!isArray()) {
173 throw new IllegalStateException("not an array"); 158 throw new IllegalStateException("not an array");
174 } 159 }
175 return countArrayDimension(this.name); 160 return countArrayDimension(this.desc);
176 } 161 }
177 162
178 public Type getArrayType() { 163 public TypeDescriptor getArrayType() {
179 if (!isArray()) { 164 if (!isArray()) {
180 throw new IllegalStateException("not an array"); 165 throw new IllegalStateException("not an array");
181 } 166 }
182 return new Type(this.name.substring(getArrayDimension(), this.name.length())); 167 return new TypeDescriptor(this.desc.substring(getArrayDimension(), this.desc.length()));
183 } 168 }
184 169
185 public boolean hasClass() { 170 public boolean containsType() {
186 return isClass() || (isArray() && getArrayType().hasClass()); 171 return isType() || (isArray() && getArrayType().containsType());
187 } 172 }
188 173
189 @Override 174 @Override
190 public boolean equals(Object other) { 175 public boolean equals(Object other) {
191 return other instanceof Type && equals((Type) other); 176 return other instanceof TypeDescriptor && equals((TypeDescriptor) other);
192 } 177 }
193 178
194 public boolean equals(Type other) { 179 public boolean equals(TypeDescriptor other) {
195 return this.name.equals(other.name); 180 return this.desc.equals(other.desc);
196 } 181 }
197 182
183 @Override
198 public int hashCode() { 184 public int hashCode() {
199 return this.name.hashCode(); 185 return this.desc.hashCode();
186 }
187
188 public TypeDescriptor remap(Function<String, String> remapper) {
189 String desc = this.desc;
190 if (isType() || (isArray() && containsType())) {
191 String replacedName = remapper.apply(this.getTypeEntry().getName());
192 if (replacedName != null) {
193 if (this.isType()) {
194 desc = "L" + replacedName + ";";
195 } else {
196 desc = getArrayPrefix(this.getArrayDimension()) + "L" + replacedName + ";";
197 }
198 }
199 }
200 return new TypeDescriptor(desc);
201 }
202
203 private static String getArrayPrefix(int dimension) {
204 StringBuilder buf = new StringBuilder();
205 for (int i = 0; i < dimension; i++) {
206 buf.append("[");
207 }
208 return buf.toString();
200 } 209 }
201 210
202 public enum Primitive { 211 public enum Primitive {
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.mapping.entry;
13
14import com.google.common.base.Preconditions;
15import cuchaz.enigma.bytecode.AccessFlags;
16import cuchaz.enigma.mapping.Signature;
17
18public class ClassDefEntry extends ClassEntry {
19 private final AccessFlags access;
20 private final Signature signature;
21
22 public ClassDefEntry(String className, Signature signature, AccessFlags access) {
23 super(className);
24 Preconditions.checkNotNull(signature, "Class signature cannot be null");
25 Preconditions.checkNotNull(access, "Class access cannot be null");
26 this.signature = signature;
27 this.access = access;
28 }
29
30 public Signature getSignature() {
31 return signature;
32 }
33
34 public AccessFlags getAccess() {
35 return access;
36 }
37}
diff --git a/src/main/java/cuchaz/enigma/mapping/ClassEntry.java b/src/main/java/cuchaz/enigma/mapping/entry/ClassEntry.java
index 788811ff..c7958256 100644
--- a/src/main/java/cuchaz/enigma/mapping/ClassEntry.java
+++ b/src/main/java/cuchaz/enigma/mapping/entry/ClassEntry.java
@@ -9,20 +9,20 @@
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11 11
12package cuchaz.enigma.mapping; 12package cuchaz.enigma.mapping.entry;
13 13
14import com.google.common.base.Preconditions;
14import com.google.common.collect.Lists; 15import com.google.common.collect.Lists;
15 16
16import java.util.List; 17import java.util.List;
17 18
18public class ClassEntry implements Entry { 19public class ClassEntry implements Entry {
19 20
20 private String name; 21 private final String name;
21 22
22 public ClassEntry(String className) { 23 public ClassEntry(String className) {
23 if (className == null) { 24 Preconditions.checkNotNull(className, "Class name cannot be null");
24 throw new IllegalArgumentException("Class name cannot be null!"); 25
25 }
26 if (className.indexOf('.') >= 0) { 26 if (className.indexOf('.') >= 0) {
27 throw new IllegalArgumentException("Class name must be in JVM format. ie, path/to/package/class$inner : " + className); 27 throw new IllegalArgumentException("Class name must be in JVM format. ie, path/to/package/class$inner : " + className);
28 } 28 }
@@ -49,12 +49,12 @@ public class ClassEntry implements Entry {
49 } 49 }
50 50
51 @Override 51 @Override
52 public ClassEntry getClassEntry() { 52 public ClassEntry getOwnerClassEntry() {
53 return this; 53 return this;
54 } 54 }
55 55
56 @Override 56 @Override
57 public ClassEntry cloneToNewClass(ClassEntry classEntry) { 57 public ClassEntry updateOwnership(ClassEntry classEntry) {
58 return classEntry; 58 return classEntry;
59 } 59 }
60 60
@@ -77,6 +77,10 @@ public class ClassEntry implements Entry {
77 return this.name; 77 return this.name;
78 } 78 }
79 79
80 public boolean isArray() {
81 return this.name.lastIndexOf('[') >= 0;
82 }
83
80 public boolean isInnerClass() { 84 public boolean isInnerClass() {
81 return this.name.lastIndexOf('$') >= 0; 85 return this.name.lastIndexOf('$') >= 0;
82 } 86 }
@@ -132,11 +136,7 @@ public class ClassEntry implements Entry {
132 } 136 }
133 137
134 public String getPackageName() { 138 public String getPackageName() {
135 int pos = this.name.lastIndexOf('/'); 139 return getPackageName(this.name);
136 if (pos > 0) {
137 return this.name.substring(0, pos);
138 }
139 return null;
140 } 140 }
141 141
142 public String getSimpleName() { 142 public String getSimpleName() {
@@ -147,6 +147,14 @@ public class ClassEntry implements Entry {
147 return this.name; 147 return this.name;
148 } 148 }
149 149
150 public static String getPackageName(String name) {
151 int pos = name.lastIndexOf('/');
152 if (pos > 0) {
153 return name.substring(0, pos);
154 }
155 return null;
156 }
157
150 public ClassEntry buildClassEntry(List<ClassEntry> classChain) { 158 public ClassEntry buildClassEntry(List<ClassEntry> classChain) {
151 assert (classChain.contains(this)); 159 assert (classChain.contains(this));
152 StringBuilder buf = new StringBuilder(); 160 StringBuilder buf = new StringBuilder();
diff --git a/src/main/java/cuchaz/enigma/mapping/Entry.java b/src/main/java/cuchaz/enigma/mapping/entry/Entry.java
index c79510b9..b612140f 100644
--- a/src/main/java/cuchaz/enigma/mapping/Entry.java
+++ b/src/main/java/cuchaz/enigma/mapping/entry/Entry.java
@@ -9,14 +9,14 @@
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11 11
12package cuchaz.enigma.mapping; 12package cuchaz.enigma.mapping.entry;
13 13
14public interface Entry { 14public interface Entry {
15 String getName(); 15 String getName();
16 16
17 String getClassName(); 17 String getClassName();
18 18
19 ClassEntry getClassEntry(); 19 ClassEntry getOwnerClassEntry();
20 20
21 Entry cloneToNewClass(ClassEntry classEntry); 21 Entry updateOwnership(ClassEntry classEntry);
22} 22}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.mapping.entry;
13
14import cuchaz.enigma.analysis.JarIndex;
15import cuchaz.enigma.mapping.ClassMapping;
16import cuchaz.enigma.mapping.FieldMapping;
17import cuchaz.enigma.mapping.MethodDescriptor;
18import cuchaz.enigma.mapping.MethodMapping;
19
20public class EntryFactory {
21 public static ClassEntry getObfClassEntry(JarIndex jarIndex, ClassMapping classMapping) {
22 ClassEntry obfClassEntry = new ClassEntry(classMapping.getObfFullName());
23 return obfClassEntry.buildClassEntry(jarIndex.getObfClassChain(obfClassEntry));
24 }
25
26 private static ClassEntry getObfClassEntry(ClassMapping classMapping) {
27 return new ClassEntry(classMapping.getObfFullName());
28 }
29
30 public static ClassEntry getDeobfClassEntry(ClassMapping classMapping) {
31 return new ClassEntry(classMapping.getDeobfName());
32 }
33
34 public static FieldEntry getObfFieldEntry(ClassMapping classMapping, FieldMapping fieldMapping) {
35 return new FieldEntry(getObfClassEntry(classMapping), fieldMapping.getObfName(), fieldMapping.getObfDesc());
36 }
37
38 public static MethodEntry getMethodEntry(ClassEntry classEntry, String name, MethodDescriptor desc) {
39 return new MethodEntry(classEntry, name, desc);
40 }
41
42 public static MethodEntry getObfMethodEntry(ClassEntry classEntry, MethodMapping methodMapping) {
43 return getMethodEntry(classEntry, methodMapping.getObfName(), methodMapping.getObfDesc());
44 }
45
46 public static MethodEntry getObfMethodEntry(ClassMapping classMapping, MethodMapping methodMapping) {
47 return getObfMethodEntry(getObfClassEntry(classMapping), methodMapping);
48 }
49}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.mapping.entry;
13
14import com.google.common.base.Preconditions;
15import cuchaz.enigma.bytecode.AccessFlags;
16import cuchaz.enigma.mapping.Signature;
17import cuchaz.enigma.mapping.TypeDescriptor;
18
19public class FieldDefEntry extends FieldEntry {
20 private final AccessFlags access;
21 private final Signature signature;
22
23 public FieldDefEntry(ClassEntry ownerEntry, String name, TypeDescriptor desc, Signature signature, AccessFlags access) {
24 super(ownerEntry, name, desc);
25 Preconditions.checkNotNull(access, "Field access cannot be null");
26 Preconditions.checkNotNull(signature, "Field signature cannot be null");
27 this.access = access;
28 this.signature = signature;
29 }
30
31 public AccessFlags getAccess() {
32 return access;
33 }
34
35 public Signature getSignature() {
36 return signature;
37 }
38
39 @Override
40 public FieldDefEntry updateOwnership(ClassEntry owner) {
41 return new FieldDefEntry(owner, this.name, this.desc, signature, access);
42 }
43}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.mapping.entry;
13
14import com.google.common.base.Preconditions;
15import cuchaz.enigma.mapping.TypeDescriptor;
16import cuchaz.enigma.utils.Utils;
17
18public class FieldEntry implements Entry {
19
20 protected final ClassEntry ownerEntry;
21 protected final String name;
22 protected final TypeDescriptor desc;
23
24 // NOTE: this argument order is important for the MethodReader/MethodWriter
25 public FieldEntry(ClassEntry ownerEntry, String name, TypeDescriptor desc) {
26 Preconditions.checkNotNull(ownerEntry, "Owner cannot be null");
27 Preconditions.checkNotNull(name, "Field name cannot be null");
28 Preconditions.checkNotNull(desc, "Field descriptor cannot be null");
29
30 this.ownerEntry = ownerEntry;
31 this.name = name;
32 this.desc = desc;
33 }
34
35 @Override
36 public ClassEntry getOwnerClassEntry() {
37 return this.ownerEntry;
38 }
39
40 @Override
41 public String getName() {
42 return this.name;
43 }
44
45 @Override
46 public String getClassName() {
47 return this.ownerEntry.getName();
48 }
49
50 public TypeDescriptor getDesc() {
51 return this.desc;
52 }
53
54 @Override
55 public FieldEntry updateOwnership(ClassEntry owner) {
56 return new FieldEntry(owner, this.name, this.desc);
57 }
58
59 @Override
60 public int hashCode() {
61 return Utils.combineHashesOrdered(this.ownerEntry, this.name, this.desc);
62 }
63
64 @Override
65 public boolean equals(Object other) {
66 return other instanceof FieldEntry && equals((FieldEntry) other);
67 }
68
69 public boolean equals(FieldEntry other) {
70 return this.ownerEntry.equals(other.ownerEntry) && this.name.equals(other.name) && this.desc.equals(other.desc);
71 }
72
73 @Override
74 public String toString() {
75 return this.ownerEntry.getName() + "." + this.name + ":" + this.desc;
76 }
77}
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 @@
1package cuchaz.enigma.mapping.entry;
2
3import com.google.common.base.Preconditions;
4import cuchaz.enigma.mapping.TypeDescriptor;
5import cuchaz.enigma.utils.Utils;
6
7/**
8 * TypeDescriptor...
9 * Created by Thog
10 * 19/10/2016
11 */
12public class LocalVariableDefEntry extends LocalVariableEntry {
13
14 protected final MethodDefEntry ownerEntry;
15 protected final TypeDescriptor desc;
16
17 public LocalVariableDefEntry(MethodDefEntry ownerEntry, int index, String name, TypeDescriptor desc) {
18 super(ownerEntry, index, name);
19 Preconditions.checkNotNull(desc, "Variable desc cannot be null");
20
21 this.ownerEntry = ownerEntry;
22 this.desc = desc;
23 }
24
25 @Override
26 public MethodDefEntry getOwnerEntry() {
27 return this.ownerEntry;
28 }
29
30 public TypeDescriptor getDesc() {
31 return desc;
32 }
33
34 @Override
35 public LocalVariableDefEntry updateOwnership(ClassEntry classEntry) {
36 return new LocalVariableDefEntry(ownerEntry.updateOwnership(classEntry), index, name, desc);
37 }
38
39 @Override
40 public int hashCode() {
41 return Utils.combineHashesOrdered(this.ownerEntry, this.desc.hashCode(), this.name.hashCode(), Integer.hashCode(this.index));
42 }
43
44 @Override
45 public boolean equals(Object other) {
46 return other instanceof LocalVariableDefEntry && equals((LocalVariableDefEntry) other);
47 }
48
49 public boolean equals(LocalVariableDefEntry other) {
50 return this.ownerEntry.equals(other.ownerEntry) && this.desc.equals(other.desc) && this.name.equals(other.name) && this.index == other.index;
51 }
52
53 @Override
54 public String toString() {
55 return this.ownerEntry + "(" + this.index + ":" + this.name + ":" + this.desc + ")";
56 }
57}
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 @@
1package cuchaz.enigma.mapping.entry;
2
3import com.google.common.base.Preconditions;
4import cuchaz.enigma.mapping.MethodDescriptor;
5import cuchaz.enigma.utils.Utils;
6
7/**
8 * TypeDescriptor...
9 * Created by Thog
10 * 19/10/2016
11 */
12public class LocalVariableEntry implements Entry {
13
14 protected final MethodEntry ownerEntry;
15 protected final String name;
16 protected final int index;
17
18 public LocalVariableEntry(MethodEntry ownerEntry, int index, String name) {
19 Preconditions.checkNotNull(ownerEntry, "Variable owner cannot be null");
20 Preconditions.checkNotNull(name, "Variable name cannot be null");
21 Preconditions.checkArgument(index >= 0, "Index must be positive");
22
23 this.ownerEntry = ownerEntry;
24 this.name = name;
25 this.index = index;
26 }
27
28 public MethodEntry getOwnerEntry() {
29 return this.ownerEntry;
30 }
31
32 public int getIndex() {
33 return index;
34 }
35
36 @Override
37 public String getName() {
38 return this.name;
39 }
40
41 @Override
42 public ClassEntry getOwnerClassEntry() {
43 return this.ownerEntry.getOwnerClassEntry();
44 }
45
46 @Override
47 public String getClassName() {
48 return this.ownerEntry.getClassName();
49 }
50
51 @Override
52 public LocalVariableEntry updateOwnership(ClassEntry classEntry) {
53 return new LocalVariableEntry(ownerEntry.updateOwnership(classEntry), index, name);
54 }
55
56 public String getMethodName() {
57 return this.ownerEntry.getName();
58 }
59
60 public MethodDescriptor getMethodDesc() {
61 return this.ownerEntry.getDesc();
62 }
63
64 @Override
65 public int hashCode() {
66 return Utils.combineHashesOrdered(this.ownerEntry, this.name.hashCode(), Integer.hashCode(this.index));
67 }
68
69 @Override
70 public boolean equals(Object other) {
71 return other instanceof LocalVariableEntry && equals((LocalVariableEntry) other);
72 }
73
74 public boolean equals(LocalVariableEntry other) {
75 return this.ownerEntry.equals(other.ownerEntry) && this.name.equals(other.name) && this.index == other.index;
76 }
77
78 @Override
79 public String toString() {
80 return this.ownerEntry + "(" + this.index + ":" + this.name + ")";
81 }
82}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.mapping.entry;
13
14import com.google.common.base.Preconditions;
15import cuchaz.enigma.bytecode.AccessFlags;
16import cuchaz.enigma.mapping.MethodDescriptor;
17import cuchaz.enigma.mapping.Signature;
18
19public class MethodDefEntry extends MethodEntry {
20
21 private final AccessFlags access;
22 private final Signature signature;
23
24 public MethodDefEntry(ClassEntry classEntry, String name, MethodDescriptor descriptor, Signature signature, AccessFlags access) {
25 super(classEntry, name, descriptor);
26 Preconditions.checkNotNull(access, "Method access cannot be null");
27 Preconditions.checkNotNull(signature, "Method signature cannot be null");
28 this.access = access;
29 this.signature = signature;
30 }
31
32 public AccessFlags getAccess() {
33 return access;
34 }
35
36 public Signature getSignature() {
37 return signature;
38 }
39
40 public int getVariableOffset(ClassDefEntry ownerEntry) {
41 // Enum constructors have an implicit "name" and "ordinal" parameter as well as "this"
42 if (ownerEntry.getAccess().isEnum() && getName().startsWith("<")) {
43 return 3;
44 } else {
45 // If we're not static, "this" is bound to index 0
46 return getAccess().isStatic() ? 0 : 1;
47 }
48 }
49
50 @Override
51 public MethodDefEntry updateOwnership(ClassEntry classEntry) {
52 return new MethodDefEntry(new ClassEntry(classEntry.getName()), name, descriptor, signature, access);
53 }
54}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.mapping.entry;
13
14import com.google.common.base.Preconditions;
15import cuchaz.enigma.mapping.MethodDescriptor;
16import cuchaz.enigma.utils.Utils;
17
18public class MethodEntry implements Entry {
19
20 protected final ClassEntry classEntry;
21 protected final String name;
22 protected final MethodDescriptor descriptor;
23
24 public MethodEntry(ClassEntry classEntry, String name, MethodDescriptor descriptor) {
25 Preconditions.checkNotNull(classEntry, "Class cannot be null");
26 Preconditions.checkNotNull(name, "Method name cannot be null");
27 Preconditions.checkNotNull(descriptor, "Method descriptor cannot be null");
28
29 this.classEntry = classEntry;
30 this.name = name;
31 this.descriptor = descriptor;
32 }
33
34 @Override
35 public ClassEntry getOwnerClassEntry() {
36 return this.classEntry;
37 }
38
39 @Override
40 public String getName() {
41 return this.name;
42 }
43
44 public MethodDescriptor getDesc() {
45 return this.descriptor;
46 }
47
48 public boolean isConstructor() {
49 return name.equals("<init>") || name.equals("<clinit>");
50 }
51
52 @Override
53 public String getClassName() {
54 return this.classEntry.getName();
55 }
56
57 @Override
58 public MethodEntry updateOwnership(ClassEntry classEntry) {
59 return new MethodEntry(new ClassEntry(classEntry.getName()), name, descriptor);
60 }
61
62 @Override
63 public int hashCode() {
64 return Utils.combineHashesOrdered(this.classEntry, this.name, this.descriptor);
65 }
66
67 @Override
68 public boolean equals(Object other) {
69 return other instanceof MethodEntry && equals((MethodEntry) other);
70 }
71
72 public boolean equals(MethodEntry other) {
73 return this.classEntry.equals(other.getOwnerClassEntry()) && this.name.equals(other.getName()) && this.descriptor.equals(other.getDesc());
74 }
75
76 @Override
77 public String toString() {
78 return this.classEntry.getName() + "." + this.name + this.descriptor;
79 }
80}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.mapping.entry;
13
14import com.strobel.assembler.metadata.FieldDefinition;
15import com.strobel.assembler.metadata.MemberReference;
16import com.strobel.assembler.metadata.MethodDefinition;
17import cuchaz.enigma.bytecode.AccessFlags;
18import cuchaz.enigma.mapping.MethodDescriptor;
19import cuchaz.enigma.mapping.Signature;
20import cuchaz.enigma.mapping.TypeDescriptor;
21
22public class ProcyonEntryFactory {
23 private final ReferencedEntryPool entryPool;
24
25 public ProcyonEntryFactory(ReferencedEntryPool entryPool) {
26 this.entryPool = entryPool;
27 }
28
29 public FieldEntry getFieldEntry(MemberReference def) {
30 ClassEntry classEntry = entryPool.getClass(def.getDeclaringType().getInternalName());
31 return entryPool.getField(classEntry, def.getName(), def.getErasedSignature());
32 }
33
34 public FieldDefEntry getFieldDefEntry(FieldDefinition def) {
35 ClassEntry classEntry = entryPool.getClass(def.getDeclaringType().getInternalName());
36 return new FieldDefEntry(classEntry, def.getName(), new TypeDescriptor(def.getErasedSignature()), Signature.createTypedSignature(def.getSignature()), new AccessFlags(def.getModifiers()));
37 }
38
39 public MethodEntry getMethodEntry(MemberReference def) {
40 ClassEntry classEntry = entryPool.getClass(def.getDeclaringType().getInternalName());
41 return entryPool.getMethod(classEntry, def.getName(), def.getErasedSignature());
42 }
43
44 public MethodDefEntry getMethodDefEntry(MethodDefinition def) {
45 ClassEntry classEntry = entryPool.getClass(def.getDeclaringType().getInternalName());
46 return new MethodDefEntry(classEntry, def.getName(), new MethodDescriptor(def.getErasedSignature()), Signature.createSignature(def.getSignature()), new AccessFlags(def.getModifiers()));
47 }
48}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.mapping.entry;
13
14import cuchaz.enigma.mapping.MethodDescriptor;
15import cuchaz.enigma.mapping.TypeDescriptor;
16
17import java.util.HashMap;
18import java.util.Map;
19
20public class ReferencedEntryPool {
21 private final Map<String, ClassEntry> classEntries = new HashMap<>();
22 private final Map<String, Map<String, MethodEntry>> methodEntries = new HashMap<>();
23 private final Map<String, Map<String, FieldEntry>> fieldEntries = new HashMap<>();
24
25 public ClassEntry getClass(String name) {
26 return this.classEntries.computeIfAbsent(name, s -> new ClassEntry(name));
27 }
28
29 public MethodEntry getMethod(ClassEntry ownerEntry, String name, String desc) {
30 return getMethod(ownerEntry, name, new MethodDescriptor(desc));
31 }
32
33 public MethodEntry getMethod(ClassEntry ownerEntry, String name, MethodDescriptor desc) {
34 String key = name + desc.toString();
35 return getClassMethods(ownerEntry.getName()).computeIfAbsent(key, s -> new MethodEntry(ownerEntry, name, desc));
36 }
37
38 public FieldEntry getField(ClassEntry ownerEntry, String name, String desc) {
39 return getField(ownerEntry, name, new TypeDescriptor(desc));
40 }
41
42 public FieldEntry getField(ClassEntry ownerEntry, String name, TypeDescriptor desc) {
43 return getClassFields(ownerEntry.getName()).computeIfAbsent(name, s -> new FieldEntry(ownerEntry, name, desc));
44 }
45
46 private Map<String, MethodEntry> getClassMethods(String name) {
47 return methodEntries.computeIfAbsent(name, s -> new HashMap<>());
48 }
49
50 private Map<String, FieldEntry> getClassFields(String name) {
51 return fieldEntries.computeIfAbsent(name, s -> new HashMap<>());
52 }
53}
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 @@
12package cuchaz.enigma.throwables; 12package cuchaz.enigma.throwables;
13 13
14import java.io.File; 14import java.io.File;
15import java.util.function.Supplier;
15 16
16public class MappingParseException extends Exception { 17public class MappingParseException extends Exception {
17 18
@@ -25,6 +26,12 @@ public class MappingParseException extends Exception {
25 filePath = file.getAbsolutePath(); 26 filePath = file.getAbsolutePath();
26 } 27 }
27 28
29 public MappingParseException(Supplier<String> filenameProvider, int line, String message) {
30 this.line = line;
31 this.message = message;
32 filePath = filenameProvider.get();
33 }
34
28 @Override 35 @Override
29 public String getMessage() { 36 public String getMessage() {
30 return "Line " + line + ": " + message + " in file " + filePath; 37 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 @@
1/*
2 * Originally:
3 * EnumSwitchRewriterTransform.java
4 *
5 * Copyright (c) 2013 Mike Strobel
6 *
7 * This source code is based on Mono.Cecil from Jb Evain, Copyright (c) Jb Evain;
8 * and ILSpy/ICSharpCode from SharpDevelop, Copyright (c) AlphaSierraPapa.
9 *
10 * This source code is subject to terms and conditions of the Apache License, Version 2.0.
11 * A copy of the license can be found in the License.html file at the root of this distribution.
12 * By using this source code in any fashion, you are agreeing to be bound by the terms of the
13 * Apache License, Version 2.0.
14 *
15 * You must not remove this notice, or any other, from this software.
16 */
17
18package oml.ast.transformers;
19
20import com.strobel.assembler.metadata.BuiltinTypes;
21import com.strobel.assembler.metadata.FieldDefinition;
22import com.strobel.assembler.metadata.MethodDefinition;
23import com.strobel.assembler.metadata.TypeDefinition;
24import com.strobel.assembler.metadata.TypeReference;
25import com.strobel.core.SafeCloseable;
26import com.strobel.core.VerifyArgument;
27import com.strobel.decompiler.DecompilerContext;
28import com.strobel.decompiler.languages.java.ast.AssignmentExpression;
29import com.strobel.decompiler.languages.java.ast.AstBuilder;
30import com.strobel.decompiler.languages.java.ast.AstNode;
31import com.strobel.decompiler.languages.java.ast.CaseLabel;
32import com.strobel.decompiler.languages.java.ast.ContextTrackingVisitor;
33import com.strobel.decompiler.languages.java.ast.Expression;
34import com.strobel.decompiler.languages.java.ast.IdentifierExpression;
35import com.strobel.decompiler.languages.java.ast.IndexerExpression;
36import com.strobel.decompiler.languages.java.ast.InvocationExpression;
37import com.strobel.decompiler.languages.java.ast.Keys;
38import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression;
39import com.strobel.decompiler.languages.java.ast.PrimitiveExpression;
40import com.strobel.decompiler.languages.java.ast.SwitchSection;
41import com.strobel.decompiler.languages.java.ast.SwitchStatement;
42import com.strobel.decompiler.languages.java.ast.TypeDeclaration;
43import com.strobel.decompiler.languages.java.ast.TypeReferenceExpression;
44import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform;
45
46import java.util.ArrayList;
47import java.util.IdentityHashMap;
48import java.util.LinkedHashMap;
49import java.util.List;
50import java.util.Map;
51
52/**
53 * Copy of {@link com.strobel.decompiler.languages.java.ast.transforms.EnumSwitchRewriterTransform} modified to:
54 * - Not rely on a field containing "$SwitchMap$" (Proguard strips it)
55 * - Ignore classes *with* SwitchMap$ names (so the original can handle it)
56 * - Ignores inner synthetics that are not package private
57 */
58@SuppressWarnings("Duplicates")
59public class ObfuscatedEnumSwitchRewriterTransform implements IAstTransform {
60 private final DecompilerContext _context;
61
62 public ObfuscatedEnumSwitchRewriterTransform(final DecompilerContext context) {
63 _context = VerifyArgument.notNull(context, "context");
64 }
65
66 @Override
67 public void run(final AstNode compilationUnit) {
68 compilationUnit.acceptVisitor(new Visitor(_context), null);
69 }
70
71 private final static class Visitor extends ContextTrackingVisitor<Void> {
72 private final static class SwitchMapInfo {
73 final String enclosingType;
74 final Map<String, List<SwitchStatement>> switches = new LinkedHashMap<>();
75 final Map<String, Map<Integer, Expression>> mappings = new LinkedHashMap<>();
76
77 TypeDeclaration enclosingTypeDeclaration;
78
79 SwitchMapInfo(final String enclosingType) {
80 this.enclosingType = enclosingType;
81 }
82 }
83
84 private final Map<String, SwitchMapInfo> _switchMaps = new LinkedHashMap<>();
85 private boolean _isSwitchMapWrapper;
86
87 protected Visitor(final DecompilerContext context) {
88 super(context);
89 }
90
91 @Override
92 public Void visitTypeDeclaration(final TypeDeclaration typeDeclaration, final Void p) {
93 final boolean oldIsSwitchMapWrapper = _isSwitchMapWrapper;
94 final TypeDefinition typeDefinition = typeDeclaration.getUserData(Keys.TYPE_DEFINITION);
95 final boolean isSwitchMapWrapper = isSwitchMapWrapper(typeDefinition);
96
97 if (isSwitchMapWrapper) {
98 final String internalName = typeDefinition.getInternalName();
99
100 SwitchMapInfo info = _switchMaps.get(internalName);
101
102 if (info == null) {
103 _switchMaps.put(internalName, info = new SwitchMapInfo(internalName));
104 }
105
106 info.enclosingTypeDeclaration = typeDeclaration;
107 }
108
109 _isSwitchMapWrapper = isSwitchMapWrapper;
110
111 try {
112 super.visitTypeDeclaration(typeDeclaration, p);
113 }
114 finally {
115 _isSwitchMapWrapper = oldIsSwitchMapWrapper;
116 }
117
118 rewrite();
119
120 return null;
121 }
122
123 @Override
124 public Void visitSwitchStatement(final SwitchStatement node, final Void data) {
125 final Expression test = node.getExpression();
126
127 if (test instanceof IndexerExpression) {
128 final IndexerExpression indexer = (IndexerExpression) test;
129 final Expression array = indexer.getTarget();
130 final Expression argument = indexer.getArgument();
131
132 if (!(array instanceof MemberReferenceExpression)) {
133 return super.visitSwitchStatement(node, data);
134 }
135
136 final MemberReferenceExpression arrayAccess = (MemberReferenceExpression) array;
137 final Expression arrayOwner = arrayAccess.getTarget();
138 final String mapName = arrayAccess.getMemberName();
139
140 if (mapName == null || mapName.startsWith("$SwitchMap$") || !(arrayOwner instanceof TypeReferenceExpression)) {
141 return super.visitSwitchStatement(node, data);
142 }
143
144 final TypeReferenceExpression enclosingTypeExpression = (TypeReferenceExpression) arrayOwner;
145 final TypeReference enclosingType = enclosingTypeExpression.getType().getUserData(Keys.TYPE_REFERENCE);
146
147 if (!isSwitchMapWrapper(enclosingType) || !(argument instanceof InvocationExpression)) {
148 return super.visitSwitchStatement(node, data);
149 }
150
151 final InvocationExpression invocation = (InvocationExpression) argument;
152 final Expression invocationTarget = invocation.getTarget();
153
154 if (!(invocationTarget instanceof MemberReferenceExpression)) {
155 return super.visitSwitchStatement(node, data);
156 }
157
158 final MemberReferenceExpression memberReference = (MemberReferenceExpression) invocationTarget;
159
160 if (!"ordinal".equals(memberReference.getMemberName())) {
161 return super.visitSwitchStatement(node, data);
162 }
163
164 final String enclosingTypeName = enclosingType.getInternalName();
165
166 SwitchMapInfo info = _switchMaps.get(enclosingTypeName);
167
168 if (info == null) {
169 _switchMaps.put(enclosingTypeName, info = new SwitchMapInfo(enclosingTypeName));
170
171 final TypeDefinition resolvedType = enclosingType.resolve();
172
173 if (resolvedType != null) {
174 AstBuilder astBuilder = context.getUserData(Keys.AST_BUILDER);
175
176 if (astBuilder == null) {
177 astBuilder = new AstBuilder(context);
178 }
179
180 try (final SafeCloseable importSuppression = astBuilder.suppressImports()) {
181 final TypeDeclaration declaration = astBuilder.createType(resolvedType);
182
183 declaration.acceptVisitor(this, data);
184 }
185 }
186 }
187
188 List<SwitchStatement> switches = info.switches.get(mapName);
189
190 if (switches == null) {
191 info.switches.put(mapName, switches = new ArrayList<>());
192 }
193
194 switches.add(node);
195 }
196
197 return super.visitSwitchStatement(node, data);
198 }
199
200 @Override
201 public Void visitAssignmentExpression(final AssignmentExpression node, final Void data) {
202 final TypeDefinition currentType = context.getCurrentType();
203 final MethodDefinition currentMethod = context.getCurrentMethod();
204
205 if (_isSwitchMapWrapper &&
206 currentType != null &&
207 currentMethod != null &&
208 currentMethod.isTypeInitializer()) {
209
210 final Expression left = node.getLeft();
211 final Expression right = node.getRight();
212
213 if (left instanceof IndexerExpression &&
214 right instanceof PrimitiveExpression) {
215
216 String mapName = null;
217
218 final Expression array = ((IndexerExpression) left).getTarget();
219 final Expression argument = ((IndexerExpression) left).getArgument();
220
221 if (array instanceof MemberReferenceExpression) {
222 mapName = ((MemberReferenceExpression) array).getMemberName();
223 }
224 else if (array instanceof IdentifierExpression) {
225 mapName = ((IdentifierExpression) array).getIdentifier();
226 }
227
228 if (mapName == null || mapName.startsWith("$SwitchMap$")) {
229 return super.visitAssignmentExpression(node, data);
230 }
231
232 if (!(argument instanceof InvocationExpression)) {
233 return super.visitAssignmentExpression(node, data);
234 }
235
236 final InvocationExpression invocation = (InvocationExpression) argument;
237 final Expression invocationTarget = invocation.getTarget();
238
239 if (!(invocationTarget instanceof MemberReferenceExpression)) {
240 return super.visitAssignmentExpression(node, data);
241 }
242
243 final MemberReferenceExpression memberReference = (MemberReferenceExpression) invocationTarget;
244 final Expression memberTarget = memberReference.getTarget();
245
246 if (!(memberTarget instanceof MemberReferenceExpression) || !"ordinal".equals(memberReference.getMemberName())) {
247 return super.visitAssignmentExpression(node, data);
248 }
249
250 final MemberReferenceExpression outerMemberReference = (MemberReferenceExpression) memberTarget;
251 final Expression outerMemberTarget = outerMemberReference.getTarget();
252
253 if (!(outerMemberTarget instanceof TypeReferenceExpression)) {
254 return super.visitAssignmentExpression(node, data);
255 }
256
257 final String enclosingType = currentType.getInternalName();
258
259 SwitchMapInfo info = _switchMaps.get(enclosingType);
260
261 if (info == null) {
262 _switchMaps.put(enclosingType, info = new SwitchMapInfo(enclosingType));
263
264 AstBuilder astBuilder = context.getUserData(Keys.AST_BUILDER);
265
266 if (astBuilder == null) {
267 astBuilder = new AstBuilder(context);
268 }
269
270 info.enclosingTypeDeclaration = astBuilder.createType(currentType);
271 }
272
273 final PrimitiveExpression value = (PrimitiveExpression) right;
274
275 assert value.getValue() instanceof Integer;
276
277 Map<Integer, Expression> mapping = info.mappings.get(mapName);
278
279 if (mapping == null) {
280 info.mappings.put(mapName, mapping = new LinkedHashMap<>());
281 }
282
283 final IdentifierExpression enumValue = new IdentifierExpression( Expression.MYSTERY_OFFSET, outerMemberReference.getMemberName());
284
285 enumValue.putUserData(Keys.MEMBER_REFERENCE, outerMemberReference.getUserData(Keys.MEMBER_REFERENCE));
286
287 mapping.put(((Number) value.getValue()).intValue(), enumValue);
288 }
289 }
290
291 return super.visitAssignmentExpression(node, data);
292 }
293
294 private void rewrite() {
295 if (_switchMaps.isEmpty()) {
296 return;
297 }
298
299 for (final SwitchMapInfo info : _switchMaps.values()) {
300 rewrite(info);
301 }
302
303 //
304 // Remove switch map type wrappers that are no longer referenced.
305 //
306
307 outer:
308 for (final SwitchMapInfo info : _switchMaps.values()) {
309 for (final String mapName : info.switches.keySet()) {
310 final List<SwitchStatement> switches = info.switches.get(mapName);
311
312 if (switches != null && !switches.isEmpty()) {
313 continue outer;
314 }
315 }
316
317 final TypeDeclaration enclosingTypeDeclaration = info.enclosingTypeDeclaration;
318
319 if (enclosingTypeDeclaration != null) {
320 enclosingTypeDeclaration.remove();
321 }
322 }
323 }
324
325 private void rewrite(final SwitchMapInfo info) {
326 if (info.switches.isEmpty()) {
327 return;
328 }
329
330 for (final String mapName : info.switches.keySet()) {
331 final List<SwitchStatement> switches = info.switches.get(mapName);
332 final Map<Integer, Expression> mappings = info.mappings.get(mapName);
333
334 if (switches != null && mappings != null) {
335 for (int i = 0; i < switches.size(); i++) {
336 if (rewriteSwitch(switches.get(i), mappings)) {
337 switches.remove(i--);
338 }
339 }
340 }
341 }
342 }
343
344 private boolean rewriteSwitch(final SwitchStatement s, final Map<Integer, Expression> mappings) {
345 final Map<Expression, Expression> replacements = new IdentityHashMap<>();
346
347 for (final SwitchSection section : s.getSwitchSections()) {
348 for (final CaseLabel caseLabel : section.getCaseLabels()) {
349 final Expression expression = caseLabel.getExpression();
350
351 if (expression.isNull()) {
352 continue;
353 }
354
355 if (expression instanceof PrimitiveExpression) {
356 final Object value = ((PrimitiveExpression) expression).getValue();
357
358 if (value instanceof Integer) {
359 final Expression replacement = mappings.get(value);
360
361 if (replacement != null) {
362 replacements.put(expression, replacement);
363 continue;
364 }
365 }
366 }
367
368 //
369 // If we can't rewrite all cases, we abort.
370 //
371
372 return false;
373 }
374 }
375
376 final IndexerExpression indexer = (IndexerExpression) s.getExpression();
377 final InvocationExpression argument = (InvocationExpression) indexer.getArgument();
378 final MemberReferenceExpression memberReference = (MemberReferenceExpression) argument.getTarget();
379 final Expression newTest = memberReference.getTarget();
380
381 newTest.remove();
382 indexer.replaceWith(newTest);
383
384 for (final Map.Entry<Expression, Expression> entry : replacements.entrySet()) {
385 entry.getKey().replaceWith(entry.getValue().clone());
386 }
387
388 return true;
389 }
390
391 private static boolean isSwitchMapWrapper(final TypeReference type) {
392 if (type == null) {
393 return false;
394 }
395
396 final TypeDefinition definition = type instanceof TypeDefinition ? (TypeDefinition) type
397 : type.resolve();
398
399 if (definition == null || !definition.isSynthetic() || !definition.isInnerClass() || !definition.isPackagePrivate()) {
400 return false;
401 }
402
403 for (final FieldDefinition field : definition.getDeclaredFields()) {
404 if (!field.getName().startsWith("$SwitchMap$") &&
405 BuiltinTypes.Integer.makeArrayType().equals(field.getFieldType())) {
406
407 return true;
408 }
409 }
410
411 return false;
412 }
413 }
414} \ No newline at end of file