====== Mixin Examples ====== This is a collection of frequently used mixins. This page is intended as a cheat sheet. See [[tutorial:mixin_introduction|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 = "", at = @At("HEAD")) private void injected(CallbackInfo ci) { doSomething3(); } Result: static { + injected(new CallbackInfo(“”, 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 cir) { cir.setReturnValue(3); } Result: public int foo() { + CallbackInfoReturnable cir = new CallbackInfoReturnable("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 [[https://github.com/LlamaLad7/MixinExtras/wiki/Local|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. Mixin: @Inject(method = "foo()V", at = @At(value = "TAIL")) private void injected(CallbackInfo ci, @Local TypeArg2 arg2) { arg1.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) { arg1.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 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 cir) { cir.setReturnValue(cir.getReturnValue() * 3); } Result: public int foo() { doSomething1(); doSomething2(); - return doSomething3() + 7; + int i = doSomething3() + 7; + CallbackInfoReturnable cir = new CallbackInfoReturnable("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); } }