From d7321b5b0d38c575e54c770f7aa18dacbacab3c8 Mon Sep 17 00:00:00 2001 From: jeff Date: Sun, 27 Jul 2014 22:33:21 -0400 Subject: added identifier renaming capability copied some code over from M3L to handle the heavy bytecode magic. It's ok... M3L will eventually depend on Enigma. Completely restructured the mappings though. This way is better. =) --- src/cuchaz/enigma/mapping/Ancestries.java | 134 ++++++++++++++ src/cuchaz/enigma/mapping/ArgumentEntry.java | 27 +++ src/cuchaz/enigma/mapping/ArgumentIndex.java | 41 +++++ src/cuchaz/enigma/mapping/ClassEntry.java | 5 + src/cuchaz/enigma/mapping/ClassIndex.java | 159 ++++++++++++++++ .../enigma/mapping/DeobfuscatedAncestries.java | 57 ++++++ src/cuchaz/enigma/mapping/EntryPair.java | 46 +++++ src/cuchaz/enigma/mapping/FieldEntry.java | 17 ++ src/cuchaz/enigma/mapping/MethodEntry.java | 21 ++- src/cuchaz/enigma/mapping/MethodIndex.java | 125 +++++++++++++ src/cuchaz/enigma/mapping/SignatureUpdater.java | 87 +++++++++ .../enigma/mapping/TranslationDirection.java | 34 ++++ src/cuchaz/enigma/mapping/TranslationMappings.java | 187 +++++++++++++++++++ src/cuchaz/enigma/mapping/Translator.java | 201 +++++++++++++++++++++ 14 files changed, 1140 insertions(+), 1 deletion(-) create mode 100644 src/cuchaz/enigma/mapping/Ancestries.java create mode 100644 src/cuchaz/enigma/mapping/ArgumentIndex.java create mode 100644 src/cuchaz/enigma/mapping/ClassIndex.java create mode 100644 src/cuchaz/enigma/mapping/DeobfuscatedAncestries.java create mode 100644 src/cuchaz/enigma/mapping/EntryPair.java create mode 100644 src/cuchaz/enigma/mapping/MethodIndex.java create mode 100644 src/cuchaz/enigma/mapping/SignatureUpdater.java create mode 100644 src/cuchaz/enigma/mapping/TranslationDirection.java create mode 100644 src/cuchaz/enigma/mapping/TranslationMappings.java create mode 100644 src/cuchaz/enigma/mapping/Translator.java (limited to 'src/cuchaz/enigma/mapping') diff --git a/src/cuchaz/enigma/mapping/Ancestries.java b/src/cuchaz/enigma/mapping/Ancestries.java new file mode 100644 index 0000000..b7a5e24 --- /dev/null +++ b/src/cuchaz/enigma/mapping/Ancestries.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.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.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 ); + ancestors.add( className ); + } + return ancestors; + } + + private boolean isJre( String className ) + { + return className.startsWith( "java/" ) + || className.startsWith( "javax/" ); + } +} diff --git a/src/cuchaz/enigma/mapping/ArgumentEntry.java b/src/cuchaz/enigma/mapping/ArgumentEntry.java index dc3b4df..c1624a8 100644 --- a/src/cuchaz/enigma/mapping/ArgumentEntry.java +++ b/src/cuchaz/enigma/mapping/ArgumentEntry.java @@ -42,6 +42,13 @@ public class ArgumentEntry implements Entry, Serializable m_name = name; } + public ArgumentEntry( ArgumentEntry other ) + { + m_methodEntry = new MethodEntry( other.m_methodEntry ); + m_index = other.m_index; + m_name = other.m_name; + } + public MethodEntry getMethodEntry( ) { return m_methodEntry; @@ -58,6 +65,26 @@ public class ArgumentEntry implements Entry, Serializable return m_name; } + public ClassEntry getClassEntry( ) + { + return m_methodEntry.getClassEntry(); + } + + public String getClassName( ) + { + return m_methodEntry.getClassName(); + } + + public String getMethodName( ) + { + return m_methodEntry.getName(); + } + + public String getMethodSignature( ) + { + return m_methodEntry.getSignature(); + } + @Override public int hashCode( ) { diff --git a/src/cuchaz/enigma/mapping/ArgumentIndex.java b/src/cuchaz/enigma/mapping/ArgumentIndex.java new file mode 100644 index 0000000..57488d1 --- /dev/null +++ b/src/cuchaz/enigma/mapping/ArgumentIndex.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * 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.Serializable; + +public class ArgumentIndex implements Serializable +{ + private static final long serialVersionUID = 8610742471440861315L; + + private String m_obfName; + private String m_deobfName; + + public ArgumentIndex( String obfName, String deobfName ) + { + m_obfName = obfName; + m_deobfName = deobfName; + } + + public String getObfName( ) + { + return m_obfName; + } + + public String getDeobfName( ) + { + return m_deobfName; + } + public void setDeobfName( String val ) + { + m_deobfName = val; + } +} diff --git a/src/cuchaz/enigma/mapping/ClassEntry.java b/src/cuchaz/enigma/mapping/ClassEntry.java index 3a75767..0968e95 100644 --- a/src/cuchaz/enigma/mapping/ClassEntry.java +++ b/src/cuchaz/enigma/mapping/ClassEntry.java @@ -33,6 +33,11 @@ public class ClassEntry implements Entry, Serializable m_name = className; } + public ClassEntry( ClassEntry other ) + { + m_name = other.m_name; + } + @Override public String getName( ) { diff --git a/src/cuchaz/enigma/mapping/ClassIndex.java b/src/cuchaz/enigma/mapping/ClassIndex.java new file mode 100644 index 0000000..699807b --- /dev/null +++ b/src/cuchaz/enigma/mapping/ClassIndex.java @@ -0,0 +1,159 @@ +/******************************************************************************* + * 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.Serializable; +import java.util.Map; + +import com.beust.jcommander.internal.Maps; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; + +public class ClassIndex implements Serializable +{ + private static final long serialVersionUID = -5148491146902340107L; + + private String m_obfName; + private String m_deobfName; + private BiMap m_fieldsObfToDeobf; + private Map m_methodsByObf; + private Map m_methodsByDeobf; + + public ClassIndex( String obfName, String deobfName ) + { + m_obfName = obfName; + m_deobfName = deobfName; + m_fieldsObfToDeobf = HashBiMap.create(); + m_methodsByObf = Maps.newHashMap(); + m_methodsByDeobf = Maps.newHashMap(); + } + + public String getObfName( ) + { + return m_obfName; + } + + public String getDeobfName( ) + { + return m_deobfName; + } + public void setDeobfName( String val ) + { + m_deobfName = val; + } + + public String getObfFieldName( String deobfName ) + { + return m_fieldsObfToDeobf.inverse().get( deobfName ); + } + + public String getDeobfFieldName( String obfName ) + { + return m_fieldsObfToDeobf.get( obfName ); + } + + public void setFieldName( String obfName, String deobfName ) + { + m_fieldsObfToDeobf.put( obfName, deobfName ); + } + + public MethodIndex getMethodByObf( String obfName, String signature ) + { + return m_methodsByObf.get( getMethodKey( obfName, signature ) ); + } + + public MethodIndex getMethodByDeobf( String deobfName, String signature ) + { + return m_methodsByDeobf.get( getMethodKey( deobfName, signature ) ); + } + + private String getMethodKey( String name, String signature ) + { + return name + signature; + } + + public void setMethodNameAndSignature( String obfName, String obfSignature, String deobfName, String deobfSignature ) + { + if( deobfName == null ) + { + throw new IllegalArgumentException( "deobf name cannot be null!" ); + } + + MethodIndex methodIndex = m_methodsByObf.get( getMethodKey( obfName, obfSignature ) ); + if( methodIndex == null ) + { + methodIndex = createMethodIndex( obfName, obfSignature ); + } + + m_methodsByDeobf.remove( getMethodKey( methodIndex.getDeobfName(), methodIndex.getDeobfSignature() ) ); + methodIndex.setDeobfName( deobfName ); + methodIndex.setDeobfSignature( deobfSignature ); + m_methodsByDeobf.put( getMethodKey( deobfName, deobfSignature ), methodIndex ); + } + + public void updateDeobfMethodSignatures( Translator translator ) + { + for( MethodIndex methodIndex : m_methodsByObf.values() ) + { + methodIndex.setDeobfSignature( translator.translateSignature( methodIndex.getObfSignature() ) ); + } + } + + public void setArgumentName( String obfMethodName, String obfMethodSignature, int index, String obfName, String deobfName ) + { + if( deobfName == null ) + { + throw new IllegalArgumentException( "deobf name cannot be null!" ); + } + + MethodIndex methodIndex = m_methodsByObf.get( getMethodKey( obfMethodName, obfMethodSignature ) ); + if( methodIndex == null ) + { + methodIndex = createMethodIndex( obfMethodName, obfMethodSignature ); + } + methodIndex.setArgumentName( index, obfName, deobfName ); + } + + private MethodIndex createMethodIndex( String obfName, String obfSignature ) + { + MethodIndex methodIndex = new MethodIndex( obfName, obfSignature, obfName, obfSignature ); + String key = getMethodKey( obfName, obfSignature ); + m_methodsByObf.put( key, methodIndex ); + m_methodsByDeobf.put( key, methodIndex ); + return methodIndex; + } + + @Override + public String toString( ) + { + StringBuilder buf = new StringBuilder(); + buf.append( m_obfName ); + buf.append( " <-> " ); + buf.append( m_deobfName ); + buf.append( "\n" ); + buf.append( "Fields:\n" ); + for( Map.Entry entry : m_fieldsObfToDeobf.entrySet() ) + { + buf.append( "\t" ); + buf.append( entry.getKey() ); + buf.append( " <-> " ); + buf.append( entry.getValue() ); + buf.append( "\n" ); + } + buf.append( "Methods:\n" ); + for( MethodIndex methodIndex : m_methodsByObf.values() ) + { + buf.append( methodIndex.toString() ); + buf.append( "\n" ); + } + return buf.toString(); + } +} diff --git a/src/cuchaz/enigma/mapping/DeobfuscatedAncestries.java b/src/cuchaz/enigma/mapping/DeobfuscatedAncestries.java new file mode 100644 index 0000000..5320f11 --- /dev/null +++ b/src/cuchaz/enigma/mapping/DeobfuscatedAncestries.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * 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 + ClassIndex 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/EntryPair.java b/src/cuchaz/enigma/mapping/EntryPair.java new file mode 100644 index 0000000..e40e999 --- /dev/null +++ b/src/cuchaz/enigma/mapping/EntryPair.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * 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 cuchaz.enigma.Util; + +public class EntryPair +{ + public Entry obf; + public Entry deobf; + + public EntryPair( Entry obf, Entry deobf ) + { + this.obf = obf; + this.deobf = deobf; + } + + @Override + public int hashCode( ) + { + return Util.combineHashesOrdered( obf, deobf ); + } + + @Override + public boolean equals( Object other ) + { + if( other instanceof EntryPair ) + { + return equals( (EntryPair)other ); + } + return false; + } + + public boolean equals( EntryPair other ) + { + return obf.equals( other.obf ) && deobf.equals( other.deobf ); + } +} diff --git a/src/cuchaz/enigma/mapping/FieldEntry.java b/src/cuchaz/enigma/mapping/FieldEntry.java index 25b665a..b9f4239 100644 --- a/src/cuchaz/enigma/mapping/FieldEntry.java +++ b/src/cuchaz/enigma/mapping/FieldEntry.java @@ -36,6 +36,18 @@ public class FieldEntry implements Entry, Serializable m_name = name; } + public FieldEntry( FieldEntry other ) + { + m_classEntry = new ClassEntry( other.m_classEntry ); + m_name = other.m_name; + } + + public FieldEntry( FieldEntry other, String newClassName ) + { + m_classEntry = new ClassEntry( newClassName ); + m_name = other.m_name; + } + public ClassEntry getClassEntry( ) { return m_classEntry; @@ -47,6 +59,11 @@ public class FieldEntry implements Entry, Serializable return m_name; } + public String getClassName( ) + { + return m_classEntry.getName(); + } + @Override public int hashCode( ) { diff --git a/src/cuchaz/enigma/mapping/MethodEntry.java b/src/cuchaz/enigma/mapping/MethodEntry.java index 4afc099..9ea2d08 100644 --- a/src/cuchaz/enigma/mapping/MethodEntry.java +++ b/src/cuchaz/enigma/mapping/MethodEntry.java @@ -42,6 +42,20 @@ public class MethodEntry implements Entry, Serializable m_signature = signature; } + public MethodEntry( MethodEntry other ) + { + m_classEntry = new ClassEntry( other.m_classEntry ); + m_name = other.m_name; + m_signature = other.m_signature; + } + + public MethodEntry( MethodEntry other, String newClassName ) + { + m_classEntry = new ClassEntry( newClassName ); + m_name = other.m_name; + m_signature = other.m_signature; + } + public ClassEntry getClassEntry( ) { return m_classEntry; @@ -58,6 +72,11 @@ public class MethodEntry implements Entry, Serializable return m_signature; } + public String getClassName( ) + { + return m_classEntry.getName(); + } + @Override public int hashCode( ) { @@ -84,6 +103,6 @@ public class MethodEntry implements Entry, Serializable @Override public String toString( ) { - return m_classEntry.getName() + "." + m_name + ":" + m_signature; + return m_classEntry.getName() + "." + m_name + m_signature; } } diff --git a/src/cuchaz/enigma/mapping/MethodIndex.java b/src/cuchaz/enigma/mapping/MethodIndex.java new file mode 100644 index 0000000..f965355 --- /dev/null +++ b/src/cuchaz/enigma/mapping/MethodIndex.java @@ -0,0 +1,125 @@ +/******************************************************************************* + * 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.Serializable; +import java.util.Map; +import java.util.TreeMap; + +public class MethodIndex implements Serializable +{ + private static final long serialVersionUID = -4409570216084263978L; + + private String m_obfName; + private String m_deobfName; + private String m_obfSignature; + private String m_deobfSignature; + private Map m_arguments; + + public MethodIndex( String obfName, String obfSignature, String deobfName, String deobfSignature ) + { + m_obfName = obfName; + m_deobfName = deobfName; + m_obfSignature = obfSignature; + m_deobfSignature = deobfSignature; + m_arguments = new TreeMap(); + } + + public String getObfName( ) + { + return m_obfName; + } + + public String getDeobfName( ) + { + return m_deobfName; + } + public void setDeobfName( String val ) + { + m_deobfName = val; + } + + public String getObfSignature( ) + { + return m_obfSignature; + } + + public String getDeobfSignature( ) + { + return m_deobfSignature; + } + public void setDeobfSignature( String val ) + { + m_deobfSignature = val; + } + + public String getObfArgumentName( int index ) + { + ArgumentIndex argumentIndex = m_arguments.get( index ); + if( argumentIndex != null ) + { + return argumentIndex.getObfName(); + } + + return null; + } + + public String getDeobfArgumentName( int index ) + { + ArgumentIndex argumentIndex = m_arguments.get( index ); + if( argumentIndex != null ) + { + return argumentIndex.getDeobfName(); + } + + return null; + } + + public void setArgumentName( int index, String obfName, String deobfName ) + { + ArgumentIndex argumentIndex = m_arguments.get( index ); + if( argumentIndex == null ) + { + argumentIndex = new ArgumentIndex( obfName, deobfName ); + m_arguments.put( index, argumentIndex ); + } + else + { + argumentIndex.setDeobfName( deobfName ); + } + } + + @Override + public String toString( ) + { + StringBuilder buf = new StringBuilder(); + buf.append( "\t" ); + buf.append( m_obfName ); + buf.append( " <-> " ); + buf.append( m_deobfName ); + buf.append( "\n" ); + buf.append( "\t" ); + buf.append( m_obfSignature ); + buf.append( " <-> " ); + buf.append( m_deobfSignature ); + buf.append( "\n" ); + buf.append( "\tArguments:\n" ); + for( ArgumentIndex argumentIndex : m_arguments.values() ) + { + buf.append( "\t\t" ); + buf.append( argumentIndex.getObfName() ); + buf.append( " <-> " ); + buf.append( argumentIndex.getDeobfName() ); + buf.append( "\n" ); + } + return buf.toString(); + } +} diff --git a/src/cuchaz/enigma/mapping/SignatureUpdater.java b/src/cuchaz/enigma/mapping/SignatureUpdater.java new file mode 100644 index 0000000..4c0dbac --- /dev/null +++ b/src/cuchaz/enigma/mapping/SignatureUpdater.java @@ -0,0 +1,87 @@ +/******************************************************************************* + * 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.IOException; +import java.io.StringReader; + +public class SignatureUpdater +{ + public interface ClassNameUpdater + { + String update( String className ); + } + + public static String update( String signature, ClassNameUpdater updater ) + { + try + { + StringBuilder buf = new StringBuilder(); + + // read the signature character-by-character + StringReader reader = new StringReader( signature ); + int i = -1; + while( ( i = reader.read() ) != -1 ) + { + char c = (char)i; + + // does this character start a class name? + if( c == 'L' ) + { + // update the class name and add it to the buffer + buf.append( 'L' ); + String className = readClass( reader ); + if( className == null ) + { + throw new IllegalArgumentException( "Malformed signature: " + signature ); + } + buf.append( updater.update( className ) ); + buf.append( ';' ); + } + else + { + // copy the character into the buffer + buf.append( c ); + } + } + + return buf.toString(); + } + catch( IOException ex ) + { + // I'm pretty sure a StringReader will never throw one of these + throw new Error( ex ); + } + } + + private static String readClass( StringReader reader ) + throws IOException + { + // read all the characters in the buffer until we hit a ';' + StringBuilder buf = new StringBuilder(); + int i = -1; + while( ( i = reader.read() ) != -1 ) + { + char c = (char)i; + + if( c == ';' ) + { + return buf.toString(); + } + else + { + buf.append( c ); + } + } + + return null; + } +} diff --git a/src/cuchaz/enigma/mapping/TranslationDirection.java b/src/cuchaz/enigma/mapping/TranslationDirection.java new file mode 100644 index 0000000..79ae0d3 --- /dev/null +++ b/src/cuchaz/enigma/mapping/TranslationDirection.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * 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 enum TranslationDirection +{ + Deobfuscating + { + @Override + public T choose( T deobfChoice, T obfChoice ) + { + return deobfChoice; + } + }, + Obfuscating + { + @Override + public T choose( T deobfChoice, T obfChoice ) + { + return obfChoice; + } + }; + + public abstract T choose( T deobfChoice, T obfChoice ); +} diff --git a/src/cuchaz/enigma/mapping/TranslationMappings.java b/src/cuchaz/enigma/mapping/TranslationMappings.java new file mode 100644 index 0000000..d6cd449 --- /dev/null +++ b/src/cuchaz/enigma/mapping/TranslationMappings.java @@ -0,0 +1,187 @@ +/******************************************************************************* + * 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.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.util.Map; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +import com.beust.jcommander.internal.Maps; + +import cuchaz.enigma.Util; + +public class TranslationMappings implements Serializable +{ + private static final long serialVersionUID = 4649790259460259026L; + + private Map m_classesByObf; + private Map m_classesByDeobf; + private Ancestries m_ancestries; + + public TranslationMappings( Ancestries ancestries ) + { + m_classesByObf = Maps.newHashMap(); + m_classesByDeobf = Maps.newHashMap(); + m_ancestries = ancestries; + } + + public static TranslationMappings newFromResource( String resource ) + throws IOException + { + InputStream in = null; + try + { + in = TranslationMappings.class.getResourceAsStream( resource ); + return newFromStream( in ); + } + finally + { + Util.closeQuietly( in ); + } + } + + public Translator getTranslator( TranslationDirection direction ) + { + return new Translator( + direction, + direction.choose( m_classesByObf, m_classesByDeobf ), + direction.choose( m_ancestries, new DeobfuscatedAncestries( m_ancestries, m_classesByObf, m_classesByDeobf ) ) + ); + } + + public void setClassName( ClassEntry obf, String deobfName ) + { + ClassIndex classIndex = m_classesByObf.get( obf.getName() ); + if( classIndex == null ) + { + classIndex = createClassIndex( obf ); + } + + m_classesByDeobf.remove( classIndex.getDeobfName() ); + classIndex.setDeobfName( deobfName ); + m_classesByDeobf.put( deobfName, classIndex ); + + updateDeobfMethodSignatures(); + + // TEMP + String translatedName = getTranslator( TranslationDirection.Deobfuscating ).translate( obf ); + assert( translatedName != null && translatedName.equals( deobfName ) ); + } + + public void setFieldName( FieldEntry obf, String deobfName ) + { + ClassIndex classIndex = m_classesByObf.get( obf.getClassName() ); + if( classIndex == null ) + { + classIndex = createClassIndex( obf.getClassEntry() ); + } + + classIndex.setFieldName( obf.getName(), deobfName ); + + // TEMP + System.out.println( classIndex ); + String translatedName = getTranslator( TranslationDirection.Deobfuscating ).translate( obf ); + assert( translatedName != null && translatedName.equals( deobfName ) ); + } + + public void setMethodName( MethodEntry obf, String deobfName ) + { + ClassIndex classIndex = m_classesByObf.get( obf.getClassName() ); + if( classIndex == null ) + { + classIndex = createClassIndex( obf.getClassEntry() ); + } + + String deobfSignature = getTranslator( TranslationDirection.Deobfuscating ).translateSignature( obf.getSignature() ); + classIndex.setMethodNameAndSignature( obf.getName(), obf.getSignature(), deobfName, deobfSignature ); + + // TODO: update ancestor/descendant methods in other classes in the inheritance hierarchy too + + // TEMP + System.out.println( classIndex ); + String translatedName = getTranslator( TranslationDirection.Deobfuscating ).translate( obf ); + assert( translatedName != null && translatedName.equals( deobfName ) ); + } + + public void setArgumentName( ArgumentEntry obf, String deobfName ) + { + ClassIndex classIndex = m_classesByObf.get( obf.getClassName() ); + if( classIndex == null ) + { + classIndex = createClassIndex( obf.getClassEntry() ); + } + + classIndex.setArgumentName( obf.getMethodName(), obf.getMethodSignature(), obf.getIndex(), obf.getName(), deobfName ); + + // TEMP + System.out.println( classIndex ); + String translatedName = getTranslator( TranslationDirection.Deobfuscating ).translate( obf ); + assert( translatedName != null && translatedName.equals( deobfName ) ); + } + + public void write( OutputStream out ) + throws IOException + { + // TEMP: just use the object output for now. We can find a more efficient storage format later + GZIPOutputStream gzipout = new GZIPOutputStream( out ); + ObjectOutputStream oout = new ObjectOutputStream( gzipout ); + oout.writeObject( this ); + gzipout.finish(); + } + + public static TranslationMappings newFromStream( InputStream in ) + throws IOException + { + try + { + return (TranslationMappings)new ObjectInputStream( new GZIPInputStream( in ) ).readObject(); + } + catch( ClassNotFoundException ex ) + { + throw new Error( ex ); + } + } + + private ClassIndex createClassIndex( ClassEntry obf ) + { + ClassIndex classIndex = new ClassIndex( obf.getName(), obf.getName() ); + m_classesByObf.put( classIndex.getObfName(), classIndex ); + m_classesByDeobf.put( classIndex.getDeobfName(), classIndex ); + return classIndex; + } + + private void updateDeobfMethodSignatures( ) + { + Translator translator = getTranslator( TranslationDirection.Deobfuscating ); + for( ClassIndex classIndex : m_classesByObf.values() ) + { + classIndex.updateDeobfMethodSignatures( translator ); + } + } + + @Override + public String toString( ) + { + StringBuilder buf = new StringBuilder(); + for( ClassIndex classIndex : m_classesByObf.values() ) + { + buf.append( classIndex.toString() ); + buf.append( "\n" ); + } + return buf.toString(); + } +} diff --git a/src/cuchaz/enigma/mapping/Translator.java b/src/cuchaz/enigma/mapping/Translator.java new file mode 100644 index 0000000..bae0dce --- /dev/null +++ b/src/cuchaz/enigma/mapping/Translator.java @@ -0,0 +1,201 @@ +/******************************************************************************* + * 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.ArrayList; +import java.util.List; +import java.util.Map; + +import cuchaz.enigma.mapping.SignatureUpdater.ClassNameUpdater; + +public class Translator +{ + private TranslationDirection m_direction; + private Map m_classes; + private Ancestries m_ancestries; + + protected Translator( TranslationDirection direction, Map classes, Ancestries ancestries ) + { + m_direction = direction; + m_classes = classes; + m_ancestries = ancestries; + } + + public String translate( ClassEntry in ) + { + return translateClass( in.getName() ); + } + + public String translateClass( String in ) + { + ClassIndex classIndex = m_classes.get( in ); + if( classIndex != null ) + { + return m_direction.choose( + classIndex.getDeobfName(), + classIndex.getObfName() + ); + } + + return null; + } + + public ClassEntry translateEntry( ClassEntry in ) + { + String name = translate( in ); + if( name == null ) + { + return in; + } + return new ClassEntry( name ); + } + + public String translate( FieldEntry in ) + { + for( String className : getSelfAndAncestors( in.getClassName() ) ) + { + // look for the class + ClassIndex classIndex = m_classes.get( className ); + if( classIndex != null ) + { + // look for the field + String deobfName = m_direction.choose( + classIndex.getDeobfFieldName( in.getName() ), + classIndex.getObfFieldName( in.getName() ) + ); + if( deobfName != null ) + { + return deobfName; + } + } + } + + return null; + } + + public FieldEntry translateEntry( FieldEntry in ) + { + String name = translate( in ); + if( name == null ) + { + name = in.getName(); + } + return new FieldEntry( + translateEntry( in.getClassEntry() ), + name + ); + } + + public String translate( MethodEntry in ) + { + for( String className : getSelfAndAncestors( in.getClassName() ) ) + { + // look for the class + ClassIndex classIndex = m_classes.get( className ); + if( classIndex != null ) + { + // look for the method + MethodIndex methodIndex = m_direction.choose( + classIndex.getMethodByObf( in.getName(), in.getSignature() ), + classIndex.getMethodByDeobf( in.getName(), in.getSignature() ) + ); + if( methodIndex != null ) + { + return m_direction.choose( + methodIndex.getDeobfName(), + methodIndex.getObfName() + ); + } + } + } + + return null; + } + + public MethodEntry translateEntry( MethodEntry in ) + { + String name = translate( in ); + if( name == null ) + { + name = in.getName(); + } + return new MethodEntry( + translateEntry( in.getClassEntry() ), + name, + translateSignature( in.getSignature() ) + ); + } + + public String translate( ArgumentEntry in ) + { + for( String className : getSelfAndAncestors( in.getClassName() ) ) + { + // look for the class + ClassIndex classIndex = m_classes.get( className ); + if( classIndex != null ) + { + // look for the method + MethodIndex methodIndex = m_direction.choose( + classIndex.getMethodByObf( in.getMethodName(), in.getMethodSignature() ), + classIndex.getMethodByDeobf( in.getMethodName(), in.getMethodSignature() ) + ); + if( methodIndex != null ) + { + return m_direction.choose( + methodIndex.getDeobfArgumentName( in.getIndex() ), + methodIndex.getObfArgumentName( in.getIndex() ) + ); + } + } + } + + return null; + } + + public ArgumentEntry translateEntry( ArgumentEntry in ) + { + String name = translate( in ); + if( name == null ) + { + name = in.getName(); + } + return new ArgumentEntry( + translateEntry( in.getMethodEntry() ), + in.getIndex(), + name + ); + } + + public String translateSignature( String signature ) + { + return SignatureUpdater.update( signature, new ClassNameUpdater( ) + { + @Override + public String update( String className ) + { + String translatedName = translateClass( className ); + if( translatedName != null ) + { + return translatedName; + } + return className; + } + } ); + } + + private List getSelfAndAncestors( String className ) + { + List ancestry = new ArrayList(); + ancestry.add( className ); + ancestry.addAll( m_ancestries.getAncestry( className ) ); + return ancestry; + } +} -- cgit v1.2.3