Minecraft modifying private variables

92 Views Asked by At

I'm trying to write a Forge mod for Minecraft 1.12.2. I want to make modifications to the main menu (which some mods do, so I know it's possible) but the things that I want to change (which again, other mods do) are private variables. The I'm doing this is by extending GuiMainMenu, but I'm having trouble with private variables. In particular, I want to bind GuiMainMenu.MINECRAFT_TITLE_TEXTURE (which is a private static final) to the texture manager so that I can draw the minecraft title texture like in the vanilla main menu, and I want to add splash text options (like so: this.splashText = "example_splash";). GuiMainMenu.splashText is a private String

Looking around thus far, it seems like people use reflection to get around this kind of issue, and that's what I've tried, with code like:

    private Field splashTextField;
    private static Field minecraftTitleTextureField;

    static {
        initField("MINECRAFT_TITLE_TEXTURES", minecraftTitleTextureField);
    }

    private static void initField(String fieldName, Field destinationField) {
        try {
            // Accessing private variable using reflection
            destinationField = GuiMainMenu.class.getDeclaredField(fieldName);
            destinationField.setAccessible(true);
        } catch (NoSuchFieldException e) {
            e.printStackTrace(); // Handle the exception appropriately
        }
    }

    // Wrapper methods to handle IllegalAccessException
    private static void setFieldValue(Object target, Field field, Object value) {
        try {
            field.set(target, value);
        } catch (IllegalAccessException e) {
            e.printStackTrace(); // Handle the exception appropriately
        }
    }

    private static Object getFieldValue(Object target, Field field) {
        try {
            return field.get(target);
        } catch (IllegalAccessException e) {
            e.printStackTrace(); // Handle the exception appropriately
            return null;
        }
    }

But when I do this, I get NullPointerExceptions when I try to do anything with either of these variables. What am I doing wrong?

1

There are 1 best solutions below

0
Liam Clink On

The commenters already gave good explanation on how to fix using reflection myself, but for posterity, I've found out that the usual way to do this for minecraft forge mod development is to use access transformers, which modifies access at runtime.

This involves adding accesstransformer.cfg to the src/main/resources/META-INF/ folder, and adding lines as specified on the github, these docs or these docs. Then, the Access Transformer needs to be enabled in the build.gradle file by adding to the minecraft block:

minecraft {
    //// stuff before
    accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg')
    //// stuff after
}

But, if you only do this, it will compile, and it will probably run ok when you do ./gradlew runClient, but when you copy the mod .jar file into your mods folder, it will not work. This is because the forge modloader needs to be told that there's an Access Transformer file, and what it's called, so that it knows to do the appropriate modification of access in runtime. This is necessary because the access transformation is done by forge, not by the mod itself. Building works with only the addition to the minecraft{} block because that does the necessary changes to the build environment for building to succeed. It's kind of like how in Linux, you have the linker and ld.

So, to get it to work at runtime, you need to add the access transformer file to the manifest, which should be done via the build.gradle file like so:

jar {
    manifest {
        attributes([
                "Specification-Title"     : "sometitle",
                "FMLAT": 'accesstransformer.cfg' // The name of the accesstransformer file
        ])
    }
}

FMLAT stands for Forge ModLoader Access Transformer.