Mixin Annotation Adjuster - Bawnorton/MixinSquared GitHub Wiki


⚠️ Usage of MixinSquared on a maintained project is discouraged ⚠️
Making a PR or raising an issue would be more suitable

MixinSquared provides a way to manipulate the annotations used in Mixins that would normally be out of a user's control as other mod's mixins are not passed through their IMixinConfigPlugin.

Implementation

To use the adjuster is you need to implement the MixinAnnotationAdjuster interface and then register it.

public class YourModMixinAnnotationAdjuster implements MixinAnnotationAdjuster {
    @Override
    public AdjustableAnnotationNode adjust(List<String> targetClassNames, String mixinClassName, MethodNode method, AdjustableAnnotationNode annotation) {
        // To leave the annotation unchanged simply:
        /* return annotation; */

        // You can check if you're inside a specific mixin and targetting a specific annotation:
        if(!mixinClassName.equals("com.bawnorton.sandbox.mixin.LivingEntityMixin")) return annotation;
        if(!annotation.is(WrapOperation.class)) return annotation;

        // You can now safely cast to the appropriate AdjustableAnnotationNode:
        // Use a normal cast or the "as" helper if you wish to chain method calls instead
        AdjustableWrapOperationNode wrapOpNode = annotation.as(AdjustableWrapOperationNode.class);
        
        // If you want to narrow down the search even further, helper methods are provided for each adjustable node to query their attributes.
        if(wrapOpNode.getAt() // "at = "
                     .get(0) // at accepts an array so "getAt" returns a list
                     .getTarget() // "target = "
                     .equals("Lnet/minecraft/entity/LivingEntity;getWorld()Lnet/minecraft/world/World;")) {
            // You can remove the annotation entirely, the specified annotation will not be present when the mixin is applied and thus, will not be injected. 
            // The handler will still be present in the resultant mixin, it'll just be treated as a normal merged method.
            return null;
        } else {
            // You can also modify the node directly with use of the helper methods.
            return wrapOpNode.withAt(at -> {
                at.get(0)
                  .setOrdinal(1); // "ordinal = "
                return at;
            });
        }
    }
}

Dealing with Obfuscation:

Available in 0.3.0-beta.1+

Since the Annotation Adjuster interacts with what Mixin uses as keys for the refmap (i.e At.target()) you may want to remap the remappable parts of the annotation first before making your changes as once a change is made to a refmap key, the remapped value will not be found, causing the mixin to fail to find its target.

By using RemappableAnnotationNode#applyRefmap, the refmap will be applied earlier in the process, allowing you to make changes to the remapped values.

Example Usage:

// compare against unobfuscated name 
if(injectAnnotatioNode.getMethod().contains("someMethod(II)Z")) {       
    // remap to obfuscated name: someMethod(II)Z -> obfuscated_name(II)Z       
    injectAnnotationNode.applyRefmap();       
    injectAnnotationNode.withMethod(methods -> {          
        // would cause targetting to fail if applyRefmap was not called          
        methods.get(0).replace("II)Z", "IIF)Z");          
        return methods;       
    } 
}

This will always remap regardless of whether you are in a dev environment or not, as long as there is a refmap key value pair for the remappable elmenets they will be remapped

Note:

All AdjustableAnnotationNode classes that are provided have getAttribute, setAttribute and withAttribute helper methods for querying and adjusting the annotation they represent.
AdjustableAnnotationNode can represent an annotation that is not recognised but there will be no helper methods, instead you can use:

  • <T>get(String key) -> Optional<T> to query an attribute.
  • getEnum(String key, Class<T> enumClass) -> Optional<T> to query an enum attribute.
  • set(String key, Object value) -> void to set a value.

⚠️ Warning ⚠️

There are minimal checks on these methods as they interact directly with the node which allows you to set or query attributes which do not normally exist on the annotation which can cause crashes.

Registering

I am using Fabric

Inside your fabric.mod.json you will need to add the following entrypoint:

  "entrypoints": {
    "mixinsquared-adjuster": [
      "com.example.mod.YourModMixinAnnotationAdjuster"
    ]
  }

I am using Forge or anything else

You will need to declare a service called com.bawnorton.mixinsquared.api.MixinAnnotationAdjuster inside the META-INF/services/ directory.
The content of this file will need to be a reference to your MixinAnnotationAdjuster implementation(s):

com.example.mod.YourModMixinAnnotationAdjuster
Screenshot 2024-05-20 at 5 44 30 PM

I want to register it myself

In the onLoad method inside a IMixinConfigPlugin you can register a MixinAnnotationAdjuster directly using the MixinAnnotationAdjusterRegistrar like so:

public class YourModMixinConfigPlugin implements IMixinConfigPlugin {
    @Override
    public void onLoad(String mixinPackage) {
        MixinAnnotationAdjusterRegistrar.register(new YourModMixinAnnotationAdjuster());
    }
    ...
}
⚠️ **GitHub.com Fallback** ⚠️