diff options
| author | 2014-08-18 00:55:30 -0400 | |
|---|---|---|
| committer | 2014-08-18 00:55:30 -0400 | |
| commit | 34c1e8e64ec4575527a19fb4cb0640c57da784db (patch) | |
| tree | 44e3f1d50f8d8b8a9ab7c26dd94b58cba750cc67 /src | |
| parent | added support for automatic reconstruction of inner and anonymous classes (diff) | |
| download | enigma-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.java | 2 | ||||
| -rw-r--r-- | src/cuchaz/enigma/Main.java | 4 | ||||
| -rw-r--r-- | src/cuchaz/enigma/TranslatingTypeLoader.java | 103 | ||||
| -rw-r--r-- | src/cuchaz/enigma/Util.java | 20 | ||||
| -rw-r--r-- | src/cuchaz/enigma/analysis/BridgeFixer.java | 2 | ||||
| -rw-r--r-- | src/cuchaz/enigma/analysis/JarIndex.java | 195 | ||||
| -rw-r--r-- | src/cuchaz/enigma/bytecode/BytecodeTools.java | 57 | ||||
| -rw-r--r-- | src/cuchaz/enigma/bytecode/ClassTranslator.java | 53 | ||||
| -rw-r--r-- | src/cuchaz/enigma/bytecode/InnerClassWriter.java | 42 | ||||
| -rw-r--r-- | src/cuchaz/enigma/mapping/Translator.java | 16 |
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; | |||
| 13 | import java.io.File; | 13 | import java.io.File; |
| 14 | 14 | ||
| 15 | import cuchaz.enigma.gui.Gui; | 15 | import cuchaz.enigma.gui.Gui; |
| 16 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 16 | 17 | ||
| 17 | public class Main | 18 | public 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; | |||
| 13 | import java.io.ByteArrayOutputStream; | 13 | import java.io.ByteArrayOutputStream; |
| 14 | import java.io.IOException; | 14 | import java.io.IOException; |
| 15 | import java.io.InputStream; | 15 | import java.io.InputStream; |
| 16 | import java.util.Map; | ||
| 16 | import java.util.jar.JarEntry; | 17 | import java.util.jar.JarEntry; |
| 17 | import java.util.jar.JarFile; | 18 | import java.util.jar.JarFile; |
| 18 | 19 | ||
| @@ -23,6 +24,7 @@ import javassist.CtClass; | |||
| 23 | import javassist.NotFoundException; | 24 | import javassist.NotFoundException; |
| 24 | import javassist.bytecode.Descriptor; | 25 | import javassist.bytecode.Descriptor; |
| 25 | 26 | ||
| 27 | import com.beust.jcommander.internal.Maps; | ||
| 26 | import com.strobel.assembler.metadata.Buffer; | 28 | import com.strobel.assembler.metadata.Buffer; |
| 27 | import com.strobel.assembler.metadata.ITypeLoader; | 29 | import com.strobel.assembler.metadata.ITypeLoader; |
| 28 | 30 | ||
| @@ -31,6 +33,7 @@ import cuchaz.enigma.analysis.JarIndex; | |||
| 31 | import cuchaz.enigma.bytecode.ClassTranslator; | 33 | import cuchaz.enigma.bytecode.ClassTranslator; |
| 32 | import cuchaz.enigma.bytecode.InnerClassWriter; | 34 | import cuchaz.enigma.bytecode.InnerClassWriter; |
| 33 | import cuchaz.enigma.bytecode.MethodParameterWriter; | 35 | import cuchaz.enigma.bytecode.MethodParameterWriter; |
| 36 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 34 | import cuchaz.enigma.mapping.Translator; | 37 | import cuchaz.enigma.mapping.Translator; |
| 35 | 38 | ||
| 36 | public class TranslatingTypeLoader implements ITypeLoader | 39 | public 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 | ||
| 13 | import java.awt.Desktop; | 13 | import java.awt.Desktop; |
| 14 | import java.io.Closeable; | 14 | import java.io.Closeable; |
| 15 | import java.io.File; | ||
| 16 | import java.io.FileOutputStream; | ||
| 15 | import java.io.IOException; | 17 | import java.io.IOException; |
| 16 | import java.io.InputStream; | 18 | import java.io.InputStream; |
| 17 | import java.io.InputStreamReader; | 19 | import java.io.InputStreamReader; |
| @@ -19,6 +21,10 @@ import java.net.URI; | |||
| 19 | import java.net.URISyntaxException; | 21 | import java.net.URISyntaxException; |
| 20 | import java.util.jar.JarFile; | 22 | import java.util.jar.JarFile; |
| 21 | 23 | ||
| 24 | import javassist.CannotCompileException; | ||
| 25 | import javassist.CtClass; | ||
| 26 | import javassist.bytecode.Descriptor; | ||
| 27 | |||
| 22 | import com.google.common.io.CharStreams; | 28 | import 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 | ******************************************************************************/ |
| 11 | package cuchaz.enigma.analysis; | 11 | package cuchaz.enigma.analysis; |
| 12 | 12 | ||
| 13 | import java.lang.reflect.Modifier; | ||
| 13 | import java.util.AbstractMap; | 14 | import java.util.AbstractMap; |
| 14 | import java.util.Collection; | 15 | import java.util.Collection; |
| 15 | import java.util.Iterator; | 16 | import 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; | |||
| 15 | import java.io.DataInputStream; | 15 | import java.io.DataInputStream; |
| 16 | import java.io.DataOutputStream; | 16 | import java.io.DataOutputStream; |
| 17 | import java.io.IOException; | 17 | import java.io.IOException; |
| 18 | import java.util.List; | ||
| 18 | import java.util.Map; | 19 | import java.util.Map; |
| 19 | import java.util.Set; | 20 | import java.util.Set; |
| 20 | 21 | ||
| @@ -25,6 +26,7 @@ import javassist.bytecode.CodeAttribute; | |||
| 25 | import javassist.bytecode.ConstPool; | 26 | import javassist.bytecode.ConstPool; |
| 26 | import javassist.bytecode.ExceptionTable; | 27 | import javassist.bytecode.ExceptionTable; |
| 27 | 28 | ||
| 29 | import com.beust.jcommander.internal.Lists; | ||
| 28 | import com.google.common.collect.Maps; | 30 | import com.google.common.collect.Maps; |
| 29 | import com.google.common.collect.Sets; | 31 | import 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 | ******************************************************************************/ |
| 11 | package cuchaz.enigma.bytecode; | 11 | package cuchaz.enigma.bytecode; |
| 12 | 12 | ||
| 13 | import java.util.HashSet; | ||
| 14 | import java.util.Set; | 13 | import java.util.Set; |
| 15 | 14 | ||
| 16 | import javassist.ClassMap; | 15 | import javassist.ClassMap; |
| @@ -20,6 +19,10 @@ import javassist.CtField; | |||
| 20 | import javassist.CtMethod; | 19 | import javassist.CtMethod; |
| 21 | import javassist.bytecode.ConstPool; | 20 | import javassist.bytecode.ConstPool; |
| 22 | import javassist.bytecode.Descriptor; | 21 | import javassist.bytecode.Descriptor; |
| 22 | import javassist.bytecode.InnerClassesAttribute; | ||
| 23 | |||
| 24 | import com.beust.jcommander.internal.Sets; | ||
| 25 | |||
| 23 | import cuchaz.enigma.mapping.ClassEntry; | 26 | import cuchaz.enigma.mapping.ClassEntry; |
| 24 | import cuchaz.enigma.mapping.FieldEntry; | 27 | import cuchaz.enigma.mapping.FieldEntry; |
| 25 | import cuchaz.enigma.mapping.MethodEntry; | 28 | import 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; | |||
| 18 | import javassist.bytecode.Descriptor; | 18 | import javassist.bytecode.Descriptor; |
| 19 | import javassist.bytecode.InnerClassesAttribute; | 19 | import javassist.bytecode.InnerClassesAttribute; |
| 20 | import cuchaz.enigma.analysis.JarIndex; | 20 | import cuchaz.enigma.analysis.JarIndex; |
| 21 | import cuchaz.enigma.mapping.Translator; | 21 | import cuchaz.enigma.mapping.ClassEntry; |
| 22 | 22 | ||
| 23 | public class InnerClassWriter | 23 | public 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 ) |