User Tools

Site Tools


tutorial:mixin_examples

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.

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);
    }
  }
tutorial/mixin_examples.txt · Last modified: 2024/11/18 15:52 by bemoty