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/Constants.java | 18 + src/cuchaz/enigma/Controller.java | 67 +++- src/cuchaz/enigma/Deobfuscator.java | 115 ++++++- src/cuchaz/enigma/TranslatingTypeLoader.java | 93 ++++++ .../enigma/bytecode/BytecodeIndexIterator.java | 180 ++++++++++ src/cuchaz/enigma/bytecode/BytecodeTools.java | 269 +++++++++++++++ src/cuchaz/enigma/bytecode/ClassTranslator.java | 171 ++++++++++ src/cuchaz/enigma/bytecode/ConstPoolEditor.java | 316 ++++++++++++++++++ src/cuchaz/enigma/bytecode/InfoType.java | 364 +++++++++++++++++++++ .../bytecode/accessors/ClassInfoAccessor.java | 69 ++++ .../bytecode/accessors/ConstInfoAccessor.java | 199 +++++++++++ .../accessors/InvokeDynamicInfoAccessor.java | 96 ++++++ .../bytecode/accessors/MemberRefInfoAccessor.java | 96 ++++++ .../accessors/MethodHandleInfoAccessor.java | 96 ++++++ .../bytecode/accessors/MethodTypeInfoAccessor.java | 69 ++++ .../accessors/NameAndTypeInfoAccessor.java | 96 ++++++ .../bytecode/accessors/StringInfoAccessor.java | 69 ++++ .../bytecode/accessors/Utf8InfoAccessor.java | 33 ++ src/cuchaz/enigma/gui/Gui.java | 53 +-- src/cuchaz/enigma/gui/RenameListener.java | 2 +- 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 ++++++++++++ 34 files changed, 3566 insertions(+), 46 deletions(-) create mode 100644 src/cuchaz/enigma/Constants.java create mode 100644 src/cuchaz/enigma/TranslatingTypeLoader.java create mode 100644 src/cuchaz/enigma/bytecode/BytecodeIndexIterator.java create mode 100644 src/cuchaz/enigma/bytecode/BytecodeTools.java create mode 100644 src/cuchaz/enigma/bytecode/ClassTranslator.java create mode 100644 src/cuchaz/enigma/bytecode/ConstPoolEditor.java create mode 100644 src/cuchaz/enigma/bytecode/InfoType.java create mode 100644 src/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java create mode 100644 src/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java create mode 100644 src/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java create mode 100644 src/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java create mode 100644 src/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java create mode 100644 src/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java create mode 100644 src/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java create mode 100644 src/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java create mode 100644 src/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java 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') diff --git a/src/cuchaz/enigma/Constants.java b/src/cuchaz/enigma/Constants.java new file mode 100644 index 00000000..09787145 --- /dev/null +++ b/src/cuchaz/enigma/Constants.java @@ -0,0 +1,18 @@ +/******************************************************************************* + * 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; + + +public class Constants +{ + public static final int MiB = 1024*1024; // 1 mebibyte + public static final int KiB = 1024; // 1 kebibyte +} diff --git a/src/cuchaz/enigma/Controller.java b/src/cuchaz/enigma/Controller.java index debc5e38..3af139e8 100644 --- a/src/cuchaz/enigma/Controller.java +++ b/src/cuchaz/enigma/Controller.java @@ -18,19 +18,23 @@ import cuchaz.enigma.analysis.SourceIndex; import cuchaz.enigma.gui.ClassSelectionListener; import cuchaz.enigma.gui.Gui; import cuchaz.enigma.gui.RenameListener; +import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.Entry; +import cuchaz.enigma.mapping.EntryPair; public class Controller implements ClassSelectionListener, CaretListener, RenameListener { private Deobfuscator m_deobfuscator; private Gui m_gui; private SourceIndex m_index; + private ClassFile m_currentFile; public Controller( Deobfuscator deobfuscator, Gui gui ) { m_deobfuscator = deobfuscator; m_gui = gui; m_index = null; + m_currentFile = null; // update GUI gui.setTitle( deobfuscator.getJarName() ); @@ -43,7 +47,51 @@ public class Controller implements ClassSelectionListener, CaretListener, Rename } @Override - public void classSelected( final ClassFile classFile ) + public void classSelected( ClassFile classFile ) + { + m_currentFile = classFile; + deobfuscate( m_currentFile ); + } + + @Override + public void caretUpdate( CaretEvent event ) + { + if( m_index != null ) + { + int pos = event.getDot(); + Entry deobfEntry = m_index.getEntry( pos ); + if( deobfEntry != null ) + { + m_gui.showEntryPair( new EntryPair( m_deobfuscator.obfuscate( deobfEntry ), deobfEntry ) ); + } + else + { + m_gui.clearEntryPair(); + } + } + } + + @Override + public void rename( Entry obfsEntry, String newName ) + { + m_deobfuscator.rename( obfsEntry, newName ); + + // did we rename the current file? + if( obfsEntry instanceof ClassEntry ) + { + ClassEntry classEntry = (ClassEntry)obfsEntry; + + // update the current file + if( classEntry.getName().equals( m_currentFile.getName() ) ) + { + m_currentFile = new ClassFile( newName ); + } + } + + deobfuscate( m_currentFile ); + } + + private void deobfuscate( final ClassFile classFile ) { m_gui.setSource( "(deobfuscating...)" ); @@ -63,21 +111,4 @@ public class Controller implements ClassSelectionListener, CaretListener, Rename } }.start(); } - - @Override - public void caretUpdate( CaretEvent event ) - { - if( m_index != null ) - { - int pos = event.getDot(); - m_gui.showEntry( m_index.getEntry( pos ) ); - } - } - - @Override - public void rename( Entry entry, String newName ) - { - // TEMP - System.out.println( "Rename " + entry + " to " + newName ); - } } diff --git a/src/cuchaz/enigma/Deobfuscator.java b/src/cuchaz/enigma/Deobfuscator.java index 97c57505..b1abd9e0 100644 --- a/src/cuchaz/enigma/Deobfuscator.java +++ b/src/cuchaz/enigma/Deobfuscator.java @@ -11,7 +11,9 @@ package cuchaz.enigma; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; import java.io.StringWriter; import java.util.ArrayList; import java.util.Collections; @@ -21,16 +23,27 @@ import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarFile; -import com.strobel.assembler.metadata.JarTypeLoader; import com.strobel.decompiler.Decompiler; import com.strobel.decompiler.DecompilerSettings; import com.strobel.decompiler.PlainTextOutput; +import cuchaz.enigma.mapping.Ancestries; +import cuchaz.enigma.mapping.ArgumentEntry; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.Entry; +import cuchaz.enigma.mapping.FieldEntry; +import cuchaz.enigma.mapping.MethodEntry; +import cuchaz.enigma.mapping.TranslationDirection; +import cuchaz.enigma.mapping.TranslationMappings; +import cuchaz.enigma.mapping.Translator; + public class Deobfuscator { private File m_file; private JarFile m_jar; private DecompilerSettings m_settings; + private Ancestries m_ancestries; + private TranslationMappings m_mappings; private static Comparator m_obfuscatedClassSorter; @@ -56,8 +69,30 @@ public class Deobfuscator { m_file = file; m_jar = new JarFile( m_file ); + + // build the ancestries + InputStream jarIn = null; + try + { + m_ancestries = new Ancestries(); + jarIn = new FileInputStream( m_file ); + m_ancestries.readFromJar( jarIn ); + } + finally + { + Util.closeQuietly( jarIn ); + } + + // init mappings + m_mappings = new TranslationMappings( m_ancestries ); + + // config the decompiler m_settings = DecompilerSettings.javaDefaults(); - m_settings.setTypeLoader( new JarTypeLoader( m_jar ) ); + m_settings.setTypeLoader( new TranslatingTypeLoader( + m_jar, + m_mappings.getTranslator( TranslationDirection.Deobfuscating ), + m_mappings.getTranslator( TranslationDirection.Obfuscating ) + ) ); m_settings.setForceExplicitImports( true ); m_settings.setShowSyntheticMembers( true ); } @@ -111,4 +146,80 @@ public class Deobfuscator Decompiler.decompile( classFile.getName(), new PlainTextOutput( buf ), m_settings ); return buf.toString(); } + + // NOTE: these methods are a bit messy... oh well + + public void rename( Entry entry, String newName ) + { + if( entry instanceof ClassEntry ) + { + m_mappings.setClassName( (ClassEntry)entry, newName ); + } + else if( entry instanceof FieldEntry ) + { + m_mappings.setFieldName( (FieldEntry)entry, newName ); + } + else if( entry instanceof MethodEntry ) + { + m_mappings.setMethodName( (MethodEntry)entry, newName ); + } + else if( entry instanceof ArgumentEntry ) + { + m_mappings.setArgumentName( (ArgumentEntry)entry, newName ); + } + else + { + throw new Error( "Unknown entry type: " + entry.getClass().getName() ); + } + } + + public Entry obfuscate( Entry in ) + { + Translator translator = m_mappings.getTranslator( TranslationDirection.Obfuscating ); + if( in instanceof ClassEntry ) + { + return translator.translateEntry( (ClassEntry)in ); + } + else if( in instanceof FieldEntry ) + { + return translator.translateEntry( (FieldEntry)in ); + } + else if( in instanceof MethodEntry ) + { + return translator.translateEntry( (MethodEntry)in ); + } + else if( in instanceof ArgumentEntry ) + { + return translator.translateEntry( (ArgumentEntry)in ); + } + else + { + throw new Error( "Unknown entry type: " + in.getClass().getName() ); + } + } + + public Entry deobfuscate( Entry in ) + { + Translator translator = m_mappings.getTranslator( TranslationDirection.Deobfuscating ); + if( in instanceof ClassEntry ) + { + return translator.translateEntry( (ClassEntry)in ); + } + else if( in instanceof FieldEntry ) + { + return translator.translateEntry( (FieldEntry)in ); + } + else if( in instanceof MethodEntry ) + { + return translator.translateEntry( (MethodEntry)in ); + } + else if( in instanceof ArgumentEntry ) + { + return translator.translateEntry( (ArgumentEntry)in ); + } + else + { + throw new Error( "Unknown entry type: " + in.getClass().getName() ); + } + } } diff --git a/src/cuchaz/enigma/TranslatingTypeLoader.java b/src/cuchaz/enigma/TranslatingTypeLoader.java new file mode 100644 index 00000000..872f4861 --- /dev/null +++ b/src/cuchaz/enigma/TranslatingTypeLoader.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * 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; + +import java.io.IOException; +import java.io.InputStream; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import javassist.ByteArrayClassPath; +import javassist.ClassPool; +import javassist.CtClass; + +import com.strobel.assembler.metadata.Buffer; +import com.strobel.assembler.metadata.ITypeLoader; + +import cuchaz.enigma.bytecode.ClassTranslator; +import cuchaz.enigma.mapping.Translator; + +public class TranslatingTypeLoader implements ITypeLoader +{ + private JarFile m_jar; + private ClassTranslator m_classTranslator; + private Translator m_obfuscatingTranslator; + + public TranslatingTypeLoader( JarFile jar, Translator deobfuscatingTranslator, Translator obfuscatingTranslator ) + { + m_jar = jar; + m_classTranslator = new ClassTranslator( deobfuscatingTranslator ); + m_obfuscatingTranslator = obfuscatingTranslator; + } + + @Override + public boolean tryLoadType( String name, Buffer out ) + { + // is this a deobufscated class name? + String obfName = m_obfuscatingTranslator.translateClass( name ); + if( obfName != null ) + { + // point to the obfuscated class + name = obfName; + } + + JarEntry entry = m_jar.getJarEntry( name + ".class" ); + if( entry == null ) + { + return false; + } + + try + { + // read the class file into a buffer + byte[] buf = new byte[(int)entry.getSize()]; + InputStream in = m_jar.getInputStream( entry ); + int bytesRead = in.read( buf ); + assert( bytesRead == buf.length ); + + // translate the class + ClassPool classPool = new ClassPool(); + classPool.insertClassPath( new ByteArrayClassPath( name, buf ) ); + try + { + CtClass c = classPool.get( name ); + m_classTranslator.translate( c ); + buf = c.toBytecode(); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + + // pass it along to the decompiler + out.reset( buf.length ); + System.arraycopy( buf, 0, out.array(), out.position(), buf.length ); + out.position( 0 ); + + return true; + } + catch( IOException ex ) + { + throw new Error( ex ); + } + } + +} diff --git a/src/cuchaz/enigma/bytecode/BytecodeIndexIterator.java b/src/cuchaz/enigma/bytecode/BytecodeIndexIterator.java new file mode 100644 index 00000000..aadbeb25 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/BytecodeIndexIterator.java @@ -0,0 +1,180 @@ +/******************************************************************************* + * 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.bytecode; + +import java.util.Iterator; + +import javassist.bytecode.BadBytecode; +import javassist.bytecode.Bytecode; +import javassist.bytecode.CodeAttribute; +import javassist.bytecode.CodeIterator; +import javassist.bytecode.Opcode; + +public class BytecodeIndexIterator implements Iterator +{ + public static class Index + { + private CodeIterator m_iter; + private int m_pos; + private boolean m_isWide; + + protected Index( CodeIterator iter, int pos, boolean isWide ) + { + m_iter = iter; + m_pos = pos; + m_isWide = isWide; + } + + public int getIndex( ) + { + if( m_isWide ) + { + return m_iter.s16bitAt( m_pos ); + } + else + { + return m_iter.byteAt( m_pos ); + } + } + + public void setIndex( int val ) + throws BadBytecode + { + if( m_isWide ) + { + m_iter.write16bit( val, m_pos ); + } + else + { + if( val < 256 ) + { + // we can write the byte + m_iter.writeByte( val, m_pos ); + } + else + { + // we need to upgrade this instruction to LDC_W + assert( m_iter.byteAt( m_pos - 1 ) == Opcode.LDC ); + m_iter.insertGap( m_pos - 1, 1 ); + m_iter.writeByte( Opcode.LDC_W, m_pos - 1 ); + m_iter.write16bit( val, m_pos ); + m_isWide = true; + + // move the iterator to the next opcode + m_iter.move( m_pos + 2 ); + } + } + + // sanity check + assert( val == getIndex() ); + } + + public boolean isValid( Bytecode bytecode ) + { + return getIndex() >= 0 && getIndex() < bytecode.getConstPool().getSize(); + } + } + + private Bytecode m_bytecode; + private CodeAttribute m_attribute; + private CodeIterator m_iter; + private Index m_next; + + public BytecodeIndexIterator( Bytecode bytecode ) + throws BadBytecode + { + m_bytecode = bytecode; + m_attribute = bytecode.toCodeAttribute(); + m_iter = m_attribute.iterator(); + + m_next = getNext(); + } + + @Override + public boolean hasNext( ) + { + return m_next != null; + } + + @Override + public Index next( ) + { + Index out = m_next; + try + { + m_next = getNext(); + } + catch( BadBytecode ex ) + { + throw new Error( ex ); + } + return out; + } + + @Override + public void remove( ) + { + throw new UnsupportedOperationException(); + } + + private Index getNext( ) + throws BadBytecode + { + while( m_iter.hasNext() ) + { + int pos = m_iter.next(); + int opcode = m_iter.byteAt( pos ); + switch( opcode ) + { + // for only these opcodes, the next two bytes are a const pool reference + case Opcode.ANEWARRAY: + case Opcode.CHECKCAST: + case Opcode.INSTANCEOF: + case Opcode.INVOKEDYNAMIC: + case Opcode.INVOKEINTERFACE: + case Opcode.INVOKESPECIAL: + case Opcode.INVOKESTATIC: + case Opcode.INVOKEVIRTUAL: + case Opcode.LDC_W: + case Opcode.LDC2_W: + case Opcode.MULTIANEWARRAY: + case Opcode.NEW: + case Opcode.PUTFIELD: + case Opcode.PUTSTATIC: + case Opcode.GETFIELD: + case Opcode.GETSTATIC: + return new Index( m_iter, pos + 1, true ); + + case Opcode.LDC: + return new Index( m_iter, pos + 1, false ); + } + } + + return null; + } + + public Iterable indices( ) + { + return new Iterable( ) + { + @Override + public Iterator iterator( ) + { + return BytecodeIndexIterator.this; + } + }; + } + + public void saveChangesToBytecode( ) + { + BytecodeTools.setBytecode( m_bytecode, m_attribute.getCode() ); + } +} diff --git a/src/cuchaz/enigma/bytecode/BytecodeTools.java b/src/cuchaz/enigma/bytecode/BytecodeTools.java new file mode 100644 index 00000000..664350ea --- /dev/null +++ b/src/cuchaz/enigma/bytecode/BytecodeTools.java @@ -0,0 +1,269 @@ +/******************************************************************************* + * 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.bytecode; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.Map; +import java.util.Set; + +import javassist.CtBehavior; +import javassist.bytecode.BadBytecode; +import javassist.bytecode.Bytecode; +import javassist.bytecode.CodeAttribute; +import javassist.bytecode.ConstPool; +import javassist.bytecode.ExceptionTable; + +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +import cuchaz.enigma.Util; +import cuchaz.enigma.bytecode.BytecodeIndexIterator.Index; +import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor; + +public class BytecodeTools +{ + public static byte[] writeBytecode( Bytecode bytecode ) + throws IOException + { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream( buf ); + try + { + // write the constant pool + new ConstPoolEditor( bytecode.getConstPool() ).writePool( out ); + + // write metadata + out.writeShort( bytecode.getMaxStack() ); + out.writeShort( bytecode.getMaxLocals() ); + out.writeShort( bytecode.getStackDepth() ); + + // write the code + out.writeShort( bytecode.getSize() ); + out.write( bytecode.get() ); + + // write the exception table + int numEntries = bytecode.getExceptionTable().size(); + out.writeShort( numEntries ); + for( int i=0; i attribute.getMaxLocals() ) + { + attribute.setMaxLocals( bytecode.getMaxLocals() ); + } + if( bytecode.getMaxStack() > attribute.getMaxStack() ) + { + attribute.setMaxStack( bytecode.getMaxStack() ); + } + + return bytecode; + } + + public static Bytecode copyBytecodeToConstPool( ConstPool dest, Bytecode bytecode ) + throws BadBytecode + { + // get the entries this bytecode needs from the const pool + Set indices = Sets.newTreeSet(); + ConstPoolEditor editor = new ConstPoolEditor( bytecode.getConstPool() ); + BytecodeIndexIterator iterator = new BytecodeIndexIterator( bytecode ); + for( Index index : iterator.indices() ) + { + assert( index.isValid( bytecode ) ); + InfoType.gatherIndexTree( indices, editor, index.getIndex() ); + } + + Map indexMap = Maps.newTreeMap(); + + ConstPool src = bytecode.getConstPool(); + ConstPoolEditor editorSrc = new ConstPoolEditor( src ); + ConstPoolEditor editorDest = new ConstPoolEditor( dest ); + + // copy entries over in order of level so the index mapping is easier + for( InfoType type : InfoType.getSortedByLevel() ) + { + for( int index : indices ) + { + ConstInfoAccessor entry = editorSrc.getItem( index ); + + // skip entries that aren't this type + if( entry.getType() != type ) + { + continue; + } + + // make sure the source entry is valid before we copy it + assert( type.subIndicesAreValid( entry, editorSrc ) ); + assert( type.selfIndexIsValid( entry, editorSrc ) ); + + // make a copy of the entry so we can modify it safely + ConstInfoAccessor entryCopy = editorSrc.getItem( index ).copy(); + assert( type.subIndicesAreValid( entryCopy, editorSrc ) ); + assert( type.selfIndexIsValid( entryCopy, editorSrc ) ); + + // remap the indices + type.remapIndices( indexMap, entryCopy ); + assert( type.subIndicesAreValid( entryCopy, editorDest ) ); + + // put the copy in the destination pool + int newIndex = editorDest.addItem( entryCopy.getItem() ); + entryCopy.setIndex( newIndex ); + assert( type.selfIndexIsValid( entryCopy, editorDest ) ) : type + ", self: " + entryCopy + " dest: " + editorDest.getItem( entryCopy.getIndex() ); + + // make sure the source entry is unchanged + assert( type.subIndicesAreValid( entry, editorSrc ) ); + assert( type.selfIndexIsValid( entry, editorSrc ) ); + + // add the index mapping so we can update the bytecode later + if( indexMap.containsKey( index ) ) + { + throw new Error( "Entry at index " + index + " already copied!" ); + } + indexMap.put( index, newIndex ); + } + } + + // make a new bytecode + Bytecode newBytecode = new Bytecode( dest, bytecode.getMaxStack(), bytecode.getMaxLocals() ); + bytecode.setStackDepth( bytecode.getStackDepth() ); + setBytecode( newBytecode, bytecode.get() ); + setExceptionTable( newBytecode, bytecode.getExceptionTable() ); + + // apply the mappings to the bytecode + BytecodeIndexIterator iter = new BytecodeIndexIterator( newBytecode ); + for( Index index : iter.indices() ) + { + int oldIndex = index.getIndex(); + Integer newIndex = indexMap.get( oldIndex ); + if( newIndex != null ) + { + // make sure this mapping makes sense + InfoType typeSrc = editorSrc.getItem( oldIndex ).getType(); + InfoType typeDest = editorDest.getItem( newIndex ).getType(); + assert( typeSrc == typeDest ); + + // apply the mapping + index.setIndex( newIndex ); + } + } + iter.saveChangesToBytecode(); + + // make sure all the indices are valid + iter = new BytecodeIndexIterator( newBytecode ); + for( Index index : iter.indices() ) + { + assert( index.isValid( newBytecode ) ); + } + + return newBytecode; + } + + public static void setBytecode( Bytecode dest, byte[] src ) + { + if( src.length > dest.getSize() ) + { + dest.addGap( src.length - dest.getSize() ); + } + assert( dest.getSize() == src.length ); + for( int i=0; i=0; i-- ) + { + dest.getExceptionTable().remove( i ); + } + + // copy the exception table + for( int i=0; i classNames = getAllClassNames( c ); + ClassMap map = new ClassMap(); + for( String className : classNames ) + { + String translatedName = m_translator.translateClass( className ); + if( translatedName != null ) + { + map.put( className, translatedName ); + } + } + if( !map.isEmpty() ) + { + c.replaceClassName( map ); + } + } + + private Set getAllClassNames( CtClass c ) + { + final Set names = new HashSet(); + ClassMap map = new ClassMap( ) + { + @Override + public Object get( Object obj ) + { + if( obj instanceof String ) + { + names.add( (String)obj ); + } + return null; + } + private static final long serialVersionUID = -202160293602070641L; + }; + c.replaceClassName( map ); + return names; + } +} diff --git a/src/cuchaz/enigma/bytecode/ConstPoolEditor.java b/src/cuchaz/enigma/bytecode/ConstPoolEditor.java new file mode 100644 index 00000000..aa6149c9 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/ConstPoolEditor.java @@ -0,0 +1,316 @@ +/******************************************************************************* + * 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.bytecode; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.HashMap; + +import javassist.bytecode.ConstPool; +import javassist.bytecode.Descriptor; +import cuchaz.enigma.bytecode.accessors.ClassInfoAccessor; +import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor; +import cuchaz.enigma.bytecode.accessors.MemberRefInfoAccessor; + +public class ConstPoolEditor +{ + private static Method m_getItem; + private static Method m_addItem; + private static Method m_addItem0; + private static Field m_items; + private static Field m_cache; + private static Field m_numItems; + private static Field m_objects; + private static Field m_elements; + private static Method m_methodWritePool; + private static Constructor m_constructorPool; + + static + { + try + { + m_getItem = ConstPool.class.getDeclaredMethod( "getItem", int.class ); + m_getItem.setAccessible( true ); + + m_addItem = ConstPool.class.getDeclaredMethod( "addItem", Class.forName( "javassist.bytecode.ConstInfo" ) ); + m_addItem.setAccessible( true ); + + m_addItem0 = ConstPool.class.getDeclaredMethod( "addItem0", Class.forName( "javassist.bytecode.ConstInfo" ) ); + m_addItem0.setAccessible( true ); + + m_items = ConstPool.class.getDeclaredField( "items" ); + m_items.setAccessible( true ); + + m_cache = ConstPool.class.getDeclaredField( "itemsCache" ); + m_cache.setAccessible( true ); + + m_numItems = ConstPool.class.getDeclaredField( "numOfItems" ); + m_numItems.setAccessible( true ); + + m_objects = Class.forName( "javassist.bytecode.LongVector" ).getDeclaredField( "objects" ); + m_objects.setAccessible( true ); + + m_elements = Class.forName( "javassist.bytecode.LongVector" ).getDeclaredField( "elements" ); + m_elements.setAccessible( true ); + + m_methodWritePool = ConstPool.class.getDeclaredMethod( "write", DataOutputStream.class ); + m_methodWritePool.setAccessible( true ); + + m_constructorPool = ConstPool.class.getDeclaredConstructor( DataInputStream.class ); + m_constructorPool.setAccessible( true ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + private ConstPool m_pool; + + public ConstPoolEditor( ConstPool pool ) + { + m_pool = pool; + } + + public void writePool( DataOutputStream out ) + { + try + { + m_methodWritePool.invoke( m_pool, out ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + public static ConstPool readPool( DataInputStream in ) + { + try + { + return m_constructorPool.newInstance( in ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + public String getMemberrefClassname( int memberrefIndex ) + { + return Descriptor.toJvmName( m_pool.getClassInfo( m_pool.getMemberClass( memberrefIndex ) ) ); + } + + public String getMemberrefName( int memberrefIndex ) + { + return m_pool.getUtf8Info( m_pool.getNameAndTypeName( m_pool.getMemberNameAndType( memberrefIndex ) ) ); + } + + public String getMemberrefType( int memberrefIndex ) + { + return m_pool.getUtf8Info( m_pool.getNameAndTypeDescriptor( m_pool.getMemberNameAndType( memberrefIndex ) ) ); + } + + public ConstInfoAccessor getItem( int index ) + { + try + { + Object entry = m_getItem.invoke( m_pool, index ); + if( entry == null ) + { + return null; + } + return new ConstInfoAccessor( entry ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + public int addItem( Object item ) + { + try + { + return (Integer)m_addItem.invoke( m_pool, item ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + public int addItemForceNew( Object item ) + { + try + { + return (Integer)m_addItem0.invoke( m_pool, item ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + @SuppressWarnings( "rawtypes" ) + public void removeLastItem( ) + { + try + { + // remove the item from the cache + HashMap cache = getCache(); + if( cache != null ) + { + Object item = getItem( m_pool.getSize() - 1 ); + cache.remove( item ); + } + + // remove the actual item + // based off of LongVector.addElement() + Object items = m_items.get( m_pool ); + Object[][] objects = (Object[][])m_objects.get( items ); + int numElements = (Integer)m_elements.get( items ) - 1; + int nth = numElements >> 7; + int offset = numElements & (128 - 1); + objects[nth][offset] = null; + + // decrement the number of items + m_elements.set( items, numElements ); + m_numItems.set( m_pool, (Integer)m_numItems.get( m_pool ) - 1 ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + @SuppressWarnings( "rawtypes" ) + /* TEMP */public HashMap getCache( ) + { + try + { + return (HashMap)m_cache.get( m_pool ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + @SuppressWarnings( { "rawtypes", "unchecked" } ) + public void changeMemberrefNameAndType( int memberrefIndex, String newName, String newType ) + { + // NOTE: when changing values, we always need to copy-on-write + try + { + // get the memberref item + Object item = getItem( memberrefIndex ).getItem(); + + // update the cache + HashMap cache = getCache(); + if( cache != null ) + { + cache.remove( item ); + } + + new MemberRefInfoAccessor( item ).setNameAndTypeIndex( m_pool.addNameAndTypeInfo( newName, newType ) ); + + // update the cache + if( cache != null ) + { + cache.put( item, item ); + } + } + catch( Exception ex ) + { + throw new Error( ex ); + } + + // make sure the change worked + assert( newName.equals( getMemberrefName( memberrefIndex ) ) ); + assert( newType.equals( getMemberrefType( memberrefIndex ) ) ); + } + + @SuppressWarnings( { "rawtypes", "unchecked" } ) + public void changeClassName( int classNameIndex, String newName ) + { + // NOTE: when changing values, we always need to copy-on-write + try + { + // get the class item + Object item = getItem( classNameIndex ).getItem(); + + // update the cache + HashMap cache = getCache(); + if( cache != null ) + { + cache.remove( item ); + } + + // add the new name and repoint the name-and-type to it + new ClassInfoAccessor( item ).setNameIndex( m_pool.addUtf8Info( newName ) ); + + // update the cache + if( cache != null ) + { + cache.put( item, item ); + } + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + public static ConstPool newConstPool( ) + { + // const pool expects the name of a class to initialize itself + // but we want an empty pool + // so give it a bogus name, and then clear the entries afterwards + ConstPool pool = new ConstPool( "a" ); + + ConstPoolEditor editor = new ConstPoolEditor( pool ); + int size = pool.getSize(); + for( int i=0; i indices, ConstPoolEditor editor, ConstInfoAccessor entry ) + { + ClassInfoAccessor accessor = new ClassInfoAccessor( entry.getItem() ); + gatherIndexTree( indices, editor, accessor.getNameIndex() ); + } + + @Override + public void remapIndices( Map map, ConstInfoAccessor entry ) + { + ClassInfoAccessor accessor = new ClassInfoAccessor( entry.getItem() ); + accessor.setNameIndex( remapIndex( map, accessor.getNameIndex() ) ); + } + + @Override + public boolean subIndicesAreValid( ConstInfoAccessor entry, ConstPoolEditor pool ) + { + ClassInfoAccessor accessor = new ClassInfoAccessor( entry.getItem() ); + ConstInfoAccessor nameEntry = pool.getItem( accessor.getNameIndex() ); + return nameEntry != null && nameEntry.getTag() == Utf8Info.getTag(); + } + }, + StringInfo( 8, 1 ) + { + @Override + public void gatherIndexTree( Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry ) + { + StringInfoAccessor accessor = new StringInfoAccessor( entry.getItem() ); + gatherIndexTree( indices, editor, accessor.getStringIndex() ); + } + + @Override + public void remapIndices( Map map, ConstInfoAccessor entry ) + { + StringInfoAccessor accessor = new StringInfoAccessor( entry.getItem() ); + accessor.setStringIndex( remapIndex( map, accessor.getStringIndex() ) ); + } + + @Override + public boolean subIndicesAreValid( ConstInfoAccessor entry, ConstPoolEditor pool ) + { + StringInfoAccessor accessor = new StringInfoAccessor( entry.getItem() ); + ConstInfoAccessor stringEntry = pool.getItem( accessor.getStringIndex() ); + return stringEntry != null && stringEntry.getTag() == Utf8Info.getTag(); + } + }, + FieldRefInfo( 9, 2 ) + { + @Override + public void gatherIndexTree( Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry ) + { + MemberRefInfoAccessor accessor = new MemberRefInfoAccessor( entry.getItem() ); + gatherIndexTree( indices, editor, accessor.getClassIndex() ); + gatherIndexTree( indices, editor, accessor.getNameAndTypeIndex() ); + } + + @Override + public void remapIndices( Map map, ConstInfoAccessor entry ) + { + MemberRefInfoAccessor accessor = new MemberRefInfoAccessor( entry.getItem() ); + accessor.setClassIndex( remapIndex( map, accessor.getClassIndex() ) ); + accessor.setNameAndTypeIndex( remapIndex( map, accessor.getNameAndTypeIndex() ) ); + } + + @Override + public boolean subIndicesAreValid( ConstInfoAccessor entry, ConstPoolEditor pool ) + { + MemberRefInfoAccessor accessor = new MemberRefInfoAccessor( entry.getItem() ); + ConstInfoAccessor classEntry = pool.getItem( accessor.getClassIndex() ); + ConstInfoAccessor nameAndTypeEntry = pool.getItem( accessor.getNameAndTypeIndex() ); + return classEntry != null && classEntry.getTag() == ClassInfo.getTag() + && nameAndTypeEntry != null && nameAndTypeEntry.getTag() == NameAndTypeInfo.getTag(); + } + }, + MethodRefInfo( 10, 2 ) // same as FieldRefInfo + { + @Override + public void gatherIndexTree( Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry ) + { + FieldRefInfo.gatherIndexTree( indices, editor, entry ); + } + + @Override + public void remapIndices( Map map, ConstInfoAccessor entry ) + { + FieldRefInfo.remapIndices( map, entry ); + } + + @Override + public boolean subIndicesAreValid( ConstInfoAccessor entry, ConstPoolEditor pool ) + { + return FieldRefInfo.subIndicesAreValid( entry, pool ); + } + }, + InterfaceMethodRefInfo( 11, 2 ) // same as FieldRefInfo + { + @Override + public void gatherIndexTree( Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry ) + { + FieldRefInfo.gatherIndexTree( indices, editor, entry ); + } + + @Override + public void remapIndices( Map map, ConstInfoAccessor entry ) + { + FieldRefInfo.remapIndices( map, entry ); + } + + @Override + public boolean subIndicesAreValid( ConstInfoAccessor entry, ConstPoolEditor pool ) + { + return FieldRefInfo.subIndicesAreValid( entry, pool ); + } + }, + NameAndTypeInfo( 12, 1 ) + { + @Override + public void gatherIndexTree( Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry ) + { + NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor( entry.getItem() ); + gatherIndexTree( indices, editor, accessor.getNameIndex() ); + gatherIndexTree( indices, editor, accessor.getTypeIndex() ); + } + + @Override + public void remapIndices( Map map, ConstInfoAccessor entry ) + { + NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor( entry.getItem() ); + accessor.setNameIndex( remapIndex( map, accessor.getNameIndex() ) ); + accessor.setTypeIndex( remapIndex( map, accessor.getTypeIndex() ) ); + } + + @Override + public boolean subIndicesAreValid( ConstInfoAccessor entry, ConstPoolEditor pool ) + { + NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor( entry.getItem() ); + ConstInfoAccessor nameEntry = pool.getItem( accessor.getNameIndex() ); + ConstInfoAccessor typeEntry = pool.getItem( accessor.getTypeIndex() ); + return nameEntry != null && nameEntry.getTag() == Utf8Info.getTag() + && typeEntry != null && typeEntry.getTag() == Utf8Info.getTag(); + } + }, + MethodHandleInfo( 15, 3 ) + { + @Override + public void gatherIndexTree( Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry ) + { + MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor( entry.getItem() ); + gatherIndexTree( indices, editor, accessor.getTypeIndex() ); + gatherIndexTree( indices, editor, accessor.getMethodRefIndex() ); + } + + @Override + public void remapIndices( Map map, ConstInfoAccessor entry ) + { + MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor( entry.getItem() ); + accessor.setTypeIndex( remapIndex( map, accessor.getTypeIndex() ) ); + accessor.setMethodRefIndex( remapIndex( map, accessor.getMethodRefIndex() ) ); + } + + @Override + public boolean subIndicesAreValid( ConstInfoAccessor entry, ConstPoolEditor pool ) + { + MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor( entry.getItem() ); + ConstInfoAccessor typeEntry = pool.getItem( accessor.getTypeIndex() ); + ConstInfoAccessor methodRefEntry = pool.getItem( accessor.getMethodRefIndex() ); + return typeEntry != null && typeEntry.getTag() == Utf8Info.getTag() + && methodRefEntry != null && methodRefEntry.getTag() == MethodRefInfo.getTag(); + } + }, + MethodTypeInfo( 16, 1 ) + { + @Override + public void gatherIndexTree( Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry ) + { + MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor( entry.getItem() ); + gatherIndexTree( indices, editor, accessor.getTypeIndex() ); + } + + @Override + public void remapIndices( Map map, ConstInfoAccessor entry ) + { + MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor( entry.getItem() ); + accessor.setTypeIndex( remapIndex( map, accessor.getTypeIndex() ) ); + } + + @Override + public boolean subIndicesAreValid( ConstInfoAccessor entry, ConstPoolEditor pool ) + { + MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor( entry.getItem() ); + ConstInfoAccessor typeEntry = pool.getItem( accessor.getTypeIndex() ); + return typeEntry != null && typeEntry.getTag() == Utf8Info.getTag(); + } + }, + InvokeDynamicInfo( 18, 2 ) + { + @Override + public void gatherIndexTree( Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry ) + { + InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor( entry.getItem() ); + gatherIndexTree( indices, editor, accessor.getBootstrapIndex() ); + gatherIndexTree( indices, editor, accessor.getNameAndTypeIndex() ); + } + + @Override + public void remapIndices( Map map, ConstInfoAccessor entry ) + { + InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor( entry.getItem() ); + accessor.setBootstrapIndex( remapIndex( map, accessor.getBootstrapIndex() ) ); + accessor.setNameAndTypeIndex( remapIndex( map, accessor.getNameAndTypeIndex() ) ); + } + + @Override + public boolean subIndicesAreValid( ConstInfoAccessor entry, ConstPoolEditor pool ) + { + InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor( entry.getItem() ); + ConstInfoAccessor bootstrapEntry = pool.getItem( accessor.getBootstrapIndex() ); + ConstInfoAccessor nameAndTypeEntry = pool.getItem( accessor.getNameAndTypeIndex() ); + return bootstrapEntry != null && bootstrapEntry.getTag() == Utf8Info.getTag() + && nameAndTypeEntry != null && nameAndTypeEntry.getTag() == NameAndTypeInfo.getTag(); + } + }; + + private static Map m_types; + + static + { + m_types = Maps.newTreeMap(); + for( InfoType type : values() ) + { + m_types.put( type.getTag(), type ); + } + } + + private int m_tag; + private int m_level; + + private InfoType( int tag, int level ) + { + m_tag = tag; + m_level = level; + } + + public int getTag( ) + { + return m_tag; + } + + public int getLevel( ) + { + return m_level; + } + + public void gatherIndexTree( Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry ) + { + // by default, do nothing + } + + public void remapIndices( Map map, ConstInfoAccessor entry ) + { + // by default, do nothing + } + + public boolean subIndicesAreValid( ConstInfoAccessor entry, ConstPoolEditor pool ) + { + // by default, everything is good + return true; + } + + public boolean selfIndexIsValid( ConstInfoAccessor entry, ConstPoolEditor pool ) + { + ConstInfoAccessor entryCheck = pool.getItem( entry.getIndex() ); + if( entryCheck == null ) + { + return false; + } + return entryCheck.getItem().equals( entry.getItem() ); + } + + public static InfoType getByTag( int tag ) + { + return m_types.get( tag ); + } + + public static List getByLevel( int level ) + { + List types = Lists.newArrayList(); + for( InfoType type : values() ) + { + if( type.getLevel() == level ) + { + types.add( type ); + } + } + return types; + } + + public static List getSortedByLevel( ) + { + List types = Lists.newArrayList(); + types.addAll( getByLevel( 0 ) ); + types.addAll( getByLevel( 1 ) ); + types.addAll( getByLevel( 2 ) ); + types.addAll( getByLevel( 3 ) ); + return types; + } + + public static void gatherIndexTree( Collection indices, ConstPoolEditor editor, int index ) + { + // add own index + indices.add( index ); + + // recurse + ConstInfoAccessor entry = editor.getItem( index ); + entry.getType().gatherIndexTree( indices, editor, entry ); + } + + private static int remapIndex( Map map, int index ) + { + Integer newIndex = map.get( index ); + if( newIndex == null ) + { + newIndex = index; + } + return newIndex; + } +} diff --git a/src/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java new file mode 100644 index 00000000..41e1d047 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java @@ -0,0 +1,69 @@ +/******************************************************************************* + * 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.bytecode.accessors; + +import java.lang.reflect.Field; + +public class ClassInfoAccessor +{ + private static Class m_class; + private static Field m_nameIndex; + + static + { + try + { + m_class = Class.forName( "javassist.bytecode.ClassInfo" ); + m_nameIndex = m_class.getDeclaredField( "name" ); + m_nameIndex.setAccessible( true ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + public static boolean isType( ConstInfoAccessor accessor ) + { + return m_class.isAssignableFrom( accessor.getItem().getClass() ); + } + + private Object m_item; + + public ClassInfoAccessor( Object item ) + { + m_item = item; + } + + public int getNameIndex( ) + { + try + { + return (Integer)m_nameIndex.get( m_item ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + public void setNameIndex( int val ) + { + try + { + m_nameIndex.set( m_item, val ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } +} diff --git a/src/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java new file mode 100644 index 00000000..3c3d3fa4 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java @@ -0,0 +1,199 @@ +/******************************************************************************* + * 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.bytecode.accessors; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import cuchaz.enigma.bytecode.InfoType; + +public class ConstInfoAccessor +{ + private static Class m_class; + private static Field m_index; + private static Method m_getTag; + + static + { + try + { + m_class = Class.forName( "javassist.bytecode.ConstInfo" ); + m_index = m_class.getDeclaredField( "index" ); + m_index.setAccessible( true ); + m_getTag = m_class.getMethod( "getTag" ); + m_getTag.setAccessible( true ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + private Object m_item; + + public ConstInfoAccessor( Object item ) + { + if( item == null ) + { + throw new IllegalArgumentException( "item cannot be null!" ); + } + m_item = item; + } + + public ConstInfoAccessor( DataInputStream in ) + throws IOException + { + try + { + // read the entry + String className = in.readUTF(); + int oldIndex = in.readInt(); + + // NOTE: ConstInfo instances write a type id (a "tag"), but they don't read it back + // so we have to read it here + in.readByte(); + + Constructor constructor = Class.forName( className ).getConstructor( DataInputStream.class, int.class ); + constructor.setAccessible( true ); + m_item = constructor.newInstance( in, oldIndex ); + } + catch( IOException ex ) + { + throw ex; + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + public Object getItem( ) + { + return m_item; + } + + public int getIndex( ) + { + try + { + return (Integer)m_index.get( m_item ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + public void setIndex( int val ) + { + try + { + m_index.set( m_item, val ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + public int getTag( ) + { + try + { + return (Integer)m_getTag.invoke( m_item ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + public ConstInfoAccessor copy( ) + { + return new ConstInfoAccessor( copyItem() ); + } + + public Object copyItem( ) + { + // I don't know of a simpler way to copy one of these silly things... + try + { + // serialize the item + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream( buf ); + write( out ); + + // deserialize the item + DataInputStream in = new DataInputStream( new ByteArrayInputStream( buf.toByteArray() ) ); + Object item = new ConstInfoAccessor( in ).getItem(); + in.close(); + + return item; + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + public void write( DataOutputStream out ) + throws IOException + { + try + { + out.writeUTF( m_item.getClass().getName() ); + out.writeInt( getIndex() ); + + Method method = m_item.getClass().getMethod( "write", DataOutputStream.class ); + method.setAccessible( true ); + method.invoke( m_item, out ); + } + catch( IOException ex ) + { + throw ex; + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + @Override + public String toString( ) + { + try + { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + PrintWriter out = new PrintWriter( buf ); + Method print = m_item.getClass().getMethod( "print", PrintWriter.class ); + print.setAccessible( true ); + print.invoke( m_item, out ); + out.close(); + return buf.toString().replace( "\n", "" ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + public InfoType getType( ) + { + return InfoType.getByTag( getTag() ); + } +} diff --git a/src/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java new file mode 100644 index 00000000..169306a4 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * 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.bytecode.accessors; + +import java.lang.reflect.Field; + +public class InvokeDynamicInfoAccessor +{ + private static Class m_class; + private static Field m_bootstrapIndex; + private static Field m_nameAndTypeIndex; + + static + { + try + { + m_class = Class.forName( "javassist.bytecode.InvokeDynamicInfo" ); + m_bootstrapIndex = m_class.getDeclaredField( "bootstrap" ); + m_bootstrapIndex.setAccessible( true ); + m_nameAndTypeIndex = m_class.getDeclaredField( "nameAndType" ); + m_nameAndTypeIndex.setAccessible( true ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + public static boolean isType( ConstInfoAccessor accessor ) + { + return m_class.isAssignableFrom( accessor.getItem().getClass() ); + } + + private Object m_item; + + public InvokeDynamicInfoAccessor( Object item ) + { + m_item = item; + } + + public int getBootstrapIndex( ) + { + try + { + return (Integer)m_bootstrapIndex.get( m_item ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + public void setBootstrapIndex( int val ) + { + try + { + m_bootstrapIndex.set( m_item, val ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + public int getNameAndTypeIndex( ) + { + try + { + return (Integer)m_nameAndTypeIndex.get( m_item ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + public void setNameAndTypeIndex( int val ) + { + try + { + m_nameAndTypeIndex.set( m_item, val ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } +} diff --git a/src/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java new file mode 100644 index 00000000..2ee3aff8 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * 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.bytecode.accessors; + +import java.lang.reflect.Field; + +public class MemberRefInfoAccessor +{ + private static Class m_class; + private static Field m_classIndex; + private static Field m_nameAndTypeIndex; + + static + { + try + { + m_class = Class.forName( "javassist.bytecode.MemberrefInfo" ); + m_classIndex = m_class.getDeclaredField( "classIndex" ); + m_classIndex.setAccessible( true ); + m_nameAndTypeIndex = m_class.getDeclaredField( "nameAndTypeIndex" ); + m_nameAndTypeIndex.setAccessible( true ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + public static boolean isType( ConstInfoAccessor accessor ) + { + return m_class.isAssignableFrom( accessor.getItem().getClass() ); + } + + private Object m_item; + + public MemberRefInfoAccessor( Object item ) + { + m_item = item; + } + + public int getClassIndex( ) + { + try + { + return (Integer)m_classIndex.get( m_item ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + public void setClassIndex( int val ) + { + try + { + m_classIndex.set( m_item, val ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + public int getNameAndTypeIndex( ) + { + try + { + return (Integer)m_nameAndTypeIndex.get( m_item ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + public void setNameAndTypeIndex( int val ) + { + try + { + m_nameAndTypeIndex.set( m_item, val ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } +} diff --git a/src/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java new file mode 100644 index 00000000..27b7aee8 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * 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.bytecode.accessors; + +import java.lang.reflect.Field; + +public class MethodHandleInfoAccessor +{ + private static Class m_class; + private static Field m_kindIndex; + private static Field m_indexIndex; + + static + { + try + { + m_class = Class.forName( "javassist.bytecode.MethodHandleInfo" ); + m_kindIndex = m_class.getDeclaredField( "refKind" ); + m_kindIndex.setAccessible( true ); + m_indexIndex = m_class.getDeclaredField( "refIndex" ); + m_indexIndex.setAccessible( true ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + public static boolean isType( ConstInfoAccessor accessor ) + { + return m_class.isAssignableFrom( accessor.getItem().getClass() ); + } + + private Object m_item; + + public MethodHandleInfoAccessor( Object item ) + { + m_item = item; + } + + public int getTypeIndex( ) + { + try + { + return (Integer)m_kindIndex.get( m_item ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + public void setTypeIndex( int val ) + { + try + { + m_kindIndex.set( m_item, val ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + public int getMethodRefIndex( ) + { + try + { + return (Integer)m_indexIndex.get( m_item ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + public void setMethodRefIndex( int val ) + { + try + { + m_indexIndex.set( m_item, val ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } +} diff --git a/src/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java new file mode 100644 index 00000000..4cba6a2a --- /dev/null +++ b/src/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java @@ -0,0 +1,69 @@ +/******************************************************************************* + * 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.bytecode.accessors; + +import java.lang.reflect.Field; + +public class MethodTypeInfoAccessor +{ + private static Class m_class; + private static Field m_descriptorIndex; + + static + { + try + { + m_class = Class.forName( "javassist.bytecode.MethodTypeInfo" ); + m_descriptorIndex = m_class.getDeclaredField( "descriptor" ); + m_descriptorIndex.setAccessible( true ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + public static boolean isType( ConstInfoAccessor accessor ) + { + return m_class.isAssignableFrom( accessor.getItem().getClass() ); + } + + private Object m_item; + + public MethodTypeInfoAccessor( Object item ) + { + m_item = item; + } + + public int getTypeIndex( ) + { + try + { + return (Integer)m_descriptorIndex.get( m_item ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + public void setTypeIndex( int val ) + { + try + { + m_descriptorIndex.set( m_item, val ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } +} diff --git a/src/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java new file mode 100644 index 00000000..03b4de3c --- /dev/null +++ b/src/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * 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.bytecode.accessors; + +import java.lang.reflect.Field; + +public class NameAndTypeInfoAccessor +{ + private static Class m_class; + private static Field m_nameIndex; + private static Field m_typeIndex; + + static + { + try + { + m_class = Class.forName( "javassist.bytecode.NameAndTypeInfo" ); + m_nameIndex = m_class.getDeclaredField( "memberName" ); + m_nameIndex.setAccessible( true ); + m_typeIndex = m_class.getDeclaredField( "typeDescriptor" ); + m_typeIndex.setAccessible( true ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + public static boolean isType( ConstInfoAccessor accessor ) + { + return m_class.isAssignableFrom( accessor.getItem().getClass() ); + } + + private Object m_item; + + public NameAndTypeInfoAccessor( Object item ) + { + m_item = item; + } + + public int getNameIndex( ) + { + try + { + return (Integer)m_nameIndex.get( m_item ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + public void setNameIndex( int val ) + { + try + { + m_nameIndex.set( m_item, val ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + public int getTypeIndex( ) + { + try + { + return (Integer)m_typeIndex.get( m_item ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + public void setTypeIndex( int val ) + { + try + { + m_typeIndex.set( m_item, val ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } +} diff --git a/src/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java new file mode 100644 index 00000000..5cdfce4d --- /dev/null +++ b/src/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java @@ -0,0 +1,69 @@ +/******************************************************************************* + * 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.bytecode.accessors; + +import java.lang.reflect.Field; + +public class StringInfoAccessor +{ + private static Class m_class; + private static Field m_stringIndex; + + static + { + try + { + m_class = Class.forName( "javassist.bytecode.StringInfo" ); + m_stringIndex = m_class.getDeclaredField( "string" ); + m_stringIndex.setAccessible( true ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + public static boolean isType( ConstInfoAccessor accessor ) + { + return m_class.isAssignableFrom( accessor.getItem().getClass() ); + } + + private Object m_item; + + public StringInfoAccessor( Object item ) + { + m_item = item; + } + + public int getStringIndex( ) + { + try + { + return (Integer)m_stringIndex.get( m_item ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + public void setStringIndex( int val ) + { + try + { + m_stringIndex.set( m_item, val ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } +} diff --git a/src/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java new file mode 100644 index 00000000..1cadd836 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * 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.bytecode.accessors; + +public class Utf8InfoAccessor +{ + private static Class m_class; + + static + { + try + { + m_class = Class.forName( "javassist.bytecode.Utf8Info" ); + } + catch( Exception ex ) + { + throw new Error( ex ); + } + } + + public static boolean isType( ConstInfoAccessor accessor ) + { + return m_class.isAssignableFrom( accessor.getItem().getClass() ); + } +} diff --git a/src/cuchaz/enigma/gui/Gui.java b/src/cuchaz/enigma/gui/Gui.java index d1a3cb21..2a539a3f 100644 --- a/src/cuchaz/enigma/gui/Gui.java +++ b/src/cuchaz/enigma/gui/Gui.java @@ -45,7 +45,7 @@ import cuchaz.enigma.ClassFile; import cuchaz.enigma.analysis.SourceIndex; import cuchaz.enigma.mapping.ArgumentEntry; import cuchaz.enigma.mapping.ClassEntry; -import cuchaz.enigma.mapping.Entry; +import cuchaz.enigma.mapping.EntryPair; import cuchaz.enigma.mapping.FieldEntry; import cuchaz.enigma.mapping.MethodEntry; @@ -69,7 +69,7 @@ public class Gui private RenameListener m_renameListener; private BoxHighlightPainter m_highlightPainter; - private Entry m_selectedEntry; + private EntryPair m_selectedEntryPair; public Gui( ) { @@ -128,9 +128,9 @@ public class Gui @Override public void actionPerformed( ActionEvent event ) { - if( m_renameListener != null && m_selectedEntry != null ) + if( m_renameListener != null && m_selectedEntryPair != null ) { - m_renameListener.rename( m_selectedEntry, m_nameField.getText() ); + m_renameListener.rename( m_selectedEntryPair.obf, m_nameField.getText() ); } } } ); @@ -142,7 +142,7 @@ public class Gui m_renamePanel.add( m_typeLabel ); m_renamePanel.add( m_nameField ); m_renamePanel.add( m_renameButton ); - clearEntry(); + clearEntryPair(); // init editor DefaultSyntaxKit.initKit(); @@ -241,7 +241,7 @@ public class Gui m_editor.addCaretListener( listener ); } - public void clearEntry( ) + public void clearEntryPair( ) { m_actionPanel.removeAll(); JLabel label = new JLabel( "No identifier selected" ); @@ -251,67 +251,72 @@ public class Gui redraw(); } - - public void showEntry( Entry entry ) + + public void showEntryPair( EntryPair pair ) { - if( entry == null ) + if( pair == null ) { - clearEntry(); + clearEntryPair(); return; } + // TEMP + System.out.println( "Pair:\n" + pair.obf + "\n" + pair.deobf ); + + m_selectedEntryPair = pair; + // layout the action panel m_actionPanel.removeAll(); m_actionPanel.add( m_renamePanel ); - m_nameField.setText( entry.getName() ); + m_nameField.setText( pair.deobf.getName() ); // layout the dynamic section JPanel dynamicPanel = new JPanel(); dynamicPanel.setLayout( new GridLayout( 3, 1, 0, 0 ) ); m_actionPanel.add( dynamicPanel ); - if( entry instanceof ClassEntry ) + if( pair.deobf instanceof ClassEntry ) { - showEntry( (ClassEntry)entry, dynamicPanel ); + showEntry( (ClassEntry)pair.deobf, dynamicPanel ); } - else if( entry instanceof FieldEntry ) + else if( pair.deobf instanceof FieldEntry ) { - showEntry( (FieldEntry)entry, dynamicPanel ); + showEntry( (FieldEntry)pair.deobf, dynamicPanel ); } - else if( entry instanceof MethodEntry ) + else if( pair.deobf instanceof MethodEntry ) { - showEntry( (MethodEntry)entry, dynamicPanel ); + showEntry( (MethodEntry)pair.deobf, dynamicPanel ); } - else if( entry instanceof ArgumentEntry ) + else if( pair.deobf instanceof ArgumentEntry ) { - showEntry( (ArgumentEntry)entry, dynamicPanel ); + showEntry( (ArgumentEntry)pair.deobf, dynamicPanel ); } else { - throw new Error( "Unknown entry type: " + entry.getClass().getName() ); + throw new Error( "Unknown entry type: " + pair.deobf.getClass().getName() ); } redraw(); } - public void showEntry( ClassEntry entry, JPanel panel ) + private void showEntry( ClassEntry entry, JPanel panel ) { m_typeLabel.setText( "Class: " ); } - public void showEntry( FieldEntry entry, JPanel panel ) + private void showEntry( FieldEntry entry, JPanel panel ) { m_typeLabel.setText( "Field: " ); addNameValue( panel, "Class", entry.getClassEntry().getName() ); } - public void showEntry( MethodEntry entry, JPanel panel ) + private void showEntry( MethodEntry entry, JPanel panel ) { m_typeLabel.setText( "Method: " ); addNameValue( panel, "Class", entry.getClassEntry().getName() ); addNameValue( panel, "Signature", entry.getSignature() ); } - public void showEntry( ArgumentEntry entry, JPanel panel ) + private void showEntry( ArgumentEntry entry, JPanel panel ) { m_typeLabel.setText( "Argument: " ); addNameValue( panel, "Class", entry.getMethodEntry().getClassEntry().getName() ); diff --git a/src/cuchaz/enigma/gui/RenameListener.java b/src/cuchaz/enigma/gui/RenameListener.java index 58cdadb0..7d45505b 100644 --- a/src/cuchaz/enigma/gui/RenameListener.java +++ b/src/cuchaz/enigma/gui/RenameListener.java @@ -14,5 +14,5 @@ import cuchaz.enigma.mapping.Entry; public interface RenameListener { - void rename( Entry entry, String newName ); + void rename( Entry obfEntry, String newName ); } diff --git a/src/cuchaz/enigma/mapping/Ancestries.java b/src/cuchaz/enigma/mapping/Ancestries.java new file mode 100644 index 00000000..b7a5e249 --- /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 dc3b4df7..c1624a83 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 00000000..57488d14 --- /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 3a757675..0968e955 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 00000000..699807b8 --- /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 00000000..5320f110 --- /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 00000000..e40e9992 --- /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 25b665ab..b9f42394 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 4afc099b..9ea2d08e 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 00000000..f965355d --- /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 00000000..4c0dbac1 --- /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 00000000..79ae0d32 --- /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 00000000..d6cd4491 --- /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 00000000..bae0dce7 --- /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