com.hea3ven.hardmodetweaks.core.ClassTransformerHardModeTweaks.java Source code

Java tutorial

Introduction

Here is the source code for com.hea3ven.hardmodetweaks.core.ClassTransformerHardModeTweaks.java

Source

/**
 * 
 * Copyright (c) 2014 Hea3veN
 * 
 *  This file is part of HardModeTweaks.
 *
 *  HardModeTweaks 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.
 *
 *  HardModeTweaks 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with HardModeTweaks.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package com.hea3ven.hardmodetweaks.core;

import java.util.Iterator;

import net.minecraft.launchwrapper.IClassTransformer;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.VarInsnNode;

public class ClassTransformerHardModeTweaks implements IClassTransformer {

    private static final ObfuscatedClass WORLD_SERVER_CLASS = new ObfuscatedClass("net.minecraft.world.WorldServer",
            "mt");
    private static final ObfuscatedMethod WORLD_SERVER_TICK = new ObfuscatedMethod(WORLD_SERVER_CLASS, "tick", "b");
    private static final ObfuscatedClass WORLD_CLIENT = new ObfuscatedClass(
            "net.minecraft.client.multiplayer.WorldClient", "bjf");
    private static final ObfuscatedMethod WORLD_CLIENT_TICK = new ObfuscatedMethod(WORLD_CLIENT, "tick", "b");
    private static final ObfuscatedMethod WORLD_CLIENT_GET_WORLD_TIME = new ObfuscatedMethod(WORLD_CLIENT,
            "getWorldTime", "J");
    private static final ObfuscatedMethod WORLD_CLIENT_SET_WORLD_TIME = new ObfuscatedMethod(WORLD_CLIENT,
            "setWorldTime", "b");
    private static final ObfuscatedClass WORLD_INFO = new ObfuscatedClass("net.minecraft.world.storage.WorldInfo",
            "ays");
    private static final ObfuscatedMethod WORLD_INFO_GET_WORLD_TIME = new ObfuscatedMethod(WORLD_INFO,
            "getWorldTime", "g");
    private static final ObfuscatedMethod WORLD_INFO_SET_WORLD_TIME = new ObfuscatedMethod(WORLD_INFO,
            "setWorldTime", "c");
    private static final ObfuscatedField WORLD_INFO_WORLD_TIME = new ObfuscatedField(WORLD_INFO, "worldTime", "h");
    private static final ObfuscatedClass WORLD = new ObfuscatedClass("net.minecraft.world.World", "ahb");
    private static final ObfuscatedMethod WORLD_GET_WORLD_INFO_MTHD = new ObfuscatedMethod(WORLD, "getWorldInfo",
            "N");
    private static final ObfuscatedField WORLD_PROVIDER = new ObfuscatedField(WORLD, "provider", "t");
    private static final ObfuscatedClass WORLD_PROVIDER_CLASS = new ObfuscatedClass(
            "net.minecraft.world.WorldProvider", "aqo");
    private static final ObfuscatedField WORLD_PROVIDER_WORLD_OBJ_FLD = new ObfuscatedField(WORLD_PROVIDER_CLASS,
            "worldObj", "b");
    private static final ObfuscatedMethod WORLD_PROVIDER_CALC_CEL_ANGLE = new ObfuscatedMethod(WORLD,
            "calculateCelestialAngle", "a");

    private Logger logger = LogManager.getLogger("HardModeTweaks.ClassTransformer");

    @Override
    public byte[] transform(String name, String transformedName, byte[] basicClass) {
        if (WORLD_SERVER_CLASS.matchesName(name)) {
            logger.info("Class WorldServer({}/{}) is loading, patching it", name, transformedName);
            return patchWorldServer(name, basicClass, WORLD_SERVER_CLASS.isObfuscated(name));
        }

        if (WORLD_CLIENT.matchesName(name)) {
            logger.info("Class WorldClient({}/{}) is loading, patching it", name, transformedName);
            return patchWorldClient(name, basicClass, WORLD_CLIENT.isObfuscated(name));
        }

        if (WORLD_INFO.matchesName(name)) {
            logger.info("Class WorldInfo({}/{}) is loading, patching it", name, transformedName);
            return patchWorldInfo(name, basicClass, WORLD_INFO.isObfuscated(name));
        }

        if (WORLD_PROVIDER_CLASS.matchesName(name)) {
            logger.info("Class WorldProvider({}/{}) is loading, patching it", name, transformedName);
            return patchWorldProvider(name, basicClass, WORLD_PROVIDER_CLASS.isObfuscated(name));
        }

        return basicClass;
    }

    //
    // ******************** WorldServer ********************
    //
    private byte[] patchWorldServer(String name, byte[] basicClass, boolean obfuscated) {
        ClassNode classNode = readClass(basicClass);

        logger.info("Looking for tick method of WorldServer");
        MethodNode tickMethod = getMethod(classNode, WORLD_SERVER_TICK.get(obfuscated), "()V");
        if (tickMethod == null)
            error("Could not find the method");
        logger.info("Patching tick({}) method of WorldServer", tickMethod.name);
        patchWorldServerTick(tickMethod, obfuscated);
        logger.info("Finished patching tick method of WorldServer");

        return writeClass(classNode);
    }

    private void patchWorldServerTick(MethodNode method, boolean obfuscated) {
        Iterator<AbstractInsnNode> iter = method.instructions.iterator();

        // Replace
        // > this.worldInfo.setWorldTime(this.worldInfo.getWorldTime() + 1L);
        // To
        // > TimeTweaksManager.addTick(this.worldInfo);
        int index = 0;
        while (iter.hasNext()) {
            AbstractInsnNode currentNode = iter.next();

            if (currentNode.getOpcode() == Opcodes.INVOKEVIRTUAL) {
                MethodInsnNode methodInsnNode = (MethodInsnNode) currentNode;
                if (WORLD_INFO_GET_WORLD_TIME.matchesNode(methodInsnNode, "()J", obfuscated)
                        && currentNode.getNext().getOpcode() == Opcodes.LCONST_1) {
                    // Found the call
                    index = method.instructions.indexOf(currentNode) - 2;
                }
            }
        }
        if (index == 0)
            error("Could not find call in WorldServer.tick method");

        method.instructions.remove(method.instructions.get(index));
        method.instructions.remove(method.instructions.get(index));
        method.instructions.remove(method.instructions.get(index));
        method.instructions.remove(method.instructions.get(index));
        method.instructions.remove(method.instructions.get(index));
        method.instructions.remove(method.instructions.get(index));
        method.instructions.insertBefore(method.instructions.get(index),
                new MethodInsnNode(Opcodes.INVOKESTATIC, "com/hea3ven/hardmodetweaks/TimeTweaksManager", "addTick",
                        "(L" + WORLD_INFO.getPath(obfuscated) + ";)V"));
    }

    //
    // ******************** WorldClient ********************
    //
    private byte[] patchWorldClient(String name, byte[] basicClass, boolean obfuscated) {
        ClassNode classNode = readClass(basicClass);

        logger.info("Looking for tick method of WorldClient");
        MethodNode tickMethod = getMethod(classNode, WORLD_CLIENT_TICK.get(obfuscated), "()V");
        if (tickMethod == null)
            error("Could not find the method");
        logger.info("Patching tick({}) method of WorldClient", tickMethod.name);
        patchWorldClientTick(tickMethod, obfuscated);
        logger.info("Finished patching tick method of WorldClient");

        return writeClass(classNode);
    }

    private void patchWorldClientTick(MethodNode method, boolean obfuscated) {
        Iterator<AbstractInsnNode> iter = method.instructions.iterator();

        // Replace
        // > this.setWorldTime(this.getWorldTime() + 1L);
        // To
        // > TimeTweaksManager.addTick(this.provider);
        int index = 0;
        while (iter.hasNext()) {
            AbstractInsnNode currentNode = iter.next();

            if (currentNode.getOpcode() == Opcodes.INVOKEVIRTUAL) {
                MethodInsnNode methodInsnNode = (MethodInsnNode) currentNode;
                if (WORLD_CLIENT_GET_WORLD_TIME.matchesNode(methodInsnNode, "()J", obfuscated)
                        && currentNode.getNext().getOpcode() == Opcodes.LCONST_1) {
                    index = method.instructions.indexOf(currentNode) - 1;
                }
            }
        }
        if (index == 0)
            error("Could not find call in WorldServer.tick method");

        method.instructions.remove(method.instructions.get(index));
        method.instructions.remove(method.instructions.get(index));
        method.instructions.remove(method.instructions.get(index));
        method.instructions.remove(method.instructions.get(index));
        method.instructions.remove(method.instructions.get(index));
        method.instructions.insertBefore(method.instructions.get(index),
                new FieldInsnNode(Opcodes.GETFIELD, WORLD.getPath(obfuscated), WORLD_PROVIDER.get(obfuscated),
                        "L" + WORLD_PROVIDER_CLASS.getPath(obfuscated) + ";"));
        method.instructions.insert(method.instructions.get(index),
                new MethodInsnNode(Opcodes.INVOKESTATIC, "com/hea3ven/hardmodetweaks/TimeTweaksManager", "addTick",
                        "(L" + WORLD_PROVIDER_CLASS.getPath(obfuscated) + ";)V"));
    }

    //
    // ******************** WorldInfo ********************
    //
    private byte[] patchWorldInfo(String name, byte[] basicClass, boolean obfuscated) {
        ClassNode classNode = readClass(basicClass);

        logger.info("Renaming getWorldTime method of WorldInfo to getRealWorldTime");
        MethodNode getWorldTimeMethod = getMethod(classNode, WORLD_INFO_GET_WORLD_TIME.get(obfuscated), "()J");
        if (getWorldTimeMethod == null)
            error("Could not find the method");
        String originalGetWorldTimeName = getWorldTimeMethod.name;
        getWorldTimeMethod.name = "getRealWorldTime";
        logger.info("Finished renaming getWorldTime method of WorldInfo");

        logger.info("Renaming setWorldTime method of WorldInfo to setRealWorldTime");
        MethodNode setWorldTimeMethod = getMethod(classNode, WORLD_INFO_SET_WORLD_TIME.get(obfuscated), "(J)V");
        if (setWorldTimeMethod == null)
            error("Could not find the method");
        String originalSetWorldTimeName = setWorldTimeMethod.name;
        setWorldTimeMethod.name = "setRealWorldTime";
        logger.info("Finished renaming setWorldTime method of WorldInfo");

        logger.info("Creating new implementation of getWorldTime({}) method of WorldInfo",
                originalGetWorldTimeName);
        classNode.methods.add(createNewGetWorldTimeMethod(originalGetWorldTimeName, obfuscated));
        logger.info("Finished adding getWorldTime method to WorldInfo");

        logger.info("Creating new implementation of setWorldTime({}) method of WorldInfo",
                originalSetWorldTimeName);
        classNode.methods.add(createNewSetWorldTimeMethod(originalSetWorldTimeName, obfuscated));
        logger.info("Finished adding setWorldTime method to WorldInfo");

        return writeClass(classNode);
    }

    private MethodNode createNewGetWorldTimeMethod(String methodName, boolean obfuscated) {
        // > long getWorldTime() {
        // >     return TimeTweaksManager.getWorldTime(this);
        // > }
        MethodNode getWorldTimeMethod = new MethodNode(Opcodes.ASM4, Opcodes.ACC_PUBLIC, methodName, "()J", null,
                null);
        getWorldTimeMethod.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
        getWorldTimeMethod.instructions
                .add(new MethodInsnNode(Opcodes.INVOKESTATIC, "com/hea3ven/hardmodetweaks/TimeTweaksManager",
                        "getWorldTime", "(L" + WORLD_INFO.getPath(obfuscated) + ";)J"));
        getWorldTimeMethod.instructions.add(new InsnNode(Opcodes.LRETURN));
        return getWorldTimeMethod;
    }

    private MethodNode createNewSetWorldTimeMethod(String methodName, boolean obfuscated) {
        // > void setWorldTime(long time) {
        // >     TimeTweaksManager.setWorldTime(this, time);
        // > }
        MethodNode setWorldTimeMethod = new MethodNode(Opcodes.ASM4, Opcodes.ACC_PUBLIC, methodName, "(J)V", null,
                null);
        setWorldTimeMethod.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
        setWorldTimeMethod.instructions.add(new VarInsnNode(Opcodes.LLOAD, 1));
        setWorldTimeMethod.instructions
                .add(new MethodInsnNode(Opcodes.INVOKESTATIC, "com/hea3ven/hardmodetweaks/TimeTweaksManager",
                        "setWorldTime", "(L" + WORLD_INFO.getPath(obfuscated) + ";J)V"));
        setWorldTimeMethod.instructions.add(new InsnNode(Opcodes.RETURN));
        return setWorldTimeMethod;
    }

    //
    // ******************** WorldProvider ********************
    //
    private byte[] patchWorldProvider(String name, byte[] basicClass, boolean obfuscated) {
        ClassNode classNode = readClass(basicClass);

        logger.info("Searching for the calculateCelestialAngle method of WorldProvider");
        MethodNode calcCelAngleMethod = getMethod(classNode, WORLD_PROVIDER_CALC_CEL_ANGLE.get(obfuscated),
                "(JF)F");
        if (calcCelAngleMethod == null)
            error("Could not find the method");
        classNode.methods.remove(calcCelAngleMethod);
        logger.info("Creating new implementation of calculateCelestialAngle({}) method of WorldProvider",
                WORLD_PROVIDER_CALC_CEL_ANGLE.get(obfuscated));
        classNode.methods
                .add(createNewCalcCelAngleMethod(WORLD_PROVIDER_CALC_CEL_ANGLE.get(obfuscated), obfuscated));
        logger.info("Finished adding calculateCelestialAngle method to WorldProvider");

        return writeClass(classNode);
    }

    private MethodNode createNewCalcCelAngleMethod(String methodName, boolean obfuscated) {
        // > float calculateCelestialAngle(long time, float off) {
        // >     return TimeTweaksManager.calculateCelestialAngle(time, off);
        // > }
        MethodNode getWorldTimeMethod = new MethodNode(Opcodes.ASM4, Opcodes.ACC_PUBLIC, methodName, "(JF)F", null,
                null);
        getWorldTimeMethod.instructions.add(new VarInsnNode(Opcodes.LLOAD, 1));
        getWorldTimeMethod.instructions.add(new VarInsnNode(Opcodes.FLOAD, 3));
        getWorldTimeMethod.instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
                "com/hea3ven/hardmodetweaks/TimeTweaksManager", "calculateCelestialAngle", "(JF)F"));
        getWorldTimeMethod.instructions.add(new InsnNode(Opcodes.FRETURN));
        return getWorldTimeMethod;
    }

    //
    // ******************** Utilities ********************
    //
    private ClassNode readClass(byte[] basicClass) {
        ClassNode classNode = new ClassNode();
        ClassReader classReader = new ClassReader(basicClass);
        classReader.accept(classNode, 0);
        return classNode;
    }

    private byte[] writeClass(ClassNode classNode) {
        ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        classNode.accept(writer);
        return writer.toByteArray();
    }

    private MethodNode getMethod(ClassNode classNode, String methodName, String methodDesc) {
        Iterator<MethodNode> methods = classNode.methods.iterator();
        while (methods.hasNext()) {
            MethodNode m = methods.next();
            if ((m.name.equals(methodName) && m.desc.equals(methodDesc)))
                return m;
        }
        return null;
    }

    private void error(String msg) {
        logger.error(msg);
        throw new RuntimeException(String.format("Failed to patch class: %s", msg));
    }

}