/******************************************************************************* * 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.List; 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.Lists; 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 getParameterTypes( String signature ) { List types = Lists.newArrayList(); for( int i=0; i 0 ) { type = "[" + type; } types.add( type ); } return types; } }