Inject¶
@Inject is one of the simplest injections in Mixin to understand. It allows you to inject any code at any line you want.
There are two types of @Injects, cancelable, and not cancelable. But first, let's talk about the main injection points.
Injecting at HEAD¶
Let's say we just want to add some extra code at the beginning of a method.
And we wanted to add a print line for "Message 0" at the beginning. We would inject at the "head" of the method.
@Inject(method = "foo", at = @At("HEAD"))
private void addMessage0(CallbackInfo ci) {
System.out.println("Message 0");
}
Which leaves us with:
public void foo(float f) {
System.out.println("Message 0");
System.out.println("Message 1");
System.out.println("Message 2");
}
Injecting at TAIL¶
Let's say we just want to add some extra code at the end of a method.
And we wanted to add a print line for "Message 3" at the end. We would inject at the "head" of the method.
@Inject(method = "foo", at = @At("TAIL"))
private void addMessage3(CallbackInfo ci) {
System.out.println("Message 3");
}
Which leaves us with:
public void foo(float f) {
System.out.println("Message 1");
System.out.println("Message 2");
System.out.println("Message 3");
}
Injecting at RETURN¶
Let's say we just want to add some extra code before a return statement.
public String foo() {
varA = this.a;
varB = this.b;
if (varA == varB) {
return "True!"
} else {
return "False!"
}
}
And we wanted to add a print, say, "Hello World!" before printing "True!". Yes, this is a dumb example, roll with it. We would inject at the first "RETURN" of the method.
@Inject(method = "foo", at = @At("RETURN"), ordinal = 0) // Remember: ordinals are zero-indexed
private void printValues(CallbackInfoReturnable cir) {
System.out.println("Hello World!");
}
Which leaves us with:
public String foo() {
varA = this.a;
varB = this.b;
if (varA == varB) {
System.out.println("Hello World!");
return "True!"
} else {
return "False!"
}
}
Injecting at INVOKE¶
Let's say we just want to add some extra code before a completely arbitrary line of code.
And we wanted to add a print between doing method A and B. We would inject by targetting the call for method B. Please note that @Injecting at a target effectively makes the code appear on the line before.
@Inject(method = "foo", at = @At("INVOKE"), target = "doMethodB()V") // TODO: check if this target makes sense
private void printValues(CallbackInfo ci) {
System.out.println("Hello World!");
}
Which leaves us with:
Cancelable vs Non-Cancelable¶
So far these examples have been non-canceling. We just add code to the code present, we do not change the flow of the method. But what if we wanted to stop this method from running? And what's with this weird CallbackInfo/CallbackInfoReturnable?
Canceling the method does what you would expect. It basically adds a return wherever we specify.
Say we wanted to cancel this method at the start:
We can simply do
@Inject(method = "foo", at = @At("HEAD"), cancelable = true) // Notice we add this here! McDev should also give you a warning if you forget.
private void cancelFoo(CallbackInfo ci) {
ci.cancel();
}
And this results in
But what if the method isn't void? We can't just cancel a non-void method, but we can change the return value instead. Say we had the following.
We can simply use a CallbackInfoReturnable.
@Inject(method = "foo", at = @At("HEAD"), cancelable = true)
private String cancelFoo(CallbackInfoReturnable cir) { // Notice the method is no longer void, but the same type as the method we inject!
cir.setReturnValue("New custom string!");
}
And that results in
Simple! The other injection points, "TAIL", "RETURN", and "INVOKE", also work here.
Warning
Be careful when using ci.cancel or cir.setReturnValue. It may cause incompatibilities with other mods. Try to use other injectors where possible.