package com.dynious.biota.asm.transformers; import com.dynious.biota.api.IPlant; import com.dynious.biota.asm.Hooks; import com.dynious.biota.asm.ITransformer; import com.dynious.biota.asm.MethodFieldObfHelper; import com.dynious.biota.asm.PlantTransformerConfig; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; import squeek.asmhelper.applecore.ObfHelper; import squeek.asmhelper.applecore.ObfRemappingClassWriter; import java.util.Iterator; import static org.objectweb.asm.Opcodes.*; public class PlantTransformer implements ITransformer { private final static String ADDED = ObfHelper.isObfuscated() ? MethodFieldObfHelper.method("net.minecraft.block.Block", "func_149726_b") : "onBlockAdded"; private final static String ADDED_DESC = ObfHelper.desc("(Lnet/minecraft/world/World;III)V"); private final static String REMOVED = ObfHelper.isObfuscated() ? MethodFieldObfHelper.method("net.minecraft.block.Block", "func_149749_a") : "breakBlock"; private final static String REMOVED_DESC = ObfHelper.desc("(Lnet/minecraft/world/World;IIILnet/minecraft/block/Block;I)V"); private final static String COLOR = ObfHelper.isObfuscated() ? MethodFieldObfHelper.method("net.minecraft.block.Block", "func_149720_d") : "colorMultiplier"; private final static String COLOR_DESC = ObfHelper.desc("(Lnet/minecraft/world/IBlockAccess;III)I"); private final static String TICK = ObfHelper.isObfuscated() ? MethodFieldObfHelper.method("net.minecraft.block.Block", "func_149674_a") : "updateTick"; private final static String TICK_DESC = ObfHelper.desc("(Lnet/minecraft/world/World;IIILjava/util/Random;)V"); @Override public String[] getClasses() { return PlantTransformerConfig.INSTANCE.getPlantClassNames(); } @Override public byte[] transform(String transformedName, byte[] clazz) { //Biota.logger.debug("Transforming: " + transformedName); ClassNode classNode = new ClassNode(); ClassReader classReader = new ClassReader(clazz); classReader.accept(classNode, 0); //Add IPlant interface for easy plant checking classNode.interfaces.add(Type.getInternalName(IPlant.class)); boolean shouldChangeColor = PlantTransformerConfig.INSTANCE.shouldPlantChangeColor(transformedName); boolean foundAdded = false; boolean foundRemoved = false; boolean foundColor = false; boolean foundTick = false; for (MethodNode methodNode : classNode.methods) { if (methodNode.name.equals(ADDED) && methodNode.desc.equals(ADDED_DESC)) { foundAdded = true; InsnList list = new InsnList(); list.add(new VarInsnNode(ALOAD, 0)); list.add(new VarInsnNode(ALOAD, 1)); list.add(new VarInsnNode(ILOAD, 2)); list.add(new VarInsnNode(ILOAD, 3)); list.add(new VarInsnNode(ILOAD, 4)); list.add(new MethodInsnNode(INVOKESTATIC, Type.getInternalName(Hooks.class), "onPlantBlockAdded", ObfHelper.desc("(Lnet/minecraft/block/Block;Lnet/minecraft/world/World;III)V"), false)); methodNode.instructions.insert(list); } else if (methodNode.name.equals(REMOVED) && methodNode.desc.equals(REMOVED_DESC)) { foundRemoved = true; InsnList list = new InsnList(); list.add(new VarInsnNode(ALOAD, 0)); list.add(new VarInsnNode(ALOAD, 1)); list.add(new VarInsnNode(ILOAD, 2)); list.add(new VarInsnNode(ILOAD, 3)); list.add(new VarInsnNode(ILOAD, 4)); list.add(new MethodInsnNode(INVOKESTATIC, Type.getInternalName(Hooks.class), "onPlantBlockRemoved", ObfHelper.desc("(Lnet/minecraft/block/Block;Lnet/minecraft/world/World;III)V"), false)); methodNode.instructions.insert(list); } else if (shouldChangeColor && methodNode.name.equals(COLOR) && methodNode.desc.equals(COLOR_DESC)) { foundColor = true; Iterator<AbstractInsnNode> iterator = methodNode.instructions.iterator(); while(iterator.hasNext()) { AbstractInsnNode node = iterator.next(); if (node.getOpcode() == IRETURN) { InsnList list = new InsnList(); list.add(new VarInsnNode(ILOAD, 2)); list.add(new VarInsnNode(ILOAD, 4)); list.add(new MethodInsnNode(INVOKESTATIC, Type.getInternalName(Hooks.class), "getColor", ObfHelper.desc("(III)I"), false)); methodNode.instructions.insertBefore(node, list); } } } else if (methodNode.name.equals(TICK) && methodNode.desc.equals(TICK_DESC)) { foundTick = true; InsnList list = new InsnList(); list.add(new VarInsnNode(ALOAD, 0)); list.add(new VarInsnNode(ALOAD, 1)); list.add(new VarInsnNode(ILOAD, 2)); list.add(new VarInsnNode(ILOAD, 3)); list.add(new VarInsnNode(ILOAD, 4)); list.add(new MethodInsnNode(INVOKESTATIC, Type.getInternalName(Hooks.class), "onPlantTick", ObfHelper.desc("(Lnet/minecraft/block/Block;Lnet/minecraft/world/World;III)V"), false)); methodNode.instructions.insert(list); } } if (!foundAdded) { //Override added method MethodVisitor mv = classNode.visitMethod(ACC_PUBLIC, ADDED, ADDED_DESC, null, null); //Call our method mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); mv.visitVarInsn(ILOAD, 2); mv.visitVarInsn(ILOAD, 3); mv.visitVarInsn(ILOAD, 4); mv.visitMethodInsn(INVOKESTATIC, Type.getInternalName(Hooks.class), "onPlantBlockAdded", ObfHelper.desc("(Lnet/minecraft/block/Block;Lnet/minecraft/world/World;III)V"), false); //Call super mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); mv.visitVarInsn(ILOAD, 2); mv.visitVarInsn(ILOAD, 3); mv.visitVarInsn(ILOAD, 4); mv.visitMethodInsn(INVOKESPECIAL, classNode.superName, ADDED, ADDED_DESC, false); //Return mv.visitInsn(RETURN); mv.visitMaxs(0, 0); } if (!foundRemoved) { //Override removed method MethodVisitor mv = classNode.visitMethod(ACC_PUBLIC, REMOVED, REMOVED_DESC, null, null); //Call our method mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); mv.visitVarInsn(ILOAD, 2); mv.visitVarInsn(ILOAD, 3); mv.visitVarInsn(ILOAD, 4); mv.visitMethodInsn(INVOKESTATIC, Type.getInternalName(Hooks.class), "onPlantBlockRemoved", ObfHelper.desc("(Lnet/minecraft/block/Block;Lnet/minecraft/world/World;III)V"), false); //Call super mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); mv.visitVarInsn(ILOAD, 2); mv.visitVarInsn(ILOAD, 3); mv.visitVarInsn(ILOAD, 4); mv.visitVarInsn(ALOAD, 5); mv.visitVarInsn(ILOAD, 6); mv.visitMethodInsn(INVOKESPECIAL, classNode.superName, REMOVED, REMOVED_DESC, false); //Return mv.visitInsn(RETURN); mv.visitMaxs(0, 0); } if (shouldChangeColor && !foundColor) { MethodVisitor mv = classNode.visitMethod(ACC_PUBLIC, COLOR, COLOR_DESC, null, null); //Call super and get color int mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); mv.visitVarInsn(ILOAD, 2); mv.visitVarInsn(ILOAD, 3); mv.visitVarInsn(ILOAD, 4); mv.visitMethodInsn(INVOKESPECIAL, classNode.superName, COLOR, COLOR_DESC, false); //Call our color change method mv.visitVarInsn(ILOAD, 2); mv.visitVarInsn(ILOAD, 4); mv.visitMethodInsn(INVOKESTATIC, Type.getInternalName(Hooks.class), "getColor", "(III)I", false); //Return altered color mv.visitInsn(IRETURN); mv.visitMaxs(0, 0); } if (!foundTick) { MethodVisitor mv = classNode.visitMethod(ACC_PUBLIC, TICK, TICK_DESC, null, null); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); mv.visitVarInsn(ILOAD, 2); mv.visitVarInsn(ILOAD, 3); mv.visitVarInsn(ILOAD, 4); mv.visitMethodInsn(INVOKESTATIC, Type.getInternalName(Hooks.class), "onPlantTick", ObfHelper.desc("(Lnet/minecraft/block/Block;Lnet/minecraft/world/World;III)V"), false); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); mv.visitVarInsn(ILOAD, 2); mv.visitVarInsn(ILOAD, 3); mv.visitVarInsn(ILOAD, 4); mv.visitVarInsn(ALOAD, 5); mv.visitMethodInsn(INVOKESPECIAL, classNode.superName, TICK, TICK_DESC, false); mv.visitInsn(RETURN); mv.visitMaxs(0, 0); } ClassWriter classWriter = new ObfRemappingClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); classNode.accept(classWriter); return classWriter.toByteArray(); } }