Skip to content
youyihj edited this page Dec 9, 2024 · 8 revisions

Mixin

@since 1.20.0

Mixin is a framework to hook into runtime class loading to modify java code. ZenUtils allows to use mixin by zenscript.

The feature is disabled by default. You should enable it in config first.

Create a mixin script

All mixin scripts are loaded by loader mixin. (i.e. #loader mixin) They are not reloadable certainly. Mixin relies a class and annotations. So we need to write zenClass and annotation mechanism. ZenUtils adds a preprocessor to resolve annotation.

Example:

#loader mixin

#mixin Mixin
#{targets: "zmaster587.libVulpes.tile.energy.TilePlugBase"}
zenClass MixinTilePlugBase {
    #mixin ModifyConstant
    #{
    #    method: "getMaxEnergy",
    #    constant: {intValue: 10000}
    #}
    function modifyMaxEnergyModifier(value as int) as int {
        return 20000000;
    }
}

The #mixin NAME is the header of mixin preprocessor, to define which annotation. A json after the header line describes the annotation contents (can be omitted). It still is considered as a preprocessor, so # is still required to be put at the start of every line. Then we write a function or a field after the json. There cannot be an empty line between the three parts.

The example is translated to such java code.

@Mixin(targets = "zmaster587.libVulpes.tile.energy.TilePlugBase")
public class MixinTilePlugBase {
    @ModifyConstant(method = "getMaxEnergy", constant = @Constant(intValue = 10000))
    private int modifyMaxEnergyModifier(int value) {
        return 20000000;
    }
}

Since v1.21.2, these three parts can be compressed to single line. #mixin Mixin can be shortened to #mixin.

#loader mixin

// after v1.21.2
#mixin {targets: "zmaster587.libVulpes.tile.energy.TilePlugBase"}
zenClass MixinTilePlugBase {
    #mixin ModifyConstant {method: "getMaxEnergy", constant: {intValue: 10000}}
    function modifyMaxEnergyModifier(value as int) as int {
        return 20000000;
    }
}

Static functions

Some functions are required to be static by mixin. But zenscript doesn't allow static functions in zenClass. Add #mixin Static to mark the function is static.

Example:

#loader mixin

#mixin Mixin
#{targets: "blusunrize.immersiveengineering.common.blocks.metal.TileEntitySilo"}
zenClass MixinTileEntitySilo {
    #mixin Static
    #mixin ModifyConstant
    #{
    #    method: "<clinit>",
    #    constant: {intValue: 41472}
    #}
    function buffStorage(value as int) as int {
        return 0xffff00;
    }
}

Inject callback

CallbackInfo and CallbackInfoReturnable is required in Inject hooks. Use mixin.CallbackInfo and mixin.CallbackInfoReturnable to import them. CallbackInfoReturnable has a generic type. But Zenscript doesn't support it. So CallbackInfoReturnable#getReturnValue always returns java.lang.Object in zenscript, you must cast it later.

Remap

In modpack environment, the game always be obfuscated. Mixin remap system is unnecessary. ZenUtils hardcodes mixin annotation to remap=false. So srg names are required to mixin inject points, mcp names are not allowed. But you still can use mcp name in function body to call mc code.

Allowed types

In mixin scripts, only native types are allowed, because mixin scripts are loaded before CraftTweaker registers its types.

this0

In mixin of java code, we use ((TargetClass) (Object) this) to cast this to target class. In zenscript, if there is only one target class, you can just call this0 variable, it is already casted to target class. You can also use it to call functions and fields of the super class, including private and protected members!

Mixin config

Where is the mixin config json? No, zenutils loads mixin classes dynamically. You needn't define mixin class name in mixin config. If the target class is client only, add #sideonly client preprocessor to make the mixin script is only loaded on client.

More examples

You can view more examples in this PR.

Clone this wiki locally