Forge 1.12.2 Coremodding: Java. Lang. Stringindexoutofboundsexception

Forge 1.12.2 Coremodding: Java. Lang. Stringindexoutofboundsexception

I try to make a coremod on 1.12.2 Forge in order to patch some missing stuff in the Lost Cities mod. (Source: )

A friend and I have written this LostCitiesClassTransformer.java: (Full source: )

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.

1 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.

1

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Robert Thorne
Author

Robert Thorne

Robert Thorne covers electric vehicle innovations, autonomous driving systems, global mobility trends, and automotive engineering developments.