SpongePowered Mixin allows you to modify compiled classes on load at runtime. Several mixins from different authors can be layered on top of the same targets, allowing for reasonable compatibility even between mods that alter the same vanilla code.
====== Configuration ======
This is an example configuration file for declaring your mixins:
{
"required": true,
"minVersion": "0.8",
"package": "fun.raccoon.example.mixin",
"compatibilityLevel": "JAVA_8",
"mixins": [
"BothMixin"
],
"client": [
"client.ClientMixin"
],
"server": [
"server.ServerMixin"
]
}
* ''required'' specifies that this set of mixins failing to apply is fatal.
* ''minVersion'' minimum Mixin version that may be used.
* ''package'' package to use as a prefix for the later specified mixins.
* ''compatibilityLevel'' minimum Java version.
* ''mixins'', ''client'', ''server'' are lists of mixins to apply. The side distinction is //physical,// meaning e.g. ''server'' mixins will not be applied to modern Minecraft's logical server built into the client distribution, only the dedicated server.
See also: [[https://github.com/SpongePowered/Mixin/wiki/Introduction-to-Mixins---The-Mixin-Environment#mixin-configuration-files|Mixin Wiki: Mixin configuration files]]
----
For Fabric to see your mixins, you'll have to specify the location of this config file in your mod configuration too.
{
/* ... */
"mixins": [
"example.mixins.json"
],
/* ... */
}
====== Mixin classes ======
A mixin class is annotated with ''@Mixin'', which allows you to specify the target class(es) it applies to, and customarily marked abstract to prevent accidental instantiation. Visibility doesn't matter, but popular convention seems to be to mark it public.
@Mixin(value = Foo.class)
public abstract class MyFooMixin
A mixin's ''value'' can also be an array of classes, if the same mixin should be applied to several classes seperately.
The annotation also accepts the following optional elements:
* ''int priority() default 1000'' The order to apply relative to other mixins, lowest value first.
* ''String[] targets() default {}'' Fully qualified locations of targets as strings if they aren't public enough to refer to them by value. Inner classes are referred to with the special syntax ''com.example.OuterClass$InnerClass''
* ''boolean remap() default true'' By default, mixins seek an obfuscation mapping to use for translating members referenced by the mixin. This is a sensible default for game modding, but in some cases you might need to turn it off.
===== Mixin methods and their annotations =====
Methods of a mixin are used to modify the target class. They can be annotated to signal how in particular they should be used to modify the target.
Conceptually, ''this'' from the perspective of these methods is the target class. Prefer to avoid treating it like it's ''Foo'' in your code, but if you need to, make sure your mixin ''extends'' and ''implements'' all necessary classes, and then cast: ''(Foo)(Object)this''
==== Overwriting a target method ====
The default (but certainly not the preference) is to simply merge each method into the target, overwriting if a method already exists by the same name. This, of course, can break all lower-priority mixins that modify the same methods, so you should avoid it.
If you really do need the overwrite behavior for some reason, annotate your method with ''@Overwrite'' to be more explicit about your intentions and to gain access to ''remap'' behavior.
==== Modifying a target method's body ====
=== Overarching concepts ===
For each annotation that modifies the body of a method, [[..:target-selector|target selectors]] are used to refer to particular class members, and [[..:injection-point|injection points]] are used to refer to specific parts of the method body. It's important to understand the syntax and behavior of these concepts before continuing.
----
Like the ''@Mixin'' itself, each annotation can specify ''boolean remap'' to enable or disable obfuscation mapping behavior.
----
Since these annotations can match any number of different points in the method body, these common ''int'' elements are provided to sanity check the number of injection points:
* ''require'' Minimum number of injections
* ''expect'' Like ''require'', but only in effect if ''-Dmixin.debug.countInjections=true''
* ''allow'' //Maximum// number of injections
=== @Inject ===
''@Inject'' places a call to your method at a specific place in your target to add new behavior. Here's an example:
public class Foo {
public int bar(int x) {
System.out.println("Hello, world!");
return x;
}
}
@Inject(method = "bar", at = @At("TAIL"))
private void injected(int x) {
System.out.println("bar got " + x);
}
public int bar(int x) {
System.out.println("Hello, world!");
System.out.println("bar got " + x); // <--
return x;
}
The injected method is always ''void'' and returning from it does nothing special. However, ''@Inject'' also accepts ''boolean cancellable'' which, if set to true, allows you to append a ''CallbackInfo'' or ''CallbackInfoReturnable'' to your argument list and use it to force the target function to return early: ''CallbackInfo.cancel()'' for void functions, ''CallbackInfoReturnable.setReturnValue(R returnValue)'' otherwise.
@Inject(method = "bar", at = @At("HEAD"), cancellable = true)
private void injected(int x, CallbackInfoReturnable cir) {
if (x == 0) {
cir.setReturnValue(x+1);
}
}
public int bar(int x) {
if (x == 0) { // <--
return x+1; // <--
} // <--
System.out.println("Hello, world!");
return x;
}
=== @Redirect ===
Instead of injecting //between// instructions, ''@Redirect'' allows you to modify the operation itself, using your method instead of the original action to determine the result. Unless you choose to delegate it, the original operation is never performed.
== Redirecting a method call ==
In the case of method calls, your method acts as if it's the method being called, and it receives all the necessary arguments to act as such. For ''T Foo.bar(args...)'', your method is ''T bar(Foo foo, args...)''.
Additionally, you can choose to receive the arguments for the //outer method//, too. Here's an example:
public class Foo {
public int x;
public int incrementX(int by) {
Crementer in = new Crementer(by);
this.x = in.crement(this.x);
return this.x;
}
}
@Redirect(method = "incrementX", at = @At(value = "INVOKE", target = "Lpath/to/Crementer;crement(I)I"))
private int actuallyDecrement(
Crementer in, // the target's class instance
int x, // the target's arguments
int by // incrementX's arguments
) {
Crementer de = new Crementer(-by);
return de.crement(x); // >:)
}
public int incrementX(int by) {
Crementer in = new Crementer(by);
this.x = (() -> { // <--
Crementer de = new Crementer(-by); // <--
return de.crement(x); // <--
})() // <--
return this.x;
}
== Redirecting field access ==
If you inject at ''FIELD''s, your method can intercept reads and writes to those fields. The signature of your method changes depending on what type of field access you're targeting:
^ opcode ^ args ^ return ^
| ''GETSTATIC'' | | '''' |
| ''GETFIELD'' | '''' | '''' |
| ''PUTSTATIC'' | '''' | ''void'' |
| ''PUTFIELD'' | '', '' | ''void'' |
== ... ==
''TODO: array access, array length builtin, new, instanceof''
=== @ModifyArg, @ModifyArgs, @ModifyVariable, @ModifyConstant ===
These annotations allow you to modify the variable references and literals in a method body.
The method provided to these annotations accepts one argument and returns a value of the same type. That method has a special purpose for filtering the injection points: the type of its argument and the type of the item being selected must be the same to be considered.
----
''@ModifyConstant'' causes a literal value in the code to be replaced. It has an optional ''constant = @Constant(...)'' element instead of ''at = @At(...)''.
Like the ''CONSTANT'' injection point, ''@Constant'' accepts ''int intValue'', ''String stringValue'', etc. to specify which literal value to match. It also supports ''ordinal'' and ''slice''.
----
''@ModifyVariable'' is intended to be used with the injection points ''LOAD'' (for which it changes the value that is read from a variable) and ''STORE'' (for which it changes the value that is written to a variable). In addition to ''at'' it optionally accepts ''String name'' to filter by variable identifier.
If you want to modify the arguments your target method receives before anything else happens, you can alternatively inject at the ''HEAD'' and supply ''argsOnly = true'' to ''@ModifyVariable''. If the target method accepts several arguments of the same type, ''@ModifyVariable'' itself accepts ''ordinal'' to specify which one to take.
----
''@ModifyArg'' changes a single argument to a method call. If there are several arguments of the same type, it optionally accepts ''int index'' to specify which to replace.
For additional context, the annotated method can optionally receive //all// of the arguments of the method, rather than just the one it intends to replace.
----
''@ModifyArgs'' is capable of modifying any number of arguments to a method call. Its annotated method returns ''void'', but it receives an ''Args'' object as an argument which it can use to ''Args.get(int index)'' and ''Args.set(int index, T value)'' the arguments it wishes to modify.
==== Proxying access to class members ====
=== @Shadow ===
If you want to access a field or method in the target class while implementing your mixin methods, and you want to avoid the ''(Foo)(Object)this'' hack, you can declare a dummy member with the same name and type signature and annotate it with ''@Shadow''. When you interact with this dummy member in your mixin code, it translates to interactions with the real thing.
=== Re-exporting private members ===
If you want to offer access to private fields to your non-mixin code, you can declare an //accessor mixin//, which is defined as an ''interface'' instead of an ''abstract class''. It should consist of the private fields you want to access annotated with ''@Accessor'', and the private methods you want to access annotated with ''@Invoker''.
Now, if some external code wants to access a private member on ''Foo'', they can cast it to ''FooAccessor'' to gain access to all the members you've specified in your accessor mixin.