summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar jeff2015-02-23 23:29:22 -0500
committerGravatar jeff2015-02-23 23:29:22 -0500
commit2dc7428e37bdd7a119f53d02ce157675509b0d63 (patch)
tree68f409ac726166e427eea3a199eb462130c53ccd
parentmake types serializable (diff)
downloadenigma-fork-2dc7428e37bdd7a119f53d02ce157675509b0d63.tar.gz
enigma-fork-2dc7428e37bdd7a119f53d02ce157675509b0d63.tar.xz
enigma-fork-2dc7428e37bdd7a119f53d02ce157675509b0d63.zip
lots of work in better handling of inner classes
also working on recognizing unobfuscated and deobfuscated jars (needed for M3L)
-rw-r--r--build.py18
-rw-r--r--src/cuchaz/enigma/CommandMain.java43
-rw-r--r--src/cuchaz/enigma/Deobfuscator.java4
-rw-r--r--src/cuchaz/enigma/MainFormatConverter.java2
-rw-r--r--src/cuchaz/enigma/TranslatingTypeLoader.java6
-rw-r--r--src/cuchaz/enigma/analysis/JarIndex.java67
-rw-r--r--src/cuchaz/enigma/bytecode/InnerClassWriter.java43
-rw-r--r--src/cuchaz/enigma/convert/ClassMatcher.java6
-rw-r--r--src/cuchaz/enigma/mapping/ClassMapping.java41
-rw-r--r--src/cuchaz/enigma/mapping/EntryFactory.java35
-rw-r--r--src/cuchaz/enigma/mapping/Mappings.java14
-rw-r--r--src/cuchaz/enigma/mapping/MappingsRenamer.java2
-rw-r--r--src/cuchaz/enigma/mapping/MappingsWriter.java4
-rw-r--r--src/cuchaz/enigma/mapping/Translator.java154
-rw-r--r--test/cuchaz/enigma/TestInnerClasses.java67
-rw-r--r--test/cuchaz/enigma/TestJarIndexLoneClass.java7
-rw-r--r--test/cuchaz/enigma/TestTranslator.java36
-rw-r--r--test/cuchaz/enigma/inputs/innerClasses/F_ClassTree.java20
-rw-r--r--test/cuchaz/enigma/inputs/translation/G_ObjectMethods.java (renamed from test/cuchaz/enigma/inputs/translation/F_ObjectMethods.java)2
-rw-r--r--test/cuchaz/enigma/inputs/translation/H_OuterClass.java26
-rw-r--r--test/cuchaz/enigma/inputs/translation/M_NamelessClass.java26
-rw-r--r--test/cuchaz/enigma/resources/translation.mappings12
22 files changed, 452 insertions, 183 deletions
diff --git a/build.py b/build.py
index f5723e0..1f80d50 100644
--- a/build.py
+++ b/build.py
@@ -18,8 +18,8 @@ import ssjb
18import ssjb.ivy 18import ssjb.ivy
19 19
20 20
21ArtifactStandalone = ssjb.ivy.Dep("cuchaz:enigma:0.7b") 21ArtifactStandalone = ssjb.ivy.Dep("cuchaz:enigma:0.8b")
22ArtifactLib = ssjb.ivy.Dep("cuchaz:enigma-lib:0.7b") 22ArtifactLib = ssjb.ivy.Dep("cuchaz:enigma-lib:0.8b")
23 23
24# dependencies 24# dependencies
25ExtraRepos = [ 25ExtraRepos = [
@@ -46,7 +46,7 @@ def buildTestJar(name, glob):
46 pathJar = os.path.join(DirBuild, "%s.jar" % name) 46 pathJar = os.path.join(DirBuild, "%s.jar" % name)
47 pathObfJar = os.path.join(DirBuild, "%s.obf.jar" % name) 47 pathObfJar = os.path.join(DirBuild, "%s.obf.jar" % name)
48 48
49 # build the deobf jar 49 # build the unobf jar
50 with ssjb.file.TempDir("tmp") as dirTemp: 50 with ssjb.file.TempDir("tmp") as dirTemp:
51 ssjb.file.copyTree(dirTemp, DirBin, ssjb.file.find(DirBin, "cuchaz/enigma/inputs/Keep.class")) 51 ssjb.file.copyTree(dirTemp, DirBin, ssjb.file.find(DirBin, "cuchaz/enigma/inputs/Keep.class"))
52 ssjb.file.copyTree(dirTemp, DirBin, ssjb.file.find(DirBin, glob)) 52 ssjb.file.copyTree(dirTemp, DirBin, ssjb.file.find(DirBin, glob))
@@ -58,13 +58,18 @@ def buildTestJar(name, glob):
58 ["@proguard.conf", "-injars", pathJar, "-outjars", pathObfJar] 58 ["@proguard.conf", "-injars", pathJar, "-outjars", pathObfJar]
59 ) 59 )
60 60
61def buildDeobfTestJar(outPath, inPath):
62 ssjb.callJava(
63 [DirBin, os.path.join(DirLib, "deps.jar")],
64 "cuchaz.enigma.CommandMain",
65 ["deobfuscate", inPath, outPath]
66 )
61 67
62def applyReadme(dirTemp): 68def applyReadme(dirTemp):
63 ssjb.file.copy(dirTemp, "license.APL2.txt") 69 ssjb.file.copy(dirTemp, "license.APL2.txt")
64 ssjb.file.copy(dirTemp, "license.GPL3.txt") 70 ssjb.file.copy(dirTemp, "license.GPL3.txt")
65 ssjb.file.copy(dirTemp, "readme.txt") 71 ssjb.file.copy(dirTemp, "readme.txt")
66 72
67
68def buildStandaloneJar(dirOut): 73def buildStandaloneJar(dirOut):
69 with ssjb.file.TempDir(os.path.join(dirOut, "tmp")) as dirTemp: 74 with ssjb.file.TempDir(os.path.join(dirOut, "tmp")) as dirTemp:
70 ssjb.file.copyTree(dirTemp, DirBin, ssjb.file.find(DirBin)) 75 ssjb.file.copyTree(dirTemp, DirBin, ssjb.file.find(DirBin))
@@ -105,10 +110,11 @@ def taskBuildTestJars():
105 buildTestJar("testConstructors", "cuchaz/enigma/inputs/constructors/*.class") 110 buildTestJar("testConstructors", "cuchaz/enigma/inputs/constructors/*.class")
106 buildTestJar("testInheritanceTree", "cuchaz/enigma/inputs/inheritanceTree/*.class") 111 buildTestJar("testInheritanceTree", "cuchaz/enigma/inputs/inheritanceTree/*.class")
107 buildTestJar("testInnerClasses", "cuchaz/enigma/inputs/innerClasses/*.class") 112 buildTestJar("testInnerClasses", "cuchaz/enigma/inputs/innerClasses/*.class")
108 buildTestJar("testTranslation", "cuchaz/enigma/inputs/translation/*.class") 113 taskBuildTranslationTestJar()
109 114
110def taskBuildTranslationTestJar(): 115def taskBuildTranslationTestJar():
111 buildTestJar("testTranslation", "cuchaz/enigma/inputs/translation/*.class") 116 buildTestJar("testTranslation", "cuchaz/enigma/inputs/translation/*.class")
117 buildDeobfTestJar(os.path.join(DirBuild, "testTranslation.deobf.jar"), os.path.join(DirBuild, "testTranslation.obf.jar"))
112 118
113def taskBuild(): 119def taskBuild():
114 ssjb.file.delete(DirBuild) 120 ssjb.file.delete(DirBuild)
diff --git a/src/cuchaz/enigma/CommandMain.java b/src/cuchaz/enigma/CommandMain.java
index 1ec2ad2..0253a92 100644
--- a/src/cuchaz/enigma/CommandMain.java
+++ b/src/cuchaz/enigma/CommandMain.java
@@ -51,7 +51,7 @@ public class CommandMain {
51 try { 51 try {
52 52
53 // process the command 53 // process the command
54 String command = getArg(args, 0, "command"); 54 String command = getArg(args, 0, "command", true);
55 if (command.equalsIgnoreCase("deobfuscate")) { 55 if (command.equalsIgnoreCase("deobfuscate")) {
56 deobfuscate(args); 56 deobfuscate(args);
57 } else if(command.equalsIgnoreCase("decompile")) { 57 } else if(command.equalsIgnoreCase("decompile")) {
@@ -70,46 +70,55 @@ public class CommandMain {
70 System.out.println("Usage:"); 70 System.out.println("Usage:");
71 System.out.println("\tjava -cp enigma.jar cuchaz.enigma.CommandMain <command>"); 71 System.out.println("\tjava -cp enigma.jar cuchaz.enigma.CommandMain <command>");
72 System.out.println("\twhere <command> is one of:"); 72 System.out.println("\twhere <command> is one of:");
73 System.out.println("\t\tdeobfuscate <mappings file> <in jar> <out jar>"); 73 System.out.println("\t\tdeobfuscate <in jar> <out jar> [<mappings file>]");
74 System.out.println("\t\tdecompile <mappings file> <in jar> <out folder>"); 74 System.out.println("\t\tdecompile <in jar> <out folder> [<mappings file>]");
75 } 75 }
76 76
77 private static void decompile(String[] args) 77 private static void decompile(String[] args)
78 throws Exception { 78 throws Exception {
79 File fileMappings = getReadableFile(getArg(args, 1, "mappings file")); 79 File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true));
80 File fileJarIn = getReadableFile(getArg(args, 2, "in jar")); 80 File fileJarOut = getWritableFolder(getArg(args, 2, "out folder", true));
81 File fileJarOut = getWritableFolder(getArg(args, 3, "out folder")); 81 File fileMappings = getReadableFile(getArg(args, 3, "mappings file", false));
82 Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn)); 82 Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn));
83 deobfuscator.writeSources(fileJarOut, new ConsoleProgressListener()); 83 deobfuscator.writeSources(fileJarOut, new ConsoleProgressListener());
84 } 84 }
85 85
86 private static void deobfuscate(String[] args) 86 private static void deobfuscate(String[] args)
87 throws Exception { 87 throws Exception {
88 File fileMappings = getReadableFile(getArg(args, 1, "mappings file")); 88 File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true));
89 File fileJarIn = getReadableFile(getArg(args, 2, "in jar")); 89 File fileJarOut = getWritableFile(getArg(args, 2, "out jar", true));
90 File fileJarOut = getWritableFile(getArg(args, 3, "out jar")); 90 File fileMappings = getReadableFile(getArg(args, 3, "mappings file", false));
91 Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn)); 91 Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn));
92 deobfuscator.writeJar(fileJarOut, new ConsoleProgressListener()); 92 deobfuscator.writeJar(fileJarOut, new ConsoleProgressListener());
93 } 93 }
94 94
95 private static Deobfuscator getDeobfuscator(File fileMappings, JarFile jar) 95 private static Deobfuscator getDeobfuscator(File fileMappings, JarFile jar)
96 throws Exception { 96 throws Exception {
97 System.out.println("Reading mappings...");
98 Mappings mappings = new MappingsReader().read(new FileReader(fileMappings));
99 System.out.println("Reading jar..."); 97 System.out.println("Reading jar...");
100 Deobfuscator deobfuscator = new Deobfuscator(jar); 98 Deobfuscator deobfuscator = new Deobfuscator(jar);
101 deobfuscator.setMappings(mappings); 99 if (fileMappings != null) {
100 System.out.println("Reading mappings...");
101 Mappings mappings = new MappingsReader().read(new FileReader(fileMappings));
102 deobfuscator.setMappings(mappings);
103 }
102 return deobfuscator; 104 return deobfuscator;
103 } 105 }
104 106
105 private static String getArg(String[] args, int i, String name) { 107 private static String getArg(String[] args, int i, String name, boolean required) {
106 if (i >= args.length) { 108 if (i >= args.length) {
107 throw new IllegalArgumentException(name + " is required"); 109 if (required) {
110 throw new IllegalArgumentException(name + " is required");
111 } else {
112 return null;
113 }
108 } 114 }
109 return args[i]; 115 return args[i];
110 } 116 }
111 117
112 private static File getWritableFile(String path) { 118 private static File getWritableFile(String path) {
119 if (path == null) {
120 return null;
121 }
113 File file = new File(path).getAbsoluteFile(); 122 File file = new File(path).getAbsoluteFile();
114 File dir = file.getParentFile(); 123 File dir = file.getParentFile();
115 if (dir == null || !dir.exists()) { 124 if (dir == null || !dir.exists()) {
@@ -119,6 +128,9 @@ public class CommandMain {
119 } 128 }
120 129
121 private static File getWritableFolder(String path) { 130 private static File getWritableFolder(String path) {
131 if (path == null) {
132 return null;
133 }
122 File dir = new File(path).getAbsoluteFile(); 134 File dir = new File(path).getAbsoluteFile();
123 if (!dir.exists()) { 135 if (!dir.exists()) {
124 throw new IllegalArgumentException("Cannot write to folder: " + dir); 136 throw new IllegalArgumentException("Cannot write to folder: " + dir);
@@ -127,6 +139,9 @@ public class CommandMain {
127 } 139 }
128 140
129 private static File getReadableFile(String path) { 141 private static File getReadableFile(String path) {
142 if (path == null) {
143 return null;
144 }
130 File file = new File(path).getAbsoluteFile(); 145 File file = new File(path).getAbsoluteFile();
131 if (!file.exists()) { 146 if (!file.exists()) {
132 throw new IllegalArgumentException("Cannot find file: " + file.getAbsolutePath()); 147 throw new IllegalArgumentException("Cannot find file: " + file.getAbsolutePath());
diff --git a/src/cuchaz/enigma/Deobfuscator.java b/src/cuchaz/enigma/Deobfuscator.java
index c1954fc..b7440a7 100644
--- a/src/cuchaz/enigma/Deobfuscator.java
+++ b/src/cuchaz/enigma/Deobfuscator.java
@@ -163,9 +163,9 @@ public class Deobfuscator {
163 } 163 }
164 164
165 // check inner classes 165 // check inner classes
166 for (ClassMapping innerClassMapping : classMapping.innerClasses()) { 166 for (ClassMapping innerClassMapping : Lists.newArrayList(classMapping.innerClasses())) {
167 if (!checkClassMapping(relatedMethodChecker, innerClassMapping)) { 167 if (!checkClassMapping(relatedMethodChecker, innerClassMapping)) {
168 System.err.println("WARNING: unable to find inner class " + innerClassMapping + ". dropping mapping."); 168 System.err.println("WARNING: unable to find inner class " + EntryFactory.getObfClassEntry(m_jarIndex, classMapping) + ". dropping mapping.");
169 classMapping.removeInnerClassMapping(innerClassMapping); 169 classMapping.removeInnerClassMapping(innerClassMapping);
170 } 170 }
171 } 171 }
diff --git a/src/cuchaz/enigma/MainFormatConverter.java b/src/cuchaz/enigma/MainFormatConverter.java
index d4bb2db..5db0e53 100644
--- a/src/cuchaz/enigma/MainFormatConverter.java
+++ b/src/cuchaz/enigma/MainFormatConverter.java
@@ -111,7 +111,7 @@ public class MainFormatConverter {
111 } 111 }
112 112
113 private static Object getFieldKey(ClassMapping classMapping, FieldMapping fieldMapping) { 113 private static Object getFieldKey(ClassMapping classMapping, FieldMapping fieldMapping) {
114 return new ClassEntry(classMapping.getObfName()).getSimpleName() + "." + fieldMapping.getObfName(); 114 return classMapping.getObfSimpleName() + "." + fieldMapping.getObfName();
115 } 115 }
116 116
117 private static String getFieldKey(FieldEntry obfFieldEntry) { 117 private static String getFieldKey(FieldEntry obfFieldEntry) {
diff --git a/src/cuchaz/enigma/TranslatingTypeLoader.java b/src/cuchaz/enigma/TranslatingTypeLoader.java
index 12cde4b..26d5e7a 100644
--- a/src/cuchaz/enigma/TranslatingTypeLoader.java
+++ b/src/cuchaz/enigma/TranslatingTypeLoader.java
@@ -110,10 +110,10 @@ public class TranslatingTypeLoader implements ITypeLoader {
110 ClassEntry obfClassEntry = m_obfuscatingTranslator.translateEntry(deobfClassEntry); 110 ClassEntry obfClassEntry = m_obfuscatingTranslator.translateEntry(deobfClassEntry);
111 111
112 // is this an inner class referenced directly? 112 // is this an inner class referenced directly?
113 String obfOuterClassName = m_jarIndex.getOuterClass(obfClassEntry.getSimpleName()); 113 ClassEntry obfOuterClassEntry = m_jarIndex.getOuterClass(obfClassEntry);
114 if (obfOuterClassName != null) { 114 if (obfOuterClassEntry != null) {
115 // this class doesn't really exist. Reference it by outer$inner instead 115 // this class doesn't really exist. Reference it by outer$inner instead
116 System.err.println(String.format("WARNING: class %s referenced by bare inner name instead of via outer class %s", deobfClassName, obfOuterClassName)); 116 System.err.println(String.format("WARNING: class %s referenced by bare inner name instead of via outer class %s", deobfClassName, obfOuterClassEntry));
117 return null; 117 return null;
118 } 118 }
119 119
diff --git a/src/cuchaz/enigma/analysis/JarIndex.java b/src/cuchaz/enigma/analysis/JarIndex.java
index 1c74f15..6e7c69d 100644
--- a/src/cuchaz/enigma/analysis/JarIndex.java
+++ b/src/cuchaz/enigma/analysis/JarIndex.java
@@ -61,9 +61,9 @@ public class JarIndex {
61 private Multimap<String,MethodEntry> m_methodImplementations; 61 private Multimap<String,MethodEntry> m_methodImplementations;
62 private Multimap<BehaviorEntry,EntryReference<BehaviorEntry,BehaviorEntry>> m_behaviorReferences; 62 private Multimap<BehaviorEntry,EntryReference<BehaviorEntry,BehaviorEntry>> m_behaviorReferences;
63 private Multimap<FieldEntry,EntryReference<FieldEntry,BehaviorEntry>> m_fieldReferences; 63 private Multimap<FieldEntry,EntryReference<FieldEntry,BehaviorEntry>> m_fieldReferences;
64 private Multimap<String,String> m_innerClasses; 64 private Multimap<ClassEntry,ClassEntry> m_innerClassesByOuter;
65 private Map<String,String> m_outerClasses; 65 private Map<ClassEntry,ClassEntry> m_outerClassesByInner;
66 private Map<String,BehaviorEntry> m_anonymousClasses; 66 private Map<ClassEntry,BehaviorEntry> m_anonymousClasses;
67 private Map<MethodEntry,MethodEntry> m_bridgedMethods; 67 private Map<MethodEntry,MethodEntry> m_bridgedMethods;
68 68
69 public JarIndex() { 69 public JarIndex() {
@@ -74,8 +74,8 @@ public class JarIndex {
74 m_methodImplementations = HashMultimap.create(); 74 m_methodImplementations = HashMultimap.create();
75 m_behaviorReferences = HashMultimap.create(); 75 m_behaviorReferences = HashMultimap.create();
76 m_fieldReferences = HashMultimap.create(); 76 m_fieldReferences = HashMultimap.create();
77 m_innerClasses = HashMultimap.create(); 77 m_innerClassesByOuter = HashMultimap.create();
78 m_outerClasses = Maps.newHashMap(); 78 m_outerClassesByInner = Maps.newHashMap();
79 m_anonymousClasses = Maps.newHashMap(); 79 m_anonymousClasses = Maps.newHashMap();
80 m_bridgedMethods = Maps.newHashMap(); 80 m_bridgedMethods = Maps.newHashMap();
81 } 81 }
@@ -129,33 +129,40 @@ public class JarIndex {
129 } 129 }
130 130
131 if (buildInnerClasses) { 131 if (buildInnerClasses) {
132
132 // step 5: index inner classes and anonymous classes 133 // step 5: index inner classes and anonymous classes
133 for (CtClass c : JarClassIterator.classes(jar)) { 134 for (CtClass c : JarClassIterator.classes(jar)) {
134 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage); 135 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
135 String outerClassName = findOuterClass(c); 136 ClassEntry innerClassEntry = EntryFactory.getClassEntry(c);
136 if (outerClassName != null) { 137 ClassEntry outerClassEntry = findOuterClass(c);
137 String innerClassName = c.getSimpleName(); 138 if (outerClassEntry != null) {
138 m_innerClasses.put(outerClassName, innerClassName); 139 m_innerClassesByOuter.put(outerClassEntry, innerClassEntry);
139 boolean innerWasAdded = m_outerClasses.put(innerClassName, outerClassName) == null; 140 boolean innerWasAdded = m_outerClassesByInner.put(innerClassEntry, outerClassEntry) == null;
140 assert (innerWasAdded); 141 assert (innerWasAdded);
141 142
142 BehaviorEntry enclosingBehavior = isAnonymousClass(c, outerClassName); 143 BehaviorEntry enclosingBehavior = isAnonymousClass(c, outerClassEntry);
143 if (enclosingBehavior != null) { 144 if (enclosingBehavior != null) {
144 m_anonymousClasses.put(innerClassName, enclosingBehavior); 145 m_anonymousClasses.put(innerClassEntry, enclosingBehavior);
145 146
146 // DEBUG 147 // DEBUG
147 // System.out.println( "ANONYMOUS: " + outerClassName + "$" + innerClassName ); 148 //System.out.println("ANONYMOUS: " + outerClassEntry.getName() + "$" + innerClassEntry.getSimpleName());
148 } else { 149 } else {
149 // DEBUG 150 // DEBUG
150 // System.out.println( "INNER: " + outerClassName + "$" + innerClassName ); 151 //System.out.println("INNER: " + outerClassEntry.getName() + "$" + innerClassEntry.getSimpleName());
151 } 152 }
152 } 153 }
153 } 154 }
154 155
155 // step 6: update other indices with inner class info 156 // step 6: update other indices with inner class info
156 Map<String,String> renames = Maps.newHashMap(); 157 Map<String,String> renames = Maps.newHashMap();
157 for (Map.Entry<String,String> entry : m_outerClasses.entrySet()) { 158 for (Map.Entry<ClassEntry,ClassEntry> mapEntry : m_innerClassesByOuter.entries()) {
158 renames.put(Constants.NonePackage + "/" + entry.getKey(), entry.getValue() + "$" + entry.getKey()); 159 ClassEntry outerClassEntry = mapEntry.getKey();
160 ClassEntry innerClassEntry = mapEntry.getValue();
161 outerClassEntry = EntryFactory.getChainedOuterClassName(this, outerClassEntry);
162 String newName = outerClassEntry.getName() + "$" + innerClassEntry.getSimpleName();
163 // DEBUG
164 //System.out.println("REPLACE: " + innerClassEntry.getName() + " WITH " + newName);
165 renames.put(innerClassEntry.getName(), newName);
159 } 166 }
160 EntryRenamer.renameClassesInSet(renames, m_obfClassEntries); 167 EntryRenamer.renameClassesInSet(renames, m_obfClassEntries);
161 m_translationIndex.renameClasses(renames); 168 m_translationIndex.renameClasses(renames);
@@ -290,7 +297,7 @@ public class JarIndex {
290 } 297 }
291 } 298 }
292 299
293 private String findOuterClass(CtClass c) { 300 private ClassEntry findOuterClass(CtClass c) {
294 301
295 // inner classes: 302 // inner classes:
296 // have constructors that can (illegally) set synthetic fields 303 // have constructors that can (illegally) set synthetic fields
@@ -341,19 +348,19 @@ public class JarIndex {
341 // do we have an answer yet? 348 // do we have an answer yet?
342 if (callerClasses.isEmpty()) { 349 if (callerClasses.isEmpty()) {
343 if (illegallySetClasses.size() == 1) { 350 if (illegallySetClasses.size() == 1) {
344 return illegallySetClasses.iterator().next().getName(); 351 return illegallySetClasses.iterator().next();
345 } else { 352 } else {
346 System.out.println(String.format("WARNING: Unable to find outer class for %s. No caller and no illegally set field classes.", classEntry)); 353 System.out.println(String.format("WARNING: Unable to find outer class for %s. No caller and no illegally set field classes.", classEntry));
347 } 354 }
348 } else { 355 } else {
349 if (callerClasses.size() == 1) { 356 if (callerClasses.size() == 1) {
350 return callerClasses.iterator().next().getName(); 357 return callerClasses.iterator().next();
351 } else { 358 } else {
352 // multiple callers, do the illegally set classes narrow it down? 359 // multiple callers, do the illegally set classes narrow it down?
353 Set<ClassEntry> intersection = Sets.newHashSet(callerClasses); 360 Set<ClassEntry> intersection = Sets.newHashSet(callerClasses);
354 intersection.retainAll(illegallySetClasses); 361 intersection.retainAll(illegallySetClasses);
355 if (intersection.size() == 1) { 362 if (intersection.size() == 1) {
356 return intersection.iterator().next().getName(); 363 return intersection.iterator().next();
357 } else { 364 } else {
358 System.out.println(String.format("WARNING: Unable to choose outer class for %s among options: %s", classEntry, callerClasses)); 365 System.out.println(String.format("WARNING: Unable to choose outer class for %s among options: %s", classEntry, callerClasses));
359 } 366 }
@@ -448,7 +455,7 @@ public class JarIndex {
448 return true; 455 return true;
449 } 456 }
450 457
451 private BehaviorEntry isAnonymousClass(CtClass c, String outerClassName) { 458 private BehaviorEntry isAnonymousClass(CtClass c, ClassEntry outerClassEntry) {
452 459
453 ClassEntry innerClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); 460 ClassEntry innerClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
454 461
@@ -669,23 +676,19 @@ public class JarIndex {
669 return behaviorEntries; 676 return behaviorEntries;
670 } 677 }
671 678
672 public Collection<String> getInnerClasses(String obfOuterClassName) { 679 public Collection<ClassEntry> getInnerClasses(ClassEntry obfOuterClassEntry) {
673 return m_innerClasses.get(obfOuterClassName); 680 return m_innerClassesByOuter.get(obfOuterClassEntry);
674 } 681 }
675 682
676 public String getOuterClass(String obfInnerClassName) { 683 public ClassEntry getOuterClass(ClassEntry obfInnerClassEntry) {
677 // make sure we use the right name 684 return m_outerClassesByInner.get(obfInnerClassEntry);
678 if (new ClassEntry(obfInnerClassName).getPackageName() != null) {
679 throw new IllegalArgumentException("Don't reference obfuscated inner classes using packages: " + obfInnerClassName);
680 }
681 return m_outerClasses.get(obfInnerClassName);
682 } 685 }
683 686
684 public boolean isAnonymousClass(String obfInnerClassName) { 687 public boolean isAnonymousClass(ClassEntry obfInnerClassEntry) {
685 return m_anonymousClasses.containsKey(obfInnerClassName); 688 return m_anonymousClasses.containsKey(obfInnerClassEntry);
686 } 689 }
687 690
688 public BehaviorEntry getAnonymousClassCaller(String obfInnerClassName) { 691 public BehaviorEntry getAnonymousClassCaller(ClassEntry obfInnerClassName) {
689 return m_anonymousClasses.get(obfInnerClassName); 692 return m_anonymousClasses.get(obfInnerClassName);
690 } 693 }
691 694
diff --git a/src/cuchaz/enigma/bytecode/InnerClassWriter.java b/src/cuchaz/enigma/bytecode/InnerClassWriter.java
index 5350b86..e82f56c 100644
--- a/src/cuchaz/enigma/bytecode/InnerClassWriter.java
+++ b/src/cuchaz/enigma/bytecode/InnerClassWriter.java
@@ -14,13 +14,12 @@ import java.util.Collection;
14 14
15import javassist.CtClass; 15import javassist.CtClass;
16import javassist.bytecode.ConstPool; 16import javassist.bytecode.ConstPool;
17import javassist.bytecode.Descriptor;
18import javassist.bytecode.EnclosingMethodAttribute; 17import javassist.bytecode.EnclosingMethodAttribute;
19import javassist.bytecode.InnerClassesAttribute; 18import javassist.bytecode.InnerClassesAttribute;
20import cuchaz.enigma.Constants;
21import cuchaz.enigma.analysis.JarIndex; 19import cuchaz.enigma.analysis.JarIndex;
22import cuchaz.enigma.mapping.BehaviorEntry; 20import cuchaz.enigma.mapping.BehaviorEntry;
23import cuchaz.enigma.mapping.ClassEntry; 21import cuchaz.enigma.mapping.ClassEntry;
22import cuchaz.enigma.mapping.EntryFactory;
24 23
25public class InnerClassWriter { 24public class InnerClassWriter {
26 25
@@ -32,18 +31,23 @@ public class InnerClassWriter {
32 31
33 public void write(CtClass c) { 32 public void write(CtClass c) {
34 33
35 // is this an inner or outer class? 34 // first, assume this is an inner class
36 String obfInnerClassName = new ClassEntry(Descriptor.toJvmName(c.getName())).getSimpleName(); 35 ClassEntry obfInnerClassEntry = EntryFactory.getClassEntry(c);
37 String obfOuterClassName = m_jarIndex.getOuterClass(obfInnerClassName); 36 ClassEntry obfOuterClassEntry = m_jarIndex.getOuterClass(obfInnerClassEntry);
38 if (obfOuterClassName == null) { 37
39 // this is an outer class 38 // see if we're right
40 obfOuterClassName = Descriptor.toJvmName(c.getName()); 39 if (obfOuterClassEntry == null) {
40
41 // nope, it's an outer class
42 obfInnerClassEntry = null;
43 obfOuterClassEntry = EntryFactory.getClassEntry(c);
41 } else { 44 } else {
42 // this is an inner class, rename it to outer$inner 45
43 ClassEntry obfClassEntry = new ClassEntry(obfOuterClassName + "$" + obfInnerClassName); 46 // yeah, it's an inner class, rename it to outer$inner
47 ClassEntry obfClassEntry = new ClassEntry(obfOuterClassEntry.getName() + "$" + obfInnerClassEntry.getSimpleName());
44 c.setName(obfClassEntry.getName()); 48 c.setName(obfClassEntry.getName());
45 49
46 BehaviorEntry caller = m_jarIndex.getAnonymousClassCaller(obfInnerClassName); 50 BehaviorEntry caller = m_jarIndex.getAnonymousClassCaller(obfInnerClassEntry);
47 if (caller != null) { 51 if (caller != null) {
48 // write the enclosing method attribute 52 // write the enclosing method attribute
49 if (caller.getName().equals("<clinit>")) { 53 if (caller.getName().equals("<clinit>")) {
@@ -55,18 +59,19 @@ public class InnerClassWriter {
55 } 59 }
56 60
57 // write the inner classes if needed 61 // write the inner classes if needed
58 Collection<String> obfInnerClassNames = m_jarIndex.getInnerClasses(obfOuterClassName); 62 Collection<ClassEntry> obfInnerClassEntries = m_jarIndex.getInnerClasses(obfOuterClassEntry);
59 if (obfInnerClassNames != null && !obfInnerClassNames.isEmpty()) { 63 if (obfInnerClassEntries != null && !obfInnerClassEntries.isEmpty()) {
60 writeInnerClasses(c, obfOuterClassName, obfInnerClassNames); 64 writeInnerClasses(c, obfOuterClassEntry, obfInnerClassEntries);
61 } 65 }
62 } 66 }
63 67
64 private void writeInnerClasses(CtClass c, String obfOuterClassName, Collection<String> obfInnerClassNames) { 68 private void writeInnerClasses(CtClass c, ClassEntry obfOuterClassEntry, Collection<ClassEntry> obfInnerClassEntries) {
65 InnerClassesAttribute attr = new InnerClassesAttribute(c.getClassFile().getConstPool()); 69 InnerClassesAttribute attr = new InnerClassesAttribute(c.getClassFile().getConstPool());
66 c.getClassFile().addAttribute(attr); 70 c.getClassFile().addAttribute(attr);
67 for (String obfInnerClassName : obfInnerClassNames) { 71 for (ClassEntry obfInnerClassEntry : obfInnerClassEntries) {
72
68 // get the new inner class name 73 // get the new inner class name
69 ClassEntry obfClassEntry = new ClassEntry(obfOuterClassName + "$" + obfInnerClassName); 74 ClassEntry obfClassEntry = new ClassEntry(obfOuterClassEntry.getName() + "$" + obfInnerClassEntry.getSimpleName());
70 75
71 // here's what the JVM spec says about the InnerClasses attribute 76 // here's what the JVM spec says about the InnerClasses attribute
72 // append( inner, outer of inner if inner is member of outer 0 ow, name after $ if inner not anonymous 0 ow, flags ); 77 // append( inner, outer of inner if inner is member of outer 0 ow, name after $ if inner not anonymous 0 ow, flags );
@@ -77,7 +82,7 @@ public class InnerClassWriter {
77 int outerClassIndex = 0; 82 int outerClassIndex = 0;
78 int innerClassSimpleNameIndex = 0; 83 int innerClassSimpleNameIndex = 0;
79 int accessFlags = 0; 84 int accessFlags = 0;
80 if (!m_jarIndex.isAnonymousClass(obfInnerClassName)) { 85 if (!m_jarIndex.isAnonymousClass(obfInnerClassEntry)) {
81 outerClassIndex = constPool.addClassInfo(obfClassEntry.getOuterClassName()); 86 outerClassIndex = constPool.addClassInfo(obfClassEntry.getOuterClassName());
82 innerClassSimpleNameIndex = constPool.addUtf8Info(obfClassEntry.getInnerClassName()); 87 innerClassSimpleNameIndex = constPool.addUtf8Info(obfClassEntry.getInnerClassName());
83 } 88 }
@@ -96,7 +101,7 @@ public class InnerClassWriter {
96 */ 101 */
97 102
98 // make sure the outer class references only the new inner class names 103 // make sure the outer class references only the new inner class names
99 c.replaceClassName(Constants.NonePackage + "/" + obfInnerClassName, obfClassEntry.getName()); 104 c.replaceClassName(obfInnerClassEntry.getName(), obfClassEntry.getName());
100 } 105 }
101 } 106 }
102} 107}
diff --git a/src/cuchaz/enigma/convert/ClassMatcher.java b/src/cuchaz/enigma/convert/ClassMatcher.java
index d70b8eb..224d004 100644
--- a/src/cuchaz/enigma/convert/ClassMatcher.java
+++ b/src/cuchaz/enigma/convert/ClassMatcher.java
@@ -222,7 +222,7 @@ public class ClassMatcher {
222 // check the method matches 222 // check the method matches
223 System.out.println("Checking methods..."); 223 System.out.println("Checking methods...");
224 for (ClassMapping classMapping : mappings.classes()) { 224 for (ClassMapping classMapping : mappings.classes()) {
225 ClassEntry classEntry = new ClassEntry(classMapping.getObfName()); 225 ClassEntry classEntry = new ClassEntry(classMapping.getObfFullName());
226 for (MethodMapping methodMapping : classMapping.methods()) { 226 for (MethodMapping methodMapping : classMapping.methods()) {
227 227
228 // skip constructors 228 // skip constructors
@@ -240,13 +240,13 @@ public class ClassMatcher {
240 240
241 // show the available methods 241 // show the available methods
242 System.err.println("\tAvailable dest methods:"); 242 System.err.println("\tAvailable dest methods:");
243 CtClass c = destLoader.loadClass(classMapping.getObfName()); 243 CtClass c = destLoader.loadClass(classMapping.getObfFullName());
244 for (CtBehavior behavior : c.getDeclaredBehaviors()) { 244 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
245 System.err.println("\t\t" + EntryFactory.getBehaviorEntry(behavior)); 245 System.err.println("\t\t" + EntryFactory.getBehaviorEntry(behavior));
246 } 246 }
247 247
248 System.err.println("\tAvailable source methods:"); 248 System.err.println("\tAvailable source methods:");
249 c = sourceLoader.loadClass(matchedClassNames.inverse().get(classMapping.getObfName())); 249 c = sourceLoader.loadClass(matchedClassNames.inverse().get(classMapping.getObfFullName()));
250 for (CtBehavior behavior : c.getDeclaredBehaviors()) { 250 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
251 System.err.println("\t\t" + EntryFactory.getBehaviorEntry(behavior)); 251 System.err.println("\t\t" + EntryFactory.getBehaviorEntry(behavior));
252 } 252 }
diff --git a/src/cuchaz/enigma/mapping/ClassMapping.java b/src/cuchaz/enigma/mapping/ClassMapping.java
index 885400b..3610e33 100644
--- a/src/cuchaz/enigma/mapping/ClassMapping.java
+++ b/src/cuchaz/enigma/mapping/ClassMapping.java
@@ -20,7 +20,8 @@ public class ClassMapping implements Serializable, Comparable<ClassMapping> {
20 20
21 private static final long serialVersionUID = -5148491146902340107L; 21 private static final long serialVersionUID = -5148491146902340107L;
22 22
23 private String m_obfName; 23 private String m_obfFullName;
24 private String m_obfSimpleName;
24 private String m_deobfName; 25 private String m_deobfName;
25 private Map<String,ClassMapping> m_innerClassesByObf; 26 private Map<String,ClassMapping> m_innerClassesByObf;
26 private Map<String,ClassMapping> m_innerClassesByDeobf; 27 private Map<String,ClassMapping> m_innerClassesByDeobf;
@@ -34,7 +35,8 @@ public class ClassMapping implements Serializable, Comparable<ClassMapping> {
34 } 35 }
35 36
36 public ClassMapping(String obfName, String deobfName) { 37 public ClassMapping(String obfName, String deobfName) {
37 m_obfName = obfName; 38 m_obfFullName = obfName;
39 m_obfSimpleName = new ClassEntry(obfName).getSimpleName();
38 m_deobfName = NameValidator.validateClassName(deobfName, false); 40 m_deobfName = NameValidator.validateClassName(deobfName, false);
39 m_innerClassesByObf = Maps.newHashMap(); 41 m_innerClassesByObf = Maps.newHashMap();
40 m_innerClassesByDeobf = Maps.newHashMap(); 42 m_innerClassesByDeobf = Maps.newHashMap();
@@ -44,8 +46,12 @@ public class ClassMapping implements Serializable, Comparable<ClassMapping> {
44 m_methodsByDeobf = Maps.newHashMap(); 46 m_methodsByDeobf = Maps.newHashMap();
45 } 47 }
46 48
47 public String getObfName() { 49 public String getObfFullName() {
48 return m_obfName; 50 return m_obfFullName;
51 }
52
53 public String getObfSimpleName() {
54 return m_obfSimpleName;
49 } 55 }
50 56
51 public String getDeobfName() { 57 public String getDeobfName() {
@@ -64,8 +70,7 @@ public class ClassMapping implements Serializable, Comparable<ClassMapping> {
64 } 70 }
65 71
66 public void addInnerClassMapping(ClassMapping classMapping) { 72 public void addInnerClassMapping(ClassMapping classMapping) {
67 assert (isSimpleClassName(classMapping.getObfName())); 73 boolean obfWasAdded = m_innerClassesByObf.put(classMapping.getObfSimpleName(), classMapping) == null;
68 boolean obfWasAdded = m_innerClassesByObf.put(classMapping.getObfName(), classMapping) == null;
69 assert (obfWasAdded); 74 assert (obfWasAdded);
70 if (classMapping.getDeobfName() != null) { 75 if (classMapping.getDeobfName() != null) {
71 assert (isSimpleClassName(classMapping.getDeobfName())); 76 assert (isSimpleClassName(classMapping.getDeobfName()));
@@ -75,7 +80,7 @@ public class ClassMapping implements Serializable, Comparable<ClassMapping> {
75 } 80 }
76 81
77 public void removeInnerClassMapping(ClassMapping classMapping) { 82 public void removeInnerClassMapping(ClassMapping classMapping) {
78 boolean obfWasRemoved = m_innerClassesByObf.remove(classMapping.getObfName()) != null; 83 boolean obfWasRemoved = m_innerClassesByObf.remove(classMapping.getObfSimpleName()) != null;
79 assert (obfWasRemoved); 84 assert (obfWasRemoved);
80 if (classMapping.getDeobfName() != null) { 85 if (classMapping.getDeobfName() != null) {
81 boolean deobfWasRemoved = m_innerClassesByDeobf.remove(classMapping.getDeobfName()) != null; 86 boolean deobfWasRemoved = m_innerClassesByDeobf.remove(classMapping.getDeobfName()) != null;
@@ -112,11 +117,11 @@ public class ClassMapping implements Serializable, Comparable<ClassMapping> {
112 return classMapping; 117 return classMapping;
113 } 118 }
114 119
115 public String getObfInnerClassName(String deobfName) { 120 public String getObfInnerClassSimpleName(String deobfName) {
116 assert (isSimpleClassName(deobfName)); 121 assert (isSimpleClassName(deobfName));
117 ClassMapping classMapping = m_innerClassesByDeobf.get(deobfName); 122 ClassMapping classMapping = m_innerClassesByDeobf.get(deobfName);
118 if (classMapping != null) { 123 if (classMapping != null) {
119 return classMapping.getObfName(); 124 return classMapping.getObfSimpleName();
120 } 125 }
121 return null; 126 return null;
122 } 127 }
@@ -163,7 +168,7 @@ public class ClassMapping implements Serializable, Comparable<ClassMapping> {
163 public void addFieldMapping(FieldMapping fieldMapping) { 168 public void addFieldMapping(FieldMapping fieldMapping) {
164 String obfKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType()); 169 String obfKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType());
165 if (m_fieldsByObf.containsKey(obfKey)) { 170 if (m_fieldsByObf.containsKey(obfKey)) {
166 throw new Error("Already have mapping for " + m_obfName + "." + obfKey); 171 throw new Error("Already have mapping for " + m_obfFullName + "." + obfKey);
167 } 172 }
168 String deobfKey = getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfType()); 173 String deobfKey = getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfType());
169 if (m_fieldsByDeobf.containsKey(deobfKey)) { 174 if (m_fieldsByDeobf.containsKey(deobfKey)) {
@@ -257,7 +262,7 @@ public class ClassMapping implements Serializable, Comparable<ClassMapping> {
257 public void addMethodMapping(MethodMapping methodMapping) { 262 public void addMethodMapping(MethodMapping methodMapping) {
258 String obfKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()); 263 String obfKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature());
259 if (m_methodsByObf.containsKey(obfKey)) { 264 if (m_methodsByObf.containsKey(obfKey)) {
260 throw new Error("Already have mapping for " + m_obfName + "." + obfKey); 265 throw new Error("Already have mapping for " + m_obfFullName + "." + obfKey);
261 } 266 }
262 boolean wasAdded = m_methodsByObf.put(obfKey, methodMapping) == null; 267 boolean wasAdded = m_methodsByObf.put(obfKey, methodMapping) == null;
263 assert (wasAdded); 268 assert (wasAdded);
@@ -339,7 +344,7 @@ public class ClassMapping implements Serializable, Comparable<ClassMapping> {
339 @Override 344 @Override
340 public String toString() { 345 public String toString() {
341 StringBuilder buf = new StringBuilder(); 346 StringBuilder buf = new StringBuilder();
342 buf.append(m_obfName); 347 buf.append(m_obfFullName);
343 buf.append(" <-> "); 348 buf.append(" <-> ");
344 buf.append(m_deobfName); 349 buf.append(m_deobfName);
345 buf.append("\n"); 350 buf.append("\n");
@@ -359,7 +364,7 @@ public class ClassMapping implements Serializable, Comparable<ClassMapping> {
359 buf.append("Inner Classes:\n"); 364 buf.append("Inner Classes:\n");
360 for (ClassMapping classMapping : m_innerClassesByObf.values()) { 365 for (ClassMapping classMapping : m_innerClassesByObf.values()) {
361 buf.append("\t"); 366 buf.append("\t");
362 buf.append(classMapping.getObfName()); 367 buf.append(classMapping.getObfSimpleName());
363 buf.append(" <-> "); 368 buf.append(" <-> ");
364 buf.append(classMapping.getDeobfName()); 369 buf.append(classMapping.getDeobfName());
365 buf.append("\n"); 370 buf.append("\n");
@@ -370,10 +375,10 @@ public class ClassMapping implements Serializable, Comparable<ClassMapping> {
370 @Override 375 @Override
371 public int compareTo(ClassMapping other) { 376 public int compareTo(ClassMapping other) {
372 // sort by a, b, c, ... aa, ab, etc 377 // sort by a, b, c, ... aa, ab, etc
373 if (m_obfName.length() != other.m_obfName.length()) { 378 if (m_obfFullName.length() != other.m_obfFullName.length()) {
374 return m_obfName.length() - other.m_obfName.length(); 379 return m_obfFullName.length() - other.m_obfFullName.length();
375 } 380 }
376 return m_obfName.compareTo(other.m_obfName); 381 return m_obfFullName.compareTo(other.m_obfFullName);
377 } 382 }
378 383
379 public boolean renameObfClass(String oldObfClassName, String newObfClassName) { 384 public boolean renameObfClass(String oldObfClassName, String newObfClassName) {
@@ -399,9 +404,9 @@ public class ClassMapping implements Serializable, Comparable<ClassMapping> {
399 } 404 }
400 } 405 }
401 406
402 if (m_obfName.equals(oldObfClassName)) { 407 if (m_obfFullName.equals(oldObfClassName)) {
403 // rename this class 408 // rename this class
404 m_obfName = newObfClassName; 409 m_obfFullName = newObfClassName;
405 return true; 410 return true;
406 } 411 }
407 return false; 412 return false;
diff --git a/src/cuchaz/enigma/mapping/EntryFactory.java b/src/cuchaz/enigma/mapping/EntryFactory.java
index dceea29..bbdfa73 100644
--- a/src/cuchaz/enigma/mapping/EntryFactory.java
+++ b/src/cuchaz/enigma/mapping/EntryFactory.java
@@ -25,25 +25,19 @@ public class EntryFactory {
25 } 25 }
26 26
27 public static ClassEntry getObfClassEntry(JarIndex jarIndex, ClassMapping classMapping) { 27 public static ClassEntry getObfClassEntry(JarIndex jarIndex, ClassMapping classMapping) {
28 return new ClassEntry(getChainedOuterClassName(jarIndex, classMapping.getObfName())); 28 return getChainedOuterClassName(jarIndex, new ClassEntry(classMapping.getObfFullName()));
29 } 29 }
30 30
31 private static String getChainedOuterClassName(JarIndex jarIndex, String obfClassName) { 31 public static ClassEntry getChainedOuterClassName(JarIndex jarIndex, ClassEntry obfClassEntry) {
32 32
33 // lookup the chain of outer classes 33 // lookup the chain of outer classes
34 List<String> obfOuterClassNames = Lists.newArrayList(); 34 List<ClassEntry> obfClassChain = Lists.newArrayList(obfClassEntry);
35 String checkName = obfClassName; 35 ClassEntry checkClassEntry = obfClassEntry;
36 while (true) { 36 while (true) {
37 37 ClassEntry obfOuterClassEntry = jarIndex.getOuterClass(checkClassEntry);
38 // if this class name has a package, then it can't be an inner class 38 if (obfOuterClassEntry != null) {
39 if (!new ClassEntry(checkName).isInDefaultPackage()) { 39 obfClassChain.add(obfOuterClassEntry);
40 break; 40 checkClassEntry = obfOuterClassEntry;
41 }
42
43 String obfOuterClassName = jarIndex.getOuterClass(checkName);
44 if (obfOuterClassName != null) {
45 obfOuterClassNames.add(obfOuterClassName);
46 checkName = obfOuterClassName;
47 } else { 41 } else {
48 break; 42 break;
49 } 43 }
@@ -51,12 +45,15 @@ public class EntryFactory {
51 45
52 // build the chained class name 46 // build the chained class name
53 StringBuilder buf = new StringBuilder(); 47 StringBuilder buf = new StringBuilder();
54 for (int i=obfOuterClassNames.size()-1; i>=0; i--) { 48 for (int i=obfClassChain.size()-1; i>=0; i--) {
55 buf.append(obfOuterClassNames.get(i)); 49 if (buf.length() == 0) {
56 buf.append("$"); 50 buf.append(obfClassChain.get(i).getName());
51 } else {
52 buf.append("$");
53 buf.append(obfClassChain.get(i).getSimpleName());
54 }
57 } 55 }
58 buf.append(obfClassName); 56 return new ClassEntry(buf.toString());
59 return buf.toString();
60 } 57 }
61 58
62 public static ClassEntry getDeobfClassEntry(ClassMapping classMapping) { 59 public static ClassEntry getDeobfClassEntry(ClassMapping classMapping) {
diff --git a/src/cuchaz/enigma/mapping/Mappings.java b/src/cuchaz/enigma/mapping/Mappings.java
index 675fdf1..a85bcbf 100644
--- a/src/cuchaz/enigma/mapping/Mappings.java
+++ b/src/cuchaz/enigma/mapping/Mappings.java
@@ -37,7 +37,7 @@ public class Mappings implements Serializable {
37 this(); 37 this();
38 38
39 for (ClassMapping classMapping : classes) { 39 for (ClassMapping classMapping : classes) {
40 m_classesByObf.put(classMapping.getObfName(), classMapping); 40 m_classesByObf.put(classMapping.getObfFullName(), classMapping);
41 if (classMapping.getDeobfName() != null) { 41 if (classMapping.getDeobfName() != null) {
42 m_classesByDeobf.put(classMapping.getDeobfName(), classMapping); 42 m_classesByDeobf.put(classMapping.getDeobfName(), classMapping);
43 } 43 }
@@ -50,10 +50,10 @@ public class Mappings implements Serializable {
50 } 50 }
51 51
52 public void addClassMapping(ClassMapping classMapping) { 52 public void addClassMapping(ClassMapping classMapping) {
53 if (m_classesByObf.containsKey(classMapping.getObfName())) { 53 if (m_classesByObf.containsKey(classMapping.getObfFullName())) {
54 throw new Error("Already have mapping for " + classMapping.getObfName()); 54 throw new Error("Already have mapping for " + classMapping.getObfFullName());
55 } 55 }
56 boolean obfWasAdded = m_classesByObf.put(classMapping.getObfName(), classMapping) == null; 56 boolean obfWasAdded = m_classesByObf.put(classMapping.getObfFullName(), classMapping) == null;
57 assert (obfWasAdded); 57 assert (obfWasAdded);
58 if (classMapping.getDeobfName() != null) { 58 if (classMapping.getDeobfName() != null) {
59 if (m_classesByDeobf.containsKey(classMapping.getDeobfName())) { 59 if (m_classesByDeobf.containsKey(classMapping.getDeobfName())) {
@@ -65,7 +65,7 @@ public class Mappings implements Serializable {
65 } 65 }
66 66
67 public void removeClassMapping(ClassMapping classMapping) { 67 public void removeClassMapping(ClassMapping classMapping) {
68 boolean obfWasRemoved = m_classesByObf.remove(classMapping.getObfName()) != null; 68 boolean obfWasRemoved = m_classesByObf.remove(classMapping.getObfFullName()) != null;
69 assert (obfWasRemoved); 69 assert (obfWasRemoved);
70 if (classMapping.getDeobfName() != null) { 70 if (classMapping.getDeobfName() != null) {
71 boolean deobfWasRemoved = m_classesByDeobf.remove(classMapping.getDeobfName()) != null; 71 boolean deobfWasRemoved = m_classesByDeobf.remove(classMapping.getDeobfName()) != null;
@@ -103,7 +103,7 @@ public class Mappings implements Serializable {
103 if (classMapping.getDeobfName() != null) { 103 if (classMapping.getDeobfName() != null) {
104 classes.put(classMapping.getDeobfName(), classMapping); 104 classes.put(classMapping.getDeobfName(), classMapping);
105 } else { 105 } else {
106 classes.put(classMapping.getObfName(), classMapping); 106 classes.put(classMapping.getObfFullName(), classMapping);
107 } 107 }
108 } 108 }
109 109
@@ -144,7 +144,7 @@ public class Mappings implements Serializable {
144 for (ClassMapping classMapping : classes()) { 144 for (ClassMapping classMapping : classes()) {
145 145
146 // add the class name 146 // add the class name
147 classNames.add(classMapping.getObfName()); 147 classNames.add(classMapping.getObfFullName());
148 148
149 // add classes from method signatures 149 // add classes from method signatures
150 for (MethodMapping methodMapping : classMapping.methods()) { 150 for (MethodMapping methodMapping : classMapping.methods()) {
diff --git a/src/cuchaz/enigma/mapping/MappingsRenamer.java b/src/cuchaz/enigma/mapping/MappingsRenamer.java
index ea343c4..16f700d 100644
--- a/src/cuchaz/enigma/mapping/MappingsRenamer.java
+++ b/src/cuchaz/enigma/mapping/MappingsRenamer.java
@@ -213,7 +213,7 @@ public class MappingsRenamer {
213 ClassMapping classMapping = m_mappings.m_classesByObf.get(obfClassName); 213 ClassMapping classMapping = m_mappings.m_classesByObf.get(obfClassName);
214 if (classMapping == null) { 214 if (classMapping == null) {
215 classMapping = new ClassMapping(obfClassName); 215 classMapping = new ClassMapping(obfClassName);
216 boolean obfWasAdded = m_mappings.m_classesByObf.put(classMapping.getObfName(), classMapping) == null; 216 boolean obfWasAdded = m_mappings.m_classesByObf.put(classMapping.getObfFullName(), classMapping) == null;
217 assert (obfWasAdded); 217 assert (obfWasAdded);
218 } 218 }
219 return classMapping; 219 return classMapping;
diff --git a/src/cuchaz/enigma/mapping/MappingsWriter.java b/src/cuchaz/enigma/mapping/MappingsWriter.java
index c7c2cc0..8b62db8 100644
--- a/src/cuchaz/enigma/mapping/MappingsWriter.java
+++ b/src/cuchaz/enigma/mapping/MappingsWriter.java
@@ -31,9 +31,9 @@ public class MappingsWriter {
31 31
32 private void write(PrintWriter out, ClassMapping classMapping, int depth) throws IOException { 32 private void write(PrintWriter out, ClassMapping classMapping, int depth) throws IOException {
33 if (classMapping.getDeobfName() == null) { 33 if (classMapping.getDeobfName() == null) {
34 out.format("%sCLASS %s\n", getIndent(depth), classMapping.getObfName()); 34 out.format("%sCLASS %s\n", getIndent(depth), classMapping.getObfFullName());
35 } else { 35 } else {
36 out.format("%sCLASS %s %s\n", getIndent(depth), classMapping.getObfName(), classMapping.getDeobfName()); 36 out.format("%sCLASS %s %s\n", getIndent(depth), classMapping.getObfFullName(), classMapping.getDeobfName());
37 } 37 }
38 38
39 for (ClassMapping innerClassMapping : sorted(classMapping.innerClasses())) { 39 for (ClassMapping innerClassMapping : sorted(classMapping.innerClasses())) {
diff --git a/src/cuchaz/enigma/mapping/Translator.java b/src/cuchaz/enigma/mapping/Translator.java
index 759dddf..d985032 100644
--- a/src/cuchaz/enigma/mapping/Translator.java
+++ b/src/cuchaz/enigma/mapping/Translator.java
@@ -10,8 +10,10 @@
10 ******************************************************************************/ 10 ******************************************************************************/
11package cuchaz.enigma.mapping; 11package cuchaz.enigma.mapping;
12 12
13import java.util.List;
13import java.util.Map; 14import java.util.Map;
14 15
16import com.beust.jcommander.internal.Lists;
15import com.google.common.collect.Maps; 17import com.google.common.collect.Maps;
16 18
17import cuchaz.enigma.analysis.TranslationIndex; 19import cuchaz.enigma.analysis.TranslationIndex;
@@ -50,54 +52,106 @@ public class Translator {
50 } 52 }
51 } 53 }
52 54
55 public <T extends Entry> String translate(T entry) {
56 if (entry instanceof ClassEntry) {
57 return translate((ClassEntry)entry);
58 } else if (entry instanceof FieldEntry) {
59 return translate((FieldEntry)entry);
60 } else if (entry instanceof MethodEntry) {
61 return translate((MethodEntry)entry);
62 } else if (entry instanceof ConstructorEntry) {
63 return translate((ConstructorEntry)entry);
64 } else if (entry instanceof ArgumentEntry) {
65 return translate((ArgumentEntry)entry);
66 } else {
67 throw new Error("Unknown entry type: " + entry.getClass().getName());
68 }
69 }
70
53 public String translateClass(String className) { 71 public String translateClass(String className) {
54 return translate(new ClassEntry(className)); 72 return translate(new ClassEntry(className));
55 } 73 }
56 74
57 public String translate(ClassEntry in) { 75 public String translate(ClassEntry in) {
58 ClassMapping classMapping = m_classes.get(in.getOuterClassName()); 76
59 if (classMapping != null) { 77 if (in.isInnerClass()) {
60 if (in.isInnerClass()) { 78
61 // translate the inner class 79 // translate everything in the class chain, or return null
62 String translatedInnerClassName = m_direction.choose( 80 List<ClassMapping> mappingsChain = getClassMappingChain(in);
63 classMapping.getDeobfInnerClassName(in.getInnerClassName()), 81 StringBuilder buf = new StringBuilder();
64 classMapping.getObfInnerClassName(in.getInnerClassName()) 82 for (ClassMapping classMapping : mappingsChain) {
83 if (classMapping == null) {
84 return null;
85 }
86 boolean isFirstClass = buf.length() == 0;
87 String name = m_direction.choose(
88 classMapping.getDeobfName(),
89 isFirstClass ? classMapping.getObfFullName() : classMapping.getObfSimpleName()
65 ); 90 );
66 if (translatedInnerClassName != null) { 91 if (name == null) {
67 // try to translate the outer name 92 return null;
68 String translatedOuterClassName = m_direction.choose(classMapping.getDeobfName(), classMapping.getObfName()); 93 }
69 if (translatedOuterClassName != null) { 94 if (!isFirstClass) {
70 return translatedOuterClassName + "$" + translatedInnerClassName; 95 buf.append("$");
71 } else {
72 return in.getOuterClassName() + "$" + translatedInnerClassName;
73 }
74 } 96 }
75 } else { 97 buf.append(name);
76 // just return outer
77 return m_direction.choose(classMapping.getDeobfName(), classMapping.getObfName());
78 } 98 }
99 return buf.toString();
100
101 } else {
102
103 // normal classes are easier
104 ClassMapping classMapping = m_classes.get(in.getName());
105 if (classMapping == null) {
106 return null;
107 }
108 return m_direction.choose(
109 classMapping.getDeobfName(),
110 classMapping.getObfFullName()
111 );
79 } 112 }
80 return null;
81 } 113 }
82 114
83 public ClassEntry translateEntry(ClassEntry in) { 115 public ClassEntry translateEntry(ClassEntry in) {
84 116
85 // can we translate the inner class?
86 String name = translate(in);
87 if (name != null) {
88 return new ClassEntry(name);
89 }
90
91 if (in.isInnerClass()) { 117 if (in.isInnerClass()) {
92 118
93 // guess not. just translate the outer class name then 119 // translate as much of the class chain as we can
94 String outerClassName = translate(in.getOuterClassEntry()); 120 List<ClassMapping> mappingsChain = getClassMappingChain(in);
95 if (outerClassName != null) { 121 String[] obfClassNames = in.getName().split("\\$");
96 return new ClassEntry(outerClassName + "$" + in.getInnerClassName()); 122 StringBuilder buf = new StringBuilder();
123 for (int i=0; i<obfClassNames.length; i++) {
124 boolean isFirstClass = buf.length() == 0;
125 String className = null;
126 ClassMapping classMapping = mappingsChain.get(i);
127 if (classMapping != null) {
128 className = m_direction.choose(
129 classMapping.getDeobfName(),
130 isFirstClass ? classMapping.getObfFullName() : classMapping.getObfSimpleName()
131 );
132 }
133 if (className == null) {
134 className = obfClassNames[i];
135 }
136 if (!isFirstClass) {
137 buf.append("$");
138 }
139 buf.append(className);
97 } 140 }
141 return new ClassEntry(buf.toString());
142
143 } else {
144
145 // normal classes are easy
146 ClassMapping classMapping = m_classes.get(in.getName());
147 if (classMapping == null) {
148 return in;
149 }
150 return m_direction.choose(
151 classMapping.getDeobfName() != null ? new ClassEntry(classMapping.getDeobfName()) : in,
152 new ClassEntry(classMapping.getObfFullName())
153 );
98 } 154 }
99
100 return in;
101 } 155 }
102 156
103 public String translate(FieldEntry in) { 157 public String translate(FieldEntry in) {
@@ -226,14 +280,36 @@ public class Translator {
226 }); 280 });
227 } 281 }
228 282
229 private ClassMapping findClassMapping(ClassEntry classEntry) { 283 private ClassMapping findClassMapping(ClassEntry in) {
230 ClassMapping classMapping = m_classes.get(classEntry.getOuterClassName()); 284 List<ClassMapping> mappingChain = getClassMappingChain(in);
231 if (classMapping != null && classEntry.isInnerClass()) { 285 return mappingChain.get(mappingChain.size() - 1);
232 classMapping = m_direction.choose( 286 }
233 classMapping.getInnerClassByObf(classEntry.getInnerClassName()), 287
234 classMapping.getInnerClassByDeobfThenObf(classEntry.getInnerClassName()) 288 private List<ClassMapping> getClassMappingChain(ClassEntry in) {
235 ); 289
290 // get a list of all the classes in the hierarchy
291 String[] parts = in.getName().split("\\$");
292 List<ClassMapping> mappingsChain = Lists.newArrayList();
293
294 // get mappings for the outer class
295 ClassMapping outerClassMapping = m_classes.get(parts[0]);
296 mappingsChain.add(outerClassMapping);
297
298 for (int i=1; i<parts.length; i++) {
299
300 // get mappings for the inner class
301 ClassMapping innerClassMapping = null;
302 if (outerClassMapping != null) {
303 innerClassMapping = m_direction.choose(
304 outerClassMapping.getInnerClassByObf(parts[i]),
305 outerClassMapping.getInnerClassByDeobfThenObf(parts[i])
306 );
307 }
308 mappingsChain.add(innerClassMapping);
309 outerClassMapping = innerClassMapping;
236 } 310 }
237 return classMapping; 311
312 assert(mappingsChain.size() == parts.length);
313 return mappingsChain;
238 } 314 }
239} 315}
diff --git a/test/cuchaz/enigma/TestInnerClasses.java b/test/cuchaz/enigma/TestInnerClasses.java
index 2e16a33..2eb5acc 100644
--- a/test/cuchaz/enigma/TestInnerClasses.java
+++ b/test/cuchaz/enigma/TestInnerClasses.java
@@ -18,23 +18,30 @@ import java.util.jar.JarFile;
18 18
19import org.junit.Test; 19import org.junit.Test;
20 20
21import static cuchaz.enigma.TestEntryFactory.*;
22
21import cuchaz.enigma.analysis.JarIndex; 23import cuchaz.enigma.analysis.JarIndex;
24import cuchaz.enigma.mapping.ClassEntry;
22 25
23public class TestInnerClasses { 26public class TestInnerClasses {
24 27
25 private JarIndex m_index; 28 private JarIndex m_index;
26 private Deobfuscator m_deobfuscator; 29 private Deobfuscator m_deobfuscator;
27 30
28 private static final String AnonymousOuter = "none/a"; 31 private static final ClassEntry AnonymousOuter = newClass("none/a");
29 private static final String AnonymousInner = "b"; 32 private static final ClassEntry AnonymousInner = newClass("none/b");
30 private static final String SimpleOuter = "none/g"; 33 private static final ClassEntry SimpleOuter = newClass("none/g");
31 private static final String SimpleInner = "h"; 34 private static final ClassEntry SimpleInner = newClass("none/h");
32 private static final String ConstructorArgsOuter = "none/e"; 35 private static final ClassEntry ConstructorArgsOuter = newClass("none/e");
33 private static final String ConstructorArgsInner = "f"; 36 private static final ClassEntry ConstructorArgsInner = newClass("none/f");
34 private static final String AnonymousWithScopeArgsOuter = "none/c"; 37 private static final ClassEntry AnonymousWithScopeArgsOuter = newClass("none/c");
35 private static final String AnonymousWithScopeArgsInner = "d"; 38 private static final ClassEntry AnonymousWithScopeArgsInner = newClass("none/d");
36 private static final String AnonymousWithOuterAccessOuter = "none/i"; 39 private static final ClassEntry AnonymousWithOuterAccessOuter = newClass("none/i");
37 private static final String AnonymousWithOuterAccessInner = "j"; 40 private static final ClassEntry AnonymousWithOuterAccessInner = newClass("none/j");
41 private static final ClassEntry ClassTreeRoot = newClass("none/k");
42 private static final ClassEntry ClassTreeLevel1 = newClass("none/l");
43 private static final ClassEntry ClassTreeLevel2 = newClass("none/m");
44 private static final ClassEntry ClassTreeLevel3 = newClass("none/n");
38 45
39 public TestInnerClasses() 46 public TestInnerClasses()
40 throws Exception { 47 throws Exception {
@@ -84,7 +91,43 @@ public class TestInnerClasses {
84 decompile(AnonymousWithOuterAccessOuter); 91 decompile(AnonymousWithOuterAccessOuter);
85 } 92 }
86 93
87 private void decompile(String name) { 94 @Test
88 m_deobfuscator.getSourceTree(name); 95 public void classTree() {
96
97 // root level
98 assertThat(m_index.containsObfClass(ClassTreeRoot), is(true));
99 assertThat(m_index.getOuterClass(ClassTreeRoot), is(nullValue()));
100 assertThat(m_index.getInnerClasses(ClassTreeRoot), containsInAnyOrder(ClassTreeLevel1));
101
102 // level 1
103 ClassEntry fullClassEntry = new ClassEntry(ClassTreeRoot.getName()
104 + "$" + ClassTreeLevel1.getSimpleName()
105 );
106 assertThat(m_index.containsObfClass(fullClassEntry), is(true));
107 assertThat(m_index.getOuterClass(ClassTreeLevel1), is(ClassTreeRoot));
108 assertThat(m_index.getInnerClasses(ClassTreeLevel1), containsInAnyOrder(ClassTreeLevel2));
109
110 // level 2
111 fullClassEntry = new ClassEntry(ClassTreeRoot.getName()
112 + "$" + ClassTreeLevel1.getSimpleName()
113 + "$" + ClassTreeLevel2.getSimpleName()
114 );
115 assertThat(m_index.containsObfClass(fullClassEntry), is(true));
116 assertThat(m_index.getOuterClass(ClassTreeLevel2), is(ClassTreeLevel1));
117 assertThat(m_index.getInnerClasses(ClassTreeLevel2), containsInAnyOrder(ClassTreeLevel3));
118
119 // level 3
120 fullClassEntry = new ClassEntry(ClassTreeRoot.getName()
121 + "$" + ClassTreeLevel1.getSimpleName()
122 + "$" + ClassTreeLevel2.getSimpleName()
123 + "$" + ClassTreeLevel3.getSimpleName()
124 );
125 assertThat(m_index.containsObfClass(fullClassEntry), is(true));
126 assertThat(m_index.getOuterClass(ClassTreeLevel3), is(ClassTreeLevel2));
127 assertThat(m_index.getInnerClasses(ClassTreeLevel3), is(empty()));
128 }
129
130 private void decompile(ClassEntry classEntry) {
131 m_deobfuscator.getSourceTree(classEntry.getName());
89 } 132 }
90} 133}
diff --git a/test/cuchaz/enigma/TestJarIndexLoneClass.java b/test/cuchaz/enigma/TestJarIndexLoneClass.java
index 768284f..0c126ad 100644
--- a/test/cuchaz/enigma/TestJarIndexLoneClass.java
+++ b/test/cuchaz/enigma/TestJarIndexLoneClass.java
@@ -26,7 +26,6 @@ import cuchaz.enigma.analysis.ClassImplementationsTreeNode;
26import cuchaz.enigma.analysis.ClassInheritanceTreeNode; 26import cuchaz.enigma.analysis.ClassInheritanceTreeNode;
27import cuchaz.enigma.analysis.EntryReference; 27import cuchaz.enigma.analysis.EntryReference;
28import cuchaz.enigma.analysis.JarIndex; 28import cuchaz.enigma.analysis.JarIndex;
29import cuchaz.enigma.analysis.MethodImplementationsTreeNode;
30import cuchaz.enigma.analysis.MethodInheritanceTreeNode; 29import cuchaz.enigma.analysis.MethodInheritanceTreeNode;
31import cuchaz.enigma.mapping.BehaviorEntry; 30import cuchaz.enigma.mapping.BehaviorEntry;
32import cuchaz.enigma.mapping.ClassEntry; 31import cuchaz.enigma.mapping.ClassEntry;
@@ -125,17 +124,17 @@ public class TestJarIndexLoneClass {
125 124
126 @Test 125 @Test
127 public void innerClasses() { 126 public void innerClasses() {
128 assertThat(m_index.getInnerClasses("none/a"), is(empty())); 127 assertThat(m_index.getInnerClasses(newClass("none/a")), is(empty()));
129 } 128 }
130 129
131 @Test 130 @Test
132 public void outerClass() { 131 public void outerClass() {
133 assertThat(m_index.getOuterClass("a"), is(nullValue())); 132 assertThat(m_index.getOuterClass(newClass("a")), is(nullValue()));
134 } 133 }
135 134
136 @Test 135 @Test
137 public void isAnonymousClass() { 136 public void isAnonymousClass() {
138 assertThat(m_index.isAnonymousClass("none/a"), is(false)); 137 assertThat(m_index.isAnonymousClass(newClass("none/a")), is(false));
139 } 138 }
140 139
141 @Test 140 @Test
diff --git a/test/cuchaz/enigma/TestTranslator.java b/test/cuchaz/enigma/TestTranslator.java
index 3fe1680..0252605 100644
--- a/test/cuchaz/enigma/TestTranslator.java
+++ b/test/cuchaz/enigma/TestTranslator.java
@@ -97,8 +97,44 @@ public class TestTranslator {
97 assertMapping(newMethod("none/c", "b", "()I"), newMethod("deobf/C_SubClass", "m2", "()I")); 97 assertMapping(newMethod("none/c", "b", "()I"), newMethod("deobf/C_SubClass", "m2", "()I"));
98 } 98 }
99 99
100 @Test
101 public void innerClasses() {
102
103 // classes
104 assertMapping(newClass("none/h"), newClass("deobf/H_OuterClass"));
105 assertMapping(newClass("none/h$i"), newClass("deobf/H_OuterClass$I_InnerClass"));
106 assertMapping(newClass("none/h$i$j"), newClass("deobf/H_OuterClass$I_InnerClass$J_InnerInnerClass"));
107 assertMapping(newClass("none/h$k"), newClass("deobf/H_OuterClass$k"));
108 assertMapping(newClass("none/h$k$l"), newClass("deobf/H_OuterClass$k$L_NamedInnerClass"));
109
110 // fields
111 assertMapping(newField("none/h$i", "a", "I"), newField("deobf/H_OuterClass$I_InnerClass", "f1", "I"));
112 assertMapping(newField("none/h$i", "a", "Ljava/lang/String;"), newField("deobf/H_OuterClass$I_InnerClass", "f2", "Ljava/lang/String;"));
113 assertMapping(newField("none/h$i$j", "a", "I"), newField("deobf/H_OuterClass$I_InnerClass$J_InnerInnerClass", "f3", "I"));
114 assertMapping(newField("none/h$k$l", "a", "I"), newField("deobf/H_OuterClass$k$L_NamedInnerClass", "f4", "I"));
115
116 // methods
117 assertMapping(newMethod("none/h$i", "a", "()V"), newMethod("deobf/H_OuterClass$I_InnerClass", "m1", "()V"));
118 assertMapping(newMethod("none/h$i$j", "a", "()V"), newMethod("deobf/H_OuterClass$I_InnerClass$J_InnerInnerClass", "m2", "()V"));
119 }
120
121 @Test
122 public void namelessClass() {
123 assertMapping(newClass("none/m"), newClass("none/m"));
124 }
125
100 private void assertMapping(Entry obf, Entry deobf) { 126 private void assertMapping(Entry obf, Entry deobf) {
101 assertThat(m_deobfTranslator.translateEntry(obf), is(deobf)); 127 assertThat(m_deobfTranslator.translateEntry(obf), is(deobf));
102 assertThat(m_obfTranslator.translateEntry(deobf), is(obf)); 128 assertThat(m_obfTranslator.translateEntry(deobf), is(obf));
129
130 String deobfName = m_deobfTranslator.translate(obf);
131 if (deobfName != null) {
132 assertThat(deobfName, is(deobf.getName()));
133 }
134
135 String obfName = m_obfTranslator.translate(deobf);
136 if (obfName != null) {
137 assertThat(obfName, is(obf.getName()));
138 }
103 } 139 }
104} 140}
diff --git a/test/cuchaz/enigma/inputs/innerClasses/F_ClassTree.java b/test/cuchaz/enigma/inputs/innerClasses/F_ClassTree.java
new file mode 100644
index 0000000..6552d8a
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/innerClasses/F_ClassTree.java
@@ -0,0 +1,20 @@
1package cuchaz.enigma.inputs.innerClasses;
2
3
4public class F_ClassTree {
5
6 public class Level1 {
7
8 public int f1;
9
10 public class Level2 {
11
12 public int f2;
13
14 public class Level3 {
15
16 public int f3;
17 }
18 }
19 }
20}
diff --git a/test/cuchaz/enigma/inputs/translation/F_ObjectMethods.java b/test/cuchaz/enigma/inputs/translation/G_ObjectMethods.java
index 4e09179..ebad5a3 100644
--- a/test/cuchaz/enigma/inputs/translation/F_ObjectMethods.java
+++ b/test/cuchaz/enigma/inputs/translation/G_ObjectMethods.java
@@ -1,6 +1,6 @@
1package cuchaz.enigma.inputs.translation; 1package cuchaz.enigma.inputs.translation;
2 2
3public class F_ObjectMethods { 3public class G_ObjectMethods {
4 4
5 public void callEmAll() 5 public void callEmAll()
6 throws Throwable { 6 throws Throwable {
diff --git a/test/cuchaz/enigma/inputs/translation/H_OuterClass.java b/test/cuchaz/enigma/inputs/translation/H_OuterClass.java
new file mode 100644
index 0000000..995c65d
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/translation/H_OuterClass.java
@@ -0,0 +1,26 @@
1package cuchaz.enigma.inputs.translation;
2
3
4public class H_OuterClass {
5
6 public class I_InnerClass {
7
8 public int f1;
9 public String f2;
10
11 public void m1() {}
12
13 public class J_InnerInnerClass {
14
15 public int f3;
16
17 public void m2() {}
18 }
19 }
20
21 public class K_NamelessClass {
22 public class L_NamedInnerClass {
23 public int f4;
24 }
25 }
26}
diff --git a/test/cuchaz/enigma/inputs/translation/M_NamelessClass.java b/test/cuchaz/enigma/inputs/translation/M_NamelessClass.java
new file mode 100644
index 0000000..afc9a9a
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/translation/M_NamelessClass.java
@@ -0,0 +1,26 @@
1package cuchaz.enigma.inputs.translation;
2
3
4public class M_NamelessClass {
5
6 public class I_InnerClass {
7
8 public int f1;
9 public String f2;
10
11 public void m1() {}
12
13 public class J_InnerInnerClass {
14
15 public int f3;
16
17 public void m2() {}
18 }
19 }
20
21 public class K_NamelessClass {
22 public class L_NamedInnerClass {
23 public int f4;
24 }
25 }
26}
diff --git a/test/cuchaz/enigma/resources/translation.mappings b/test/cuchaz/enigma/resources/translation.mappings
index d87d437..5dffaf1 100644
--- a/test/cuchaz/enigma/resources/translation.mappings
+++ b/test/cuchaz/enigma/resources/translation.mappings
@@ -17,3 +17,15 @@ CLASS none/c deobf/C_SubClass
17 FIELD c f4 I 17 FIELD c f4 I
18 METHOD a m1 ()I 18 METHOD a m1 ()I
19 METHOD c m3 ()I 19 METHOD c m3 ()I
20CLASS none/h deobf/H_OuterClass
21 CLASS none/i I_InnerClass
22 CLASS none/j J_InnerInnerClass
23 FIELD a f3 I
24 METHOD a m2 ()V
25 FIELD a f1 I
26 FIELD a f2 Ljava/lang/String;
27 METHOD a m1 ()V
28 CLASS none/k
29 CLASS none/l L_NamedInnerClass
30 FIELD a f4 I
31CLASS none/m \ No newline at end of file