From bba7c6a19c15bc82946176c79a4eba3612b25f17 Mon Sep 17 00:00:00 2001 From: jeff Date: Mon, 11 Aug 2014 00:02:00 -0400 Subject: added method inheritance browsing also finally fixed method renamer to rename all method implementations in the inheritance hierarchy. --- src/cuchaz/enigma/Deobfuscator.java | 4 +- src/cuchaz/enigma/analysis/Ancestries.java | 235 +++++++++++++++++++++ .../enigma/analysis/ClassInheritanceTreeNode.java | 99 +++++++++ .../enigma/analysis/DeobfuscatedAncestries.java | 59 ++++++ .../enigma/analysis/MethodInheritanceTreeNode.java | 134 ++++++++++++ src/cuchaz/enigma/analysis/TreeDumpVisitor.java | 10 + .../enigma/gui/ClassInheritanceTreeNode.java | 79 ------- src/cuchaz/enigma/gui/Gui.java | 61 ++++-- src/cuchaz/enigma/gui/GuiController.java | 39 ++-- src/cuchaz/enigma/mapping/Ancestries.java | 154 -------------- .../enigma/mapping/DeobfuscatedAncestries.java | 57 ----- src/cuchaz/enigma/mapping/Mappings.java | 2 + src/cuchaz/enigma/mapping/Renamer.java | 28 +++ src/cuchaz/enigma/mapping/Translator.java | 1 + 14 files changed, 634 insertions(+), 328 deletions(-) create mode 100644 src/cuchaz/enigma/analysis/Ancestries.java create mode 100644 src/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java create mode 100644 src/cuchaz/enigma/analysis/DeobfuscatedAncestries.java create mode 100644 src/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java delete mode 100644 src/cuchaz/enigma/gui/ClassInheritanceTreeNode.java delete mode 100644 src/cuchaz/enigma/mapping/Ancestries.java delete mode 100644 src/cuchaz/enigma/mapping/DeobfuscatedAncestries.java (limited to 'src/cuchaz') diff --git a/src/cuchaz/enigma/Deobfuscator.java b/src/cuchaz/enigma/Deobfuscator.java index 8d4394cd..5321d2de 100644 --- a/src/cuchaz/enigma/Deobfuscator.java +++ b/src/cuchaz/enigma/Deobfuscator.java @@ -31,9 +31,9 @@ import com.strobel.decompiler.languages.java.ast.AstBuilder; import com.strobel.decompiler.languages.java.ast.CompilationUnit; import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor; +import cuchaz.enigma.analysis.Ancestries; import cuchaz.enigma.analysis.SourceIndex; import cuchaz.enigma.analysis.SourceIndexVisitor; -import cuchaz.enigma.mapping.Ancestries; import cuchaz.enigma.mapping.ArgumentEntry; import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.ClassMapping; @@ -207,7 +207,7 @@ public class Deobfuscator } else if( obfEntry instanceof MethodEntry ) { - m_renamer.setMethodName( (MethodEntry)obfEntry, newName ); + m_renamer.setMethodTreeName( (MethodEntry)obfEntry, newName ); } else if( obfEntry instanceof ArgumentEntry ) { diff --git a/src/cuchaz/enigma/analysis/Ancestries.java b/src/cuchaz/enigma/analysis/Ancestries.java new file mode 100644 index 00000000..e6c8bbf1 --- /dev/null +++ b/src/cuchaz/enigma/analysis/Ancestries.java @@ -0,0 +1,235 @@ +/******************************************************************************* + * 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.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import javassist.ByteArrayClassPath; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.NotFoundException; +import javassist.bytecode.Descriptor; +import javassist.bytecode.MethodInfo; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; + +import cuchaz.enigma.Constants; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.MethodEntry; +import cuchaz.enigma.mapping.Translator; + +public class Ancestries implements Serializable +{ + private static final long serialVersionUID = 738687982126844179L; + + private Map m_superclasses; + private Multimap m_methodImplementations; + + public Ancestries( ) + { + m_superclasses = Maps.newHashMap(); + m_methodImplementations = HashMultimap.create(); + } + + @SuppressWarnings( "unchecked" ) + public void readFromJar( InputStream in ) + throws IOException + { + ClassPool classPool = new ClassPool(); + + ZipInputStream zin = new ZipInputStream( in ); + ZipEntry entry; + while( ( entry = zin.getNextEntry() ) != null ) + { + // filter out non-classes + if( entry.isDirectory() || !entry.getName().endsWith( ".class" ) ) + { + continue; + } + + // read the class into a buffer + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + byte[] buf = new byte[Constants.KiB]; + int totalNumBytesRead = 0; + while( zin.available() > 0 ) + { + int numBytesRead = zin.read( buf ); + if( numBytesRead < 0 ) + { + break; + } + bos.write( buf, 0, numBytesRead ); + + // sanity checking + totalNumBytesRead += numBytesRead; + if( totalNumBytesRead > Constants.MiB ) + { + throw new Error( "Class file " + entry.getName() + " larger than 1 MiB! Something is wrong!" ); + } + } + + // determine the class name (ie chop off the ".class") + String className = Descriptor.toJavaName( entry.getName().substring( 0, entry.getName().length() - ".class".length() ) ); + + // get a javassist handle for the class + classPool.insertClassPath( new ByteArrayClassPath( className, bos.toByteArray() ) ); + try + { + CtClass c = classPool.get( className ); + addSuperclass( c.getName(), c.getClassFile().getSuperclass() ); + addMethodImplementations( c.getName(), (List)c.getClassFile().getMethods() ); + } + catch( NotFoundException ex ) + { + throw new Error( "Unable to load class: " + className ); + } + } + } + + private void addMethodImplementations( String name, List methods ) + { + for( MethodInfo method : methods ) + { + m_methodImplementations.put( name, getMethodKey( method.getName(), method.getDescriptor() ) ); + } + } + + public void addSuperclass( String className, String superclassName ) + { + className = Descriptor.toJvmName( className ); + superclassName = Descriptor.toJvmName( superclassName ); + + if( className.equals( superclassName ) ) + { + throw new IllegalArgumentException( "Class cannot be its own superclass! " + className ); + } + + if( !isJre( className ) && !isJre( superclassName ) ) + { + m_superclasses.put( className, superclassName ); + } + } + + public String getSuperclassName( String className ) + { + return m_superclasses.get( className ); + } + + public List getAncestry( String className ) + { + List ancestors = new ArrayList(); + while( className != null ) + { + className = getSuperclassName( className ); + if( className != null ) + { + ancestors.add( className ); + } + } + return ancestors; + } + + public List getSubclasses( String className ) + { + // linear search is fast enough for now + List subclasses = Lists.newArrayList(); + for( Map.Entry entry : m_superclasses.entrySet() ) + { + String subclass = entry.getKey(); + String superclass = entry.getValue(); + if( className.equals( superclass ) ) + { + subclasses.add( subclass ); + } + } + return subclasses; + } + + public boolean isMethodImplemented( MethodEntry methodEntry ) + { + return isMethodImplemented( methodEntry.getClassName(), methodEntry.getName(), methodEntry.getSignature() ); + } + + public boolean isMethodImplemented( String className, String methodName, String methodSignature ) + { + Collection implementations = m_methodImplementations.get( className ); + if( implementations == null ) + { + return false; + } + return implementations.contains( getMethodKey( methodName, methodSignature ) ); + } + + public ClassInheritanceTreeNode getClassInheritance( Translator deobfuscatingTranslator, ClassEntry obfClassEntry ) + { + // get the root node + List ancestry = getAncestry( obfClassEntry.getName() ); + ClassInheritanceTreeNode rootNode = new ClassInheritanceTreeNode( deobfuscatingTranslator, ancestry.get( ancestry.size() - 1 ) ); + + // expand all children recursively + rootNode.load( this, true ); + + return rootNode; + } + + public MethodInheritanceTreeNode getMethodInheritance( Translator deobfuscatingTranslator, MethodEntry obfMethodEntry ) + { + // travel to the ancestor implementation + String baseImplementationClassName = obfMethodEntry.getClassName(); + for( String ancestorClassName : getAncestry( obfMethodEntry.getClassName() ) ) + { + if( isMethodImplemented( ancestorClassName, obfMethodEntry.getName(), obfMethodEntry.getSignature() ) ) + { + baseImplementationClassName = ancestorClassName; + } + } + + // make a root node at the base + MethodEntry methodEntry = new MethodEntry( + new ClassEntry( baseImplementationClassName ), + obfMethodEntry.getName(), + obfMethodEntry.getSignature() + ); + MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode( + deobfuscatingTranslator, + methodEntry, + isMethodImplemented( methodEntry ) + ); + + // expand the full tree + rootNode.load( this, true ); + + return rootNode; + } + + private boolean isJre( String className ) + { + return className.startsWith( "java/" ) + || className.startsWith( "javax/" ); + } + + private String getMethodKey( String name, String signature ) + { + return name + signature; + } +} diff --git a/src/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java b/src/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java new file mode 100644 index 00000000..2ed141ff --- /dev/null +++ b/src/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * 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 javax.swing.tree.DefaultMutableTreeNode; + +import com.google.common.collect.Lists; + +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.Translator; + +public class ClassInheritanceTreeNode extends DefaultMutableTreeNode +{ + private static final long serialVersionUID = 4432367405826178490L; + + private Translator m_deobfuscatingTranslator; + private String m_obfClassName; + + public ClassInheritanceTreeNode( Translator deobfuscatingTranslator, String obfClassName ) + { + m_deobfuscatingTranslator = deobfuscatingTranslator; + m_obfClassName = obfClassName; + } + + public String getObfClassName( ) + { + return m_obfClassName; + } + + public String getDeobfClassName( ) + { + return m_deobfuscatingTranslator.translateClass( m_obfClassName ); + } + + @Override + public String toString( ) + { + String deobfClassName = getDeobfClassName(); + if( deobfClassName != null ) + { + return deobfClassName; + } + return m_obfClassName; + } + + public void load( Ancestries ancestries, boolean recurse ) + { + // get all the child nodes + List nodes = Lists.newArrayList(); + for( String subclassName : ancestries.getSubclasses( m_obfClassName ) ) + { + nodes.add( new ClassInheritanceTreeNode( m_deobfuscatingTranslator, subclassName ) ); + } + + // add them to this node + for( ClassInheritanceTreeNode node : nodes ) + { + this.add( node ); + } + + if( recurse ) + { + for( ClassInheritanceTreeNode node : nodes ) + { + node.load( ancestries, true ); + } + } + } + + public static ClassInheritanceTreeNode findNode( ClassInheritanceTreeNode node, ClassEntry entry ) + { + // is this the node? + if( node.getObfClassName().equals( entry.getName() ) ) + { + return node; + } + + // recurse + for( int i=0; i m_classesByObf; + private Map m_classesByDeobf; + + public DeobfuscatedAncestries( Ancestries ancestries, Map classesByObf, Map classesByDeobf ) + { + m_ancestries = ancestries; + m_classesByObf = classesByObf; + m_classesByDeobf = classesByDeobf; + } + + @Override + public String getSuperclassName( String deobfClassName ) + { + // obfuscate the class name + ClassMapping classIndex = m_classesByDeobf.get( deobfClassName ); + if( classIndex == null ) + { + return null; + } + String obfClassName = classIndex.getObfName(); + + // get the superclass + String obfSuperclassName = m_ancestries.getSuperclassName( obfClassName ); + if( obfSuperclassName == null ) + { + return null; + } + + // deobfuscate the superclass name + classIndex = m_classesByObf.get( obfSuperclassName ); + if( classIndex == null ) + { + return null; + } + + return classIndex.getDeobfName(); + } +} diff --git a/src/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java b/src/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java new file mode 100644 index 00000000..1fecf489 --- /dev/null +++ b/src/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java @@ -0,0 +1,134 @@ +/******************************************************************************* + * 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 javax.swing.tree.DefaultMutableTreeNode; + +import com.google.common.collect.Lists; + +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.MethodEntry; +import cuchaz.enigma.mapping.Translator; + +public class MethodInheritanceTreeNode extends DefaultMutableTreeNode +{ + private static final long serialVersionUID = 1096677030991810007L; + + private Translator m_deobfuscatingTranslator; + private MethodEntry m_entry; + private boolean m_isImplemented; + + public MethodInheritanceTreeNode( Translator deobfuscatingTranslator, MethodEntry entry, boolean isImplemented ) + { + m_deobfuscatingTranslator = deobfuscatingTranslator; + m_entry = entry; + m_isImplemented = isImplemented; + } + + public MethodEntry getMethodEntry( ) + { + return m_entry; + } + + public String getDeobfClassName( ) + { + return m_deobfuscatingTranslator.translateClass( m_entry.getClassName() ); + } + + public String getDeobfMethodName( ) + { + return m_deobfuscatingTranslator.translate( m_entry ); + } + + public boolean isImplemented( ) + { + return m_isImplemented; + } + + @Override + public String toString( ) + { + String className = getDeobfClassName(); + if( className == null ) + { + className = m_entry.getClassName(); + } + + if( !m_isImplemented ) + { + return className; + } + else + { + String methodName = getDeobfMethodName(); + if( methodName == null ) + { + methodName = m_entry.getName(); + } + return className + "." + methodName + "()"; + } + } + + public void load( Ancestries ancestries, boolean recurse ) + { + // get all the child nodes + List nodes = Lists.newArrayList(); + for( String subclassName : ancestries.getSubclasses( m_entry.getClassName() ) ) + { + MethodEntry methodEntry = new MethodEntry( + new ClassEntry( subclassName ), + m_entry.getName(), + m_entry.getSignature() + ); + nodes.add( new MethodInheritanceTreeNode( + m_deobfuscatingTranslator, + methodEntry, + ancestries.isMethodImplemented( subclassName, m_entry.getName(), m_entry.getSignature() ) + ) ); + } + + // add them to this node + for( MethodInheritanceTreeNode node : nodes ) + { + this.add( node ); + } + + if( recurse ) + { + for( MethodInheritanceTreeNode node : nodes ) + { + node.load( ancestries, true ); + } + } + } + + public static MethodInheritanceTreeNode findNode( MethodInheritanceTreeNode node, MethodEntry entry ) + { + // is this the node? + if( node.getMethodEntry().equals( entry ) ) + { + return node; + } + + // recurse + for( int i=0; i nodes = Lists.newArrayList(); - for( String subclassName : ancestries.getSubclasses( m_obfClassName ) ) - { - nodes.add( new ClassInheritanceTreeNode( m_deobfuscatingTranslator, subclassName ) ); - } - - // add them to this node - for( ClassInheritanceTreeNode node : nodes ) - { - this.add( node ); - } - - if( recurse ) - { - for( ClassInheritanceTreeNode node : nodes ) - { - node.load( ancestries, true ); - } - } - } -} diff --git a/src/cuchaz/enigma/gui/Gui.java b/src/cuchaz/enigma/gui/Gui.java index 62f23918..db676777 100644 --- a/src/cuchaz/enigma/gui/Gui.java +++ b/src/cuchaz/enigma/gui/Gui.java @@ -66,6 +66,8 @@ import jsyntaxpane.DefaultSyntaxKit; import com.google.common.collect.Lists; import cuchaz.enigma.Constants; +import cuchaz.enigma.analysis.ClassInheritanceTreeNode; +import cuchaz.enigma.analysis.MethodInheritanceTreeNode; import cuchaz.enigma.analysis.Token; import cuchaz.enigma.mapping.ArgumentEntry; import cuchaz.enigma.mapping.ClassEntry; @@ -343,10 +345,24 @@ public class Gui { if( event.getClickCount() == 2 ) { - ClassInheritanceTreeNode node = (ClassInheritanceTreeNode)m_inheritanceTree.getSelectionPath().getLastPathComponent(); - if( node != null ) + // get the selected node + Object node = m_inheritanceTree.getSelectionPath().getLastPathComponent(); + if( node == null ) { - m_controller.openEntry( new ClassEntry( node.getObfClassName() ) ); + return; + } + + if( node instanceof ClassInheritanceTreeNode ) + { + m_controller.openEntry( new ClassEntry( ((ClassInheritanceTreeNode)node).getObfClassName() ) ); + } + else if( node instanceof MethodInheritanceTreeNode ) + { + MethodInheritanceTreeNode methodNode = (MethodInheritanceTreeNode)node; + if( methodNode.isImplemented() ) + { + m_controller.openEntry( methodNode.getMethodEntry() ); + } } } } @@ -836,31 +852,46 @@ public class Gui return; } - // get the current class if( m_selectedEntryPair.obf instanceof ClassEntry ) { + // get the class inheritance ClassInheritanceTreeNode classNode = m_controller.getClassInheritance( (ClassEntry)m_selectedEntryPair.obf ); - // build the path from the root to the class node - List nodes = Lists.newArrayList(); - TreeNode node = classNode; - do - { - nodes.add( node ); - node = node.getParent(); - } - while( node != null ); - Collections.reverse( nodes ); - TreePath path = new TreePath( nodes.toArray() ); + // show the tree at the root + TreePath path = getPathToRoot( classNode ); + m_inheritanceTree.setModel( new DefaultTreeModel( (TreeNode)path.getPathComponent( 0 ) ) ); + m_inheritanceTree.expandPath( path ); + m_inheritanceTree.setSelectionRow( m_inheritanceTree.getRowForPath( path ) ); + } + else if( m_selectedEntryPair.obf instanceof MethodEntry ) + { + // get the method inheritance + MethodInheritanceTreeNode classNode = m_controller.getMethodInheritance( (MethodEntry)m_selectedEntryPair.obf ); // show the tree at the root + TreePath path = getPathToRoot( classNode ); m_inheritanceTree.setModel( new DefaultTreeModel( (TreeNode)path.getPathComponent( 0 ) ) ); m_inheritanceTree.expandPath( path ); m_inheritanceTree.setSelectionRow( m_inheritanceTree.getRowForPath( path ) ); } + redraw(); } + private TreePath getPathToRoot( TreeNode node ) + { + List nodes = Lists.newArrayList(); + TreeNode n = node; + do + { + nodes.add( n ); + n = n.getParent(); + } + while( n != null ); + Collections.reverse( nodes ); + return new TreePath( nodes.toArray() ); + } + private void openEntry( ) { if( m_selectedEntryPair == null ) diff --git a/src/cuchaz/enigma/gui/GuiController.java b/src/cuchaz/enigma/gui/GuiController.java index a4228c72..1946b4a3 100644 --- a/src/cuchaz/enigma/gui/GuiController.java +++ b/src/cuchaz/enigma/gui/GuiController.java @@ -20,6 +20,8 @@ import java.util.Stack; import com.google.common.collect.Lists; import cuchaz.enigma.Deobfuscator; +import cuchaz.enigma.analysis.ClassInheritanceTreeNode; +import cuchaz.enigma.analysis.MethodInheritanceTreeNode; import cuchaz.enigma.analysis.SourceIndex; import cuchaz.enigma.analysis.Token; import cuchaz.enigma.mapping.ClassEntry; @@ -27,8 +29,8 @@ import cuchaz.enigma.mapping.Entry; import cuchaz.enigma.mapping.EntryPair; import cuchaz.enigma.mapping.MappingsReader; import cuchaz.enigma.mapping.MappingsWriter; +import cuchaz.enigma.mapping.MethodEntry; import cuchaz.enigma.mapping.TranslationDirection; -import cuchaz.enigma.mapping.Translator; public class GuiController { @@ -128,27 +130,22 @@ public class GuiController return m_deobfuscator.entryIsObfuscatedIdenfitier( m_deobfuscator.obfuscateEntry( deobfEntry ) ); } - public ClassInheritanceTreeNode getClassInheritance( ClassEntry classEntry ) + public ClassInheritanceTreeNode getClassInheritance( ClassEntry obfClassEntry ) { - Translator deobfuscatingTranslator = m_deobfuscator.getTranslator( TranslationDirection.Deobfuscating ); - - // create a node for this class - ClassInheritanceTreeNode thisNode = new ClassInheritanceTreeNode( deobfuscatingTranslator, classEntry.getName() ); - - // expand all children recursively - thisNode.load( m_deobfuscator.getAncestries(), true ); - - // get the ancestors too - ClassInheritanceTreeNode node = thisNode; - for( String superclassName : m_deobfuscator.getAncestries().getAncestry( classEntry.getName() ) ) - { - // add the parent node - ClassInheritanceTreeNode parentNode = new ClassInheritanceTreeNode( deobfuscatingTranslator, superclassName ); - parentNode.add( node ); - node = parentNode; - } - - return thisNode; + ClassInheritanceTreeNode rootNode = m_deobfuscator.getAncestries().getClassInheritance( + m_deobfuscator.getTranslator( TranslationDirection.Deobfuscating ), + obfClassEntry + ); + return ClassInheritanceTreeNode.findNode( rootNode, obfClassEntry ); + } + + public MethodInheritanceTreeNode getMethodInheritance( MethodEntry obfMethodEntry ) + { + MethodInheritanceTreeNode rootNode = m_deobfuscator.getAncestries().getMethodInheritance( + m_deobfuscator.getTranslator( TranslationDirection.Deobfuscating ), + obfMethodEntry + ); + return MethodInheritanceTreeNode.findNode( rootNode, obfMethodEntry ); } public void rename( Entry obfEntry, String newName ) diff --git a/src/cuchaz/enigma/mapping/Ancestries.java b/src/cuchaz/enigma/mapping/Ancestries.java deleted file mode 100644 index 894cf802..00000000 --- a/src/cuchaz/enigma/mapping/Ancestries.java +++ /dev/null @@ -1,154 +0,0 @@ -/******************************************************************************* - * 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; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; - -import javassist.ByteArrayClassPath; -import javassist.ClassPool; -import javassist.CtClass; -import javassist.NotFoundException; -import javassist.bytecode.Descriptor; - -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; - -import cuchaz.enigma.Constants; - -public class Ancestries implements Serializable -{ - private static final long serialVersionUID = 738687982126844179L; - - private Map m_superclasses; - - public Ancestries( ) - { - m_superclasses = Maps.newHashMap(); - } - - public void readFromJar( InputStream in ) - throws IOException - { - ClassPool classPool = new ClassPool(); - - ZipInputStream zin = new ZipInputStream( in ); - ZipEntry entry; - while( ( entry = zin.getNextEntry() ) != null ) - { - // filter out non-classes - if( entry.isDirectory() || !entry.getName().endsWith( ".class" ) ) - { - continue; - } - - // read the class into a buffer - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - byte[] buf = new byte[Constants.KiB]; - int totalNumBytesRead = 0; - while( zin.available() > 0 ) - { - int numBytesRead = zin.read( buf ); - if( numBytesRead < 0 ) - { - break; - } - bos.write( buf, 0, numBytesRead ); - - // sanity checking - totalNumBytesRead += numBytesRead; - if( totalNumBytesRead > Constants.MiB ) - { - throw new Error( "Class file " + entry.getName() + " larger than 1 MiB! Something is wrong!" ); - } - } - - // determine the class name (ie chop off the ".class") - String className = Descriptor.toJavaName( entry.getName().substring( 0, entry.getName().length() - ".class".length() ) ); - - // get a javassist handle for the class - classPool.insertClassPath( new ByteArrayClassPath( className, bos.toByteArray() ) ); - try - { - CtClass c = classPool.get( className ); - addSuperclass( c.getName(), c.getClassFile().getSuperclass() ); - } - catch( NotFoundException ex ) - { - throw new Error( "Unable to load class: " + className ); - } - } - } - - public void addSuperclass( String className, String superclassName ) - { - className = Descriptor.toJvmName( className ); - superclassName = Descriptor.toJvmName( superclassName ); - - if( className.equals( superclassName ) ) - { - throw new IllegalArgumentException( "Class cannot be its own superclass! " + className ); - } - - if( !isJre( className ) && !isJre( superclassName ) ) - { - m_superclasses.put( className, superclassName ); - } - } - - public String getSuperclassName( String className ) - { - return m_superclasses.get( className ); - } - - public List getAncestry( String className ) - { - List ancestors = new ArrayList(); - while( className != null ) - { - className = getSuperclassName( className ); - if( className != null ) - { - ancestors.add( className ); - } - } - return ancestors; - } - - public List getSubclasses( String className ) - { - // linear search is fast enough for now - List subclasses = Lists.newArrayList(); - for( Map.Entry entry : m_superclasses.entrySet() ) - { - String subclass = entry.getKey(); - String superclass = entry.getValue(); - if( className.equals( superclass ) ) - { - subclasses.add( subclass ); - } - } - return subclasses; - } - - private boolean isJre( String className ) - { - return className.startsWith( "java/" ) - || className.startsWith( "javax/" ); - } -} diff --git a/src/cuchaz/enigma/mapping/DeobfuscatedAncestries.java b/src/cuchaz/enigma/mapping/DeobfuscatedAncestries.java deleted file mode 100644 index dcb0741a..00000000 --- a/src/cuchaz/enigma/mapping/DeobfuscatedAncestries.java +++ /dev/null @@ -1,57 +0,0 @@ -/******************************************************************************* - * 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; - -import java.util.Map; - -public class DeobfuscatedAncestries extends Ancestries -{ - private static final long serialVersionUID = 8316248774892618324L; - - private Ancestries m_ancestries; - private Map m_classesByObf; - private Map m_classesByDeobf; - - protected DeobfuscatedAncestries( Ancestries ancestries, Map classesByObf, Map classesByDeobf ) - { - m_ancestries = ancestries; - m_classesByObf = classesByObf; - m_classesByDeobf = classesByDeobf; - } - - @Override - public String getSuperclassName( String deobfClassName ) - { - // obfuscate the class name - ClassMapping classIndex = m_classesByDeobf.get( deobfClassName ); - if( classIndex == null ) - { - return null; - } - String obfClassName = classIndex.getObfName(); - - // get the superclass - String obfSuperclassName = m_ancestries.getSuperclassName( obfClassName ); - if( obfSuperclassName == null ) - { - return null; - } - - // deobfuscate the superclass name - classIndex = m_classesByObf.get( obfSuperclassName ); - if( classIndex == null ) - { - return null; - } - - return classIndex.getDeobfName(); - } -} diff --git a/src/cuchaz/enigma/mapping/Mappings.java b/src/cuchaz/enigma/mapping/Mappings.java index 4dff6935..c7cb6a67 100644 --- a/src/cuchaz/enigma/mapping/Mappings.java +++ b/src/cuchaz/enigma/mapping/Mappings.java @@ -20,6 +20,8 @@ import java.util.zip.GZIPInputStream; import com.google.common.collect.Maps; import cuchaz.enigma.Util; +import cuchaz.enigma.analysis.Ancestries; +import cuchaz.enigma.analysis.DeobfuscatedAncestries; public class Mappings implements Serializable { diff --git a/src/cuchaz/enigma/mapping/Renamer.java b/src/cuchaz/enigma/mapping/Renamer.java index b7aa35ca..5a75c016 100644 --- a/src/cuchaz/enigma/mapping/Renamer.java +++ b/src/cuchaz/enigma/mapping/Renamer.java @@ -15,6 +15,9 @@ import java.io.ObjectOutputStream; import java.io.OutputStream; import java.util.zip.GZIPOutputStream; +import cuchaz.enigma.analysis.Ancestries; +import cuchaz.enigma.analysis.MethodInheritanceTreeNode; + public class Renamer { private Ancestries m_ancestries; @@ -54,6 +57,30 @@ public class Renamer classMapping.setFieldName( obf.getName(), deobfName ); } + public void setMethodTreeName( MethodEntry obf, String deobfName ) + { + // get the method tree + setMethodTreeName( + m_ancestries.getMethodInheritance( m_mappings.getTranslator( m_ancestries, TranslationDirection.Deobfuscating ), obf ), + deobfName + ); + } + + private void setMethodTreeName( MethodInheritanceTreeNode node, String deobfName ) + { + if( node.isImplemented() ) + { + // apply the name here + setMethodName( node.getMethodEntry(), deobfName ); + } + + // recurse + for( int i=0; i