I try to make a coremod on 1.12.2 Forge in order to patch some missing stuff in the Lost Cities mod. (Source: https://github.com/McJtyMods/LostCities/blob/1.12/src/main/java/mcjty/lostcities/dimensions/world/lost/BuildingInfo.java)
A friend and I have written this LostCitiesClassTransformer.java: (Full source: https://github.com/Nick1st/LCPatches)
package seemdmax.lcpatches; import static org.objectweb.asm.Opcodes.ALOAD; import static org.objectweb.asm.Opcodes.IFGT; import static org.objectweb.asm.Opcodes.IFLE; import static org.objectweb.asm.Opcodes.IFNE; import static org.objectweb.asm.Opcodes.ILOAD; import static org.objectweb.asm.Opcodes.INVOKEINTERFACE; import static org.objectweb.asm.Opcodes.INVOKESTATIC; import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; import static org.objectweb.asm.Opcodes.GETFIELD; import java.util.Arrays; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.InsnList; import org.objectweb.asm.tree.JumpInsnNode; import org.objectweb.asm.tree.LabelNode; import org.objectweb.asm.tree.LdcInsnNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.VarInsnNode; import mcjty.lostcities.api.ILostCityBuilding; import net.minecraft.launchwrapper.IClassTransformer; public class LostCitiesClassTransformer implements IClassTransformer { private static final String[] classesBeingTransformed = { "mcjty.lostcities.dimensions.world.lost.BuildingInfo", "mcjty.lostcities.dimensions.world.terraingen.LostCitiesTerrainGenerator"}; @Override public byte[] transform(String name, String transformedName, byte[] classBeingTransformed) { boolean isObfuscated = !name.equals(transformedName); int index = Arrays.asList(classesBeingTransformed).indexOf(transformedName); return index != -1 ? transform(index, classBeingTransformed, isObfuscated) : classBeingTransformed; } private static byte[] transform(int index, byte[] classBeingTransformed, boolean isObfuscated) { System.out.println("Transform " + classesBeingTransformed[index] + " got called!"); try { ClassNode classNode = new ClassNode(); ClassReader classReader = new ClassReader(classBeingTransformed); classReader.accept(classNode, 2); System.out.println("Transforming " + classesBeingTransformed[index] + " Is Obf: " + isObfuscated); switch (index) { case 0: transformLCCellars(classNode, isObfuscated); break; case 1: transformBuildingBorders(classNode, isObfuscated); break; } ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS /*| ClassWriter.COMPUTE_FRAMES*/); classNode.accept(classWriter); return classWriter.toByteArray(); } catch (Exception e) { e.printStackTrace(); } return classBeingTransformed; } private static void transformBuildingBorders(ClassNode terrainGenClass, boolean isObfuscated) { final String METHOD = isObfuscated ? "generateBuilding" : "generateBuilding"; final String METHOD_DESC = isObfuscated ? "(Lmcjty/lostcities/dimensions/world/lost/BuildingInfo;Lmcjty/lostcities/dimensions/world/ChunkHeightmap;)V" : "(Lmcjty/lostcities/dimensions/world/lost/BuildingInfo;Lmcjty/lostcities/dimensions/world/ChunkHeightmap;)V"; for (MethodNode method : terrainGenClass.methods) { if (method.name.equals(METHOD) && method.desc.equals(METHOD_DESC)) { System.out.println("Found method in BuildingInfo to transform"); AbstractInsnNode targetNode = null; for (AbstractInsnNode instruction : method.instructions.toArray()) { if (instruction.getOpcode() == ALOAD) { if (((VarInsnNode) instruction).var == 1 & instruction.getNext().getOpcode() == GETFIELD & instruction.getNext().getNext().getOpcode() == IFLE) { System.out.println("Matched"); targetNode = instruction.getNext().getNext(); break; } } } if (targetNode != null) { System.out.println("Target Node valid"); InsnList toInsert = new InsnList(); toInsert.add(new VarInsnNode(ALOAD, 1)); toInsert.add( new MethodInsnNode(INVOKEVIRTUAL, "mcjty/lostcities/dimensions/world/lost/BuildingInfo", "floorsBelowGround", "I", false)); toInsert.add( new MethodInsnNode(INVOKEVIRTUAL, "seemdmax/lcpatches/Constants", "getKey", "()Ljava/lang/String;", false)); toInsert.add( new MethodInsnNode(INVOKEVIRTUAL, "java/lang/String", "endsWith", "(Ljava/lang/String;)Z", false)); toInsert.add(new JumpInsnNode(IFNE, ((JumpInsnNode) targetNode).label)); method.instructions.insert(targetNode, toInsert); System.out.println("Transform done!"); } else { System.out.println("Something went wrong transforming LostCitiesTerrainGenerator!"); } } } } private static void transformLCCellars(ClassNode buildingInfoClass, boolean isObfuscated) { //This is already working } }
The original code I want to patch (Bytecode Outline):
L55 LINENUMBER 2338 L55 FRAME CHOP 3 ALOAD 1 GETFIELD mcjty/lostcities/dimensions/world/lost/BuildingInfo.floorsBelowGround : I IFLE L68
The Bytecode Outline containing my changes:
L55 LINENUMBER 2338 L55 FRAME CHOP 3 ALOAD 1 GETFIELD mcjty/lostcities/dimensions/world/lost/BuildingInfo.floorsBelowGround : I IFLE L68 ALOAD 1 INVOKEVIRTUAL mcjty/lostcities/dimensions/world/lost/BuildingInfo.getBuildingType ()Ljava/lang/String; LDC "#NOBORDER" INVOKEVIRTUAL java/lang/String.endsWith (Ljava/lang/String;)Z IFNE L68
The stacktrace I get:
java.lang.StringIndexOutOfBoundsException: String index out of range: 1 at java.lang.String.charAt(String.java:658) at org.objectweb.asm.Type.getArgumentsAndReturnSizes(Type.java:420) at org.objectweb.asm.MethodWriter.visitMethodInsn(MethodWriter.java:931) at org.objectweb.asm.tree.MethodInsnNode.accept(MethodInsnNode.java:133) at org.objectweb.asm.tree.InsnList.accept(InsnList.java:162) at org.objectweb.asm.tree.MethodNode.accept(MethodNode.java:817) at org.objectweb.asm.tree.MethodNode.accept(MethodNode.java:727) at org.objectweb.asm.tree.ClassNode.accept(ClassNode.java:412) at seemdmax.lcpatches.LostCitiesClassTransformer.transform(LostCitiesClassTransformer.java:59) at seemdmax.lcpatches.LostCitiesClassTransformer.transform(LostCitiesClassTransformer.java:38) at net.minecraftforge.fml.common.asm.ASMTransformerWrapper$TransformerWrapper.transform(ASMTransformerWrapper.java:256) at net.minecraft.launchwrapper.LaunchClassLoader.runTransformers(LaunchClassLoader.java:279) at net.minecraft.launchwrapper.LaunchClassLoader.findClass(LaunchClassLoader.java:176) at java.lang.ClassLoader.loadClass(ClassLoader.java:418) at java.lang.ClassLoader.loadClass(ClassLoader.java:351) at mcjty.lostcities.dimensions.world.LostCityChunkGenerator.<init>(LostCityChunkGenerator.java:170) at mcjty.lostcities.dimensions.world.LostWorldType.getChunkGenerator(LostWorldType.java:55) at net.minecraft.world.WorldProvider.createChunkGenerator(WorldProvider.java:86) at net.minecraft.world.WorldServer.createChunkProvider(WorldServer.java:890) at net.minecraft.world.WorldServer.<init>(WorldServer.java:124) at net.minecraft.server.integrated.IntegratedServer.loadAllWorlds(IntegratedServer.java:122) at net.minecraft.server.integrated.IntegratedServer.init(IntegratedServer.java:160) at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:552) at java.lang.Thread.run(Thread.java:748)
Why do I monkey-patch a open-source mod? I need it for a modpack; Curseforge doesn’t allow modified jars in modpacks. McJty said, that he don’t want a second LC Project, but he’s fine with patching LC using coremods/mixins etc.
Any help would be appreciated. If you need to know more details, feel free to ask. Thanks in advance.
Advertisement
Answer
Here’s the problem:
toInsert.add( new MethodInsnNode(INVOKEVIRTUAL, "mcjty/lostcities/dimensions/world/lost/BuildingInfo", "floorsBelowGround", "I", false));
You’re trying to call floorsBelowGround
like it’s a method, but it’s actually a field. You need to make a GETFIELD
FieldInsnNode
instead of an INVOKEVIRTUAL
MethodInsnNode
.