Table of Contents
The Fabric Wiki's Mixin segments is under heavy reviews, pages on the topic are subject to major edits or rewrites. This page in particular is noted as being unreliable to learn Mixin as a tool from. Learning by example should be used sparingly, as it may lead to a lack of understanding. Prioritize, if you are unable to learn through documentation, presenting your intention in detail in a Mixin support channel on the Fabric or SpongePowered Discord servers and discussing with experienced devs there.
Mixin Examples
This is a collection of frequently used mixins. This page is intended as a cheat sheet. See Mixin Introduction if you haven't already.
Mixing into a private inner class
Use the targets parameter and a $
sign to get the inner class.
@Mixin(targets = "net.minecraft.client.render.block.BlockModelRenderer$AmbientOcclusionCalculator") public class AmbientOcclusionCalculatorMixin { // do your stuff here }
Access the this instance of the class your mixin is targeting
Note: Double casting this
should be avoided when possible. If you intend to use a method or field from the target class, use @Shadow
. If the method or field is from a parent of the target class, have your mixin extend the direct parent of the target class.
Mixin:
@Mixin(TargetClass.class) public class MyMixin extends EveryThingThatTargetClassExtends implements EverythingThatTargetClassImplements { @Inject(method = "foo()V", at = @At("HEAD")) private void injected(CallbackInfo ci) { TargetClass thisObject = (TargetClass)(Object)this; } }
Injecting into the head of a static block
Mixin:
@Inject(method = "<clinit>", at = @At("HEAD")) private void injected(CallbackInfo ci) { doSomething3(); }
Result:
static { + injected(new CallbackInfo(“<clinit>”, false)); doSomething1(); doSomething2(); }
Injecting into the head of a method
Mixin:
@Inject(method = "foo()V", at = @At("HEAD")) private void injected(CallbackInfo ci) { doSomething4(); }
Result:
public void foo() { + injected(new CallbackInfo("foo", false)); doSomething1(); doSomething2(); doSomething3(); }
Injecting into the tail of a method
Mixin:
@Inject(method = "foo()V", at = @At("TAIL")) private void injected(CallbackInfo ci) { doSomething4(); }
Result:
public void foo() { doSomething1(); if (doSomething2()) { return; } doSomething3(); + injected(new CallbackInfo("foo", false)); }
Injecting into the returns of a method
Mixin:
@Inject(method = "foo()V", at = @At("RETURN")) private void injected(CallbackInfo ci) { doSomething4(); }
Result:
public void foo() { doSomething1(); if (doSomething2()) { + injected(new CallbackInfo("foo", false)); return; } doSomething3(); + injected(new CallbackInfo("foo", false)); }
Injecting into the point before a method call
Mixin:
@Inject(method = "foo()V", at = @At(value = "INVOKE", target = "La/b/c/Something;doSomething()V")) private void injected(CallbackInfo ci) { doSomething3(); }
Result:
public void foo() { doSomething1(); Something something = new Something(); + injected(new CallbackInfo("foo", false)); something.doSomething(); doSomething2(); }
Injecting into the point after a method call
Mixin:
@Inject(method = "foo()V", at = @At(value = "INVOKE", target = "La/b/c/Something;doSomething()V", shift = At.Shift.AFTER)) private void injected(CallbackInfo ci) { doSomething3(); }
Result:
public void foo() { doSomething1(); Something something = new Something(); something.doSomething(); + injected(new CallbackInfo("foo", false)); doSomething2(); }
Injecting into the point with shift amount
Mixin:
@Inject(method = "foo()V", at = @At(value = "INVOKE", target = "La/b/c/Something;doSomething()V", shift = At.Shift.BY, by = 2)) private void injected(CallbackInfo ci) { doSomething3(); }
Result:
public void foo() { doSomething1(); Something something = new Something(); something.doSomething(); doSomething2(); + injected(new CallbackInfo("foo", false)); }
Injecting with a slice
Mixin:
@Inject( method = "foo()V", at = @At( value = "INVOKE", target = "La/b/c/Something;doSomething()V" ), slice = @Slice( from = @At(value = "INVOKE", target = "La/b/c/Something;doSomething2()V"), to = @At(value = "INVOKE", target = "La/b/c/Something;doSomething3()V") ) ) private void injected(CallbackInfo ci) { doSomething5(); }
Result:
public class Something { public void foo() { this.doSomething1(); + // It will not inject into here because this is outside of the slice section this.doSomething(); this.doSomething2(); + injected(new CallbackInfo("foo", false)); this.doSomething(); this.doSomething3(); + // It will not inject into here because this is outside of the slice section this.doSomething(); this.doSomething4(); } }
Injecting and cancelling
Mixin:
@Inject(method = "foo()V", at = @At("HEAD"), cancellable = true) private void injected(CallbackInfo ci) { ci.cancel(); }
Result:
public void foo() { + CallbackInfo ci = new CallbackInfo("foo", true); + injected(ci); + if (ci.isCancelled()) return; doSomething1(); doSomething2(); doSomething3(); }
Injecting and cancelling with a return value
Mixin:
@Inject(method = "foo()I;", at = @At("HEAD"), cancellable = true) private void injected(CallbackInfoReturnable<Integer> cir) { cir.setReturnValue(3); }
Result:
public int foo() { + CallbackInfoReturnable<Integer> cir = new CallbackInfoReturnable<Integer>("foo", true); + injected(cir); + if (cir.isCancelled()) return cir.getReturnValue(); doSomething1(); doSomething2(); doSomething3(); return 10; }
Capturing local values
Capture locals without MixinExtras
Mixin:
@Inject(method = "foo()V", at = @At(value = "TAIL"), locals = LocalCapture.CAPTURE_FAILHARD) private void injected(CallbackInfo ci, TypeArg1 arg1) { //CAPTURE_FAILHARD: If the calculated locals are different from the expected values, throws an error. arg1.doSomething4(); }
Result:
public void foo() { TypeArg1 arg1 = getArg1(); arg1.doSomething1(); arg1.doSomething2(); TypeArg2 arg2 = getArg2(); arg2.doSomething3(); + injected(new CallbackInfo("foo", false), arg1); }
Capture locals with MixinExtras
See the oficial MixinExtra's Wiki.
MixinExtras required Fabric Loader 0.15 or above, or you have to manually specify it in
build.gradle
.
If there are multiple locals with that type, you have to specify
ordinal
or it will throw an error.
the use of
@Local
is recommended over LocalCapture
Mixin:
@Inject(method = "foo()V", at = @At(value = "TAIL")) private void injected(CallbackInfo ci, @Local TypeArg2 arg2) { arg2.doSomething4(); }
Result:
public void foo() { TypeArg1 arg1 = getArg1(); arg1.doSomething1(); arg1.doSomething2(); TypeArg2 arg2 = getArg2(); arg2.doSomething3(); + injected(new CallbackInfo("foo", false), arg2); }
Capturing one of multiple locals of a type
Mixin:
@Inject(method = "foo()V", at = @At(value = "TAIL")) private void injected(CallbackInfo ci, @Local(ordinal = 2) TypeArg arg) { arg.doSomething4(); }
Result:
public void foo() { TypeArg arg1 = getArg1(); TypeArg arg2 = getArg2(); TypeArg arg3 = getArg3(); TypeArg arg4 = getArg4(); doSomething(); + injected(new CallbackInfo("foo", false), arg3); }
Modifying locals
This requires MixinExtras.
Mixin:
@Inject(method = "foo()V", at = @At(value = "INVOKE", target = "doSomething()V", shift = At.Shift.AFTER)) private static void injected(CallbackInfo ci, @Local LocalRef<String> localRef) { localRef.set(localRef.get() + " - modified") }
Result:
public void foo() { String s = "example string"; doSomething(); + s = s + " - modified"; doSomething2(s); }
Modifying a return value
Mixin:
@Inject(method = "foo()I;", at = @At("RETURN"), cancellable = true) private void injected(CallbackInfoReturnable<Integer> cir) { cir.setReturnValue(cir.getReturnValue() * 3); }
Result:
public int foo() { doSomething1(); doSomething2(); - return doSomething3() + 7; + int i = doSomething3() + 7; + CallbackInfoReturnable<Integer> cir = new CallbackInfoReturnable<Integer>("foo", true, i); + injected(cir); + if (cir.isCancelled()) return cir.getReturnValue(); + return i; }
Redirecting a method call
Mixin:
@Redirect(method = "foo()V", at = @At(value = "INVOKE", target = "La/b/c/Something;doSomething(I)I")) private int injected(Something something, int x) { return x + 3; }
Result:
public void foo() { doSomething1(); Something something = new Something(); - int i = something.doSomething(10); + int i = injected(something, 10); doSomething2(); }
Redirecting a get field
Mixin:
@Redirect(method = "foo()V", at = @At(value = "FIELD", target = "La/b/c/Something;aaa:I", opcode = Opcodes.GETFIELD)) private int injected(Something something) { return 12345; }
Result:
public class Something { public int aaa; public void foo() { doSomething1(); - if (this.aaa > doSomething2()) { + if (injected(this) > doSomething2()) { doSomething3(); } doSomething4(); } }
Redirecting a put field
Mixin:
@Redirect(method = "foo()V", at = @At(value = "FIELD", target = "La/b/c/Something;aaa:I", opcode = Opcodes.PUTFIELD)) private void injected(Something something, int x) { something.aaa = x + doSomething5(); }
Result:
public class Something { public int aaa; public void foo() { doSomething1(); - this.aaa = doSomething2() + doSomething3(); + inject(this, doSomething2() + doSomething3()); doSomething4(); } }
Modifying an argument
Mixin:
@ModifyArg(method = "foo()V", at = @At(value = "INVOKE", target = "La/b/c/Something;doSomething(ZIII)V"), index = 2) private int injected(int x) { return x * 3; }
Result:
public void foo() { doSomething1(); Something something = new Something(); - something.doSomething(true, 1, 4, 5); + something.doSomething(true, 1, injected(4), 5); doSomething2(); }
Modifying multiple arguments
Mixin:
@ModifyArgs(method = "foo()V", at = @At(value = "INVOKE", target = "La/b/c/Something;doSomething(IDZ)V")) private void injected(Args args) { int a0 = args.get(0); double a1 = args.get(1); boolean a2 = args.get(2); args.set(0, a0 + 3); args.set(1, a1 * 2.0D); args.set(2, !a2); }
Result:
public void foo() { doSomething1(); Something something = new Something(); - something.doSomething(3, 2.5D, true); + // Actually, synthetic subclass of Args is generated at runtime, + // but we omit the details to make it easier to understand the concept. + Args args = new Args(new Object[] { 3, 2.5D, true }); + injected(args); + something.doSomething(args.get(0), args.get(1), args.get(2)); doSomething2(); }
Modifying a parameter
Mixin:
@ModifyVariable(method = "foo(ZIII)V", at = @At("HEAD"), ordinal = 1) private int injected(int y) { return y * 3; }
Result:
public void foo(boolean b, int x, int y, int z) { + y = injected(y); doSomething1(); doSomething2(); doSomething3(); }
Modifying a local variable on an assignment
Mixin:
@ModifyVariable(method = "foo()V", at = @At("STORE"), ordinal = 1) private double injected(double x) { return x * 1.5D; }
Result:
public void foo() { int i0 = doSomething1(); double d0 = doSomething2(); - double d1 = doSomething3() + 0.8D; + double d1 = injected(doSomething3() + 0.8D); double d2 = doSomething4(); }
Modifying a constant
Mixin:
@ModifyConstant(method = "foo()V", constant = @Constant(intValue = 4)) private int injected(int value) { return ++value; }
Result:
public void foo() { - for (int i = 0; i < 4; i++) { + for (int i = 0; i < injected(4); i++) { doSomething(i); } }