portablejim.veinminer.asm.ItemInWorldManagerTransformer.java Source code

Java tutorial

Introduction

Here is the source code for portablejim.veinminer.asm.ItemInWorldManagerTransformer.java

Source

/* This file is part of VeinMiner.
 *
 *    VeinMiner is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU Lesser General Public License as
 *    published by the Free Software Foundation, either version 3 of
 *     the License, or (at your option) any later version.
 *
 *    VeinMiner is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public
 *    License along with VeinMiner.
 *    If not, see <http://www.gnu.org/licenses/>.
 */

package portablejim.veinminer.asm;

import cpw.mods.fml.common.FMLLog;
import cpw.mods.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper;
import cpw.mods.fml.relauncher.IClassTransformer;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.LocalVariablesSorter;
import org.objectweb.asm.tree.*;
import portablejim.veinminer.lib.Logger;
import portablejim.veinminer.lib.ModInfo;
import portablejim.veinminer.util.BlockID;

import java.util.HashMap;
import java.util.logging.Level;

/**
 * Modifies ItemInWorldManager to add a call to VeinMiner.blockMined() to
 * capture the mining of a block to start VeinMiner.
 *
 * It also stores the result of tryHarvestBlock() (whether the attempt to mine
 * a block was successful) and uses it as an argument to blockMined().
 */

@SuppressWarnings("UnusedDeclaration")
public class ItemInWorldManagerTransformer extends GenericTransformer implements IClassTransformer {

    final String targetClassName = "portablejim/veinminer/VeinMiner";
    final String targetClassType = "Lportablejim/veinminer/VeinMiner;";
    final String targetMethodName = "blockMined";
    final String targetMethodType = "(%s%sIIIZ%s)V";
    final String blockIdClassName = "portablejim/veinminer/util/BlockID";

    public ItemInWorldManagerTransformer() {
        super();
        srgMappings = new HashMap<String, String>();
        srgMappings.put("uncheckedTryHarvestBlock", "func_73082_a");
        srgMappings.put("tryHarvestBlock", "func_73084_b");
        srgMappings.put("theWorld", "field_73092_a");
        srgMappings.put("thisPlayerMP", "field_73090_b");
        srgMappings.put("destroyBlockInWorldPartially", "func_72888_f");
        srgMappings.put("onBlockClicked", "func_73074_a");
    }

    @Override
    public byte[] transform(String name, String transformedName, byte[] bytes) {

        if ("net.minecraft.item.ItemInWorldManager".equals(transformedName)) {
            obfuscated = !transformedName.equals(name);
            bytes = transformItemInWorldManager(name, bytes);
        }
        return bytes;
    }

