Mixins are a powerful and important tool used in the Fabric ecosystem. Their primary use case is modifying existing code in the base game, whether it be through injecting custom logic, removing mechanics, or modifying values. Note that mixins must be written in Java, even if you use Kotlin or another language.

Since Fabric Loader 0.15, MixinExtras has become bundled in Fabric Loader, then you can directly use MixinExtras to better manupulate the mixins.

Registering Mixins

Introduction

In this tutorial, you will learn how to register your Mixins through the resources/fabric.mod.json.
Inside your resources folder is where your fabric.mod.json should be.
Use this link to view the Fabric Example Mod’s resources folder: Fabric Example Mod Resources
Inside your fabric.mod.json is where you define where Fabric should look for your mixins.json.

Register Mixins with Fabric

To register a mixin you have to tell Fabric where to look. To tell Fabric where to look you need to add elements to the mixins array inside fabric.mod.json

{
  "mixins": [
    "modid.mixins.json"
  ]
}

Providing a String "<modid>.mixins.json" inside the mixins array tells Fabric to load the mixins defined inside the file <modid>.mixins.json.

Register Mixins

In the previous section, you learned about registering your <modid>.mixins.json files.
We still have to define which mixins to load and where these mixins are located.
Inside your registered <modid>.mixins.json:

{
  "required": true,
  "minVersion": "0.8",
  "package": "net.fabricmc.example.mixin",
  "compatibilityLevel": "JAVA_17",
  "mixins": [],
  "client": [
    "TitleScreenMixin"
  ],
  "server": [],
  "injectors": {
    "defaultRequire": 1
  }
}

The 4 main fields you should worry about when getting started with mixins are the package field, and the mixins, client, server arrays.

The package field defines which folder (package) to find the Mixins in.
The mixins array defines which classes should be loaded on both the physical client and physical server.
The client array defines which classes should be loaded only on the physical client.
The server array defines which classes should be loaded only on the physical server.

Following that logic: net.fabricmc.example.mixin.TitleScreenMixin is the mixin class that will be loaded on the client.

Mixin Injects

Injects allows you to place custom code at a specified position inside an existing method. For a working example, view the Practical Example category at the bottom of this page. The standard form of an inject is as shown:

@Inject(method = "METHOD NAME OR SIGNATURE", at = @At("INJECTION POINT REFERENCE"))
private void injectMethod(METHOD ARGS, CallbackInfo info) {
 
}

The Injection Point Reference defines where the code inside the method body is injected inside the target method. The following table describes a few of the options:

NameDescription
HEADTop of the method
RETURNBefore every return statement
INVOKEAt a method call
TAILBefore the final return statement
In the case of injection points that reference statements or members, the target value can be set inside @At. Target value is specified using JVM bytecode descriptors.

方法描述符包括方法名称,接着一系列包含输入类型的括号,以及输出类型。Java 中定义的像 Object m(int i, double[] d, Thread t) 这样的方法会有 m(I[DLjava/lang/Thread;)Ljava/lang/Object; 这样的方法描述符。
在这个返回类型为 void 的例子中,你需要使用 V(空描述符)作为,例如,void foo(String bar) 就会是 foo(Ljava/lang/String;)V。
泛型将会移除,因为泛型在运行的时候不存在,因为像 Pair<Integer, ? extends Task<? super VillagerEntity>‍> 这样的就会变成 Lcom/mojang/datafixers/util/Pair。(不会写问AI)

@Inject methods always have a void return type. The method name does not matter and neither does the access modifier; using something that describes what the inject does is best. The target method’s arguments are placed first in the method’s header, followed by a CallbackInfo object. If the target method has a return type (T), CallbackInfoReturnable<T> is used instead of CallbackInfo.
(共两个:CallbackInfo 或 CallbackInfoReturnable<T>)

Returning & Cancelling from Inject

To cancel or return early inside a method, use CallbackInfo#cancel or CallbackInfoReturnable<T>#setReturnValue(T). Note that cancel does not have to be called after setReturnValue. In both instances, cancellable will have to be set to true in the inject annotation: