summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar jeff2015-02-25 22:42:34 -0500
committerGravatar jeff2015-02-25 22:42:34 -0500
commit9809078524bd3bd40fbf7aa411f6e0dca02fd009 (patch)
tree19f9ee0613231c747e728bc61f8a1b6ffa58e5b7
parentmore work getting inner class trees working in obf'd and deobf'd land (diff)
downloadenigma-fork-9809078524bd3bd40fbf7aa411f6e0dca02fd009.tar.gz
enigma-fork-9809078524bd3bd40fbf7aa411f6e0dca02fd009.tar.xz
enigma-fork-9809078524bd3bd40fbf7aa411f6e0dca02fd009.zip
fixed lots of issues with inner class reconstruction, particularly for inner class trees
also fixed lots of issues with reading jars that aren't Minecraft. =P
-rw-r--r--readme.txt7
-rw-r--r--src/cuchaz/enigma/Deobfuscator.java33
-rw-r--r--src/cuchaz/enigma/TranslatingTypeLoader.java113
-rw-r--r--src/cuchaz/enigma/analysis/JarIndex.java29
-rw-r--r--src/cuchaz/enigma/bytecode/InnerClassWriter.java84
-rw-r--r--src/cuchaz/enigma/mapping/ClassEntry.java22
-rw-r--r--src/cuchaz/enigma/mapping/EntryFactory.java34
-rw-r--r--test/cuchaz/enigma/TestDeobfed.java79
-rw-r--r--test/cuchaz/enigma/TestJarIndexDeobfed.java52
9 files changed, 241 insertions, 212 deletions
diff --git a/readme.txt b/readme.txt
index db294cd..68d1330 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,5 +1,5 @@
1 1
2Enigma v0.7 beta 2Enigma v0.8 beta
3A tool for deobfuscation of Java bytecode 3A tool for deobfuscation of Java bytecode
4 4
5Copyright Jeff Martin, 2015 5Copyright Jeff Martin, 2015
@@ -26,3 +26,8 @@ Launch the GUI:
26 26
27Use Enigma on the command line: 27Use Enigma on the command line:
28 java -cp enigma.jar cuchaz.enigma.CommandMain 28 java -cp enigma.jar cuchaz.enigma.CommandMain
29
30
31OPEN SOURCE
32
33https://bitbucket.org/cuchaz/enigma
diff --git a/src/cuchaz/enigma/Deobfuscator.java b/src/cuchaz/enigma/Deobfuscator.java
index b7440a7..0b7808d 100644
--- a/src/cuchaz/enigma/Deobfuscator.java
+++ b/src/cuchaz/enigma/Deobfuscator.java
@@ -204,33 +204,36 @@ public class Deobfuscator {
204 } 204 }
205 } 205 }
206 206
207 public CompilationUnit getSourceTree(String obfClassName) { 207 public CompilationUnit getSourceTree(String className) {
208 // is this class deobfuscated? 208
209 // we don't know if this class name is obfuscated or deobfuscated
209 // we need to tell the decompiler the deobfuscated name so it doesn't get freaked out 210 // we need to tell the decompiler the deobfuscated name so it doesn't get freaked out
210 // the decompiler only sees the deobfuscated class, so we need to load it by the deobfuscated name 211 // the decompiler only sees classes after deobfuscation, so we need to load it by the deobfuscated name if there is one
211 String lookupClassName = obfClassName;
212 ClassMapping classMapping = m_mappings.getClassByObf(obfClassName);
213 if (classMapping != null && classMapping.getDeobfName() != null) {
214 lookupClassName = classMapping.getDeobfName();
215 }
216 212
217 // is this class even in the jar? 213 // first, assume class name is deobf
218 if (!m_jarIndex.containsObfClass(new ClassEntry(obfClassName))) { 214 String deobfClassName = className;
219 return null; 215
216 // if it wasn't actually deobf, then we can find a mapping for it and get the deobf name
217 ClassMapping classMapping = m_mappings.getClassByObf(className);
218 if (classMapping != null && classMapping.getDeobfName() != null) {
219 deobfClassName = classMapping.getDeobfName();
220 } 220 }
221 221
222 // set the type loader 222 // set the type loader
223 m_settings.setTypeLoader(new TranslatingTypeLoader( 223 TranslatingTypeLoader loader = new TranslatingTypeLoader(
224 m_jar, 224 m_jar,
225 m_jarIndex, 225 m_jarIndex,
226 getTranslator(TranslationDirection.Obfuscating), 226 getTranslator(TranslationDirection.Obfuscating),
227 getTranslator(TranslationDirection.Deobfuscating) 227 getTranslator(TranslationDirection.Deobfuscating)
228 )); 228 );
229 m_settings.setTypeLoader(loader);
229 230
230 // see if procyon can find the type 231 // see if procyon can find the type
231 TypeReference type = new MetadataSystem(m_settings.getTypeLoader()).lookupType(lookupClassName); 232 TypeReference type = new MetadataSystem(loader).lookupType(deobfClassName);
232 if (type == null) { 233 if (type == null) {
233 throw new Error("Unable to find type: " + lookupClassName + " (obf name: " + obfClassName + ")"); 234 throw new Error(String.format("Unable to find type: %s (deobf: %s)\nTried class names: %s",
235 className, deobfClassName, loader.getClassNamesToTry(deobfClassName)
236 ));
234 } 237 }
235 TypeDefinition resolvedType = type.resolve(); 238 TypeDefinition resolvedType = type.resolve();
236 239
diff --git a/src/cuchaz/enigma/TranslatingTypeLoader.java b/src/cuchaz/enigma/TranslatingTypeLoader.java
index 26d5e7a..94ad6eb 100644
--- a/src/cuchaz/enigma/TranslatingTypeLoader.java
+++ b/src/cuchaz/enigma/TranslatingTypeLoader.java
@@ -13,6 +13,7 @@ package cuchaz.enigma;
13import java.io.ByteArrayOutputStream; 13import java.io.ByteArrayOutputStream;
14import java.io.IOException; 14import java.io.IOException;
15import java.io.InputStream; 15import java.io.InputStream;
16import java.util.List;
16import java.util.Map; 17import java.util.Map;
17import java.util.jar.JarEntry; 18import java.util.jar.JarEntry;
18import java.util.jar.JarFile; 19import java.util.jar.JarFile;
@@ -24,6 +25,7 @@ import javassist.CtClass;
24import javassist.NotFoundException; 25import javassist.NotFoundException;
25import javassist.bytecode.Descriptor; 26import javassist.bytecode.Descriptor;
26 27
28import com.beust.jcommander.internal.Lists;
27import com.google.common.collect.Maps; 29import com.google.common.collect.Maps;
28import com.strobel.assembler.metadata.Buffer; 30import com.strobel.assembler.metadata.Buffer;
29import com.strobel.assembler.metadata.ClasspathTypeLoader; 31import com.strobel.assembler.metadata.ClasspathTypeLoader;
@@ -65,19 +67,20 @@ public class TranslatingTypeLoader implements ITypeLoader {
65 } 67 }
66 68
67 @Override 69 @Override
68 public boolean tryLoadType(String deobfClassName, Buffer out) { 70 public boolean tryLoadType(String className, Buffer out) {
71
69 // check the cache 72 // check the cache
70 byte[] data; 73 byte[] data;
71 if (m_cache.containsKey(deobfClassName)) { 74 if (m_cache.containsKey(className)) {
72 data = m_cache.get(deobfClassName); 75 data = m_cache.get(className);
73 } else { 76 } else {
74 data = loadType(deobfClassName); 77 data = loadType(className);
75 m_cache.put(deobfClassName, data); 78 m_cache.put(className, data);
76 } 79 }
77 80
78 if (data == null) { 81 if (data == null) {
79 // chain to default type loader 82 // chain to default type loader
80 return m_defaultTypeLoader.tryLoadType(deobfClassName, out); 83 return m_defaultTypeLoader.tryLoadType(className, out);
81 } 84 }
82 85
83 // send the class to the decompiler 86 // send the class to the decompiler
@@ -88,6 +91,7 @@ public class TranslatingTypeLoader implements ITypeLoader {
88 } 91 }
89 92
90 public CtClass loadClass(String deobfClassName) { 93 public CtClass loadClass(String deobfClassName) {
94
91 byte[] data = loadType(deobfClassName); 95 byte[] data = loadType(deobfClassName);
92 if (data == null) { 96 if (data == null) {
93 return null; 97 return null;
@@ -104,40 +108,35 @@ public class TranslatingTypeLoader implements ITypeLoader {
104 } 108 }
105 } 109 }
106 110
107 private byte[] loadType(String deobfClassName) { 111 private byte[] loadType(String className) {
108 112
109 ClassEntry deobfClassEntry = new ClassEntry(deobfClassName); 113 // NOTE: don't know if class name is obf or deobf
110 ClassEntry obfClassEntry = m_obfuscatingTranslator.translateEntry(deobfClassEntry); 114 ClassEntry classEntry = new ClassEntry(className);
111 115 ClassEntry obfClassEntry = m_obfuscatingTranslator.translateEntry(classEntry);
112 // is this an inner class referenced directly? 116
113 ClassEntry obfOuterClassEntry = m_jarIndex.getOuterClass(obfClassEntry); 117 // is this an inner class referenced directly? (ie trying to load b instead of a$b)
114 if (obfOuterClassEntry != null) { 118 if (!obfClassEntry.isInnerClass()) {
115 // this class doesn't really exist. Reference it by outer$inner instead 119 List<ClassEntry> classChain = m_jarIndex.getObfClassChain(obfClassEntry);
116 System.err.println(String.format("WARNING: class %s referenced by bare inner name instead of via outer class %s", deobfClassName, obfOuterClassEntry)); 120 if (classChain.size() > 1) {
117 return null; 121 System.err.println(String.format("WARNING: no class %s after inner class reconstruction. Try %s",
118 } 122 className, obfClassEntry.buildClassEntry(classChain)
119 123 ));
120 /* DEBUG 124 return null;
121 if( !Arrays.asList( "java", "org", "io" ).contains( deobfClassName.split( "/" )[0] ) ) { 125 }
122 System.out.println( String.format( "Looking for %s (%s)", deobfClassEntry.getName(), obfClassEntry.getName() ) );
123 } 126 }
124 */
125 127
126 // get the jar entry 128 // is this a class we should even know about?
127 String classFileName; 129 if (!m_jarIndex.containsObfClass(obfClassEntry)) {
128 if (obfClassEntry.isInnerClass()) { 130 return null;
129 // use just the inner class name for inner classes
130 classFileName = obfClassEntry.getInnerClassName();
131 } else if (obfClassEntry.getPackageName().equals(Constants.NonePackage)) {
132 // use the outer class simple name for classes in the none package
133 classFileName = obfClassEntry.getSimpleName();
134 } else {
135 // otherwise, just use the class name (ie for classes in packages)
136 classFileName = obfClassEntry.getName();
137 } 131 }
138 132
139 JarEntry entry = m_jar.getJarEntry(classFileName + ".class"); 133 // DEBUG
140 if (entry == null) { 134 //System.out.println(String.format("Looking for %s (obf: %s)", classEntry.getName(), obfClassEntry.getName()));
135
136 // find the class in the jar
137 String classInJarName = findClassInJar(obfClassEntry);
138 if (classInJarName == null) {
139 // couldn't find it
141 return null; 140 return null;
142 } 141 }
143 142
@@ -145,7 +144,7 @@ public class TranslatingTypeLoader implements ITypeLoader {
145 // read the class file into a buffer 144 // read the class file into a buffer
146 ByteArrayOutputStream data = new ByteArrayOutputStream(); 145 ByteArrayOutputStream data = new ByteArrayOutputStream();
147 byte[] buf = new byte[1024 * 1024]; // 1 KiB 146 byte[] buf = new byte[1024 * 1024]; // 1 KiB
148 InputStream in = m_jar.getInputStream(entry); 147 InputStream in = m_jar.getInputStream(m_jar.getJarEntry(classInJarName + ".class"));
149 while (true) { 148 while (true) {
150 int bytesRead = in.read(buf); 149 int bytesRead = in.read(buf);
151 if (bytesRead <= 0) { 150 if (bytesRead <= 0) {
@@ -158,15 +157,15 @@ public class TranslatingTypeLoader implements ITypeLoader {
158 buf = data.toByteArray(); 157 buf = data.toByteArray();
159 158
160 // load the javassist handle to the raw class 159 // load the javassist handle to the raw class
161 String javaClassFileName = Descriptor.toJavaName(classFileName);
162 ClassPool classPool = new ClassPool(); 160 ClassPool classPool = new ClassPool();
163 classPool.insertClassPath(new ByteArrayClassPath(javaClassFileName, buf)); 161 String classInJarJavaName = Descriptor.toJavaName(classInJarName);
164 CtClass c = classPool.get(javaClassFileName); 162 classPool.insertClassPath(new ByteArrayClassPath(classInJarJavaName, buf));
163 CtClass c = classPool.get(classInJarJavaName);
165 164
166 c = transformClass(c); 165 c = transformClass(c);
167 166
168 // sanity checking 167 // sanity checking
169 assertClassName(c, deobfClassEntry); 168 assertClassName(c, classEntry);
170 169
171 // DEBUG 170 // DEBUG
172 //Util.writeClass( c ); 171 //Util.writeClass( c );
@@ -178,6 +177,38 @@ public class TranslatingTypeLoader implements ITypeLoader {
178 } 177 }
179 } 178 }
180 179
180 private String findClassInJar(ClassEntry obfClassEntry) {
181
182 // try to find the class in the jar
183 for (String className : getClassNamesToTry(obfClassEntry)) {
184 JarEntry jarEntry = m_jar.getJarEntry(className + ".class");
185 if (jarEntry != null) {
186 return className;
187 }
188 }
189
190 // didn't find it ;_;
191 return null;
192 }
193
194 public List<String> getClassNamesToTry(String className) {
195 return getClassNamesToTry(m_obfuscatingTranslator.translateEntry(new ClassEntry(className)));
196 }
197
198 public List<String> getClassNamesToTry(ClassEntry obfClassEntry) {
199 List<String> classNamesToTry = Lists.newArrayList();
200 classNamesToTry.add(obfClassEntry.getName());
201 if (obfClassEntry.getPackageName().equals(Constants.NonePackage)) {
202 // taking off the none package, if any
203 classNamesToTry.add(obfClassEntry.getSimpleName());
204 }
205 if (obfClassEntry.isInnerClass()) {
206 // try just the inner class name
207 classNamesToTry.add(obfClassEntry.getInnerClassName());
208 }
209 return classNamesToTry;
210 }
211
181 public CtClass transformClass(CtClass c) 212 public CtClass transformClass(CtClass c)
182 throws IOException, NotFoundException, CannotCompileException { 213 throws IOException, NotFoundException, CannotCompileException {
183 214
diff --git a/src/cuchaz/enigma/analysis/JarIndex.java b/src/cuchaz/enigma/analysis/JarIndex.java
index 1afcb76..e0a8bf5 100644
--- a/src/cuchaz/enigma/analysis/JarIndex.java
+++ b/src/cuchaz/enigma/analysis/JarIndex.java
@@ -12,6 +12,7 @@ package cuchaz.enigma.analysis;
12 12
13import java.lang.reflect.Modifier; 13import java.lang.reflect.Modifier;
14import java.util.Collection; 14import java.util.Collection;
15import java.util.Collections;
15import java.util.HashSet; 16import java.util.HashSet;
16import java.util.List; 17import java.util.List;
17import java.util.Map; 18import java.util.Map;
@@ -156,11 +157,8 @@ public class JarIndex {
156 157
157 // step 6: update other indices with inner class info 158 // step 6: update other indices with inner class info
158 Map<String,String> renames = Maps.newHashMap(); 159 Map<String,String> renames = Maps.newHashMap();
159 for (Map.Entry<ClassEntry,ClassEntry> mapEntry : m_innerClassesByOuter.entries()) { 160 for (ClassEntry innerClassEntry : m_innerClassesByOuter.values()) {
160 ClassEntry outerClassEntry = mapEntry.getKey(); 161 String newName = innerClassEntry.buildClassEntry(getObfClassChain(innerClassEntry)).getName();
161 ClassEntry innerClassEntry = mapEntry.getValue();
162 outerClassEntry = EntryFactory.getChainedOuterClassName(this, outerClassEntry);
163 String newName = outerClassEntry.getName() + "$" + innerClassEntry.getSimpleName();
164 if (!innerClassEntry.getName().equals(newName)) { 162 if (!innerClassEntry.getName().equals(newName)) {
165 // DEBUG 163 // DEBUG
166 //System.out.println("REPLACE: " + innerClassEntry.getName() + " WITH " + newName); 164 //System.out.println("REPLACE: " + innerClassEntry.getName() + " WITH " + newName);
@@ -780,4 +778,25 @@ public class JarIndex {
780 public MethodEntry getBridgedMethod(MethodEntry bridgeMethodEntry) { 778 public MethodEntry getBridgedMethod(MethodEntry bridgeMethodEntry) {
781 return m_bridgedMethods.get(bridgeMethodEntry); 779 return m_bridgedMethods.get(bridgeMethodEntry);
782 } 780 }
781
782 public List<ClassEntry> getObfClassChain(ClassEntry obfClassEntry) {
783
784 // build class chain in inner-to-outer order
785 List<ClassEntry> obfClassChain = Lists.newArrayList(obfClassEntry);
786 ClassEntry checkClassEntry = obfClassEntry;
787 while (true) {
788 ClassEntry obfOuterClassEntry = getOuterClass(checkClassEntry);
789 if (obfOuterClassEntry != null) {
790 obfClassChain.add(obfOuterClassEntry);
791 checkClassEntry = obfOuterClassEntry;
792 } else {
793 break;
794 }
795 }
796
797 // switch to outer-to-inner order
798 Collections.reverse(obfClassChain);
799
800 return obfClassChain;
801 }
783} 802}
diff --git a/src/cuchaz/enigma/bytecode/InnerClassWriter.java b/src/cuchaz/enigma/bytecode/InnerClassWriter.java
index 3bebd17..dd21a78 100644
--- a/src/cuchaz/enigma/bytecode/InnerClassWriter.java
+++ b/src/cuchaz/enigma/bytecode/InnerClassWriter.java
@@ -11,7 +11,6 @@
11package cuchaz.enigma.bytecode; 11package cuchaz.enigma.bytecode;
12 12
13import java.util.Collection; 13import java.util.Collection;
14import java.util.Collections;
15import java.util.List; 14import java.util.List;
16 15
17import javassist.CtClass; 16import javassist.CtClass;
@@ -36,35 +35,14 @@ public class InnerClassWriter {
36 35
37 public void write(CtClass c) { 36 public void write(CtClass c) {
38 37
39 // build the class chain (inner to outer)
40 ClassEntry obfClassEntry = EntryFactory.getClassEntry(c); 38 ClassEntry obfClassEntry = EntryFactory.getClassEntry(c);
41 List<ClassEntry> obfClassChain = Lists.newArrayList(); 39 List<ClassEntry> obfClassChain = m_index.getObfClassChain(obfClassEntry);
42 ClassEntry checkClassEntry = obfClassEntry;
43 while (checkClassEntry != null) {
44 obfClassChain.add(checkClassEntry);
45 checkClassEntry = m_index.getOuterClass(checkClassEntry);
46 }
47
48 // change order: outer to inner
49 Collections.reverse(obfClassChain);
50
51 // does this class have any inner classes?
52 Collection<ClassEntry> obfInnerClassEntries = m_index.getInnerClasses(obfClassEntry);
53 40
54 boolean isInnerClass = obfClassChain.size() > 1; 41 boolean isInnerClass = obfClassChain.size() > 1;
55 if (isInnerClass) { 42 if (isInnerClass) {
56 43
57 // it's an inner class, rename it to the fully qualified name 44 // it's an inner class, rename it to the fully qualified name
58 StringBuilder buf = new StringBuilder(); 45 c.setName(obfClassEntry.buildClassEntry(obfClassChain).getName());
59 for (ClassEntry obfChainClassEntry : obfClassChain) {
60 if (buf.length() == 0) {
61 buf.append(obfChainClassEntry.getName());
62 } else {
63 buf.append("$");
64 buf.append(obfChainClassEntry.getSimpleName());
65 }
66 }
67 c.setName(buf.toString());
68 46
69 BehaviorEntry caller = m_index.getAnonymousClassCaller(obfClassEntry); 47 BehaviorEntry caller = m_index.getAnonymousClassCaller(obfClassEntry);
70 if (caller != null) { 48 if (caller != null) {
@@ -78,6 +56,9 @@ public class InnerClassWriter {
78 } 56 }
79 } 57 }
80 58
59 // does this class have any inner classes?
60 Collection<ClassEntry> obfInnerClassEntries = m_index.getInnerClasses(obfClassEntry);
61
81 if (isInnerClass || !obfInnerClassEntries.isEmpty()) { 62 if (isInnerClass || !obfInnerClassEntries.isEmpty()) {
82 63
83 // create an inner class attribute 64 // create an inner class attribute
@@ -86,7 +67,11 @@ public class InnerClassWriter {
86 67
87 // write the ancestry, but not the outermost class 68 // write the ancestry, but not the outermost class
88 for (int i=1; i<obfClassChain.size(); i++) { 69 for (int i=1; i<obfClassChain.size(); i++) {
89 writeInnerClass(attr, obfClassChain, obfClassChain.get(i)); 70 ClassEntry obfInnerClassEntry = obfClassChain.get(i);
71 writeInnerClass(attr, obfClassChain, obfInnerClassEntry);
72
73 // update references to use the fully qualified inner class name
74 c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(obfClassChain).getName());
90 } 75 }
91 76
92 // write the inner classes 77 // write the inner classes
@@ -96,34 +81,34 @@ public class InnerClassWriter {
96 List<ClassEntry> extendedObfClassChain = Lists.newArrayList(obfClassChain); 81 List<ClassEntry> extendedObfClassChain = Lists.newArrayList(obfClassChain);
97 extendedObfClassChain.add(obfInnerClassEntry); 82 extendedObfClassChain.add(obfInnerClassEntry);
98 83
99 String fullyQualifiedInnerClassName = writeInnerClass(attr, extendedObfClassChain, obfInnerClassEntry); 84 writeInnerClass(attr, extendedObfClassChain, obfInnerClassEntry);
100 85
101 // make sure we only reference the fully qualified inner class name 86 // update references to use the fully qualified inner class name
102 c.replaceClassName(obfInnerClassEntry.getName(), fullyQualifiedInnerClassName); 87 c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(extendedObfClassChain).getName());
103 } 88 }
104 } 89 }
105 } 90 }
106 91
107 private String writeInnerClass(InnerClassesAttribute attr, List<ClassEntry> obfClassChain, ClassEntry obfClassEntry) { 92 private void writeInnerClass(InnerClassesAttribute attr, List<ClassEntry> obfClassChain, ClassEntry obfClassEntry) {
108 93
109 // get the new inner class name 94 // get the new inner class name
110 String obfInnerClassName = getFullyQualifiedName(obfClassChain, obfClassEntry); 95 ClassEntry obfInnerClassEntry = obfClassEntry.buildClassEntry(obfClassChain);
111 String obfParentClassName = getFullyQualifiedParentName(obfClassChain, obfClassEntry); 96 ClassEntry obfOuterClassEntry = obfInnerClassEntry.getOuterClassEntry();
112 97
113 // here's what the JVM spec says about the InnerClasses attribute 98 // here's what the JVM spec says about the InnerClasses attribute
114 // append(inner, parent, 0 if anonymous else simple name, flags); 99 // append(inner, parent, 0 if anonymous else simple name, flags);
115 100
116 // update the attribute with this inner class 101 // update the attribute with this inner class
117 ConstPool constPool = attr.getConstPool(); 102 ConstPool constPool = attr.getConstPool();
118 int innerClassIndex = constPool.addClassInfo(obfInnerClassName); 103 int innerClassIndex = constPool.addClassInfo(obfInnerClassEntry.getName());
119 int parentClassIndex = constPool.addClassInfo(obfParentClassName); 104 int parentClassIndex = constPool.addClassInfo(obfOuterClassEntry.getName());
120 int innerClassSimpleNameIndex = 0; 105 int innerClassNameIndex = 0;
121 int accessFlags = 0; 106 int accessFlags = 0;
122 if (!m_index.isAnonymousClass(obfClassEntry)) { 107 if (!m_index.isAnonymousClass(obfClassEntry)) {
123 innerClassSimpleNameIndex = constPool.addUtf8Info(obfClassEntry.getSimpleName()); 108 innerClassNameIndex = constPool.addUtf8Info(obfInnerClassEntry.getInnerClassName());
124 } 109 }
125 110
126 attr.append(innerClassIndex, parentClassIndex, innerClassSimpleNameIndex, accessFlags); 111 attr.append(innerClassIndex, parentClassIndex, innerClassNameIndex, accessFlags);
127 112
128 /* DEBUG 113 /* DEBUG
129 System.out.println(String.format("\tOBF: %s -> ATTR: %s,%s,%s (replace %s with %s)", 114 System.out.println(String.format("\tOBF: %s -> ATTR: %s,%s,%s (replace %s with %s)",
@@ -135,32 +120,5 @@ public class InnerClassWriter {
135 obfClassEntry.getName() 120 obfClassEntry.getName()
136 )); 121 ));
137 */ 122 */
138
139 return obfInnerClassName;
140 }
141
142 private String getFullyQualifiedParentName(List<ClassEntry> classChain, ClassEntry classEntry) {
143 assert(classChain.size() > 1);
144 assert(classChain.contains(classEntry));
145 StringBuilder buf = new StringBuilder();
146 for (int i=0; classChain.get(i) != classEntry; i++) {
147 ClassEntry chainEntry = classChain.get(i);
148 if (buf.length() == 0) {
149 buf.append(chainEntry.getName());
150 } else {
151 buf.append("$");
152 buf.append(chainEntry.getSimpleName());
153 }
154 }
155 return buf.toString();
156 }
157
158 private String getFullyQualifiedName(List<ClassEntry> classChain, ClassEntry classEntry) {
159 boolean isInner = classChain.size() > 1;
160 if (isInner) {
161 return getFullyQualifiedParentName(classChain, classEntry) + "$" + classEntry.getSimpleName();
162 } else {
163 return classEntry.getName();
164 }
165 } 123 }
166} 124}
diff --git a/src/cuchaz/enigma/mapping/ClassEntry.java b/src/cuchaz/enigma/mapping/ClassEntry.java
index 69f171a..69e66bc 100644
--- a/src/cuchaz/enigma/mapping/ClassEntry.java
+++ b/src/cuchaz/enigma/mapping/ClassEntry.java
@@ -11,6 +11,7 @@
11package cuchaz.enigma.mapping; 11package cuchaz.enigma.mapping;
12 12
13import java.io.Serializable; 13import java.io.Serializable;
14import java.util.List;
14 15
15public class ClassEntry implements Entry, Serializable { 16public class ClassEntry implements Entry, Serializable {
16 17
@@ -114,13 +115,28 @@ public class ClassEntry implements Entry, Serializable {
114 } 115 }
115 116
116 public String getSimpleName() { 117 public String getSimpleName() {
117 if (isInnerClass()) {
118 return getInnerClassName();
119 }
120 int pos = m_name.lastIndexOf('/'); 118 int pos = m_name.lastIndexOf('/');
121 if (pos > 0) { 119 if (pos > 0) {
122 return m_name.substring(pos + 1); 120 return m_name.substring(pos + 1);
123 } 121 }
124 return m_name; 122 return m_name;
125 } 123 }
124
125 public ClassEntry buildClassEntry(List<ClassEntry> classChain) {
126 assert(classChain.contains(this));
127 StringBuilder buf = new StringBuilder();
128 for (ClassEntry chainEntry : classChain) {
129 if (buf.length() == 0) {
130 buf.append(chainEntry.getName());
131 } else {
132 buf.append("$");
133 buf.append(chainEntry.isInnerClass() ? chainEntry.getInnerClassName() : chainEntry.getSimpleName());
134 }
135
136 if (chainEntry == this) {
137 break;
138 }
139 }
140 return new ClassEntry(buf.toString());
141 }
126} 142}
diff --git a/src/cuchaz/enigma/mapping/EntryFactory.java b/src/cuchaz/enigma/mapping/EntryFactory.java
index bbdfa73..f4d62c8 100644
--- a/src/cuchaz/enigma/mapping/EntryFactory.java
+++ b/src/cuchaz/enigma/mapping/EntryFactory.java
@@ -1,7 +1,5 @@
1package cuchaz.enigma.mapping; 1package cuchaz.enigma.mapping;
2 2
3import java.util.List;
4
5import javassist.CtBehavior; 3import javassist.CtBehavior;
6import javassist.CtClass; 4import javassist.CtClass;
7import javassist.CtConstructor; 5import javassist.CtConstructor;
@@ -13,7 +11,6 @@ import javassist.expr.FieldAccess;
13import javassist.expr.MethodCall; 11import javassist.expr.MethodCall;
14import javassist.expr.NewExpr; 12import javassist.expr.NewExpr;
15 13
16import com.beust.jcommander.internal.Lists;
17import com.strobel.assembler.metadata.MethodDefinition; 14import com.strobel.assembler.metadata.MethodDefinition;
18 15
19import cuchaz.enigma.analysis.JarIndex; 16import cuchaz.enigma.analysis.JarIndex;
@@ -25,35 +22,8 @@ public class EntryFactory {
25 } 22 }
26 23
27 public static ClassEntry getObfClassEntry(JarIndex jarIndex, ClassMapping classMapping) { 24 public static ClassEntry getObfClassEntry(JarIndex jarIndex, ClassMapping classMapping) {
28 return getChainedOuterClassName(jarIndex, new ClassEntry(classMapping.getObfFullName())); 25 ClassEntry obfClassEntry = new ClassEntry(classMapping.getObfFullName());
29 } 26 return obfClassEntry.buildClassEntry(jarIndex.getObfClassChain(obfClassEntry));
30
31 public static ClassEntry getChainedOuterClassName(JarIndex jarIndex, ClassEntry obfClassEntry) {
32
33 // lookup the chain of outer classes
34 List<ClassEntry> obfClassChain = Lists.newArrayList(obfClassEntry);
35 ClassEntry checkClassEntry = obfClassEntry;
36 while (true) {
37 ClassEntry obfOuterClassEntry = jarIndex.getOuterClass(checkClassEntry);
38 if (obfOuterClassEntry != null) {
39 obfClassChain.add(obfOuterClassEntry);
40 checkClassEntry = obfOuterClassEntry;
41 } else {
42 break;
43 }
44 }
45
46 // build the chained class name
47 StringBuilder buf = new StringBuilder();
48 for (int i=obfClassChain.size()-1; i>=0; i--) {
49 if (buf.length() == 0) {
50 buf.append(obfClassChain.get(i).getName());
51 } else {
52 buf.append("$");
53 buf.append(obfClassChain.get(i).getSimpleName());
54 }
55 }
56 return new ClassEntry(buf.toString());
57 } 27 }
58 28
59 public static ClassEntry getDeobfClassEntry(ClassMapping classMapping) { 29 public static ClassEntry getDeobfClassEntry(ClassMapping classMapping) {
diff --git a/test/cuchaz/enigma/TestDeobfed.java b/test/cuchaz/enigma/TestDeobfed.java
new file mode 100644
index 0000000..3c2ae51
--- /dev/null
+++ b/test/cuchaz/enigma/TestDeobfed.java
@@ -0,0 +1,79 @@
1package cuchaz.enigma;
2
3
4import static cuchaz.enigma.TestEntryFactory.*;
5import static org.hamcrest.MatcherAssert.*;
6import static org.hamcrest.Matchers.*;
7
8import java.util.jar.JarFile;
9
10import org.junit.BeforeClass;
11import org.junit.Test;
12
13import cuchaz.enigma.analysis.JarIndex;
14
15
16public class TestDeobfed {
17
18 private static JarFile m_jar;
19 private static JarIndex m_index;
20
21 @BeforeClass
22 public static void beforeClass()
23 throws Exception {
24 m_jar = new JarFile("build/testTranslation.deobf.jar");
25 m_index = new JarIndex();
26 m_index.indexJar(m_jar, true);
27 }
28
29 @Test
30 public void obfEntries() {
31 assertThat(m_index.getObfClassEntries(), containsInAnyOrder(
32 newClass("cuchaz/enigma/inputs/Keep"),
33 newClass("none/a"),
34 newClass("none/b"),
35 newClass("none/c"),
36 newClass("none/d"),
37 newClass("none/d$e"),
38 newClass("none/f"),
39 newClass("none/g"),
40 newClass("none/h"),
41 newClass("none/h$i"),
42 newClass("none/h$i$j"),
43 newClass("none/h$k"),
44 newClass("none/h$k$l"),
45 newClass("none/m"),
46 newClass("none/m$n"),
47 newClass("none/m$n$o"),
48 newClass("none/m$p"),
49 newClass("none/m$p$q"),
50 newClass("none/m$p$q$r"),
51 newClass("none/m$p$q$s")
52 ));
53 }
54
55 @Test
56 public void decompile()
57 throws Exception {
58 Deobfuscator deobfuscator = new Deobfuscator(m_jar);
59 deobfuscator.getSourceTree("none/a");
60 deobfuscator.getSourceTree("none/b");
61 deobfuscator.getSourceTree("none/c");
62 deobfuscator.getSourceTree("none/d");
63 deobfuscator.getSourceTree("none/d$e");
64 deobfuscator.getSourceTree("none/f");
65 deobfuscator.getSourceTree("none/g");
66 deobfuscator.getSourceTree("none/h");
67 deobfuscator.getSourceTree("none/h$i");
68 deobfuscator.getSourceTree("none/h$i$j");
69 deobfuscator.getSourceTree("none/h$k");
70 deobfuscator.getSourceTree("none/h$k$l");
71 deobfuscator.getSourceTree("none/m");
72 deobfuscator.getSourceTree("none/m$n");
73 deobfuscator.getSourceTree("none/m$n$o");
74 deobfuscator.getSourceTree("none/m$p");
75 deobfuscator.getSourceTree("none/m$p$q");
76 deobfuscator.getSourceTree("none/m$p$q$r");
77 deobfuscator.getSourceTree("none/m$p$q$s");
78 }
79}
diff --git a/test/cuchaz/enigma/TestJarIndexDeobfed.java b/test/cuchaz/enigma/TestJarIndexDeobfed.java
deleted file mode 100644
index f776e4f..0000000
--- a/test/cuchaz/enigma/TestJarIndexDeobfed.java
+++ /dev/null
@@ -1,52 +0,0 @@
1package cuchaz.enigma;
2
3
4import static cuchaz.enigma.TestEntryFactory.*;
5import static org.hamcrest.MatcherAssert.*;
6import static org.hamcrest.Matchers.*;
7
8import java.util.jar.JarFile;
9
10import org.junit.BeforeClass;
11import org.junit.Test;
12
13import cuchaz.enigma.analysis.JarIndex;
14
15
16public class TestJarIndexDeobfed {
17
18 private static JarIndex m_index;
19
20 @BeforeClass
21 public static void beforeClass()
22 throws Exception {
23 m_index = new JarIndex();
24 m_index.indexJar(new JarFile("build/testTranslation.deobf.jar"), true);
25 }
26
27 @Test
28 public void obfEntries() {
29 assertThat(m_index.getObfClassEntries(), containsInAnyOrder(
30 newClass("cuchaz/enigma/inputs/Keep"),
31 newClass("none/a"),
32 newClass("none/b"),
33 newClass("none/c"),
34 newClass("none/d"),
35 newClass("none/d$e"),
36 newClass("none/f"),
37 newClass("none/g"),
38 newClass("none/h"),
39 newClass("none/h$i"),
40 newClass("none/h$i$j"),
41 newClass("none/h$k"),
42 newClass("none/h$k$l"),
43 newClass("none/m"),
44 newClass("none/m$n"),
45 newClass("none/m$n$o"),
46 newClass("none/m$p"),
47 newClass("none/m$p$q"),
48 newClass("none/m$p$q$r"),
49 newClass("none/m$p$q$s")
50 ));
51 }
52}