====== Injectors (DRAFT) ======
Mixin provides many ways to modify classes, but the one modders should prioritize the most is injectors. Injectors are a type of tool which includes ''@Inject'', ''@WrapOperation'', ''@ModifyArg'' to only mention a few.\\
Injectors are characterized by the fact that they do not remove any of the target class's bytecode. This allows for highly compatible modifications that can chain with others directed at the same target. Following the broader principle that Mixin modifications should be as compatible and precise as possible, injectors are therefore the primary tool for adding new operations or modifying existing ones.
Injectors make modifications to methods in the target class's bytecode, this includes synthetic methods like lambdas or areas of a class that are implicitly a method such as the static initializer containing a class's field.
===== General Structure =====
In the source code of a Mixin class, an injector consists of two elements: the annotation(s), and the "handler" method, also simply called the handler, decorated by the annotation. The typical structure of an injector's annotation and method will look as follows:
@InjectorAnnotation(method = "", at = @At(value = ""))
private ReturnType handlerMethod() {
}
The handler method's return type and parameters are highly dependent on the specific injector, as they vary based on how the handler method is integrated into the target class. Additionally, the handler method may need to be static depending on the target.
==== Target method ====
Injectors are applied to methods. Assuming the method does not have an overload, a method of the same name and return type but with different parameters, it can be specified by its name without any additional specification.
Mixin's JavaDocs provides helpful documentation on specifying the target method at the documentation for the [[https://jenkins.liteloader.com/job/Mixin/javadoc/org/spongepowered/asm/mixin/injection/struct/MemberInfo.html|MemberInfo]] class.
=== Overloads ===
For overloads, the method's descriptor for its parameters must be appended to the name. This is detailed upon on [[https://docs.fabricmc.net/develop/mixins/bytecode#field-and-method-descriptors|the relevant Docs page]]'s method and field descriptor section.
[[https://mcdev.io|MCDev]]'s autocompletion also accounts for overloads, so just autocompleting the annotation's ''method ='' attribute with the proper name and parameters should automatically specify it.
=== Constructor, Static Initializer ===
Constructor methods are unnamed in source code, but are specified using the name ''''. In the case of multiple constructors, the same as named method overloads applies.
The static initialization code, which includes things such as the initialization of static fields, is specified using the name ''''. Injecting into the static initialization code is useful if you need to, for example, inject into the value which a static field is initialized to. //However//, static blocks in the Mixin class will be merged into the target class aswell, this can be used to reassign static fields without using an injector. This should be prioritized unless you need to specifically modify the initial value of a given field, which cannot be done directly through a static block.
=== Lambda Methods ===
Lambda methods are unnamed in source code, and turned into a synthetic method in bytecode where Mixin operates, and as such can be a bit tricky to inject into directly. There are two approaches to injecting into a lambda method's body.
The first, and most solid, approach is to "compose" the lambda if possible. This means using an injector which wraps the lambda's call site (''@WrapOperation'', ''@ModifyExpressionValue'' or ''@ModifyArg'' typically), and calling it in your own lambda which also contains your additional operations, and then returning the composed lambda:
@ModifyExpressionValue(...)
private Consumer exampleComposition(Consumer original) {
Consumer composedConsumer = itemStack -> {
// New operations here or after the original call
original.accept(itemStack);
};
return composedConsumer;
}
This is very useful for when you need to add operations at the head or tail of a lambda, as it both chains with other injectors, and avoids needing to directly inject into the lambda method. Since you are still mixing into the lambda's enclosing method, you also still have access to all the variables outside of the lambda's scope.
If for whatever reason this is not suitable, you can open the target class's bytecode ([[https://docs.fabricmc.net/develop/getting-started/intellij-idea/tips-and-tricks#viewing-bytecode|IntelliJ]], [[https://docs.fabricmc.net/develop/getting-started/vscode/tips-and-tricks#viewing-bytecode|VSCode]]), and look for the lambda's bytecode by searching for the ''synthetic'' keyword. Lambdas in bytecode are either named ''lambda$enclosingMethod$indexNumber'', for example ''lambda$shear$2'' would be the second lambda in the class, declared in the method ''shear''; or they may be named in the intermediary ''method_XXXXX'' format.
It's possible to either leverage [[https://mcdev.io|MCDev]]'s autocompletion by first typing ''method_'' in the ''method = '' attribute and pressing ''CTRL + SPACE'' and seeing, if the lambdas show up in intermediary, which one has the parameter and return type you're looking for, or doing the same but by starting to type ''lambda$'' in the attribute rather than ''method_''.
If you must however look at the bytecode, it is possible to quickly find synthetic methods by searching for the ''synthetic'' keyword. You may search for ''synthetic method_'' to test if they are in intermediary format. You can then look at the lambda method body in source code to identify key elements such as parameters and certain stand-out method calls or constants, and use that to figure out which method's bytecode corresponds to the lambda you wish to mix into.
Once you have specified the lambda's bytecode name successfully, you can now modify it as you would normally. Be aware that lambdas are not passed all of the variables from the outer scope, and only capture their parameters' values.
===== The @At annotation =====
Most injectors need to specify where in the target method to inject/modify instructions, this is done through the ''at'' attribute, which can take in one or more ''@At'' annotations.
==== Injection Point, the value attribute ====
The ''@At'' annotation takes one necessary ''value'' attribute, followed by optional ones: the "injection point". Injection Points serve as a "base" indicating what type of instruction in the target method to select for. They are referenced via a String, and are tied to an ''InjectionPoint'' subclass. Some common injection points include:
^ String Form in Annotation ^ Function ^
| ''HEAD'' | Inject at the earliest possible point in the method |
| ''RETURN'' | Inject before every ''RETURN'' instruction in the target method |
| ''TAIL'' | Inject before the very last ''RETURN'' instruction in the target method |
| ''INVOKE'' | Inject before a method call/invoke instruction |
| ''FIELD'' | Inject before a field access instruction |
| ''MIXINEXTRAS:EXPRESSION'' | Target via an [[https://github.com/LlamaLad7/MixinExtras/wiki/Expressions|Expression]]. |
More details on the base Mixin injection points can be found at [[https://github.com/SpongePowered/Mixin/wiki/Injection-Point-Reference|the Injection Point Reference]].
It is very important to note that for injectors which rely on modifying or wrapping their targets (''@WrapOperation'', ''@ModifyArg'', ''@ModifyExpressionValue''...) rather than injecting new instructions relative to the target (''@Inject'', ''@ModifyVariable''...) the injection point will not be used to inject "before" the target, but to specify the target to modify/wrap directly.
However, the injection points themselves are at times insufficient, such as with ''INVOKE'', which on its own would target //every// method call. In the next section, we will cover how to further specify target instructions beyond the injection point.
===== Target Discriminators =====
The term of discriminators here refers to a set of features provided by Mixin and MixinExtras to narrow down which instruction to target. There are some which are only supported by specific injection points, but if two discriminators are supported by the injection point used, then they can be combined to further refine which instruction to target.
However, not all discriminators are necessarily good to use, as some are more "brittle" than others. Brittleness here means likely to break from changes made to the target method which shouldn't concern the functionality you're interacting with. This is particularly the case for [[#ordinal|ordinals]], which should only be used when other discriminators are unavailable or not applicable.
==== Injection Point Specifiers ====
Injection points can be supplied an additional specifier from the following list to modify how they behave:
^ Specifier Name ^ Function ^
| ''ALL'' | Default when not slicing. Takes in all instructions that match discriminators |
| ''FIRST'' | Default when slicing. Takes in the first matching instruction |
| ''LAST'' | Takes in the last matching instruction |
| ''ONE'' | There must be only one matching instruction, otherwise fail and throw an exception. |
| ''DEFAULT'' | Explicitly use the default specifier behavior for the consumer |
JavaDocs for specifiers can be found [[https://jenkins.liteloader.com/job/Mixin/javadoc/org/spongepowered/asm/mixin/injection/InjectionPoint.Specifier.html|here]].
Specifiers ''FIRST'' and ''LAST'' should be used instead of their [[#ordinal|ordinal]] equivalents when they are relevant, and ''ONE'' can be used to enforce there being only one matching target when using the Mixin, thus making it impossible to silently target multiple instructions between updates, for example.
The default behavior being ''FIRST'' for [[#slice|slices]] should be kept in mind if you intend to inject into every matching instruction when slicing, which would necessitate explicitly specifying ''ALL''.
Specifiers are compatible with every injection point, including ''MIXINEXTRAS:EXPRESSION'', and are appended as a suffix which separates the injection point from the specifier with a colon '':''. For instance:
@At(value = "INVOKE:FIRST", target = "...")
Would take the first call to the method specified by ''target''.
==== Target Attribute ====
The ''target'' attribute is added to the ''@At'' to specify an identifying characteristic of the target which sets it apart from other instructions corresponding to the same injection point. For ''INVOKE'', this would be specifying which method is being invoked, and for ''FIELD'' this would be which field is being accessed.
The target is specified by using the target's [[https://docs.fabricmc.net/develop/mixins/bytecode#field-and-method-descriptors|descriptor]], based on [[https://docs.fabricmc.net/develop/mixins/bytecode#field-and-method-descriptors|type descriptors]]. This can be entirely autocompleted using [[https://mcdev.io|the MCDev plugin]].
The ''target'' attribute is specified as with any other annotation attribute. For example:
@At(value = "INVOKE", target = "")
Would narrow targeting down from "all method calls" to "all calls to that specific method".
This discriminator is not particularly brittle in any particular way, and should mostly always be used on its relevant injection points in combination with other discriminators for the target. If there is only one instance of a method being called, using it alone would work, but it may be good to pair it with the '':ONE'' [[#injection_point_specifiers|specifier]] to make the injector throw an error if our assumption of there only being one call to that method is broken in an update.
==== Ordinal ====
Ordinals allow selecting one instruction among the ones returned by the other discriminators and injection point used, similarly to zero-indexed array indices. This means that an ordinal of 0 corresponds to the first instruction in the available list, whilst an ordinal of 3 would correspond to the fourth. Ordinals can be used for any injection point.
It is specified as an integer attribute in the ''@At'' annotation:
@At(value = "INJECTION POINT", ordinal = )
:!: Ordinals are generally the most brittle discriminator, as it may break from a new matching instruction being added prior to the one selected by the ordinal. A [[#injection_point_specifiers|specifier]] of '':LAST'' should generally be used in place of an ordinal of the last ordinal available; in most cases it is recommendable to use an [[#expressions|expression]] and/or a [[#slice|slice]] instead of an ordinal, as those are much more expressive and less likely to break between updates.
==== Slice ====
A slice uses one or two additional ''@At''s to specify a range to include when searching for instructions matching the main ''@At'', it is usable for any injection point. Slicing is specified via the ''slice'' attribute, which takes a ''@Slice'' annotation (code differently formatted to distinguish between the different ''@At''s more clearly):
@At(
value = "INJECTION POINT",
slice = @Slice(
from = @At(...)
to = @At(...)
)
)
''from'' defaults to ''@At("HEAD")'', and ''to'' defaults to ''@At("TAIL")'', you may only specify one's value if needed.
:!: When slicing, the default [[#injection_point_specifiers|specifier]] behavior of the injection point shifts from '':ALL'' to '':FIRST''. This means you must explicitly use '':ALL'' for the injector to be able to target multiple instructions within a slice.
Slicing is a lot less brittle than ordinals assuming the ''from'' and ''to'' can be specified without being brittle themselves. Similarly to other discriminators, slicing can be used to complement other targeting tools and be combined with them.
==== Opcode ====
''opcode'' is a discriminator used specifically for the ''FIELD'' injection point, and is specified within the ''@At'' annotation. The ''opcode'' attribute takes an integer value corresponding to the bytecode opcode. This should be filled using [[https://asm.ow2.io|ASM]]'s ''Opcodes'' interface's presets, which are named to match the Opcode corresponding to their values:
@At(value = "FIELD", target = "...", opcode = Opcodes.<...>)
Opcodes for ''FIELD'' would include ''GETSTATIC'', ''PUTSTATIC'' for accessing a static field, and ''GETFIELD'', ''PUTFIELD'' for a non-static field. This can be auto-generated by [[https://mcdev.io|MCDev]], which will give a warning to remind you to use an opcode in the event your ''FIELD'' injector does not use one already, with a quick-fix action to add it.
''opcode'' should always complement a proper [[#target_attribute|target]], and may be used along with any other discriminator that is not incompatible with the ''FIELD'' injection point.
==== Expressions ====
MixinExtras 0.5.0 (bundled in Fabric Loader version 0.17.0+) adds a new injection point: ''MIXINEXTRAS:EXPRESSION'', which does not take a ''target'', but instead uses an ''@Expression'' annotation to store a Java-like String that will correspond to the instructions to target.
This allows, importantly, to define the surrounding context of a target, allowing for high precision without having to resort to use less expressive or even brittle discriminators such as [[#ordinal|ordinals]]. The possibility of adding context rather than targeting preset instructions or groups of instructions also allows targeting much more complex expressions in the target class; it is sometimes the //only way// to target a specific set of instructions.
The ''@Expression'' annotation uses "identifiers" to stand in for the non-constant elements of an expression. Those identifiers are then defined and mapped to their bytecode counterparts using a ''@Definition'' annotation each:
@Definition(...)
@Expression("...")
@InjectorAnnotation(method = "...", at = @At(value = "MIXINEXTRAS:EXPRESSION"))
Additionally, identifiers or entire parts of an expression can be substituted by "wildcards", which are a way to explicitly leave a part of an expression ambiguous. Wildcards are mainly useful to make a less cluttered injectors when the identifiers are unnecessary, or to avoid referencing elements that would be problematic or brittle to define.
This is mainly the case for local variables on obfuscated versions' MC code (Minecraft code is obfuscated up to and including 1.21.11) where defining locals is in general very brittle due to being unable to define them by name, instead needing to rely on type, ordinals and whether the local variable is part of the method arguments. This is no longer an issue when targeting unobfuscated code, however, as defining locals by name is very reliable.
[[https://mcdev.io|MCDev]] is very useful for writing expressions, as autocompleting the different identifiers will automatically generate ''@Definition''s for them. Additionally, the plugin allows you to open a flow diagram for expressions debugging which corresponds to the bytecode of different expressions in the decompiled source code by right clicking on either an expression string, or on the targeted source code, and then pressing "MixinExtras Flow Diagram".
The concept, language and limitations of Expressions are much more precisely elaborated upon in its official Wiki pages, which contain many examples for its syntax and use-cases. It is very important to read both the main page and the setup page to be able to use Expressions properly:
* [[https://github.com/LlamaLad7/MixinExtras/wiki/Expressions|Main Tutorial]]
* [[https://github.com/LlamaLad7/MixinExtras/wiki/Expressions-Setup|Setup]]
* [[https://github.com/LlamaLad7/MixinExtras/wiki/Expression-Language|Full language specification]]
===== Choosing the right Injector =====
FIXME //DRAFT NOTE: This segment is still unfinished.//
Knowing the overall structure and usage of injectors in //general// does not help in knowing which injector is right for your use-case. This section aims to go over the different Mixin and MixinExtras injectors, and where they are best-suited.
==== Whether an Injector is Needed ====
It should first be mentioned that there are many situations where using an Injector or even a Mixin is unnecessary. Mixins should be resorted to if the given modding framework does not provide other, more convenient, ways to achieve the specified goal.
=== Existing Events ===
Whilst using Mixins can be functionally equivalent to events, since Fabric uses Mixins to back its events already, some developers may prefer going through the Fabric-defined event to avoid needing to create a Mixin class and writing an injector when an event already exists. Events are also ported between versions by the Fabric API's team of maintainers, whilst you will need to maintain your own Mixins manually. On the other hand, there is not necessarily a guarantee that the events you use will be uniformly ported through different MC versions.
Mixins are however a fully valid option if the existing events either do not fill your use-case or dissatisfy you in any way. If you find that a useful event does not exist, you may create the Mixin implementation for it and contribute it into Fabric API yourself.
=== Attaching Data ===
Mixins are able to merge new members into target classes and modify existing methods related to data management, which can theoretically be used to effectively add data to existing classes for your own use-cases. This, however, is not advised as it takes a great amount of work, and Fabric already provides a Data Attachment API for this purpose, along with other means to save data persistently. Using Mixins to do it manually will often result in unnecessary maintenance and a higher likelihood for bugs.
=== Merging static blocks ===
Sometimes, a Mixin is needed, but an injector may be unnecessary. As mentioned in the previous section, Mixin is able to merge new members, but this also extends to static blocks. If you for example want to reassign a static field's value during class initialization, you may use a static block which shadows the field to reassign its value. This can be a much simpler and less brittle option than trying to do certain injections into the field's initial value.
As with injectors, though, conserving the original values in your calculations is very important for compatibility and chaining with other mods who may want to do the same thing. Injectors should be used if that proves to be a challenge or an impossibility when reassigning the value via a static block.
==== Modifying Vs Adding Operations or Values ====
=== Adding ===
The first question to ask yourself when trying to choose the correct injector for a given situation is whether you wish to modify existing values and operations, or whether you want to add new operations altogether. ''@Inject'' for example is only ever rarely suitable for other purposes than adding new operations, as it is limited to adding a call to your injector's handler method, and possibly injecting an early return. This is not suited for modifying values, method calls, etc.
There are however contexts where ''@Inject'' may also not be the injector to choose for adding new operations, such as when needing to get more context relative to the operation you are injecting next to. It can be wise to use ''@WrapOperation'' or ''@ModifyExpressionValue'' to be able to capture one or more values relevant to an operation, and then adding one's own operations before or after it. Those injectors are also more relevant for wrapping the operation itself with something, such as a try/catch.
=== Modifying ===
In order to modify existing operations and values, the three main tools are ''@WrapOperation'', which is ideal for modifying things such as comparisons, method calls, and a variety of other operations, whilst also giving context, ''@ModifyExpressionValue'', which is exceptionally flexible in the amount of expressions and operations it can target, as long as the given operation results in a value; and ''@ModifyArg'', which is preferable for modifying method arguments, comparatively to using a ''@ModifyExpressionValue'' on a given method's passed argument.
===== Further Reading =====
==== Injector-Specific pages ====
The following are pages currently available to give information about a specific injector:
* [[tutorial:mixin_injects|@Inject]]
* MixinExtras Injectors:
* [[https://github.com/LlamaLad7/MixinExtras/wiki/ModifyExpressionValue|@ModifyExpressionValue]]
* [[https://github.com/LlamaLad7/MixinExtras/wiki/ModifyReceiver|@ModifyReceiver]]
* [[https://github.com/LlamaLad7/MixinExtras/wiki/ModifyReturnValue|@ModifyReturnValue]]
* [[https://github.com/LlamaLad7/MixinExtras/wiki/WrapMethod|@WrapMethod]]
* [[https://github.com/LlamaLad7/MixinExtras/wiki/WrapOperation|@WrapOperation]]
* [[https://github.com/LlamaLad7/MixinExtras/wiki/WrapWithCondition|@WrapWithCondition]]
* //TODO or WIP//
* @ModifyVariable
* @ModifyArg
* @ModifyArgs
==== Fabric Pages on Mixin ====
The following pages are generally trusted pages of the Fabric or Docs:
* [[tutorial:mixin_introduction|Introduction to Mixins]]
* [[https://docs.fabricmc.net/develop/mixins/bytecode|Java Bytecode]] (Docs)
* [[tutorial:mixin_your_first_mixin|Tutorial: Making your first Mixin]]
* [[tutorial:mixin_accessors|Mixin Accessors and Invokers]]
* [[tutorial:mixin_export|Exporting and Dumping Mixin Targets]]
==== External Resources ====
The following are official resources for Mixin and MixinExtras:
* [[https://github.com/SpongePowered/Mixin/wiki/|Mixin Wiki]]
* [[https://jenkins.liteloader.com/job/Mixin/javadoc/|Mixin JavaDoc]]
* [[https://github.com/LlamaLad7/MixinExtras/wiki/|MixinExtras Wiki]]
----
===== To the Attention of Draft Reviewers and Contributors =====
This page is made with the purpose of giving general, shared, information between injectors, aswell as giving a cursory guidance as to which injectors may be fitting for general situations.
==== Goals ====
* Give Wiki readers a more informed and thorough guidance on injector syntax and purpose
* Give cursory guidance on what certain injectors are more useful for, allowing for a more informed approach to selecting an injector
* Overall reduce the need for beginners to ask repeatedly what injectors are, what injectors are for, and how to use them
==== Non-Goals ====
* Give in-detail description of each injector, which should be done on injector-specific pages
* Replace asking for help in support channels
* Replace existing well-maintained documentation on injectors, mainly the MixinExtras Wiki's injector and Expressions pages
==== Things to do before getting out of drafting ====
* Get proper reviewing on the page as a whole, a review by LlamaLad7 would be ideal
* Possibly extract target discriminators segment into its own page draft
* Finish work on the injector selection guidance
* Reassess the example code blocks' syntax and whether they may be improved
=== Review feedback to likely implement ===
* Expanding upon when not to use a Mixin, particularly in the data attachment section, for which looking into cases where Fabric doesn't provide a helpful API to note would be helpful.
==== Plans after getting out of Drafting ====
* Keep a close eye on the page for the month or so after first pushing it
* Work on more injector-specific pages, with non MixinExtras injectors as a priority
* Cull general injector information from [[tutorial:mixin_injects]] which should ideally now be here.
**Thank you kindly for reviewing this draft.**