| Both sides previous revisionPrevious revisionNext revision | Previous revision |
| drafts:mixin_injectors [2026/01/19 10:30] – gauntrecluse | drafts:mixin_injectors [2026/02/20 20:39] (current) – [Injection Point, the value attribute] Add CTOR_HEAD injection point gauntrecluse |
|---|
| 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. | 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. | [[https://mcdev.io|MCDev]]'s autocompletion also accounts for overloads, so just autocompleting the annotation's ''method ='' attribute with the proper name, and accounting for the autocompleted method's parameters should successfully add the correct method descriptor. |
| |
| === Constructor, Static Initializer === | === Constructor, Static Initializer === |
| It's possible to 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_''. | It's possible to 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 [[https://docs.fabricmc.net/develop/getting-started/intellij-idea/tips-and-tricks#viewing-bytecode|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. | If you must 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. | 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. |
| ^ String Form in Annotation ^ Function ^ | ^ String Form in Annotation ^ Function ^ |
| | ''HEAD'' | Inject at the earliest possible point in the method | | | ''HEAD'' | Inject at the earliest possible point in the method | |
| | | ''CTOR_HEAD'' | For constructors, inject at the earliest possible point after the super call. | |
| | ''RETURN'' | Inject before every ''RETURN'' instruction in the target method | | | ''RETURN'' | Inject before every ''RETURN'' instruction in the target method | |
| | ''TAIL'' | Inject before the very last ''RETURN'' instruction in the target method | | | ''TAIL'' | Inject before the very last ''RETURN'' instruction in the target method | |
| ==== Injection Point Specifiers ==== | ==== Injection Point Specifiers ==== |
| Injection points can be supplied an additional specifier from the following list to modify how they behave: | Injection points can be supplied an additional specifier from the following list to modify how they behave: |
| ^ Specifier Name ^ Function ^ | ^ Specifier Name ^ Function ^ |
| | ''ALL'' | Default when not slicing. Takes in all instructions that match discriminators | | | ''ALL'' | Default when not slicing. Takes in all instructions that match discriminators | |
| | ''FIRST'' | Default when slicing. Takes in the first matching instruction | | | ''FIRST'' | Default when slicing. Takes in the first matching instruction | |
| | ''LAST'' | Takes in the last matching instruction | | | ''LAST'' | Takes in the last matching instruction | |
| | ''ONE'' | There must be only one matching instruction, otherwise fail and throw an exception. | | | ''ONE'' | There must be only one matching instruction, otherwise fail and throw an exception. | |
| | ''DEFAULT'' | Explicitly use the default specifier behavior for the consumer | | | ''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]]. | JavaDocs for specifiers can be found [[https://jenkins.liteloader.com/job/Mixin/javadoc/org/spongepowered/asm/mixin/injection/InjectionPoint.Specifier.html|here]]. |
| |
| |
| ==== Slice ==== | ==== 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): | 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), for example: |
| <code java> | <code java> |
| @At( | @InjectorAnnotation( |
| value = "INJECTION POINT", | method = "...", |
| | at = @At(...), |
| slice = @Slice( | slice = @Slice( |
| from = @At(...) | from = @At(...), |
| to = @At(...) | to = @At(...) |
| ) | ) |
| ) | ) |
| </code> | </code> |
| | ''from'' defaults to ''@At("HEAD")'', and ''to'' defaults to ''@At("TAIL")'', you may only specify one's value if needed. They otherwise can be specified as normal ''@At''s. |
| ''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. | :!: 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. |
| 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. | 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. | This allows, importantly, to define the surrounding context of a target, allowing for high precision without having to resort to use less expressive or more 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. |
| | |
| | Using expressions is sometimes the //only way// to directly target a specific set of instructions, this is the case for: |
| | * comparisons |
| | * array operations |
| | * array literals |
| | * binary expressions |
| | * method references |
| | * unary expressions |
| | * string concatenation |
| | * loading and storing of local variables outside of ''@ModifyVariable'''s ''STORE'' and ''LOAD'' injection points. |
| |
| 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: | 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: |
| </code> | </code> |
| |
| 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. | Additionally, identifiers or entire parts of an expression can be substituted by "wildcards", represented by a ''?'' operator, 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. They can also be used to make an expression match multiple targets by intentionally leaving select parts ambiguous. |
| |
| 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. | 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". | [[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".\\ |
| | Doing so on an expression string will also highlight the plugin's best attempt at matching your expression to the target class's bytecode, which can help highlight which parts of an expression are failing to match your target. Hovering over individual "blocks" in the diagram also allows you to see the corresponding bytecode instruction. |
| |
| 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: | 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: |
| |
| ==== Whether an Injector is Needed ==== | ==== 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. | It should first be mentioned that there are many situations where using an Injector or even a Mixin is unnecessary. |
| |
| === Existing Events === | === 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. | 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 unnecessarily. Events are also ported forward through updates when possible 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, which is especially relevant if you plan to backport your mod. |
| |
| Mixins are however a fully valid option if the existing events either do not fill your use-case or dissatisfy you in any way.\\ | 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. See the ''#api'' channel in the Fabric Discord to ask for support, and the [[https://docs.fabricmc.net/develop/events#custom-events|relevant Docs section]] for creating custom events. | You may also [[https://docs.fabricmc.net/develop/events#custom-events|create your own events]] if you are developing a library for example, where other mods would want to use your event. |
| |
| === Attaching Data === | === 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 [[https://docs.fabricmc.net/develop/data-attachments|Data Attachment API]] for this purpose, along with other means to save data persistently such as [[https://docs.fabricmc.net/develop/saved-data|Saved Data]]. Using Mixins to do it manually can often result in unnecessary maintenance and a higher likelihood for bugs. | 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 [[https://docs.fabricmc.net/develop/data-attachments|Data Attachment API]] for this purpose, along with other means to save data persistently such as [[https://docs.fabricmc.net/develop/saved-data|Saved Data]]. Using Mixins to do it manually can often result in unnecessary maintenance and a higher likelihood for bugs. |
| |
| There are cases where Fabric may not provide a sufficient API for adding data for your goals. In those cases, using Mixin to merge methods and fields to store data for certain classes is a valid approach. In those cases, it is however wise to first ask for advice if you are not experienced with it yourself. | There are cases where Fabric may not provide a sufficient API for adding data for your goals. In those cases, using Mixin to merge methods and fields to store data for certain classes is a valid approach. In those cases, it is however wise to first ask for advice if you are not experienced with it yourself, as this requires you to be very careful. |
| |
| === Merging static blocks and Members === | === Merging static blocks and Members === |
| |
| ==== Adding Vs Modifying ==== | ==== Adding Vs Modifying ==== |
| | 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. |
| |
| === Adding new Operations === | === Adding new Operations === |
| 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. | ''@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. | 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. |
| |
| ''@WrapMethod'' can also be used for wrapping a method in a try/catch, or when trying to //always// do something before or after a method is called. This can be used if you are particularly afraid of an injector cancelling your callback, but is unable to target a specific return statement. | ''@WrapMethod'' can also be used for wrapping a method in a try/catch, or when trying to more consistently do something before or after a method is called. This can be used if you are particularly afraid of an injector cancelling your added operations, but is unable to target a specific return statement and overall does not have access to a lot of context about the target method's flow. |
| | |
| === Modifying Operations === | |
| In order to modify specific operations such as method calls, comparisons, field writes, instantiations, and [[https://github.com/LlamaLad7/MixinExtras/wiki/WrapOperation|more]], ''@WrapOperation'' is often preferred as it both can chain with other injections and often gives the most context for the target. This is preferable to ''@Redirect''s, unless the intent is specifically to clash with other redirectors. | |
| |
| When the context from ''@WrapOperation'' is unnecessary, or when wanting to target an expression which is not covered by it, ''@ModifyExpressionValue'' can be used as a more flexible alternative with less context. This can typically include modifying individual boolean expressions within an ''if'' condition((boolean logic between multiple values in the form of ''&&'' or ''||'' is not currently targetable directly, requiring individual boolean values and expression be targeted instead.)). This is, similarly to ''@WrapOperation'', preferable to ''@Redirect''s and ''@ModifyConstant''s. | === Modifying Operations & Values === |
| | In order to modify specific operations such as method calls, comparisons, field gets/puts, instantiations, and [[https://github.com/LlamaLad7/MixinExtras/wiki/WrapOperation|more]], ''@WrapOperation'' is often preferred as it both can chain with other injections and often gives the most context for the target. |
| |
| === Modifying Values === | When the context from ''@WrapOperation'' is unnecessary, or when wanting to target an expression which is not covered by it, ''@ModifyExpressionValue'' can be used as a more flexible alternative with less context. This can typically include modifying individual boolean expressions within an ''if'' condition((boolean logic between multiple values in the form of ''&&'' or ''||'' is not currently targetable directly, requiring individual boolean expressions be targeted instead.)). |
| Similarly to the previous segment, many expressions which return a value can be targeted by ''@WrapOperation'', and even moreso by ''@ModifyExpressionValue'', the latter of which can target constant values. In the context of modifying arguments passed to a method call, however, it may be preferable to use either ''@ModifyArg'' or ''@ModifyArgs'', which chain and are an overall more direct approach to modifying method arguments. | |
| |
| | === Modifying Method Arguments === |
| | In order to modify method call arguments' values, ''@ModifyArg'' should be used for modifying a single argument's value, or ''@WrapOperation'' on the method call itself to modify multiple arguments, and on individual arguments to benefit from the context it provides for the original operation. ''@WrapOperation'' generally replaces ''@ModifyArgs'' because the latter provides less type safety. |
| |
| |