summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar jeff2014-08-18 00:55:30 -0400
committerGravatar jeff2014-08-18 00:55:30 -0400
commit34c1e8e64ec4575527a19fb4cb0640c57da784db (patch)
tree44e3f1d50f8d8b8a9ab7c26dd94b58cba750cc67 /src
parentadded support for automatic reconstruction of inner and anonymous classes (diff)
downloadenigma-fork-34c1e8e64ec4575527a19fb4cb0640c57da784db.tar.gz
enigma-fork-34c1e8e64ec4575527a19fb4cb0640c57da784db.tar.xz
enigma-fork-34c1e8e64ec4575527a19fb4cb0640c57da784db.zip
crap-ton of bug fixes for inner classes
Diffstat (limited to 'src')
-rw-r--r--src/cuchaz/enigma/Deobfuscator.java2
-rw-r--r--src/cuchaz/enigma/Main.java4
-rw-r--r--src/cuchaz/enigma/TranslatingTypeLoader.java103
-rw-r--r--src/cuchaz/enigma/Util.java20
-rw-r--r--src/cuchaz/enigma/analysis/BridgeFixer.java2
-rw-r--r--src/cuchaz/enigma/analysis/JarIndex.java195
-rw-r--r--src/cuchaz/enigma/bytecode/BytecodeTools.java57
-rw-r--r--src/cuchaz/enigma/bytecode/ClassTranslator.java53
-rw-r--r--src/cuchaz/enigma/bytecode/InnerClassWriter.java42
-rw-r--r--src/cuchaz/enigma/mapping/Translator.java16
10 files changed, 392 insertions, 102 deletions
diff --git a/src/cuchaz/enigma/Deobfuscator.java b/src/cuchaz/enigma/Deobfuscator.java
index 9a0ec13..323aa2e 100644
--- a/src/cuchaz/enigma/Deobfuscator.java
+++ b/src/cuchaz/enigma/Deobfuscator.java
@@ -62,6 +62,8 @@ public class Deobfuscator
62 62
63 // config the decompiler 63 // config the decompiler
64 m_settings = DecompilerSettings.javaDefaults(); 64 m_settings = DecompilerSettings.javaDefaults();
65 // DEBUG
66 //m_settings.setShowSyntheticMembers( true );
65 67
66 // init mappings 68 // init mappings
67 setMappings( new Mappings() ); 69 setMappings( new Mappings() );
diff --git a/src/cuchaz/enigma/Main.java b/src/cuchaz/enigma/Main.java
index 6a300ed..20d73c2 100644
--- a/src/cuchaz/enigma/Main.java
+++ b/src/cuchaz/enigma/Main.java
@@ -13,6 +13,7 @@ package cuchaz.enigma;
13import java.io.File; 13import java.io.File;
14 14
15import cuchaz.enigma.gui.Gui; 15import cuchaz.enigma.gui.Gui;
16import cuchaz.enigma.mapping.ClassEntry;
16 17
17public class Main 18public class Main
18{ 19{
@@ -30,6 +31,9 @@ public class Main
30 { 31 {
31 gui.getController().openMappings( getFile( args[1] ) ); 32 gui.getController().openMappings( getFile( args[1] ) );
32 } 33 }
34
35 // DEBUG
36 //gui.getController().openEntry( new ClassEntry( "bah$bag" ) ); // bah,bag
33 } 37 }
34 38
35 private static File getFile( String path ) 39 private static File getFile( String path )
diff --git a/src/cuchaz/enigma/TranslatingTypeLoader.java b/src/cuchaz/enigma/TranslatingTypeLoader.java
index c1d96ae..ae27f37 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.Map;
16import java.util.jar.JarEntry; 17import java.util.jar.JarEntry;
17import java.util.jar.JarFile; 18import java.util.jar.JarFile;
18 19
@@ -23,6 +24,7 @@ import javassist.CtClass;
23import javassist.NotFoundException; 24import javassist.NotFoundException;
24import javassist.bytecode.Descriptor; 25import javassist.bytecode.Descriptor;
25 26
27import com.beust.jcommander.internal.Maps;
26import com.strobel.assembler.metadata.Buffer; 28import com.strobel.assembler.metadata.Buffer;
27import com.strobel.assembler.metadata.ITypeLoader; 29import com.strobel.assembler.metadata.ITypeLoader;
28 30
@@ -31,6 +33,7 @@ import cuchaz.enigma.analysis.JarIndex;
31import cuchaz.enigma.bytecode.ClassTranslator; 33import cuchaz.enigma.bytecode.ClassTranslator;
32import cuchaz.enigma.bytecode.InnerClassWriter; 34import cuchaz.enigma.bytecode.InnerClassWriter;
33import cuchaz.enigma.bytecode.MethodParameterWriter; 35import cuchaz.enigma.bytecode.MethodParameterWriter;
36import cuchaz.enigma.mapping.ClassEntry;
34import cuchaz.enigma.mapping.Translator; 37import cuchaz.enigma.mapping.Translator;
35 38
36public class TranslatingTypeLoader implements ITypeLoader 39public class TranslatingTypeLoader implements ITypeLoader
@@ -39,6 +42,7 @@ public class TranslatingTypeLoader implements ITypeLoader
39 private JarIndex m_jarIndex; 42 private JarIndex m_jarIndex;
40 private Translator m_obfuscatingTranslator; 43 private Translator m_obfuscatingTranslator;
41 private Translator m_deobfuscatingTranslator; 44 private Translator m_deobfuscatingTranslator;
45 private Map<String,byte[]> m_cache;
42 46
43 public TranslatingTypeLoader( JarFile jar, JarIndex jarIndex, Translator obfuscatingTranslator, Translator deobfuscatingTranslator ) 47 public TranslatingTypeLoader( JarFile jar, JarIndex jarIndex, Translator obfuscatingTranslator, Translator deobfuscatingTranslator )
44 { 48 {
@@ -46,32 +50,71 @@ public class TranslatingTypeLoader implements ITypeLoader
46 m_jarIndex = jarIndex; 50 m_jarIndex = jarIndex;
47 m_obfuscatingTranslator = obfuscatingTranslator; 51 m_obfuscatingTranslator = obfuscatingTranslator;
48 m_deobfuscatingTranslator = deobfuscatingTranslator; 52 m_deobfuscatingTranslator = deobfuscatingTranslator;
53 m_cache = Maps.newHashMap();
49 } 54 }
50 55
51 @Override 56 @Override
52 public boolean tryLoadType( String deobfClassName, Buffer out ) 57 public boolean tryLoadType( String deobfClassName, Buffer out )
53 { 58 {
59 // check the cache
60 byte[] data;
61 if( m_cache.containsKey( deobfClassName ) )
62 {
63 data = m_cache.get( deobfClassName );
64 }
65 else
66 {
67 data = loadType( deobfClassName );
68 m_cache.put( deobfClassName, data );
69 }
70
71 if( data == null )
72 {
73 return false;
74 }
75
76 // send the class to the decompiler
77 out.reset( data.length );
78 System.arraycopy( data, 0, out.array(), out.position(), data.length );
79 out.position( 0 );
80 return true;
81 }
82
83 private byte[] loadType( String deobfClassName )
84 {
54 // what class file should we actually load? 85 // what class file should we actually load?
55 String obfClassName = m_obfuscatingTranslator.translateClass( deobfClassName ); 86 ClassEntry deobfClassEntry = new ClassEntry( deobfClassName );
56 if( obfClassName == null ) 87 ClassEntry obfClassEntry = m_obfuscatingTranslator.translateEntry( deobfClassEntry );
88
89 // is this an inner class referenced directly?
90 if( m_jarIndex.getOuterClass( obfClassEntry.getName() ) != null )
57 { 91 {
58 obfClassName = deobfClassName; 92 // this class doesn't really exist. Reference it by outer$inner instead
93 System.err.println( String.format( "WARNING: class %s referenced by bare inner name", deobfClassName ) );
94 return null;
59 } 95 }
60 String classFileName = obfClassName;
61 96
62 // is this an inner class? 97 /* DEBUG
63 if( obfClassName.indexOf( '$' ) >= 0 ) 98 if( !Arrays.asList( "java", "org", "io" ).contains( deobfClassName.split( "/" )[0] ) )
64 { 99 {
65 // the file name is the bare inner class name 100 System.out.println( String.format( "Looking for %s (%s)", deobfClassEntry.getName(), obfClassEntry.getName() ) );
66 String[] parts = obfClassName.split( "\\$" );
67 classFileName = parts[parts.length - 1];
68 } 101 }
102 */
69 103
70 // get the jar entry 104 // get the jar entry
105 String classFileName;
106 if( obfClassEntry.isInnerClass() )
107 {
108 classFileName = obfClassEntry.getInnerClassName();
109 }
110 else
111 {
112 classFileName = obfClassEntry.getOuterClassName();
113 }
71 JarEntry entry = m_jar.getJarEntry( classFileName + ".class" ); 114 JarEntry entry = m_jar.getJarEntry( classFileName + ".class" );
72 if( entry == null ) 115 if( entry == null )
73 { 116 {
74 return false; 117 return null;
75 } 118 }
76 119
77 try 120 try
@@ -93,35 +136,49 @@ public class TranslatingTypeLoader implements ITypeLoader
93 in.close(); 136 in.close();
94 buf = data.toByteArray(); 137 buf = data.toByteArray();
95 138
96 // load the javassist handle to the class 139 // load the javassist handle to the raw class
97 String javaClassFileName = Descriptor.toJavaName( classFileName ); 140 String javaClassFileName = Descriptor.toJavaName( classFileName );
98 ClassPool classPool = new ClassPool(); 141 ClassPool classPool = new ClassPool();
99 classPool.insertClassPath( new ByteArrayClassPath( javaClassFileName, buf ) ); 142 classPool.insertClassPath( new ByteArrayClassPath( javaClassFileName, buf ) );
100 CtClass c = classPool.get( javaClassFileName ); 143 CtClass c = classPool.get( javaClassFileName );
101 144
145 // reconstruct inner classes
146 new InnerClassWriter( m_jarIndex ).write( c );
147
148 // re-get the javassist handle since we changed class names
149 String javaClassReconstructedName = Descriptor.toJavaName( obfClassEntry.getName() );
150 classPool = new ClassPool();
151 classPool.insertClassPath( new ByteArrayClassPath( javaClassReconstructedName, c.toBytecode() ) );
152 c = classPool.get( javaClassReconstructedName );
153
154 // check that the file is correct after inner class reconstruction (ie cause Javassist to fail fast if something is wrong)
155 assertClassName( c, obfClassEntry );
156
102 // do all kinds of deobfuscating transformations on the class 157 // do all kinds of deobfuscating transformations on the class
103 new InnerClassWriter( m_deobfuscatingTranslator, m_jarIndex ).write( c );
104 new BridgeFixer().fixBridges( c ); 158 new BridgeFixer().fixBridges( c );
105 new MethodParameterWriter( m_deobfuscatingTranslator ).writeMethodArguments( c ); 159 new MethodParameterWriter( m_deobfuscatingTranslator ).writeMethodArguments( c );
106 new ClassTranslator( m_deobfuscatingTranslator ).translate( c ); 160 new ClassTranslator( m_deobfuscatingTranslator ).translate( c );
107 161
108 // sanity checking 162 // sanity checking
109 assert( Descriptor.toJvmName( c.getName() ).equals( deobfClassName ) ) 163 assertClassName( c, deobfClassEntry );
110 : String.format( "%s is not %s", Descriptor.toJvmName( c.getName() ), deobfClassName );
111 assert( Descriptor.toJvmName( c.getClassFile().getName() ).equals( deobfClassName ) )
112 : String.format( "%s is not %s", Descriptor.toJvmName( c.getClassFile().getName() ), deobfClassName );
113
114 // pass the transformed class along to the decompiler
115 buf = c.toBytecode();
116 out.reset( buf.length );
117 System.arraycopy( buf, 0, out.array(), out.position(), buf.length );
118 out.position( 0 );
119 164
120 return true; 165 // we have a transformed class!
166 return c.toBytecode();
121 } 167 }
122 catch( IOException | NotFoundException | CannotCompileException ex ) 168 catch( IOException | NotFoundException | CannotCompileException ex )
123 { 169 {
124 throw new Error( ex ); 170 throw new Error( ex );
125 } 171 }
126 } 172 }
173
174 private void assertClassName( CtClass c, ClassEntry obfClassEntry )
175 {
176 String name1 = Descriptor.toJvmName( c.getName() );
177 assert( name1.equals( obfClassEntry.getName() ) )
178 : String.format( "Looking for %s, instead found %s", obfClassEntry.getName(), name1 );
179
180 String name2 = Descriptor.toJvmName( c.getClassFile().getName() );
181 assert( name2.equals( obfClassEntry.getName() ) )
182 : String.format( "Looking for %s, instead found %s", obfClassEntry.getName(), name2 );
183 }
127} 184}
diff --git a/src/cuchaz/enigma/Util.java b/src/cuchaz/enigma/Util.java
index 84927fd..3686ef0 100644
--- a/src/cuchaz/enigma/Util.java
+++ b/src/cuchaz/enigma/Util.java
@@ -12,6 +12,8 @@ package cuchaz.enigma;
12 12
13import java.awt.Desktop; 13import java.awt.Desktop;
14import java.io.Closeable; 14import java.io.Closeable;
15import java.io.File;
16import java.io.FileOutputStream;
15import java.io.IOException; 17import java.io.IOException;
16import java.io.InputStream; 18import java.io.InputStream;
17import java.io.InputStreamReader; 19import java.io.InputStreamReader;
@@ -19,6 +21,10 @@ import java.net.URI;
19import java.net.URISyntaxException; 21import java.net.URISyntaxException;
20import java.util.jar.JarFile; 22import java.util.jar.JarFile;
21 23
24import javassist.CannotCompileException;
25import javassist.CtClass;
26import javassist.bytecode.Descriptor;
27
22import com.google.common.io.CharStreams; 28import com.google.common.io.CharStreams;
23 29
24 30
@@ -106,4 +112,18 @@ public class Util
106 } 112 }
107 } 113 }
108 } 114 }
115
116 public static void writeClass( CtClass c )
117 {
118 String name = Descriptor.toJavaName( c.getName() );
119 File file = new File( name + ".class" );
120 try( FileOutputStream out = new FileOutputStream( file ) )
121 {
122 out.write( c.toBytecode() );
123 }
124 catch( IOException | CannotCompileException ex )
125 {
126 throw new Error( ex );
127 }
128 }
109} 129}
diff --git a/src/cuchaz/enigma/analysis/BridgeFixer.java b/src/cuchaz/enigma/analysis/BridgeFixer.java
index db441d2..ee90f51 100644
--- a/src/cuchaz/enigma/analysis/BridgeFixer.java
+++ b/src/cuchaz/enigma/analysis/BridgeFixer.java
@@ -41,6 +41,8 @@ public class BridgeFixer
41 { 41 {
42 bridgedMethod.setName( method.getName() ); 42 bridgedMethod.setName( method.getName() );
43 method.setModifiers( method.getModifiers() | AccessFlag.BRIDGE ); 43 method.setModifiers( method.getModifiers() | AccessFlag.BRIDGE );
44
45 // TODO: rename all references to this method?
44 } 46 }
45 } 47 }
46 } 48 }
diff --git a/src/cuchaz/enigma/analysis/JarIndex.java b/src/cuchaz/enigma/analysis/JarIndex.java
index 34e8986..7d68c35 100644
--- a/src/cuchaz/enigma/analysis/JarIndex.java
+++ b/src/cuchaz/enigma/analysis/JarIndex.java
@@ -10,6 +10,7 @@
10 ******************************************************************************/ 10 ******************************************************************************/
11package cuchaz.enigma.analysis; 11package cuchaz.enigma.analysis;
12 12
13import java.lang.reflect.Modifier;
13import java.util.AbstractMap; 14import java.util.AbstractMap;
14import java.util.Collection; 15import java.util.Collection;
15import java.util.Iterator; 16import java.util.Iterator;
@@ -92,8 +93,8 @@ public class JarIndex
92 // pass 2: index inner classes and anonymous classes 93 // pass 2: index inner classes and anonymous classes
93 for( CtClass c : JarClassIterator.classes( jar ) ) 94 for( CtClass c : JarClassIterator.classes( jar ) )
94 { 95 {
95 String outerClassName = isInnerClass( c ); 96 String outerClassName = findOuterClass( c );
96 if( outerClassName != null ) 97 if( outerClassName != null )// /* TEMP */ && false )
97 { 98 {
98 String innerClassName = Descriptor.toJvmName( c.getName() ); 99 String innerClassName = Descriptor.toJvmName( c.getName() );
99 m_innerClasses.put( outerClassName, innerClassName ); 100 m_innerClasses.put( outerClassName, innerClassName );
@@ -102,6 +103,14 @@ public class JarIndex
102 if( isAnonymousClass( c, outerClassName ) ) 103 if( isAnonymousClass( c, outerClassName ) )
103 { 104 {
104 m_anonymousClasses.add( innerClassName ); 105 m_anonymousClasses.add( innerClassName );
106
107 // DEBUG
108 System.out.println( "ANONYMOUS: " + outerClassName + "$" + innerClassName );
109 }
110 else
111 {
112 // DEBUG
113 System.out.println( "INNER: " + outerClassName + "$" + innerClassName );
105 } 114 }
106 } 115 }
107 } 116 }
@@ -175,6 +184,10 @@ public class JarIndex
175 @Override 184 @Override
176 public void edit( ConstructorCall call ) 185 public void edit( ConstructorCall call )
177 { 186 {
187 boolean isSuper = call.getMethodName().equals( "super" );
188 // TODO: make method reference class, update method calls tree to use Invocation instances
189 // this might end up being a big refactor... =(
190
178 String className = Descriptor.toJvmName( call.getClassName() ); 191 String className = Descriptor.toJvmName( call.getClassName() );
179 ConstructorEntry calledConstructorEntry = new ConstructorEntry( 192 ConstructorEntry calledConstructorEntry = new ConstructorEntry(
180 new ClassEntry( className ), 193 new ClassEntry( className ),
@@ -201,75 +214,168 @@ public class JarIndex
201 } 214 }
202 } 215 }
203 216
204 @SuppressWarnings( "unchecked" ) 217 private String findOuterClass( CtClass c )
205 private String isInnerClass( CtClass c )
206 { 218 {
207 // inner classes: 219 // inner classes:
208 // the outer class is always a synthetic field 220 // have constructors that can (illegally) set synthetic fields
209 // there's at least one constructor with the type of the synthetic field as an argument 221 // the outer class is the only class that calls constructors
210 222
211 for( FieldInfo field : (List<FieldInfo>)c.getClassFile().getFields() ) 223 // use the synthetic fields to find the synthetic constructors
224 for( CtConstructor constructor : c.getDeclaredConstructors() )
212 { 225 {
213 boolean isSynthetic = (field.getAccessFlags() & AccessFlag.SYNTHETIC) != 0; 226 if( !isIllegalConstructor( constructor ) )
214 if( !isSynthetic )
215 { 227 {
216 continue; 228 continue;
217 } 229 }
218 230
219 // skip non-class types 231 // who calls this constructor?
220 if( !field.getDescriptor().startsWith( "L" ) ) 232 Set<ClassEntry> callerClasses = Sets.newHashSet();
233 ConstructorEntry constructorEntry = new ConstructorEntry(
234 new ClassEntry( Descriptor.toJvmName( c.getName() ) ),
235 constructor.getMethodInfo().getDescriptor()
236 );
237 for( Entry callerEntry : getMethodCallers( constructorEntry ) )
221 { 238 {
222 continue; 239 callerClasses.add( callerEntry.getClassEntry() );
223 } 240 }
224 241
225 // get the outer class from the field type 242 // is this called by exactly one class?
226 String outerClassName = Descriptor.toJvmName( Descriptor.toClassName( field.getDescriptor() ) ); 243 if( callerClasses.size() == 1 )
227 244 {
228 // look for a constructor where this type is the first parameter 245 return callerClasses.iterator().next().getName();
229 CtConstructor targetConstructor = null; 246 }
230 for( CtConstructor constructor : c.getDeclaredConstructors() ) 247 else if( callerClasses.size() > 1 )
248 {
249 // TEMP
250 System.out.println( "WARNING: Illegal class called by more than one class!" + callerClasses );
251 }
252 }
253
254 return null;
255 }
256
257 @SuppressWarnings( "unchecked" )
258 private boolean isIllegalConstructor( CtConstructor constructor )
259 {
260 // illegal constructors only set synthetic member fields, then call super()
261 String className = constructor.getDeclaringClass().getName();
262
263 // collect all the field accesses, constructor calls, and method calls
264 final List<FieldAccess> illegalFieldWrites = Lists.newArrayList();
265 final List<ConstructorCall> constructorCalls = Lists.newArrayList();
266 final List<MethodCall> methodCalls = Lists.newArrayList();
267 try
268 {
269 constructor.instrument( new ExprEditor( )
231 { 270 {
232 String signature = Descriptor.getParamDescriptor( constructor.getMethodInfo().getDescriptor() ); 271 @Override
233 if( Descriptor.numOfParameters( signature ) < 1 ) 272 public void edit( FieldAccess fieldAccess )
234 { 273 {
235 continue; 274 if( fieldAccess.isWriter() && constructorCalls.isEmpty() )
275 {
276 illegalFieldWrites.add( fieldAccess );
277 }
236 } 278 }
237 279
238 // match the first parameter to the outer class 280 @Override
239 Descriptor.Iterator iter = new Descriptor.Iterator( signature ); 281 public void edit( ConstructorCall constructorCall )
240 int pos = iter.next();
241 if( iter.isParameter() && signature.charAt( pos ) == 'L' )
242 { 282 {
243 String argumentDesc = signature.substring( pos, signature.indexOf(';', pos) + 1 ); 283 constructorCalls.add( constructorCall );
244 String argumentClassName = Descriptor.toJvmName( Descriptor.toClassName( argumentDesc ) ); 284 }
245 if( argumentClassName.equals( outerClassName ) ) 285
246 { 286 @Override
247 targetConstructor = constructor; 287 public void edit( MethodCall methodCall )
248 break; 288 {
249 } 289 methodCalls.add( methodCall );
250 } 290 }
291 } );
292 }
293 catch( CannotCompileException ex )
294 {
295 // we're not compiling anything... this is stupid
296 throw new Error( ex );
297 }
298
299 // method calls are not allowed
300 if( !methodCalls.isEmpty() )
301 {
302 return false;
303 }
304
305 // is there only one constructor call?
306 if( constructorCalls.size() != 1 )
307 {
308 return false;
309 }
310
311 // is the call to super?
312 ConstructorCall constructorCall = constructorCalls.get( 0 );
313 if( !constructorCall.getMethodName().equals( "super" ) )
314 {
315 return false;
316 }
317
318 // are there any illegal field writes?
319 if( illegalFieldWrites.isEmpty() )
320 {
321 return false;
322 }
323
324 // are all the writes to synthetic fields?
325 for( FieldAccess fieldWrite : illegalFieldWrites )
326 {
327 // all illegal writes have to be to the local class
328 if( !fieldWrite.getClassName().equals( className ) )
329 {
330 System.err.println( String.format( "WARNING: illegal write to non-member field %s.%s", fieldWrite.getClassName(), fieldWrite.getFieldName() ) );
331 return false;
251 } 332 }
252 if( targetConstructor == null ) 333
334 // find the field
335 FieldInfo fieldInfo = null;
336 for( FieldInfo info : (List<FieldInfo>)constructor.getDeclaringClass().getClassFile().getFields() )
253 { 337 {
254 continue; 338 if( info.getName().equals( fieldWrite.getFieldName() ) )
339 {
340 fieldInfo = info;
341 break;
342 }
343 }
344 if( fieldInfo == null )
345 {
346 // field is in a superclass or something, can't be a local synthetic member
347 return false;
255 } 348 }
256 349
257 // yeah, this is an inner class 350 // is this field synthetic?
258 return outerClassName; 351 boolean isSynthetic = (fieldInfo.getAccessFlags() & AccessFlag.SYNTHETIC) != 0;
352 if( !isSynthetic )
353 {
354 System.err.println( String.format( "WARNING: illegal write to non synthetic field %s.%s", className, fieldInfo.getName() ) );
355 return false;
356 }
259 } 357 }
260 358
261 return null; 359 // we passed all the tests!
360 return true;
262 } 361 }
263 362
264 private boolean isAnonymousClass( CtClass c, String outerClassName ) 363 private boolean isAnonymousClass( CtClass c, String outerClassName )
265 { 364 {
266 String innerClassName = Descriptor.toJvmName( c.getName() ); 365 String innerClassName = Descriptor.toJvmName( c.getName() );
267 366
268 // anonymous classes: 367 // anonymous classes:
368 // can't be abstract
269 // have only one constructor 369 // have only one constructor
270 // it's called exactly once by the outer class 370 // it's called exactly once by the outer class
271 // type of inner class not referenced anywhere in outer class 371 // type of inner class not referenced anywhere in outer class
272 372
373 // is absract?
374 if( Modifier.isAbstract( c.getModifiers() ) )
375 {
376 return false;
377 }
378
273 // is there exactly one constructor? 379 // is there exactly one constructor?
274 if( c.getDeclaredConstructors().length != 1 ) 380 if( c.getDeclaredConstructors().length != 1 )
275 { 381 {
@@ -282,7 +388,16 @@ public class JarIndex
282 new ClassEntry( innerClassName ), 388 new ClassEntry( innerClassName ),
283 constructor.getMethodInfo().getDescriptor() 389 constructor.getMethodInfo().getDescriptor()
284 ); 390 );
285 return getMethodCallers( constructorEntry ).size() == 1; 391 if( getMethodCallers( constructorEntry ).size() != 1 )
392 {
393 return false;
394 }
395
396 // TODO: check outer class doesn't reference type
397 // except this is hard because we can't just load the outer class now
398 // we'd have to pre-index those references in the JarIndex
399
400 return true;
286 } 401 }
287 402
288 public Set<String> getObfClassNames( ) 403 public Set<String> getObfClassNames( )
diff --git a/src/cuchaz/enigma/bytecode/BytecodeTools.java b/src/cuchaz/enigma/bytecode/BytecodeTools.java
index 664350e..0de9bd6 100644
--- a/src/cuchaz/enigma/bytecode/BytecodeTools.java
+++ b/src/cuchaz/enigma/bytecode/BytecodeTools.java
@@ -15,6 +15,7 @@ import java.io.ByteArrayOutputStream;
15import java.io.DataInputStream; 15import java.io.DataInputStream;
16import java.io.DataOutputStream; 16import java.io.DataOutputStream;
17import java.io.IOException; 17import java.io.IOException;
18import java.util.List;
18import java.util.Map; 19import java.util.Map;
19import java.util.Set; 20import java.util.Set;
20 21
@@ -25,6 +26,7 @@ import javassist.bytecode.CodeAttribute;
25import javassist.bytecode.ConstPool; 26import javassist.bytecode.ConstPool;
26import javassist.bytecode.ExceptionTable; 27import javassist.bytecode.ExceptionTable;
27 28
29import com.beust.jcommander.internal.Lists;
28import com.google.common.collect.Maps; 30import com.google.common.collect.Maps;
29import com.google.common.collect.Sets; 31import com.google.common.collect.Sets;
30 32
@@ -266,4 +268,59 @@ public class BytecodeTools
266 ); 268 );
267 } 269 }
268 } 270 }
271
272 public static List<String> getParameterTypes( String signature )
273 {
274 List<String> types = Lists.newArrayList();
275 for( int i=0; i<signature.length(); )
276 {
277 char c = signature.charAt( i );
278
279 // handle parens
280 if( c == '(' )
281 {
282 i++;
283 c = signature.charAt( i );
284 }
285 if( c == ')' )
286 {
287 break;
288 }
289
290 // find a type
291 String type = null;
292
293 int arrayDim = 0;
294 while( c == '[' )
295 {
296 // advance to array type
297 arrayDim++;
298 i++;
299 c = signature.charAt( i );
300 }
301
302 if( c == 'L' )
303 {
304 // read class type
305 int pos = signature.indexOf( ';', i + 1 );
306 String className = signature.substring( i + 1, pos );
307 type = "L" + className + ";";
308 i = pos + 1;
309 }
310 else
311 {
312 // read primitive type
313 type = signature.substring( i, i + 1 );
314 i++;
315 }
316
317 // was it an array?
318 while( arrayDim-- > 0 )
319 {
320 type = "[" + type;
321 }
322 types.add( type );
323 }
324 return types;
325 }
269} 326}
diff --git a/src/cuchaz/enigma/bytecode/ClassTranslator.java b/src/cuchaz/enigma/bytecode/ClassTranslator.java
index 3b5beeb..9ce06a5 100644
--- a/src/cuchaz/enigma/bytecode/ClassTranslator.java
+++ b/src/cuchaz/enigma/bytecode/ClassTranslator.java
@@ -10,7 +10,6 @@
10 ******************************************************************************/ 10 ******************************************************************************/
11package cuchaz.enigma.bytecode; 11package cuchaz.enigma.bytecode;
12 12
13import java.util.HashSet;
14import java.util.Set; 13import java.util.Set;
15 14
16import javassist.ClassMap; 15import javassist.ClassMap;
@@ -20,6 +19,10 @@ import javassist.CtField;
20import javassist.CtMethod; 19import javassist.CtMethod;
21import javassist.bytecode.ConstPool; 20import javassist.bytecode.ConstPool;
22import javassist.bytecode.Descriptor; 21import javassist.bytecode.Descriptor;
22import javassist.bytecode.InnerClassesAttribute;
23
24import com.beust.jcommander.internal.Sets;
25
23import cuchaz.enigma.mapping.ClassEntry; 26import cuchaz.enigma.mapping.ClassEntry;
24import cuchaz.enigma.mapping.FieldEntry; 27import cuchaz.enigma.mapping.FieldEntry;
25import cuchaz.enigma.mapping.MethodEntry; 28import cuchaz.enigma.mapping.MethodEntry;
@@ -133,25 +136,47 @@ public class ClassTranslator
133 136
134 // translate all the class names referenced in the code 137 // translate all the class names referenced in the code
135 // the above code only changed method/field/reference names and types, but not the class names themselves 138 // the above code only changed method/field/reference names and types, but not the class names themselves
136 Set<String> classNames = getAllClassNames( c ); 139 Set<ClassEntry> classEntries = getAllClassEntries( c );
137 ClassMap map = new ClassMap(); 140 ClassMap map = new ClassMap();
138 for( String className : classNames ) 141 for( ClassEntry obfClassEntry : classEntries )
139 { 142 {
140 String translatedName = m_translator.translateClass( className ); 143 map.put( obfClassEntry.getName(), m_translator.translateEntry( obfClassEntry ).getName() );
141 if( translatedName != null )
142 {
143 map.put( className, translatedName );
144 }
145 } 144 }
146 if( !map.isEmpty() ) 145 c.replaceClassName( map );
146
147 // translate the names in the InnerClasses attribute
148 InnerClassesAttribute attr = (InnerClassesAttribute)c.getClassFile().getAttribute( InnerClassesAttribute.tag );
149 if( attr != null )
147 { 150 {
148 c.replaceClassName( map ); 151 for( int i=0; i<attr.tableLength(); i++ )
152 {
153 ClassEntry obfClassEntry = new ClassEntry( Descriptor.toJvmName( attr.innerClass( i ) ) );
154 ClassEntry deobfClassEntry = m_translator.translateEntry( obfClassEntry );
155 attr.setInnerClassIndex( i, constants.addClassInfo( deobfClassEntry.getName() ) );
156 if( attr.outerClassIndex( i ) != 0 )
157 {
158 attr.setOuterClassIndex( i, constants.addClassInfo( deobfClassEntry.getOuterClassName() ) );
159 }
160 if( attr.innerNameIndex( i ) != 0 )
161 {
162 attr.setInnerNameIndex( i, constants.addUtf8Info( deobfClassEntry.getInnerClassName() ) );
163 }
164
165 /* DEBUG
166 System.out.println( String.format( "\tOBF: %s DEOBF: %s-> ATTR: %s,%s,%s",
167 obfClassEntry, deobfClassEntry,
168 attr.outerClass( i ),
169 attr.innerClass( i ),
170 attr.innerName( i )
171 ) );
172 */
173 }
149 } 174 }
150 } 175 }
151 176
152 private Set<String> getAllClassNames( CtClass c ) 177 private Set<ClassEntry> getAllClassEntries( CtClass c )
153 { 178 {
154 final Set<String> names = new HashSet<String>(); 179 final Set<ClassEntry> entries = Sets.newHashSet();
155 ClassMap map = new ClassMap( ) 180 ClassMap map = new ClassMap( )
156 { 181 {
157 @Override 182 @Override
@@ -159,13 +184,13 @@ public class ClassTranslator
159 { 184 {
160 if( obj instanceof String ) 185 if( obj instanceof String )
161 { 186 {
162 names.add( (String)obj ); 187 entries.add( new ClassEntry( (String)obj ) );
163 } 188 }
164 return null; 189 return null;
165 } 190 }
166 private static final long serialVersionUID = -202160293602070641L; 191 private static final long serialVersionUID = -202160293602070641L;
167 }; 192 };
168 c.replaceClassName( map ); 193 c.replaceClassName( map );
169 return names; 194 return entries;
170 } 195 }
171} 196}
diff --git a/src/cuchaz/enigma/bytecode/InnerClassWriter.java b/src/cuchaz/enigma/bytecode/InnerClassWriter.java
index b0e33ac..c412b1a 100644
--- a/src/cuchaz/enigma/bytecode/InnerClassWriter.java
+++ b/src/cuchaz/enigma/bytecode/InnerClassWriter.java
@@ -18,16 +18,14 @@ import javassist.bytecode.ConstPool;
18import javassist.bytecode.Descriptor; 18import javassist.bytecode.Descriptor;
19import javassist.bytecode.InnerClassesAttribute; 19import javassist.bytecode.InnerClassesAttribute;
20import cuchaz.enigma.analysis.JarIndex; 20import cuchaz.enigma.analysis.JarIndex;
21import cuchaz.enigma.mapping.Translator; 21import cuchaz.enigma.mapping.ClassEntry;
22 22
23public class InnerClassWriter 23public class InnerClassWriter
24{ 24{
25 private Translator m_deobfuscatingTranslator;
26 private JarIndex m_jarIndex; 25 private JarIndex m_jarIndex;
27 26
28 public InnerClassWriter( Translator deobfuscatingTranslator, JarIndex jarIndex ) 27 public InnerClassWriter( JarIndex jarIndex )
29 { 28 {
30 m_deobfuscatingTranslator = deobfuscatingTranslator;
31 m_jarIndex = jarIndex; 29 m_jarIndex = jarIndex;
32 } 30 }
33 31
@@ -44,7 +42,8 @@ public class InnerClassWriter
44 else 42 else
45 { 43 {
46 // this is an inner class, rename it to outer$inner 44 // this is an inner class, rename it to outer$inner
47 c.setName( obfOuterClassName + "$" + obfClassName ); 45 ClassEntry obfClassEntry = new ClassEntry( obfOuterClassName + "$" + obfClassName );
46 c.setName( obfClassEntry.getName() );
48 } 47 }
49 48
50 // write the inner classes if needed 49 // write the inner classes if needed
@@ -62,31 +61,20 @@ public class InnerClassWriter
62 for( String obfInnerClassName : obfInnerClassNames ) 61 for( String obfInnerClassName : obfInnerClassNames )
63 { 62 {
64 // deobfuscate the class names 63 // deobfuscate the class names
65 String deobfOuterClassName = m_deobfuscatingTranslator.translateClass( obfOuterClassName ); 64 ClassEntry obfClassEntry = new ClassEntry( obfOuterClassName + "$" + obfInnerClassName );
66 if( deobfOuterClassName == null ) 65
67 {
68 deobfOuterClassName = obfOuterClassName;
69 }
70 String obfOuterInnerClassName = obfOuterClassName + "$" + obfInnerClassName;
71 String deobfOuterInnerClassName = m_deobfuscatingTranslator.translateClass( obfOuterInnerClassName );
72 if( deobfOuterInnerClassName == null )
73 {
74 deobfOuterInnerClassName = obfOuterInnerClassName;
75 }
76 String deobfInnerClassName = deobfOuterInnerClassName.substring( deobfOuterInnerClassName.lastIndexOf( '$' ) + 1 );
77
78 // here's what the JVM spec says about the InnerClasses attribute 66 // here's what the JVM spec says about the InnerClasses attribute
79 // append( inner, outer of inner if inner is member of outer 0 ow, name after $ if inner not anonymous 0 ow, flags ); 67 // append( inner, outer of inner if inner is member of outer 0 ow, name after $ if inner not anonymous 0 ow, flags );
80 68
81 // update the attribute with this inner class 69 // update the attribute with this inner class
82 ConstPool constPool = c.getClassFile().getConstPool(); 70 ConstPool constPool = c.getClassFile().getConstPool();
83 int innerClassIndex = constPool.addClassInfo( deobfOuterInnerClassName ); 71 int innerClassIndex = constPool.addClassInfo( obfClassEntry.getName() );
84 int outerClassIndex = 0; 72 int outerClassIndex = 0;
85 int innerClassSimpleNameIndex = 0; 73 int innerClassSimpleNameIndex = 0;
86 if( !m_jarIndex.isAnonymousClass( obfInnerClassName ) ) 74 if( !m_jarIndex.isAnonymousClass( obfInnerClassName ) )
87 { 75 {
88 outerClassIndex = constPool.addClassInfo( deobfOuterClassName ); 76 outerClassIndex = constPool.addClassInfo( obfClassEntry.getOuterClassName() );
89 innerClassSimpleNameIndex = constPool.addUtf8Info( deobfInnerClassName ); 77 innerClassSimpleNameIndex = constPool.addUtf8Info( obfClassEntry.getInnerClassName() );
90 } 78 }
91 79
92 attr.append( 80 attr.append(
@@ -96,8 +84,18 @@ public class InnerClassWriter
96 c.getClassFile().getAccessFlags() & ~AccessFlag.SUPER 84 c.getClassFile().getAccessFlags() & ~AccessFlag.SUPER
97 ); 85 );
98 86
87 /* DEBUG
88 System.out.println( String.format( "\tOBF: %s -> ATTR: %s,%s,%s (replace %s with %s)",
89 obfClassEntry,
90 attr.outerClass( attr.tableLength() - 1 ),
91 attr.innerClass( attr.tableLength() - 1 ),
92 attr.innerName( attr.tableLength() - 1 ),
93 obfInnerClassName, obfClassEntry.getName()
94 ) );
95 */
96
99 // make sure the outer class references only the new inner class names 97 // make sure the outer class references only the new inner class names
100 c.replaceClassName( obfInnerClassName, deobfOuterInnerClassName ); 98 c.replaceClassName( obfInnerClassName, obfClassEntry.getName() );
101 } 99 }
102 } 100 }
103} 101}
diff --git a/src/cuchaz/enigma/mapping/Translator.java b/src/cuchaz/enigma/mapping/Translator.java
index 76f45cd..fc41f94 100644
--- a/src/cuchaz/enigma/mapping/Translator.java
+++ b/src/cuchaz/enigma/mapping/Translator.java
@@ -72,11 +72,21 @@ public class Translator
72 public ClassEntry translateEntry( ClassEntry in ) 72 public ClassEntry translateEntry( ClassEntry in )
73 { 73 {
74 String name = translate( in ); 74 String name = translate( in );
75 if( name == null ) 75 if( name != null )
76 {
77 return new ClassEntry( name );
78 }
79
80 if( in.isInnerClass() )
76 { 81 {
77 return in; 82 // just translate the outer class name
83 String outerClassName = translate( in.getOuterClassEntry() );
84 if( outerClassName != null )
85 {
86 return new ClassEntry( outerClassName + "$" + in.getInnerClassName() );
87 }
78 } 88 }
79 return new ClassEntry( name ); 89 return in;
80 } 90 }
81 91
82 public String translate( FieldEntry in ) 92 public String translate( FieldEntry in )