package com.austinv11.collectiveframework.minecraft.asm; import com.austinv11.collectiveframework.minecraft.CollectiveFramework; import net.minecraft.launchwrapper.IClassTransformer; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.*; import java.util.Iterator; public class Transformer implements IClassTransformer, Opcodes { public static boolean didCheck = false; public static boolean isDev = false; private static int procreationHookCounter = 1; @Override public byte[] transform(String className, String newClassName, byte[] byteCode) { if (className.equals("net.minecraft.client.gui.FontRenderer")) { CollectiveFramework.LOGGER.info("Applying color code patch"); return transformFontRenderer(byteCode); } else if (className.equals("net.minecraft.client.renderer.entity.RenderEnchantmentTable")) { CollectiveFramework.LOGGER.info("Hooking into RenderEnchantmentTable#renderTileEntityAt(Lnet/minecraft/tileentity/TileEntityEnchantmentTable;DDDF)V"); return transformRenderEnchantmentTable(byteCode); } else if (className.equals("net.minecraft.client.gui.GuiMainMenu")) { CollectiveFramework.LOGGER.info("Hooking into GuiMainMenu#initGui()V"); return transformGuiMainMenu(byteCode); } else if (className.equals("net.minecraft.entity.passive.EntityAnimal") || //Why the hell is this handled in two separate places? className.equals("net.minecraft.entity.ai.EntityAIMate")) { CollectiveFramework.LOGGER.info("Adding hooks procreation hooks ("+procreationHookCounter+"/2)"); procreationHookCounter++; return transformProcreation(byteCode); } return byteCode; } private byte[] transformRenderEnchantmentTable(byte[] byteCode) { ClassNode classNode = new ClassNode(); ClassReader classReader = new ClassReader(byteCode); classReader.accept(classNode, 0); for (MethodNode m : classNode.methods) if (checkDeobfAndObfNames(m.name, "renderTileEntityAt", "func_147500_a")) if (m.desc.contains("TileEntityEnchantmentTable")) { Iterator<AbstractInsnNode> nodes = m.instructions.iterator(); while (nodes.hasNext()) { AbstractInsnNode node = nodes.next(); if (node.getOpcode() == GETSTATIC) { InsnList instructions = new InsnList(); instructions.add(new VarInsnNode(ALOAD, 1)); instructions.add(new MethodInsnNode(INVOKESTATIC, "com/austinv11/collectiveframework/minecraft/hooks/ClientHooks", "getBookTexture", "(Lnet/minecraft/tileentity/TileEntityEnchantmentTable;)Lnet/minecraft/util/ResourceLocation;", false)); instructions.add(new VarInsnNode(ASTORE, 7)); instructions.add(new VarInsnNode(ALOAD, 7)); m.instructions.insertBefore(node, instructions); m.instructions.remove(node); } } } ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); classNode.accept(writer); return writer.toByteArray(); } //Adding a useful feature from the 'Essentials' bukkit plugin, allowing the use of '&' for color codes //See http://ess.khhq.net/mc/ private byte[] transformFontRenderer(byte[] byteCode) { ClassNode classNode = new ClassNode(); ClassReader classReader = new ClassReader(byteCode); classReader.accept(classNode, 0); for (MethodNode m : classNode.methods) { if (checkDeobfAndObfNames(m.name, "renderStringAtPos", "func_78255_a")) { InsnList instructions = new InsnList(); instructions.add(new VarInsnNode(ALOAD, 1)); instructions.add(new MethodInsnNode(INVOKESTATIC, "com/austinv11/collectiveframework/minecraft/hooks/ClientHooks", "getStringToRender", "(Ljava/lang/String;)Ljava/lang/String;", false)); instructions.add(new VarInsnNode(ASTORE, 1)); m.instructions.insert(instructions); break; } } ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); classNode.accept(writer); return writer.toByteArray(); } private byte[] transformGuiMainMenu(byte[] byteCode) { ClassNode classNode = new ClassNode(); ClassReader classReader = new ClassReader(byteCode); classReader.accept(classNode, 0); for (MethodNode m : classNode.methods) { if (checkDeobfAndObfNames(m.name, "initGui", "func_73866_w_")) { InsnList instructions = new InsnList(); instructions.add(new MethodInsnNode(INVOKESTATIC, "com/austinv11/collectiveframework/minecraft/hooks/ClientHooks", "click", "()V", false)); m.instructions.insert(instructions); break; } } ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); classNode.accept(writer); return writer.toByteArray(); } private byte[] transformProcreation(byte[] byteCode) { ClassNode classNode = new ClassNode(); ClassReader classReader = new ClassReader(byteCode); classReader.accept(classNode, 0); int invokeVirtualCounter = 0; for (MethodNode m : classNode.methods) { if (checkDeobfAndObfNames(m.name, "procreate", "func_70876_c")) { Iterator<AbstractInsnNode> nodes = m.instructions.iterator(); while (nodes.hasNext()) { AbstractInsnNode node = nodes.next(); if (node.getOpcode() == ASTORE) { InsnList instructions = new InsnList(); instructions.add(new VarInsnNode(ALOAD, 2)); instructions.add(new VarInsnNode(ALOAD, 0)); instructions.add(new VarInsnNode(ALOAD, 1)); instructions.add(new MethodInsnNode(INVOKESTATIC, "com/austinv11/collectiveframework/minecraft/hooks/CommonHooks", "procreatePre", "(Lnet/minecraft/entity/EntityAgeable;Lnet/minecraft/entity/passive/EntityAnimal;Lnet/minecraft/entity/passive/EntityAnimal;)Lnet/minecraft/entity/EntityAgeable;", false)); instructions.add(new VarInsnNode(ASTORE, 2)); m.instructions.insert(node, instructions); } else if (node.getOpcode() == INVOKEVIRTUAL) { if (invokeVirtualCounter != 9) { invokeVirtualCounter++; } else { InsnList instructions = new InsnList(); instructions.add(new VarInsnNode(ALOAD, 2)); instructions.add(new VarInsnNode(ALOAD, 0)); instructions.add(new VarInsnNode(ALOAD, 1)); instructions.add(new MethodInsnNode(INVOKESTATIC, "com/austinv11/collectiveframework/minecraft/hooks/CommonHooks", "procreatePost", "(Lnet/minecraft/entity/EntityAgeable;Lnet/minecraft/entity/passive/EntityAnimal;Lnet/minecraft/entity/passive/EntityAnimal;)V", false)); m.instructions.insert(node, instructions); } } } break; } else if (checkDeobfAndObfNames(m.name, "spawnBaby", "func_75388_i")) { Iterator<AbstractInsnNode> nodes = m.instructions.iterator(); while (nodes.hasNext()) { AbstractInsnNode node = nodes.next(); if (node.getOpcode() == ASTORE) { InsnList instructions = new InsnList(); instructions.add(new VarInsnNode(ALOAD, 1)); instructions.add(new VarInsnNode(ALOAD, 0)); instructions.add(new FieldInsnNode(GETFIELD, "net/minecraft/entity/ai/EntityAIMate", "targetMate", "Lnet/minecraft/entity/passive/EntityAnimal;")); instructions.add(new VarInsnNode(ALOAD, 0)); instructions.add(new FieldInsnNode(GETFIELD, "net/minecraft/entity/ai/EntityAIMate", "targetMate", "Lnet/minecraft/entity/passive/EntityAnimal;")); instructions.add(new MethodInsnNode(INVOKESTATIC, "com/austinv11/collectiveframework/minecraft/hooks/CommonHooks", "procreatePre", "(Lnet/minecraft/entity/EntityAgeable;Lnet/minecraft/entity/passive/EntityAnimal;Lnet/minecraft/entity/passive/EntityAnimal;)Lnet/minecraft/entity/EntityAgeable;", false)); instructions.add(new VarInsnNode(ASTORE, 1)); m.instructions.insert(node, instructions); } else if (node.getOpcode() == INVOKEVIRTUAL) { if (invokeVirtualCounter != 9) { invokeVirtualCounter++; } else { InsnList instructions = new InsnList(); instructions.add(new VarInsnNode(ALOAD, 1)); instructions.add(new VarInsnNode(ALOAD, 0)); instructions.add(new FieldInsnNode(GETFIELD, "net/minecraft/entity/ai/EntityAIMate", "targetMate", "Lnet/minecraft/entity/passive/EntityAnimal;")); instructions.add(new VarInsnNode(ALOAD, 0)); instructions.add(new FieldInsnNode(GETFIELD, "net/minecraft/entity/ai/EntityAIMate", "targetMate", "Lnet/minecraft/entity/passive/EntityAnimal;")); instructions.add(new MethodInsnNode(INVOKESTATIC, "com/austinv11/collectiveframework/minecraft/hooks/CommonHooks", "procreatePost", "(Lnet/minecraft/entity/EntityAgeable;Lnet/minecraft/entity/passive/EntityAnimal;Lnet/minecraft/entity/passive/EntityAnimal;)V", false)); m.instructions.insert(node, instructions); } } } break; } } ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); classNode.accept(writer); return writer.toByteArray(); } private boolean checkDeobfAndObfNames(String input, String deobf, String obf) { if (!didCheck && (input.equals(deobf) || input.equals(obf))) { didCheck = true; if (input.equals(deobf)) { isDev = true; } else { isDev = false; } } return input.equals(deobf) || input.equals(obf); } }