package com.bioxx.tfc2.asm.transform; import java.util.ArrayList; import java.util.HashMap; import net.minecraft.block.Block; import net.minecraft.init.Blocks; import net.minecraft.launchwrapper.IClassTransformer; import net.minecraft.nbt.NBTTagCompound; import com.bioxx.tfc2.ASMConstants; import com.bioxx.tfc2.TFCBlocks; import com.bioxx.tfc2.api.interfaces.IFoodStatsTFC; import com.bioxx.tfc2.api.types.EnumFoodGroup; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; import squeek.asmhelper.com.bioxx.tfc2.ASMHelper; import squeek.asmhelper.com.bioxx.tfc2.ObfHelper; public class ModuleFood implements IClassTransformer { @Override public byte[] transform(String name, String transformedName, byte[] basicClass) { ClassNode classNode = ASMHelper.readClassFromBytes(basicClass); if (transformedName.equals("net.minecraft.item.Item")) { String desc = ASMHelper.toMethodDescriptor("V",ObfHelper.toObfClassName(ASMConstants.ITEMSTACK), ObfHelper.toObfClassName(ASMConstants.PLAYER), ASMConstants.LIST, "Z"); MethodNode methodNode = ASMHelper.findMethodNodeOfClass(classNode, "a", "addInformation", desc); if (methodNode != null) { addInformationHook(classNode, methodNode); } else { //throw new RuntimeException("Item: addInformation (func_77624_a) method not found"); String msg = "Item: addInformation ("+ desc +") method not found! | "; for(MethodNode m : classNode.methods) { msg += m.name+"("+ m.desc +")"+", "; } throw new RuntimeException(msg); } return ASMHelper.writeClassToBytes(classNode); } else if (transformedName.equals("net.minecraft.item.ItemFood")) { classNode.interfaces.add(ASMHelper.toInternalClassName("com.bioxx.tfc2.api.interfaces.IFood")); MethodNode onDecayedMethod = new MethodNode(Opcodes.ACC_PUBLIC,"onDecayed",ASMHelper.toMethodDescriptor(ObfHelper.toObfClassName(ASMConstants.ITEMSTACK), ObfHelper.toObfClassName(ASMConstants.ITEMSTACK),ObfHelper.toObfClassName(ASMConstants.WORLD), "I","I","I"),null, null); onDecayedMethod.instructions.add(new InsnNode(Opcodes.ACONST_NULL)); onDecayedMethod.instructions.add(new InsnNode(Opcodes.ARETURN)); classNode.methods.add(onDecayedMethod); String methDesc = ASMHelper.toMethodDescriptor("V", ObfHelper.toObfClassName(ASMConstants.ITEM), ObfHelper.toObfClassName(ASMConstants.CREATIVETABS), "Ljava/util/List<"+ASMHelper.toDescriptor(ObfHelper.toObfClassName(ASMConstants.ITEMSTACK)+";>")); MethodNode addSubItemsMethod = new MethodNode(Opcodes.ACC_PUBLIC,"getSubItems",ASMHelper.toMethodDescriptor("V", ObfHelper.toObfClassName(ASMConstants.ITEM), ObfHelper.toObfClassName(ASMConstants.CREATIVETABS), ASMConstants.LIST),methDesc, null); AnnotationNode addSubAnnotation = new AnnotationNode("Lnet/minecraftforge/fml/relauncher/SideOnly;"); addSubAnnotation.visitEnum("value", "Lnet/minecraftforge/fml/relauncher/Side;", "CLIENT"); addSubItemsMethod.visibleAnnotations = new ArrayList<AnnotationNode>(); addSubItemsMethod.visibleAnnotations.add(addSubAnnotation); addSubItemsMethod.instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); addSubItemsMethod.instructions.add(new VarInsnNode(Opcodes.ALOAD, 2)); addSubItemsMethod.instructions.add(new VarInsnNode(Opcodes.ALOAD, 3)); addSubItemsMethod.instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "com/bioxx/tfc2/core/Food","getSubItems",ASMHelper.toMethodDescriptor("V",ObfHelper.toObfClassName(ASMConstants.ITEM), ObfHelper.toObfClassName(ASMConstants.CREATIVETABS), ASMConstants.LIST), false)); addSubItemsMethod.instructions.add(new InsnNode(Opcodes.RETURN)); classNode.methods.add(addSubItemsMethod); return ASMHelper.writeClassToBytes(classNode); } else if (transformedName.equals("net.minecraft.item.ItemFishFood")) { MethodNode methodNode = ASMHelper.findMethodNodeOfClass(classNode, "a", "getSubItems", ASMHelper.toMethodDescriptor("V",ObfHelper.toObfClassName(ASMConstants.ITEM), ObfHelper.toObfClassName(ASMConstants.CREATIVETABS), ASMConstants.LIST)); if (methodNode != null) { AbstractInsnNode finalNode = ASMHelper.findLastInstructionWithOpcode(methodNode, Opcodes.RETURN); InsnList toInject = new InsnList(); toInject.add(new VarInsnNode(Opcodes.ALOAD, 3)); toInject.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "com/bioxx/tfc2/core/Food","addDecayTimerForCreative",ASMHelper.toMethodDescriptor("V",ASMConstants.LIST), false)); methodNode.instructions.insertBefore(finalNode, toInject); } else throw new RuntimeException("ItemFishFood: getSubItems (a) method not found"); return ASMHelper.writeClassToBytes(classNode); } else if (transformedName.equals("net.minecraft.util.FoodStats")) { classNode.interfaces.add(ASMHelper.toInternalClassName("com.bioxx.tfc2.api.interfaces.IFoodStatsTFC")); String fieldNutritionMap = "nutritionMap"; String fieldWaterLevel = "waterLevel"; classNode.fields.add(new FieldNode(Opcodes.ACC_PUBLIC, fieldNutritionMap,"Ljava/util/HashMap;", "Ljava/util/HashMap<Lcom/bioxx/tfc2/api/types/EnumFoodGroup;Ljava/lang/Float;>;", null)); tryAddFieldGetter(classNode, "getNutritionMap", fieldNutritionMap, "Ljava/util/HashMap;"); classNode.fields.add(new FieldNode(Opcodes.ACC_PUBLIC, fieldWaterLevel,"F", null, null)); tryAddFieldGetter(classNode, "getWaterLevel", fieldWaterLevel, "F"); tryAddFieldSetter(classNode, "setWaterLevel", fieldWaterLevel, "F"); for (MethodNode method : classNode.methods) { if (method.name.equals("<init>")) { MethodNode defaultConstructor = ASMHelper.findMethodNodeOfClass(classNode, "<init>", ASMHelper.toMethodDescriptor("V")); if(defaultConstructor != null) { InsnList toInject = new InsnList(); toInject.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this toInject.add(new TypeInsnNode(Opcodes.NEW, "java/util/HashMap")); toInject.add(new InsnNode(Opcodes.DUP)); toInject.add(new MethodInsnNode(Opcodes.INVOKESPECIAL,"java/util/HashMap", "<init>", "()V", false)); toInject.add(new FieldInsnNode(Opcodes.PUTFIELD, classNode.name, fieldNutritionMap, "Ljava/util/HashMap;")); toInject.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this toInject.add(new LdcInsnNode(new Float(20f))); toInject.add(new FieldInsnNode(Opcodes.PUTFIELD, classNode.name, fieldWaterLevel, ASMHelper.toDescriptor("F"))); //Set Grain default Value toInject.add(new VarInsnNode(Opcodes.ALOAD, 0)); toInject.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, fieldNutritionMap, "Ljava/util/HashMap;")); toInject.add(new FieldInsnNode(Opcodes.GETSTATIC, ASMHelper.toInternalClassName(ASMConstants.ENUMFOODGROUP), "Grain", ASMHelper.toDescriptor(ASMConstants.ENUMFOODGROUP))); toInject.add(new LdcInsnNode(new Float(20f))); toInject.add(new MethodInsnNode(Opcodes.INVOKESTATIC,"java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false)); toInject.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,"java/util/HashMap", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", false)); toInject.add(new InsnNode(Opcodes.POP)); //Set Veg default Value toInject.add(new VarInsnNode(Opcodes.ALOAD, 0)); toInject.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, fieldNutritionMap, "Ljava/util/HashMap;")); toInject.add(new FieldInsnNode(Opcodes.GETSTATIC, ASMHelper.toInternalClassName(ASMConstants.ENUMFOODGROUP), "Vegetable", ASMHelper.toDescriptor(ASMConstants.ENUMFOODGROUP))); toInject.add(new LdcInsnNode(new Float(20f))); toInject.add(new MethodInsnNode(Opcodes.INVOKESTATIC,"java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false)); toInject.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,"java/util/HashMap", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", false)); toInject.add(new InsnNode(Opcodes.POP)); //Set Fruit default Value toInject.add(new VarInsnNode(Opcodes.ALOAD, 0)); toInject.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, fieldNutritionMap, "Ljava/util/HashMap;")); toInject.add(new FieldInsnNode(Opcodes.GETSTATIC, ASMHelper.toInternalClassName(ASMConstants.ENUMFOODGROUP), "Fruit", ASMHelper.toDescriptor(ASMConstants.ENUMFOODGROUP))); toInject.add(new LdcInsnNode(new Float(20f))); toInject.add(new MethodInsnNode(Opcodes.INVOKESTATIC,"java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false)); toInject.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,"java/util/HashMap", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", false)); toInject.add(new InsnNode(Opcodes.POP)); //Set Protein default Value toInject.add(new VarInsnNode(Opcodes.ALOAD, 0)); toInject.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, fieldNutritionMap, "Ljava/util/HashMap;")); toInject.add(new FieldInsnNode(Opcodes.GETSTATIC, ASMHelper.toInternalClassName(ASMConstants.ENUMFOODGROUP), "Protein", ASMHelper.toDescriptor(ASMConstants.ENUMFOODGROUP))); toInject.add(new LdcInsnNode(new Float(20f))); toInject.add(new MethodInsnNode(Opcodes.INVOKESTATIC,"java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false)); toInject.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,"java/util/HashMap", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", false)); toInject.add(new InsnNode(Opcodes.POP)); //Set Dairy default Value toInject.add(new VarInsnNode(Opcodes.ALOAD, 0)); toInject.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, fieldNutritionMap, "Ljava/util/HashMap;")); toInject.add(new FieldInsnNode(Opcodes.GETSTATIC, ASMHelper.toInternalClassName(ASMConstants.ENUMFOODGROUP), "Dairy", ASMHelper.toDescriptor(ASMConstants.ENUMFOODGROUP))); toInject.add(new LdcInsnNode(new Float(20f))); toInject.add(new MethodInsnNode(Opcodes.INVOKESTATIC,"java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false)); toInject.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,"java/util/HashMap", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", false)); toInject.add(new InsnNode(Opcodes.POP)); AbstractInsnNode finalNode = ASMHelper.findLastInstructionWithOpcode(defaultConstructor, Opcodes.RETURN); defaultConstructor.instructions.insertBefore(finalNode, toInject); } else throw new RuntimeException("FoodStats: defaultConstructor()V method not found"); } } MethodNode methodNode = ASMHelper.findMethodNodeOfClass(classNode, "a", "addStats", ASMHelper.toMethodDescriptor("V",ObfHelper.toObfClassName(ASMConstants.ITEM_FOOD), ObfHelper.toObfClassName(ASMConstants.ITEMSTACK))); if (methodNode != null) { AbstractInsnNode finalNode = ASMHelper.findLastInstructionWithOpcode(methodNode, Opcodes.INVOKEVIRTUAL); InsnList toInject = new InsnList(); toInject.add(new VarInsnNode(Opcodes.ALOAD, 0)); toInject.add(new VarInsnNode(Opcodes.ALOAD, 2)); toInject.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "com/bioxx/tfc2/core/Food","addNutrition",ASMHelper.toMethodDescriptor("V",ObfHelper.toObfClassName(ASMConstants.FOOD_STATS), ObfHelper.toObfClassName(ASMConstants.ITEMSTACK)), false)); methodNode.instructions.insert(finalNode, toInject); } else throw new RuntimeException("FoodStats: addStats (a) method not found"); methodNode = ASMHelper.findMethodNodeOfClass(classNode, "a", "readNBT", ASMHelper.toMethodDescriptor("V",ObfHelper.toObfClassName(ASMConstants.NBTTAGCOMPOUND))); if (methodNode != null) { AbstractInsnNode finalNode = ASMHelper.findLastInstructionWithOpcode(methodNode, Opcodes.PUTFIELD); InsnList toInject = new InsnList(); toInject.add(new VarInsnNode(Opcodes.ALOAD, 0)); toInject.add(new TypeInsnNode(Opcodes.CHECKCAST, "com/bioxx/tfc2/api/interfaces/IFoodStatsTFC")); toInject.add(new VarInsnNode(Opcodes.ALOAD, 1)); toInject.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "com/bioxx/tfc2/asm/transform/ModuleFood","readNBT",ASMHelper.toMethodDescriptor("V","Lcom/bioxx/tfc2/api/interfaces/IFoodStatsTFC;", ObfHelper.toObfClassName(ASMConstants.NBTTAGCOMPOUND) ), false)); methodNode.instructions.insert(finalNode, toInject); } else throw new RuntimeException("FoodStats: readNBT (a) method not found"); methodNode = ASMHelper.findMethodNodeOfClass(classNode, "b", "writeNBT", ASMHelper.toMethodDescriptor("V",ObfHelper.toObfClassName(ASMConstants.NBTTAGCOMPOUND))); if (methodNode != null) { AbstractInsnNode finalNode = ASMHelper.findLastInstructionWithOpcode(methodNode, Opcodes.INVOKEVIRTUAL); InsnList toInject = new InsnList(); toInject.add(new VarInsnNode(Opcodes.ALOAD, 0)); toInject.add(new TypeInsnNode(Opcodes.CHECKCAST, "com/bioxx/tfc2/api/interfaces/IFoodStatsTFC")); toInject.add(new VarInsnNode(Opcodes.ALOAD, 1)); toInject.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "com/bioxx/tfc2/asm/transform/ModuleFood","writeNBT",ASMHelper.toMethodDescriptor("V","Lcom/bioxx/tfc2/api/interfaces/IFoodStatsTFC;", ObfHelper.toObfClassName(ASMConstants.NBTTAGCOMPOUND) ), false)); methodNode.instructions.insert(finalNode, toInject); } else throw new RuntimeException("FoodStats: writeNBT (b) method not found"); return ASMHelper.writeClassToBytes(classNode); } else if (transformedName.equals("com.pam.harvestcraft.blocks.BlockPamCrop")) { MethodNode methodNode = ASMHelper.findMethodNodeOfClass(classNode, "isSuitableSoilBlock", "isSuitableSoilBlock", ASMHelper.toMethodDescriptor("Z",ObfHelper.toObfClassName(ASMConstants.BLOCK))); if(methodNode != null) { AbstractInsnNode finalNode = ASMHelper.findLastInstructionWithOpcode(methodNode, Opcodes.GETSTATIC); methodNode.instructions.insert(finalNode, new FieldInsnNode(Opcodes.GETSTATIC,"com/bioxx/tfc2/TFCBlocks", "Farmland", ObfHelper.toObfClassName(ASMConstants.BLOCK))); methodNode.instructions.remove(finalNode); } else throw new RuntimeException("BlockPamCrop: isSuitableSoilBlock method not found"); } /*else if (transformedName.equals("net.minecraft.item.ItemSeedFood")) { MethodNode methodNode = ASMHelper.findMethodNodeOfClass(classNode, "<init>", "<init>", ASMHelper.toMethodDescriptor("V","I", "F", ObfHelper.toObfClassName(ASMConstants.BLOCK), ObfHelper.toObfClassName(ASMConstants.BLOCK))); if(methodNode != null) { AbstractInsnNode finalNode = ASMHelper.find(methodNode.instructions, new VarInsnNode(Opcodes.ALOAD, 4)); methodNode.instructions.insert(finalNode, new FieldInsnNode(Opcodes.GETSTATIC,"com/bioxx/tfc2/TFCBlocks", "Farmland", ObfHelper.toObfClassName(ASMConstants.BLOCK))); methodNode.instructions.remove(finalNode); } else throw new RuntimeException("ItemSeedFood: <init>(IFLBlockBlock) method not found"); }*/ return basicClass; } public void init(int healAmount, float saturation, Block crops, Block soil) { if(soil == Blocks.FARMLAND) soil = TFCBlocks.Farmland; } private void addInformationHook(ClassNode classNode, MethodNode method) { InsnList list = new InsnList(); list.add(new VarInsnNode(Opcodes.ALOAD, 1)); list.add(new VarInsnNode(Opcodes.ALOAD, 2)); list.add(new VarInsnNode(Opcodes.ALOAD, 3)); list.add(new VarInsnNode(Opcodes.ALOAD, 0)); list.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "com/bioxx/tfc2/core/Food","addInformation",ASMHelper.toMethodDescriptor("V",ASMConstants.ITEMSTACK, ASMConstants.PLAYER, ASMConstants.LIST, ASMConstants.ITEM), false)); method.instructions.insert(list); } private boolean tryAddFieldGetter(ClassNode classNode, String methodName, String fieldName, String fieldDescriptor) { String methodDescriptor = ASMHelper.toMethodDescriptor(fieldDescriptor); if (ASMHelper.findMethodNodeOfClass(classNode, methodName, methodDescriptor) != null) return false; MethodVisitor mv = classNode.visitMethod(Opcodes.ACC_PUBLIC, methodName, methodDescriptor, null, null); mv.visitVarInsn(Opcodes.ALOAD, 0); mv.visitFieldInsn(Opcodes.GETFIELD, ASMHelper.toInternalClassName(classNode.name), fieldName, fieldDescriptor); mv.visitInsn(Type.getType(fieldDescriptor).getOpcode(Opcodes.IRETURN)); mv.visitMaxs(0, 0); return true; } private boolean tryAddFieldSetter(ClassNode classNode, String methodName, String fieldName, String fieldDescriptor) { String methodDescriptor = ASMHelper.toMethodDescriptor("V", fieldDescriptor); if (ASMHelper.findMethodNodeOfClass(classNode, methodName, methodDescriptor) != null) return false; MethodVisitor mv = classNode.visitMethod(Opcodes.ACC_PUBLIC, methodName, methodDescriptor, null, null); mv.visitVarInsn(Opcodes.ALOAD, 0); mv.visitVarInsn(Type.getType(fieldDescriptor).getOpcode(Opcodes.ILOAD), 1); mv.visitFieldInsn(Opcodes.PUTFIELD, ASMHelper.toInternalClassName(classNode.name), fieldName, fieldDescriptor); mv.visitInsn(Opcodes.RETURN); mv.visitMaxs(0, 0); return true; } public static void readNBT(IFoodStatsTFC fs, NBTTagCompound nbt) { HashMap map = fs.getNutritionMap(); map.put(EnumFoodGroup.Fruit, nbt.getFloat("fruitnutrition")); map.put(EnumFoodGroup.Vegetable, nbt.getFloat("vegetablenutrition")); map.put(EnumFoodGroup.Grain, nbt.getFloat("grainnutrition")); map.put(EnumFoodGroup.Protein, nbt.getFloat("proteinnutrition")); map.put(EnumFoodGroup.Dairy, nbt.getFloat("dairynutrition")); fs.setWaterLevel(nbt.getFloat("waterLevel")); } public static void writeNBT(IFoodStatsTFC fs, NBTTagCompound nbt) { nbt.setFloat("fruitnutrition", fs.getNutritionMap().get(EnumFoodGroup.Fruit)); nbt.setFloat("vegetablenutrition", fs.getNutritionMap().get(EnumFoodGroup.Vegetable)); nbt.setFloat("grainnutrition", fs.getNutritionMap().get(EnumFoodGroup.Grain)); nbt.setFloat("proteinnutrition", fs.getNutritionMap().get(EnumFoodGroup.Protein)); nbt.setFloat("dairynutrition", fs.getNutritionMap().get(EnumFoodGroup.Dairy)); nbt.setFloat("waterLevel", fs.getWaterLevel()); } }