User Tools

Site Tools


drafts:mixin_injectors

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
drafts:mixin_injectors [2026/01/19 10:41] – [Expressions] Add list of Expression-exclusive targets based on Llama's info, expand on flow diagram debugging gauntreclusedrafts:mixin_injectors [2026/02/20 20:39] (current) – [Injection Point, the value attribute] Add CTOR_HEAD injection point gauntrecluse
Line 66: Line 66:
 ^ 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                 |
Line 85: Line 86:
 ==== 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]].
  
Line 130: Line 131:
  
 ==== 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.
Line 161: Line 162:
 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.+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 target a specific set of instructions, this is the case for: +Using expressions is sometimes the //only way// to directly target a specific set of instructions, this is the case for: 
   * comparisons   * comparisons
   * array operations   * array operations
Line 171: Line 172:
   * unary expressions   * unary expressions
   * string concatenation   * string concatenation
-  * local variable references outside of ''@ModifyVariable''+  * 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:
Line 180: Line 181:
 </code> </code>
  
-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.+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.
Line 201: Line 202:
  
 ==== 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 ===
Line 220: Line 221:
  
 ==== 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'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 directlyrequiring individual boolean values and expression be targeted instead.)). This issimilarly to ''@WrapOperation'', preferable to ''@Redirect''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 unnecessaryor 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 directlyrequiring 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.
  
  
drafts/mixin_injectors.1768819283.txt.gz · Last modified: 2026/01/19 10:41 by gauntrecluse