From 6c4440ac1133bfaa7871d1049d174528a289ef30 Mon Sep 17 00:00:00 2001 From: hg Date: Sun, 17 Aug 2014 10:56:17 -0400 Subject: added support for automatic reconstruction of inner and anonymous classes also added class to restore bridge method flags taken out by the obfuscator --- src/cuchaz/enigma/Deobfuscator.java | 51 +++---- src/cuchaz/enigma/TranslatingTypeLoader.java | 56 ++------ src/cuchaz/enigma/analysis/Ancestries.java | 20 +++ src/cuchaz/enigma/analysis/BridgeFixer.java | 91 ++++++++++++ src/cuchaz/enigma/analysis/JarIndex.java | 155 ++++++++++++++++++--- src/cuchaz/enigma/analysis/SourceIndex.java | 31 +++-- src/cuchaz/enigma/analysis/TreeDumpVisitor.java | 45 +++++- src/cuchaz/enigma/bytecode/InnerClassWriter.java | 64 ++++++--- src/cuchaz/enigma/gui/Gui.java | 16 ++- src/cuchaz/enigma/gui/GuiController.java | 5 +- src/cuchaz/enigma/mapping/ClassEntry.java | 28 ++++ src/cuchaz/enigma/mapping/ClassMapping.java | 97 +++++++++++-- .../enigma/mapping/MappingParseException.java | 31 +++++ src/cuchaz/enigma/mapping/MappingsReader.java | 76 +++++++--- src/cuchaz/enigma/mapping/MappingsWriter.java | 40 ++++-- src/cuchaz/enigma/mapping/Renamer.java | 64 ++++----- src/cuchaz/enigma/mapping/Translator.java | 104 +++++++++----- 17 files changed, 743 insertions(+), 231 deletions(-) create mode 100644 src/cuchaz/enigma/analysis/BridgeFixer.java create mode 100644 src/cuchaz/enigma/mapping/MappingParseException.java (limited to 'src') diff --git a/src/cuchaz/enigma/Deobfuscator.java b/src/cuchaz/enigma/Deobfuscator.java index 127a0d9..9a0ec13 100644 --- a/src/cuchaz/enigma/Deobfuscator.java +++ b/src/cuchaz/enigma/Deobfuscator.java @@ -62,7 +62,6 @@ public class Deobfuscator // config the decompiler m_settings = DecompilerSettings.javaDefaults(); - m_settings.setShowSyntheticMembers( true ); // init mappings setMappings( new Mappings() ); @@ -109,9 +108,15 @@ public class Deobfuscator { for( String obfClassName : m_jarIndex.getObfClassNames() ) { + // skip inner classes + if( m_jarIndex.getOuterClass( obfClassName ) != null ) + { + continue; + } + // separate the classes ClassMapping classMapping = m_mappings.getClassByObf( obfClassName ); - if( classMapping != null ) + if( classMapping != null && !classMapping.getObfName().equals( classMapping.getDeobfName() ) ) { deobfClasses.add( classMapping.getDeobfName() ); } @@ -151,6 +156,7 @@ public class Deobfuscator // render the AST into source StringWriter buf = new StringWriter(); root.acceptVisitor( new InsertParenthesesVisitor(), null ); + //root.acceptVisitor( new TreeDumpVisitor( new File( "tree.txt" ) ), null ); root.acceptVisitor( new JavaOutputVisitor( new PlainTextOutput( buf ), m_settings ), null ); // build the source index @@ -281,33 +287,30 @@ public class Deobfuscator } } - public boolean entryIsObfuscatedIdenfitier( Entry obfEntry ) + public boolean isObfuscatedIdentifier( Entry obfEntry ) { if( obfEntry instanceof ClassEntry ) { - // obf classes must be in the list - return m_jarIndex.getObfClassNames().contains( obfEntry.getName() ); - } - else if( obfEntry instanceof FieldEntry ) - { - return m_jarIndex.getObfClassNames().contains( obfEntry.getClassName() ); - } - else if( obfEntry instanceof MethodEntry ) - { - return m_jarIndex.getObfClassNames().contains( obfEntry.getClassName() ); - } - else if( obfEntry instanceof ConstructorEntry ) - { - return m_jarIndex.getObfClassNames().contains( obfEntry.getClassName() ); + if( obfEntry.getName().indexOf( '$' ) >= 0 ) + { + String[] parts = obfEntry.getName().split( "\\$" ); + assert( parts.length == 2 ); // not supporting recursively-nested classes + String outerClassName = parts[0]; + String innerClassName = parts[1]; + + // both classes must be in the list + return m_jarIndex.getObfClassNames().contains( outerClassName ) + && m_jarIndex.getObfClassNames().contains( innerClassName ); + } + else + { + // class must be in the list + return m_jarIndex.getObfClassNames().contains( obfEntry.getName() ); + } } - else if( obfEntry instanceof ArgumentEntry ) + else { - // arguments only appear in method declarations - // since we only show declarations for obf classes, these are always obfuscated - return true; + return isObfuscatedIdentifier( obfEntry.getClassEntry() ); } - - // assume everything else is not obfuscated - return false; } } diff --git a/src/cuchaz/enigma/TranslatingTypeLoader.java b/src/cuchaz/enigma/TranslatingTypeLoader.java index fdfcea0..c1d96ae 100644 --- a/src/cuchaz/enigma/TranslatingTypeLoader.java +++ b/src/cuchaz/enigma/TranslatingTypeLoader.java @@ -26,6 +26,7 @@ import javassist.bytecode.Descriptor; import com.strobel.assembler.metadata.Buffer; import com.strobel.assembler.metadata.ITypeLoader; +import cuchaz.enigma.analysis.BridgeFixer; import cuchaz.enigma.analysis.JarIndex; import cuchaz.enigma.bytecode.ClassTranslator; import cuchaz.enigma.bytecode.InnerClassWriter; @@ -50,12 +51,6 @@ public class TranslatingTypeLoader implements ITypeLoader @Override public boolean tryLoadType( String deobfClassName, Buffer out ) { - // TEMP - if( !deobfClassName.startsWith( "java" ) && !deobfClassName.startsWith( "org" ) ) - { - System.out.println( "Looking for: " + deobfClassName ); - } - // what class file should we actually load? String obfClassName = m_obfuscatingTranslator.translateClass( deobfClassName ); if( obfClassName == null ) @@ -64,26 +59,12 @@ public class TranslatingTypeLoader implements ITypeLoader } String classFileName = obfClassName; - // is this a properly-referenced inner class? - boolean isInnerClass = deobfClassName.indexOf( '$' ) >= 0; - if( isInnerClass ) - { - // get just the bare inner class name - String[] parts = deobfClassName.split( "\\$" ); - String deobfClassFileName = parts[parts.length - 1]; - - // make sure the bare inner class name is obfuscated - classFileName = m_obfuscatingTranslator.translateClass( deobfClassFileName ); - if( classFileName == null ) - { - classFileName = deobfClassFileName; - } - } - - // TEMP - if( !deobfClassName.startsWith( "java" ) && !deobfClassName.startsWith( "org" ) ) + // is this an inner class? + if( obfClassName.indexOf( '$' ) >= 0 ) { - System.out.println( "\tLooking at class file: " + classFileName ); + // the file name is the bare inner class name + String[] parts = obfClassName.split( "\\$" ); + classFileName = parts[parts.length - 1]; } // get the jar entry @@ -118,26 +99,20 @@ public class TranslatingTypeLoader implements ITypeLoader classPool.insertClassPath( new ByteArrayClassPath( javaClassFileName, buf ) ); CtClass c = classPool.get( javaClassFileName ); - if( isInnerClass ) - { - // rename the class to what procyon expects - c.setName( deobfClassName ); - } - else - { - // maybe it's an outer class - new InnerClassWriter( m_deobfuscatingTranslator, m_jarIndex ).writeInnerClasses( c ); - } - + // do all kinds of deobfuscating transformations on the class + new InnerClassWriter( m_deobfuscatingTranslator, m_jarIndex ).write( c ); + new BridgeFixer().fixBridges( c ); new MethodParameterWriter( m_deobfuscatingTranslator ).writeMethodArguments( c ); new ClassTranslator( m_deobfuscatingTranslator ).translate( c ); - assert( Descriptor.toJvmName( c.getName() ).equals( deobfClassName ) ); - assert( c.getClassFile().getName().equals( deobfClassName ) ); - - buf = c.toBytecode(); + // sanity checking + assert( Descriptor.toJvmName( c.getName() ).equals( deobfClassName ) ) + : String.format( "%s is not %s", Descriptor.toJvmName( c.getName() ), deobfClassName ); + assert( Descriptor.toJvmName( c.getClassFile().getName() ).equals( deobfClassName ) ) + : String.format( "%s is not %s", Descriptor.toJvmName( c.getClassFile().getName() ), deobfClassName ); // pass the transformed class along to the decompiler + buf = c.toBytecode(); out.reset( buf.length ); System.arraycopy( buf, 0, out.array(), out.position(), buf.length ); out.position( 0 ); @@ -149,5 +124,4 @@ public class TranslatingTypeLoader implements ITypeLoader throw new Error( ex ); } } - } diff --git a/src/cuchaz/enigma/analysis/Ancestries.java b/src/cuchaz/enigma/analysis/Ancestries.java index 83c239c..b9d8cbf 100644 --- a/src/cuchaz/enigma/analysis/Ancestries.java +++ b/src/cuchaz/enigma/analysis/Ancestries.java @@ -47,6 +47,26 @@ public class Ancestries implements Serializable } } + public void renameClasses( Map renames ) + { + Map newSuperclasses = Maps.newHashMap(); + for( Map.Entry entry : m_superclasses.entrySet() ) + { + String subclass = renames.get( entry.getKey() ); + if( subclass == null ) + { + subclass = entry.getKey(); + } + String superclass = renames.get( entry.getValue() ); + if( superclass == null ) + { + superclass = entry.getValue(); + } + newSuperclasses.put( subclass, superclass ); + } + m_superclasses = newSuperclasses; + } + public String getSuperclassName( String className ) { return m_superclasses.get( className ); diff --git a/src/cuchaz/enigma/analysis/BridgeFixer.java b/src/cuchaz/enigma/analysis/BridgeFixer.java new file mode 100644 index 0000000..db441d2 --- /dev/null +++ b/src/cuchaz/enigma/analysis/BridgeFixer.java @@ -0,0 +1,91 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.analysis; + +import java.util.List; + +import javassist.CannotCompileException; +import javassist.CtClass; +import javassist.CtMethod; +import javassist.NotFoundException; +import javassist.bytecode.AccessFlag; +import javassist.expr.ExprEditor; +import javassist.expr.MethodCall; + +import com.beust.jcommander.internal.Lists; + +public class BridgeFixer +{ + public void fixBridges( CtClass c ) + { + // bridge methods are scrubbed and marked as synthetic methods by the obfuscator + // need to figure out which synthetics are bridge methods and restore them + for( CtMethod method : c.getDeclaredMethods() ) + { + // skip non-synthetic methods + if( ( method.getModifiers() & AccessFlag.SYNTHETIC ) == 0 ) + { + continue; + } + + CtMethod bridgedMethod = getBridgedMethod( method ); + if( bridgedMethod != null ) + { + bridgedMethod.setName( method.getName() ); + method.setModifiers( method.getModifiers() | AccessFlag.BRIDGE ); + } + } + } + + private CtMethod getBridgedMethod( CtMethod method ) + { + // bridge methods just call another method, cast it to the return type, and return the result + // let's see if we can detect this scenario + + // get all the called methods + final List methodCalls = Lists.newArrayList(); + try + { + method.instrument( new ExprEditor( ) + { + @Override + public void edit( MethodCall call ) + { + methodCalls.add( call ); + } + } ); + } + catch( CannotCompileException ex ) + { + // this is stupid... we're not even compiling anything + throw new Error( ex ); + } + + // is there just one? + if( methodCalls.size() != 1 ) + { + return null; + } + MethodCall call = methodCalls.get( 0 ); + + try + { + // we have a bridge method! + return call.getMethod(); + } + catch( NotFoundException ex ) + { + // can't find the type? not a bridge method + ex.printStackTrace( System.err ); + return null; + } + } +} diff --git a/src/cuchaz/enigma/analysis/JarIndex.java b/src/cuchaz/enigma/analysis/JarIndex.java index 9962bfa..34e8986 100644 --- a/src/cuchaz/enigma/analysis/JarIndex.java +++ b/src/cuchaz/enigma/analysis/JarIndex.java @@ -10,7 +10,9 @@ ******************************************************************************/ package cuchaz.enigma.analysis; +import java.util.AbstractMap; import java.util.Collection; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -37,6 +39,7 @@ import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; +import cuchaz.enigma.mapping.ArgumentEntry; import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.ConstructorEntry; import cuchaz.enigma.mapping.Entry; @@ -53,6 +56,7 @@ public class JarIndex private Multimap m_fieldCalls; private Multimap m_innerClasses; private Map m_outerClasses; + private Set m_anonymousClasses; public JarIndex( ) { @@ -63,6 +67,7 @@ public class JarIndex m_fieldCalls = HashMultimap.create(); m_innerClasses = HashMultimap.create(); m_outerClasses = Maps.newHashMap(); + m_anonymousClasses = Sets.newHashSet(); } public void indexJar( JarFile jar ) @@ -84,7 +89,7 @@ public class JarIndex } } - // pass 2: index inner classes + // pass 2: index inner classes and anonymous classes for( CtClass c : JarClassIterator.classes( jar ) ) { String outerClassName = isInnerClass( c ); @@ -93,8 +98,21 @@ public class JarIndex String innerClassName = Descriptor.toJvmName( c.getName() ); m_innerClasses.put( outerClassName, innerClassName ); m_outerClasses.put( innerClassName, outerClassName ); + + if( isAnonymousClass( c, outerClassName ) ) + { + m_anonymousClasses.add( innerClassName ); + } } } + + // step 3: update other indicies with inner class info + Map renames = Maps.newHashMap(); + for( Map.Entry entry : m_outerClasses.entrySet() ) + { + renames.put( entry.getKey(), entry.getValue() + "$" + entry.getKey() ); + } + renameClasses( renames ); } private void indexBehavior( CtBehavior behavior ) @@ -186,14 +204,10 @@ public class JarIndex @SuppressWarnings( "unchecked" ) private String isInnerClass( CtClass c ) { - String innerClassName = Descriptor.toJvmName( c.getName() ); - - // first, is this an anonymous class? - // for anonymous classes: + // inner classes: // the outer class is always a synthetic field // there's at least one constructor with the type of the synthetic field as an argument - // this constructor is called exactly once by the class of the synthetic field - + for( FieldInfo field : (List)c.getClassFile().getFields() ) { boolean isSynthetic = (field.getAccessFlags() & AccessFlag.SYNTHETIC) != 0; @@ -230,16 +244,8 @@ public class JarIndex String argumentClassName = Descriptor.toJvmName( Descriptor.toClassName( argumentDesc ) ); if( argumentClassName.equals( outerClassName ) ) { - // is this constructor called exactly once? - ConstructorEntry constructorEntry = new ConstructorEntry( - new ClassEntry( innerClassName ), - constructor.getMethodInfo().getDescriptor() - ); - if( this.getMethodCallers( constructorEntry ).size() == 1 ) - { - targetConstructor = constructor; - break; - } + targetConstructor = constructor; + break; } } } @@ -255,6 +261,30 @@ public class JarIndex return null; } + private boolean isAnonymousClass( CtClass c, String outerClassName ) + { + String innerClassName = Descriptor.toJvmName( c.getName() ); + + // anonymous classes: + // have only one constructor + // it's called exactly once by the outer class + // type of inner class not referenced anywhere in outer class + + // is there exactly one constructor? + if( c.getDeclaredConstructors().length != 1 ) + { + return false; + } + CtConstructor constructor = c.getDeclaredConstructors()[0]; + + // is this constructor called exactly once? + ConstructorEntry constructorEntry = new ConstructorEntry( + new ClassEntry( innerClassName ), + constructor.getMethodInfo().getDescriptor() + ); + return getMethodCallers( constructorEntry ).size() == 1; + } + public Set getObfClassNames( ) { return m_obfClassNames; @@ -343,4 +373,95 @@ public class JarIndex { return m_outerClasses.get( obfInnerClassName ); } + + public boolean isAnonymousClass( String obfInnerClassName ) + { + return m_anonymousClasses.contains( obfInnerClassName ); + } + + private void renameClasses( Map renames ) + { + m_ancestries.renameClasses( renames ); + renameMultimap( renames, m_methodImplementations ); + renameMultimap( renames, m_methodCalls ); + renameMultimap( renames, m_fieldCalls ); + } + + private void renameMultimap( Map renames, Multimap map ) + { + // for each key/value pair... + Set> entriesToAdd = Sets.newHashSet(); + Iterator> iter = map.entries().iterator(); + while( iter.hasNext() ) + { + Map.Entry entry = iter.next(); + iter.remove(); + entriesToAdd.add( new AbstractMap.SimpleEntry( + renameEntry( renames, entry.getKey() ), + renameEntry( renames, entry.getValue() ) + ) ); + } + for( Map.Entry entry : entriesToAdd ) + { + map.put( entry.getKey(), entry.getValue() ); + } + } + + @SuppressWarnings( "unchecked" ) + private T renameEntry( Map renames, T entry ) + { + if( entry instanceof String ) + { + String stringEntry = (String)entry; + if( renames.containsKey( stringEntry ) ) + { + return (T)renames.get( stringEntry ); + } + } + else if( entry instanceof ClassEntry ) + { + ClassEntry classEntry = (ClassEntry)entry; + return (T)new ClassEntry( renameEntry( renames, classEntry.getClassName() ) ); + } + else if( entry instanceof FieldEntry ) + { + FieldEntry fieldEntry = (FieldEntry)entry; + return (T)new FieldEntry( + renameEntry( renames, fieldEntry.getClassEntry() ), + fieldEntry.getName() + ); + } + else if( entry instanceof ConstructorEntry ) + { + ConstructorEntry constructorEntry = (ConstructorEntry)entry; + return (T)new ConstructorEntry( + renameEntry( renames, constructorEntry.getClassEntry() ), + constructorEntry.getSignature() + ); + } + else if( entry instanceof MethodEntry ) + { + MethodEntry methodEntry = (MethodEntry)entry; + return (T)new MethodEntry( + renameEntry( renames, methodEntry.getClassEntry() ), + methodEntry.getName(), + methodEntry.getSignature() + ); + } + else if( entry instanceof ArgumentEntry ) + { + ArgumentEntry argumentEntry = (ArgumentEntry)entry; + return (T)new ArgumentEntry( + renameEntry( renames, argumentEntry.getMethodEntry() ), + argumentEntry.getIndex(), + argumentEntry.getName() + ); + } + else + { + throw new Error( "Not an entry: " + entry ); + } + + return entry; + } } diff --git a/src/cuchaz/enigma/analysis/SourceIndex.java b/src/cuchaz/enigma/analysis/SourceIndex.java index 531f3e0..645a71d 100644 --- a/src/cuchaz/enigma/analysis/SourceIndex.java +++ b/src/cuchaz/enigma/analysis/SourceIndex.java @@ -17,7 +17,7 @@ import java.util.TreeMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.strobel.decompiler.languages.Region; -import com.strobel.decompiler.languages.java.ast.AstNode; +import com.strobel.decompiler.languages.java.ast.Identifier; import cuchaz.enigma.mapping.Entry; @@ -51,19 +51,27 @@ public class SourceIndex return m_source; } - public Token getToken( AstNode node ) + public Token getToken( Identifier node ) { // get a token for this node's region Region region = node.getRegion(); if( region.getBeginLine() == 0 || region.getEndLine() == 0 ) { - throw new IllegalArgumentException( "Invalid region: " + region ); + System.err.println( "WARNING: " + node.getNodeType() + " node has invalid region: " + region ); + return null; } Token token = new Token( toPos( region.getBeginLine(), region.getBeginColumn() ), toPos( region.getEndLine(), region.getEndColumn() ) ); + // for tokens representing inner classes, make sure we only get the simple name + int pos = node.getName().lastIndexOf( '$' ); + if( pos >= 0 ) + { + token.end -= pos + 1; + } + // HACKHACK: sometimes node regions are off by one // I think this is a bug in Procyon, but it's easy to work around if( !Character.isJavaIdentifierStart( m_source.charAt( token.start ) ) ) @@ -79,16 +87,23 @@ public class SourceIndex return token; } - public void add( AstNode node, Entry deobfEntry ) + public void add( Identifier node, Entry deobfEntry ) { - m_tokens.put( getToken( node ), deobfEntry ); + Token token = getToken( node ); + if( token != null ) + { + m_tokens.put( token, deobfEntry ); + } } - public void addDeclaration( AstNode node, Entry deobfEntry ) + public void addDeclaration( Identifier node, Entry deobfEntry ) { Token token = getToken( node ); - m_tokens.put( token, deobfEntry ); - m_declarations.put( deobfEntry, token ); + if( token != null ) + { + m_tokens.put( token, deobfEntry ); + m_declarations.put( deobfEntry, token ); + } } public Token getToken( int pos ) diff --git a/src/cuchaz/enigma/analysis/TreeDumpVisitor.java b/src/cuchaz/enigma/analysis/TreeDumpVisitor.java index 05d0e6b..ac3e92d 100644 --- a/src/cuchaz/enigma/analysis/TreeDumpVisitor.java +++ b/src/cuchaz/enigma/analysis/TreeDumpVisitor.java @@ -10,6 +10,11 @@ ******************************************************************************/ package cuchaz.enigma.analysis; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; + import com.strobel.componentmodel.Key; import com.strobel.decompiler.languages.java.ast.Annotation; import com.strobel.decompiler.languages.java.ast.AnonymousObjectCreationExpression; @@ -87,10 +92,42 @@ import com.strobel.decompiler.patterns.Pattern; public class TreeDumpVisitor implements IAstVisitor { + private File m_file; + private Writer m_out; + + public TreeDumpVisitor( File file ) + { + m_file = file; + m_out = null; + } + + @Override + public Void visitCompilationUnit( CompilationUnit node, Void ignored ) + { + try + { + m_out = new FileWriter( m_file ); + recurse( node, ignored ); + m_out.close(); + return null; + } + catch( IOException ex ) + { + throw new Error( ex ); + } + } + private Void recurse( AstNode node, Void ignored ) { // show the tree - System.out.println( getIndent( node ) + node.getClass().getSimpleName() + dumpUserData( node ) + " " + node.getRegion() ); + try + { + m_out.write( getIndent( node ) + node.getClass().getSimpleName() + dumpUserData( node ) + " " + node.getRegion() + "\n" ); + } + catch( IOException ex ) + { + throw new Error( ex ); + } // recurse for( final AstNode child : node.getChildren() ) @@ -378,12 +415,6 @@ public class TreeDumpVisitor implements IAstVisitor return recurse( node, ignored ); } - @Override - public Void visitCompilationUnit( CompilationUnit node, Void ignored ) - { - return recurse( node, ignored ); - } - @Override public Void visitPackageDeclaration( PackageDeclaration node, Void ignored ) { diff --git a/src/cuchaz/enigma/bytecode/InnerClassWriter.java b/src/cuchaz/enigma/bytecode/InnerClassWriter.java index d4abe4e..b0e33ac 100644 --- a/src/cuchaz/enigma/bytecode/InnerClassWriter.java +++ b/src/cuchaz/enigma/bytecode/InnerClassWriter.java @@ -14,6 +14,7 @@ import java.util.Collection; import javassist.CtClass; import javassist.bytecode.AccessFlag; +import javassist.bytecode.ConstPool; import javassist.bytecode.Descriptor; import javassist.bytecode.InnerClassesAttribute; import cuchaz.enigma.analysis.JarIndex; @@ -29,21 +30,33 @@ public class InnerClassWriter m_deobfuscatingTranslator = deobfuscatingTranslator; m_jarIndex = jarIndex; } - - public void writeInnerClasses( CtClass c ) + + public void write( CtClass c ) { - // is this an outer class with inner classes? - String obfOuterClassName = Descriptor.toJvmName( c.getName() ); + // get the outer class name + String obfClassName = Descriptor.toJvmName( c.getName() ); + String obfOuterClassName = m_jarIndex.getOuterClass( obfClassName ); + if( obfOuterClassName == null ) + { + // this is an outer class + obfOuterClassName = obfClassName; + } + else + { + // this is an inner class, rename it to outer$inner + c.setName( obfOuterClassName + "$" + obfClassName ); + } + + // write the inner classes if needed Collection obfInnerClassNames = m_jarIndex.getInnerClasses( obfOuterClassName ); - if( obfInnerClassNames != null && !obfInnerClassNames.isEmpty() ) + if( obfInnerClassNames != null ) { - writeInnerClasses( c, obfInnerClassNames ); + writeInnerClasses( c, obfOuterClassName, obfInnerClassNames ); } } - - private void writeInnerClasses( CtClass c, Collection obfInnerClassNames ) + + private void writeInnerClasses( CtClass c, String obfOuterClassName, Collection obfInnerClassNames ) { - String obfOuterClassName = Descriptor.toJvmName( c.getName() ); InnerClassesAttribute attr = new InnerClassesAttribute( c.getClassFile().getConstPool() ); c.getClassFile().addAttribute( attr ); for( String obfInnerClassName : obfInnerClassNames ) @@ -54,26 +67,37 @@ public class InnerClassWriter { deobfOuterClassName = obfOuterClassName; } - String deobfInnerClassName = m_deobfuscatingTranslator.translateClass( obfInnerClassName ); - if( deobfInnerClassName == null ) + String obfOuterInnerClassName = obfOuterClassName + "$" + obfInnerClassName; + String deobfOuterInnerClassName = m_deobfuscatingTranslator.translateClass( obfOuterInnerClassName ); + if( deobfOuterInnerClassName == null ) + { + deobfOuterInnerClassName = obfOuterInnerClassName; + } + String deobfInnerClassName = deobfOuterInnerClassName.substring( deobfOuterInnerClassName.lastIndexOf( '$' ) + 1 ); + + // here's what the JVM spec says about the InnerClasses attribute + // append( inner, outer of inner if inner is member of outer 0 ow, name after $ if inner not anonymous 0 ow, flags ); + + // update the attribute with this inner class + ConstPool constPool = c.getClassFile().getConstPool(); + int innerClassIndex = constPool.addClassInfo( deobfOuterInnerClassName ); + int outerClassIndex = 0; + int innerClassSimpleNameIndex = 0; + if( !m_jarIndex.isAnonymousClass( obfInnerClassName ) ) { - deobfInnerClassName = obfInnerClassName; + outerClassIndex = constPool.addClassInfo( deobfOuterClassName ); + innerClassSimpleNameIndex = constPool.addUtf8Info( deobfInnerClassName ); } - // update the attribute - String deobfOuterInnerClassName = deobfOuterClassName + "$" + deobfInnerClassName; attr.append( - deobfOuterInnerClassName, - deobfOuterClassName, - deobfInnerClassName, + innerClassIndex, + outerClassIndex, + innerClassSimpleNameIndex, c.getClassFile().getAccessFlags() & ~AccessFlag.SUPER ); // make sure the outer class references only the new inner class names c.replaceClassName( obfInnerClassName, deobfOuterInnerClassName ); - - // TEMP - System.out.println( "\tInner " + obfInnerClassName + " -> " + deobfOuterInnerClassName ); } } } diff --git a/src/cuchaz/enigma/gui/Gui.java b/src/cuchaz/enigma/gui/Gui.java index 3b67a32..9ed6dd8 100644 --- a/src/cuchaz/enigma/gui/Gui.java +++ b/src/cuchaz/enigma/gui/Gui.java @@ -78,6 +78,7 @@ import cuchaz.enigma.mapping.Entry; import cuchaz.enigma.mapping.EntryPair; import cuchaz.enigma.mapping.FieldEntry; import cuchaz.enigma.mapping.IllegalNameException; +import cuchaz.enigma.mapping.MappingParseException; import cuchaz.enigma.mapping.MethodEntry; public class Gui @@ -508,6 +509,10 @@ public class Gui { throw new Error( ex ); } + catch( MappingParseException ex ) + { + JOptionPane.showMessageDialog( m_frame, ex.getMessage() ); + } } } } ); @@ -862,9 +867,17 @@ public class Gui private void startRename( ) { + // get the class name + String className = m_selectedEntryPair.deobf.getName(); + int pos = className.lastIndexOf( '$' ); + if( pos >= 0 ) + { + className = className.substring( pos + 1 ); + } + // init the text box final JTextField text = new JTextField(); - text.setText( m_selectedEntryPair.deobf.getName() ); + text.setText( className ); text.setPreferredSize( new Dimension( 360, text.getPreferredSize().height ) ); text.addKeyListener( new KeyAdapter( ) { @@ -905,6 +918,7 @@ public class Gui } catch( IllegalNameException ex ) { + ex.printStackTrace( System.err ); text.setBorder( BorderFactory.createLineBorder( Color.red, 1 ) ); } return; diff --git a/src/cuchaz/enigma/gui/GuiController.java b/src/cuchaz/enigma/gui/GuiController.java index 534b0cc..ffeb99a 100644 --- a/src/cuchaz/enigma/gui/GuiController.java +++ b/src/cuchaz/enigma/gui/GuiController.java @@ -31,6 +31,7 @@ import cuchaz.enigma.mapping.ConstructorEntry; import cuchaz.enigma.mapping.Entry; import cuchaz.enigma.mapping.EntryPair; import cuchaz.enigma.mapping.FieldEntry; +import cuchaz.enigma.mapping.MappingParseException; import cuchaz.enigma.mapping.MappingsReader; import cuchaz.enigma.mapping.MappingsWriter; import cuchaz.enigma.mapping.MethodEntry; @@ -75,7 +76,7 @@ public class GuiController } public void openMappings( File file ) - throws IOException + throws IOException, MappingParseException { FileReader in = new FileReader( file ); m_deobfuscator.setMappings( new MappingsReader().read( in ) ); @@ -135,7 +136,7 @@ public class GuiController public boolean entryIsObfuscatedIdenfitier( Entry deobfEntry ) { - return m_deobfuscator.entryIsObfuscatedIdenfitier( m_deobfuscator.obfuscateEntry( deobfEntry ) ); + return m_deobfuscator.isObfuscatedIdentifier( m_deobfuscator.obfuscateEntry( deobfEntry ) ); } public ClassInheritanceTreeNode getClassInheritance( ClassEntry obfClassEntry ) diff --git a/src/cuchaz/enigma/mapping/ClassEntry.java b/src/cuchaz/enigma/mapping/ClassEntry.java index 738e8e3..dad6da9 100644 --- a/src/cuchaz/enigma/mapping/ClassEntry.java +++ b/src/cuchaz/enigma/mapping/ClassEntry.java @@ -82,4 +82,32 @@ public class ClassEntry implements Entry, Serializable { return m_name; } + + public boolean isInnerClass( ) + { + return m_name.lastIndexOf( '$' ) >= 0; + } + + public String getOuterClassName( ) + { + if( isInnerClass() ) + { + return m_name.substring( 0, m_name.lastIndexOf( '$' ) ); + } + return m_name; + } + + public String getInnerClassName( ) + { + if( !isInnerClass() ) + { + throw new Error( "This is not an inner class!" ); + } + return m_name.substring( m_name.lastIndexOf( '$' ) + 1 ); + } + + public ClassEntry getOuterClassEntry( ) + { + return new ClassEntry( getOuterClassName() ); + } } diff --git a/src/cuchaz/enigma/mapping/ClassMapping.java b/src/cuchaz/enigma/mapping/ClassMapping.java index c6826f3..c7f930c 100644 --- a/src/cuchaz/enigma/mapping/ClassMapping.java +++ b/src/cuchaz/enigma/mapping/ClassMapping.java @@ -21,6 +21,8 @@ public class ClassMapping implements Serializable, Comparable private String m_obfName; private String m_deobfName; + private Map m_innerClassesByObf; + private Map m_innerClassesByDeobf; private Map m_fieldsByObf; private Map m_fieldsByDeobf; private Map m_methodsByObf; @@ -31,6 +33,8 @@ public class ClassMapping implements Serializable, Comparable { m_obfName = obfName; m_deobfName = NameValidator.validateClassName( deobfName ); + m_innerClassesByObf = Maps.newHashMap(); + m_innerClassesByDeobf = Maps.newHashMap(); m_fieldsByObf = Maps.newHashMap(); m_fieldsByDeobf = Maps.newHashMap(); m_methodsByObf = Maps.newHashMap(); @@ -51,6 +55,72 @@ public class ClassMapping implements Serializable, Comparable m_deobfName = NameValidator.validateClassName( val ); } + //// INNER CLASSES //////// + + public Iterable innerClasses( ) + { + assert( m_innerClassesByObf.size() == m_innerClassesByDeobf.size() ); + return m_innerClassesByObf.values(); + } + + protected void addInnerClassMapping( ClassMapping classMapping ) + { + m_innerClassesByObf.put( classMapping.getObfName(), classMapping ); + m_innerClassesByDeobf.put( classMapping.getDeobfName(), classMapping ); + } + + public ClassMapping getOrCreateInnerClass( String obfName ) + { + ClassMapping classMapping = m_innerClassesByObf.get( obfName ); + if( classMapping == null ) + { + classMapping = new ClassMapping( obfName, obfName ); + m_innerClassesByObf.put( obfName, classMapping ); + m_innerClassesByDeobf.put( obfName, classMapping ); + } + return classMapping; + } + + public ClassMapping getInnerClassByObf( String obfName ) + { + return m_innerClassesByObf.get( obfName ); + } + + public ClassMapping getInnerClassByDeobf( String deobfName ) + { + return m_innerClassesByDeobf.get( deobfName ); + } + + public String getObfInnerClassName( String deobfName ) + { + ClassMapping classMapping = m_innerClassesByDeobf.get( deobfName ); + if( classMapping != null ) + { + return classMapping.getObfName(); + } + return null; + } + + public String getDeobfInnerClassName( String obfName ) + { + ClassMapping classMapping = m_innerClassesByObf.get( obfName ); + if( classMapping != null ) + { + return classMapping.getDeobfName(); + } + return null; + } + + public void setInnerClassName( String obfName, String deobfName ) + { + ClassMapping classMapping = getOrCreateInnerClass( obfName ); + m_innerClassesByDeobf.remove( classMapping.getDeobfName() ); + classMapping.setDeobfName( deobfName ); + m_innerClassesByDeobf.put( deobfName, classMapping ); + } + + //// FIELDS //////// + public Iterable fields( ) { assert( m_fieldsByObf.size() == m_fieldsByDeobf.size() ); @@ -62,18 +132,7 @@ public class ClassMapping implements Serializable, Comparable m_fieldsByObf.put( fieldMapping.getObfName(), fieldMapping ); m_fieldsByDeobf.put( fieldMapping.getDeobfName(), fieldMapping ); } - - public Iterable methods( ) - { - assert( m_methodsByObf.size() == m_methodsByDeobf.size() ); - return m_methodsByObf.values(); - } - protected void addMethodMapping( MethodMapping methodMapping ) - { - m_methodsByObf.put( getMethodKey( methodMapping.getObfName(), methodMapping.getObfSignature() ), methodMapping ); - m_methodsByDeobf.put( getMethodKey( methodMapping.getDeobfName(), methodMapping.getDeobfSignature() ), methodMapping ); - } public String getObfFieldName( String deobfName ) { @@ -110,6 +169,20 @@ public class ClassMapping implements Serializable, Comparable m_fieldsByDeobf.put( deobfName, fieldMapping ); } + //// METHODS //////// + + public Iterable methods( ) + { + assert( m_methodsByObf.size() == m_methodsByDeobf.size() ); + return m_methodsByObf.values(); + } + + protected void addMethodMapping( MethodMapping methodMapping ) + { + m_methodsByObf.put( getMethodKey( methodMapping.getObfName(), methodMapping.getObfSignature() ), methodMapping ); + m_methodsByDeobf.put( getMethodKey( methodMapping.getDeobfName(), methodMapping.getDeobfSignature() ), methodMapping ); + } + public MethodMapping getMethodByObf( String obfName, String signature ) { return m_methodsByObf.get( getMethodKey( obfName, signature ) ); @@ -155,6 +228,8 @@ public class ClassMapping implements Serializable, Comparable } } + //// ARGUMENTS //////// + public void setArgumentName( String obfMethodName, String obfMethodSignature, int argumentIndex, String argumentName ) { MethodMapping methodIndex = m_methodsByObf.get( getMethodKey( obfMethodName, obfMethodSignature ) ); diff --git a/src/cuchaz/enigma/mapping/MappingParseException.java b/src/cuchaz/enigma/mapping/MappingParseException.java new file mode 100644 index 0000000..4fcc1f1 --- /dev/null +++ b/src/cuchaz/enigma/mapping/MappingParseException.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +public class MappingParseException extends Exception +{ + private static final long serialVersionUID = -5487280332892507236L; + + private int m_line; + private String m_message; + + public MappingParseException( int line, String message ) + { + m_line = line; + m_message = message; + } + + @Override + public String getMessage( ) + { + return "Line " + m_line + ": " + m_message; + } +} diff --git a/src/cuchaz/enigma/mapping/MappingsReader.java b/src/cuchaz/enigma/mapping/MappingsReader.java index b039409..4cebb3a 100644 --- a/src/cuchaz/enigma/mapping/MappingsReader.java +++ b/src/cuchaz/enigma/mapping/MappingsReader.java @@ -13,25 +13,27 @@ package cuchaz.enigma.mapping; import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; +import java.util.Deque; import java.util.NoSuchElementException; import java.util.Scanner; +import com.google.common.collect.Queues; + import cuchaz.enigma.Util; public class MappingsReader { public Mappings read( Reader in ) - throws IOException + throws IOException, MappingParseException { return read( new BufferedReader( in ) ); } public Mappings read( BufferedReader in ) - throws IOException + throws IOException, MappingParseException { Mappings mappings = new Mappings(); - ClassMapping classMapping = null; - MethodMapping methodMapping = null; + Deque mappingStack = Queues.newArrayDeque(); int lineNumber = 0; String line = null; @@ -47,12 +49,28 @@ public class MappingsReader } // skip blank lines - line = line.trim(); - if( line.length() <= 0 ) + if( line.trim().length() <= 0 ) { continue; } + // get the indent of this line + int indent = 0; + for( int i=0; i> List sorted( Iterable classes ) @@ -86,4 +92,14 @@ public class MappingsWriter Collections.sort( out ); return out; } + + private String getIndent( int depth ) + { + StringBuilder buf = new StringBuilder(); + for( int i=0; i m_classes; + public Map m_classes; private Ancestries m_ancestries; protected Translator( TranslationDirection direction, Map classes, Ancestries ancestries ) @@ -30,22 +30,42 @@ public class Translator m_ancestries = ancestries; } - public String translate( ClassEntry in ) + public String translateClass( String className ) { - return translateClass( in.getName() ); + return translate( new ClassEntry( className ) ); } - public String translateClass( String in ) + public String translate( ClassEntry in ) { - ClassMapping classIndex = m_classes.get( in ); - if( classIndex != null ) + ClassMapping classMapping = m_classes.get( in.getOuterClassName() ); + if( classMapping != null ) { - return m_direction.choose( - classIndex.getDeobfName(), - classIndex.getObfName() - ); + if( in.isInnerClass() ) + { + // look for the inner class + String translatedInnerClassName = m_direction.choose( + classMapping.getDeobfInnerClassName( in.getInnerClassName() ), + classMapping.getObfInnerClassName( in.getInnerClassName() ) + ); + if( translatedInnerClassName != null ) + { + // return outer$inner + String translatedOuterClassName = m_direction.choose( + classMapping.getDeobfName(), + classMapping.getObfName() + ); + return translatedOuterClassName + "$" + translatedInnerClassName; + } + } + else + { + // just return outer + return m_direction.choose( + classMapping.getDeobfName(), + classMapping.getObfName() + ); + } } - return null; } @@ -64,21 +84,20 @@ public class Translator for( String className : getSelfAndAncestors( in.getClassName() ) ) { // look for the class - ClassMapping classIndex = m_classes.get( className ); - if( classIndex != null ) + ClassMapping classMapping = findClassMapping( new ClassEntry( className ) ); + if( classMapping != null ) { // look for the field - String deobfName = m_direction.choose( - classIndex.getDeobfFieldName( in.getName() ), - classIndex.getObfFieldName( in.getName() ) + String translatedName = m_direction.choose( + classMapping.getDeobfFieldName( in.getName() ), + classMapping.getObfFieldName( in.getName() ) ); - if( deobfName != null ) + if( translatedName != null ) { - return deobfName; + return translatedName; } } } - return null; } @@ -99,20 +118,20 @@ public class Translator { for( String className : getSelfAndAncestors( in.getClassName() ) ) { - // look for the class - ClassMapping classIndex = m_classes.get( className ); - if( classIndex != null ) + // look for class + ClassMapping classMapping = findClassMapping( new ClassEntry( className ) ); + if( classMapping != null ) { // look for the method - MethodMapping methodIndex = m_direction.choose( - classIndex.getMethodByObf( in.getName(), in.getSignature() ), - classIndex.getMethodByDeobf( in.getName(), in.getSignature() ) + MethodMapping methodMapping = m_direction.choose( + classMapping.getMethodByObf( in.getName(), in.getSignature() ), + classMapping.getMethodByDeobf( in.getName(), in.getSignature() ) ); - if( methodIndex != null ) + if( methodMapping != null ) { return m_direction.choose( - methodIndex.getDeobfName(), - methodIndex.getObfName() + methodMapping.getDeobfName(), + methodMapping.getObfName() ); } } @@ -148,19 +167,19 @@ public class Translator for( String className : getSelfAndAncestors( in.getClassName() ) ) { // look for the class - ClassMapping classIndex = m_classes.get( className ); - if( classIndex != null ) + ClassMapping classMapping = findClassMapping( new ClassEntry( className ) ); + if( classMapping != null ) { // look for the method - MethodMapping methodIndex = m_direction.choose( - classIndex.getMethodByObf( in.getMethodName(), in.getMethodSignature() ), - classIndex.getMethodByDeobf( in.getMethodName(), in.getMethodSignature() ) + MethodMapping methodMapping = m_direction.choose( + classMapping.getMethodByObf( in.getMethodName(), in.getMethodSignature() ), + classMapping.getMethodByDeobf( in.getMethodName(), in.getMethodSignature() ) ); - if( methodIndex != null ) + if( methodMapping != null ) { return m_direction.choose( - methodIndex.getDeobfArgumentName( in.getIndex() ), - methodIndex.getObfArgumentName( in.getIndex() ) + methodMapping.getDeobfArgumentName( in.getIndex() ), + methodMapping.getObfArgumentName( in.getIndex() ) ); } } @@ -207,4 +226,17 @@ public class Translator ancestry.addAll( m_ancestries.getAncestry( className ) ); return ancestry; } + + private ClassMapping findClassMapping( ClassEntry classEntry ) + { + ClassMapping classMapping = m_classes.get( classEntry.getOuterClassName() ); + if( classMapping != null && classEntry.isInnerClass() ) + { + classMapping = m_direction.choose( + classMapping.getInnerClassByObf( classEntry.getInnerClassName() ), + classMapping.getInnerClassByDeobf( classEntry.getInnerClassName() ) + ); + } + return classMapping; + } } -- cgit v1.2.3