    private InsnList buildBlockIdFunctionCall(String obfuscatedClassName, String worldType, int blockVarIndex) {
        InsnList blockIdFunctionCall = new InsnList();
        blockIdFunctionCall.add(new TypeInsnNode(Opcodes.NEW, blockIdClassName));
        blockIdFunctionCall.add(new InsnNode(Opcodes.DUP));
        blockIdFunctionCall.add(new VarInsnNode(Opcodes.ALOAD, 0));
        blockIdFunctionCall.add(new FieldInsnNode(Opcodes.GETFIELD, obfuscatedClassName.replace(".", "/"),
                getCorrectName("theWorld"), typemap.get(getCorrectName("theWorld"))));
        blockIdFunctionCall.add(new VarInsnNode(Opcodes.ILOAD, 1));
        blockIdFunctionCall.add(new VarInsnNode(Opcodes.ILOAD, 2));
        blockIdFunctionCall.add(new VarInsnNode(Opcodes.ILOAD, 3));
        blockIdFunctionCall.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, blockIdClassName, "<init>",
                String.format("(%sIII)V", worldType)));

        blockIdFunctionCall.add(new VarInsnNode(Opcodes.ASTORE, blockVarIndex));

        return blockIdFunctionCall;
    }

    private int insertCallAfterTryHarvestBlockFunction(MethodNode curMethod, String obfuscatedClassName)
            throws IndexOutOfBoundsException {
        return insertCallAfterTryHarvestBlockFunction(curMethod, obfuscatedClassName, 0);
    }

    private int insertCallAfterTryHarvestBlockFunction(MethodNode curMethod, String obfuscatedClassName,
            int startIndex) throws IndexOutOfBoundsException {
        LocalVariablesSorter varSorter = new LocalVariablesSorter(curMethod.access, curMethod.desc, curMethod);

        String worldType = typemap.get(getCorrectName("theWorld"));
        String playerType = typemap.get(getCorrectName("thisPlayerMP"));

        while (!isMethodWithName(curMethod.instructions.get(startIndex), "tryHarvestBlock")) {
            ++startIndex;
        }

        do {
            --startIndex;
        } while (curMethod.instructions.get(startIndex).getType() == AbstractInsnNode.VAR_INSN);

        int blockVarIndex = varSorter.newLocal(Type.getType(BlockID.class));
        curMethod.instructions.insert(curMethod.instructions.get(startIndex),
                buildBlockIdFunctionCall(obfuscatedClassName, worldType, blockVarIndex));
        ++startIndex;

        while (!isMethodWithName(curMethod.instructions.get(startIndex), "tryHarvestBlock")) {
            ++startIndex;
        }

        // Add variable to store result
        int newVarIndex = varSorter.newLocal(Type.BOOLEAN_TYPE);
        VarInsnNode newVar = new VarInsnNode(Opcodes.ISTORE, newVarIndex);
        curMethod.instructions.insert(curMethod.instructions.get(startIndex), newVar);
        ++startIndex;

        // Add in function call to call function
        InsnList veinMinerFunctionCall = new InsnList();
        veinMinerFunctionCall
                .add(new FieldInsnNode(Opcodes.GETSTATIC, targetClassName, "instance", targetClassType));
        veinMinerFunctionCall.add(new VarInsnNode(Opcodes.ALOAD, 0));
        veinMinerFunctionCall.add(new FieldInsnNode(Opcodes.GETFIELD, obfuscatedClassName.replace(".", "/"),
                getCorrectName("theWorld"), typemap.get(getCorrectName("theWorld"))));
        veinMinerFunctionCall.add(new VarInsnNode(Opcodes.ALOAD, 0));
        veinMinerFunctionCall.add(new FieldInsnNode(Opcodes.GETFIELD, obfuscatedClassName.replace(".", "/"),
                getCorrectName("thisPlayerMP"), typemap.get(getCorrectName("thisPlayerMP"))));
        veinMinerFunctionCall.add(new VarInsnNode(Opcodes.ILOAD, 1));
        veinMinerFunctionCall.add(new VarInsnNode(Opcodes.ILOAD, 2));
        veinMinerFunctionCall.add(new VarInsnNode(Opcodes.ILOAD, 3));
        veinMinerFunctionCall.add(new VarInsnNode(Opcodes.ILOAD, newVarIndex));
        veinMinerFunctionCall.add(new VarInsnNode(Opcodes.ALOAD, blockVarIndex));

        String blockIdClassType = String.format("L%s;", blockIdClassName);
        veinMinerFunctionCall.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, targetClassName, targetMethodName,
                String.format(targetMethodType, worldType, playerType, blockIdClassType)));
        curMethod.instructions.insert(curMethod.instructions.get(startIndex), veinMinerFunctionCall);
        ++startIndex;

        // Get rid of un-needed POP.
        while (curMethod.instructions.get(startIndex).getOpcode() != Opcodes.POP) {
            ++startIndex;
        }
        curMethod.instructions.remove(curMethod.instructions.get(startIndex));

        return startIndex;
    }

    public byte[] transformItemInWorldManager(String obfuscatedClassName, byte[] bytes) {
        ClassNode classNode = new ClassNode();
        ClassReader classReader = new ClassReader(bytes);
        classReader.accept(classNode, 0);

        // Setup type map
        for (FieldNode variable : classNode.fields) {
            String srgVariableName = FMLDeobfuscatingRemapper.INSTANCE.mapFieldName(obfuscatedClassName,
                    variable.name, variable.desc);
            if (getCorrectName("theWorld").equals(srgVariableName)
                    || getCorrectName("thisPlayerMP").equals(srgVariableName)) {
                typemap.put(srgVariableName, variable.desc);
            }
        }

        try {
            for (MethodNode curMethod : classNode.methods) {
                String srgFunctionName = FMLDeobfuscatingRemapper.INSTANCE.mapMethodName(obfuscatedClassName,
                        curMethod.name, curMethod.desc);

                if (getCorrectName("uncheckedTryHarvestBlock").equals(srgFunctionName)) {
                    Logger.debug("Inserting call to uncheckedTryHarvestBlock (%s)", srgFunctionName);
                    insertCallAfterTryHarvestBlockFunction(curMethod, obfuscatedClassName);
                } else if (getCorrectName("onBlockClicked").equals(srgFunctionName)) {
                    Logger.debug("Inserting call to onBlockClicked (%s)", srgFunctionName);
                    int afterFirst = insertCallAfterTryHarvestBlockFunction(curMethod, obfuscatedClassName);
                    insertCallAfterTryHarvestBlockFunction(curMethod, obfuscatedClassName, afterFirst);

                }
            }
        } catch (IndexOutOfBoundsException e) {
            FMLLog.getLogger().log(Level.WARNING,
                    "[%s] Problem inserting all required code. This mod may not function correctly. Please report a bug.",
                    ModInfo.MOD_NAME);
        }

        ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        classNode.accept(writer);
        return writer.toByteArray();
    }
}