From 9827278412ab068ce408184aec9d190773794360 Mon Sep 17 00:00:00 2001 From: DominicBytes Date: Wed, 15 Jul 2026 07:19:15 -0500 Subject: [PATCH 1/2] fix: support explicit 26.1 mixin adaptations --- .../sinytra/adapter/env/ann/ConstantData.java | 3 +- .../adapter/patch/mixin/MixinTypes.java | 5 +- .../patch/mixin/ModifyConstantMixin.java | 55 +++++++++++++++++++ .../injection/InjectionPointResolver.java | 4 +- .../transform/PipelineMethodTransformer.java | 5 +- .../transform/patch/MethodPatchBuilder.java | 4 ++ .../patch/MethodPatchBuilderImpl.java | 15 +++++ .../patch/test/MethodPatchExtensionTest.java | 39 +++++++++++++ 8 files changed, 125 insertions(+), 5 deletions(-) create mode 100644 core/src/main/java/org/sinytra/adapter/patch/mixin/ModifyConstantMixin.java create mode 100644 test/src/test/java/org/sinytra/adapter/patch/test/MethodPatchExtensionTest.java diff --git a/core/src/main/java/org/sinytra/adapter/env/ann/ConstantData.java b/core/src/main/java/org/sinytra/adapter/env/ann/ConstantData.java index 753f8505..3cc1fe64 100644 --- a/core/src/main/java/org/sinytra/adapter/env/ann/ConstantData.java +++ b/core/src/main/java/org/sinytra/adapter/env/ann/ConstantData.java @@ -13,7 +13,7 @@ public class ConstantData { private static final PropertyContainerTemplate TEMPLATE = PropertyContainerTemplate.builder() - .requireOne(Keys.DOUBLE_VALUE, Keys.CLASS_VALUE) + .requireOne(Keys.INT_VALUE, Keys.DOUBLE_VALUE, Keys.CLASS_VALUE) .build(); private final PropertyContainer properties; @@ -47,6 +47,7 @@ public static Optional parse(AnnotationHandle handle) { } public static class Keys { + public static final PropertyKey INT_VALUE = PropertyKey.create("intValue", Integer.class); public static final PropertyKey DOUBLE_VALUE = PropertyKey.create("doubleValue", Double.class); public static final PropertyKey CLASS_VALUE = PropertyKey.create("classValue", Type.class); } diff --git a/core/src/main/java/org/sinytra/adapter/patch/mixin/MixinTypes.java b/core/src/main/java/org/sinytra/adapter/patch/mixin/MixinTypes.java index 840f9fa6..5f688b2b 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/mixin/MixinTypes.java +++ b/core/src/main/java/org/sinytra/adapter/patch/mixin/MixinTypes.java @@ -4,6 +4,7 @@ import org.sinytra.adapter.env.util.MixinAnnotations; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.ModifyArg; +import org.spongepowered.asm.mixin.injection.ModifyConstant; import org.spongepowered.asm.mixin.injection.ModifyVariable; import org.spongepowered.asm.mixin.injection.Redirect; @@ -14,12 +15,13 @@ public class MixinTypes { private static final Map MIXIN_TYPES = new HashMap<>(); // TODO: - // MixinConstants.MODIFY_ARGS, MixinConstants.MODIFY_CONST, MixinConstants.WRAP_WITH_CONDITION, + // MixinConstants.MODIFY_ARGS, MixinConstants.WRAP_WITH_CONDITION, // MixinConstants.OVERWRITE (dont forget special modifyTarget handling) // MixinConstants.ACCESSOR public static final MixinType INJECT = new InjectMixin(); public static final MixinType MODIFY_VAR = new ModifyVariableMixin(); public static final MixinType MODIFY_ARG = new ModifyArgMixin(); + public static final MixinType MODIFY_CONST = new ModifyConstantMixin(); public static final MixinType MODIFY_RET = new ModifyReturnValueMixin(); public static final MixinType REDIRECT = new RedirectMixin(); public static final MixinType WRAP_OP = new WrapOperationMixin(); @@ -29,6 +31,7 @@ public class MixinTypes { registerMixinType(Inject.class, INJECT); registerMixinType(ModifyVariable.class, MODIFY_VAR); registerMixinType(ModifyArg.class, MODIFY_ARG); + registerMixinType(ModifyConstant.class, MODIFY_CONST); registerMixinType(MixinAnnotations.MODIFY_RETURN_VAL_INTERNAL_NAME, MODIFY_RET); registerMixinType(Redirect.class, REDIRECT); registerMixinType(MixinAnnotations.WRAP_OPERATION_INTERNAL_NAME, WRAP_OP); diff --git a/core/src/main/java/org/sinytra/adapter/patch/mixin/ModifyConstantMixin.java b/core/src/main/java/org/sinytra/adapter/patch/mixin/ModifyConstantMixin.java new file mode 100644 index 00000000..36ffd727 --- /dev/null +++ b/core/src/main/java/org/sinytra/adapter/patch/mixin/ModifyConstantMixin.java @@ -0,0 +1,55 @@ +package org.sinytra.adapter.patch.mixin; + +import org.sinytra.adapter.env.ctx.MixinContext; +import org.sinytra.adapter.env.param.MethodParameters; +import org.sinytra.adapter.patch.Recipe; +import org.sinytra.adapter.patch.TxResult; +import org.sinytra.adapter.patch.config.Configuration; +import org.sinytra.adapter.patch.config.ConfigurationTemplates; +import org.sinytra.adapter.patch.config.MutableConfiguration; +import org.sinytra.adapter.patch.config.PropertyContainerTemplate; +import org.sinytra.adapter.patch.config.key.MixinKeys; +import org.sinytra.adapter.patch.processor.Processors; +import org.sinytra.adapter.patch.resolver.Resolvers; + +import java.util.EnumSet; +import java.util.List; +import java.util.Set; + +import static org.sinytra.adapter.env.param.MethodParameters.ParamGroup.CAPTURED_PARAMS; +import static org.sinytra.adapter.env.param.MethodParameters.ParamGroup.SINGLE_ANY; + +public class ModifyConstantMixin implements MixinType { + private static final PropertyContainerTemplate TEMPLATE = ConfigurationTemplates.MIXIN_BASE.extend() + .require(MixinKeys.TARGET_CONSTANT) + .pluralKeys(MixinKeys.TARGET_METHOD, MixinKeys.TARGET_CONSTANT) + .build(); + + @Override + public PropertyContainerTemplate getConfigurationTemplate() { + return TEMPLATE; + } + + @Override + public Set getFlags() { + return EnumSet.of(MixinFlag.RETURN_TYPE_SENSITIVE); + } + + @Override + public TxResult preProcess(MixinContext context, MutableConfiguration clean, Resolvers resolvers, Processors processors) { + clean.setParameters(MethodParameters.create(context.methodNode(), List.of(SINGLE_ANY, CAPTURED_PARAMS))); + return TxResult.SUCCESS; + } + + @Override + public TxResult postProcess(MixinContext context, Configuration clean, MutableConfiguration dirty, Recipe recipe) { + if (dirty.getTargetMethod() == null) { + return TxResult.FAIL; + } + + dirty.inheritProperyIfAbsent(MixinKeys.TARGET_CONSTANT); + dirty.inheritParameters(); + dirty.inheritReturnType(); + return TxResult.SUCCESS; + } +} diff --git a/core/src/main/java/org/sinytra/adapter/patch/resolver/injection/InjectionPointResolver.java b/core/src/main/java/org/sinytra/adapter/patch/resolver/injection/InjectionPointResolver.java index d20851f4..df945aae 100644 --- a/core/src/main/java/org/sinytra/adapter/patch/resolver/injection/InjectionPointResolver.java +++ b/core/src/main/java/org/sinytra/adapter/patch/resolver/injection/InjectionPointResolver.java @@ -6,6 +6,7 @@ import org.sinytra.adapter.env.ctx.MixinContext; import org.sinytra.adapter.patch.Recipe; import org.sinytra.adapter.patch.config.Configuration; +import org.sinytra.adapter.patch.config.key.MixinKeys; import org.sinytra.adapter.patch.resolver.CompoundResolver; import org.sinytra.adapter.env.ctx.TargetPair; import org.sinytra.adapter.util.MethodQualifier; @@ -24,7 +25,8 @@ public InjectionPointResolver() { @Override protected boolean canApply(Recipe recipe) { - return recipe.dirty().getAtData() == null; + return recipe.dirty().getAtData() == null + && !recipe.clean().hasProperty(MixinKeys.TARGET_CONSTANT); } @Nullable diff --git a/core/src/main/java/org/sinytra/adapter/transform/PipelineMethodTransformer.java b/core/src/main/java/org/sinytra/adapter/transform/PipelineMethodTransformer.java index 769dbae6..47926e8d 100644 --- a/core/src/main/java/org/sinytra/adapter/transform/PipelineMethodTransformer.java +++ b/core/src/main/java/org/sinytra/adapter/transform/PipelineMethodTransformer.java @@ -46,11 +46,12 @@ public PipelineMethodTransformer(List methodPatches, boolean patche @Override public PatchResult apply(MixinContext context, Configuration config) { + boolean hasManualPatch = this.patchResolver.matches(config); TargetPair cleanTarget = context.methods().findOwnMethodPair(context.cleanLookup(), config.getTargetMethod()); - if (cleanTarget == null) return PatchResult.PASS; + if (cleanTarget == null && !hasManualPatch) return PatchResult.PASS; TargetPair dirtyTarget = context.methods().findOwnMethodPair(context.dirtyLookup(), config.getTargetMethod()); - if (!this.patchResolver.matches(config) && !failsDirtyInjectionCheck(context, config, dirtyTarget) && hasValidSlice(context, config, dirtyTarget)) + if (!hasManualPatch && !failsDirtyInjectionCheck(context, config, dirtyTarget) && hasValidSlice(context, config, dirtyTarget)) return PatchResult.PASS; LOGGER.debug(MIXINPATCH, "Considering method {}", context.getMixinId()); diff --git a/core/src/main/java/org/sinytra/adapter/transform/patch/MethodPatchBuilder.java b/core/src/main/java/org/sinytra/adapter/transform/patch/MethodPatchBuilder.java index 8be5d2e2..ae5142b3 100644 --- a/core/src/main/java/org/sinytra/adapter/transform/patch/MethodPatchBuilder.java +++ b/core/src/main/java/org/sinytra/adapter/transform/patch/MethodPatchBuilder.java @@ -30,6 +30,10 @@ public interface MethodPatchBuilder { MethodPatchBuilder modifyTarget(String method); + MethodPatchBuilder modifyOrdinal(int ordinal); + + MethodPatchBuilder modifyInjectionPointOrdinal(int ordinal); + MethodPatchBuilder modifyInjectionPoint(String target); MethodPatchBuilder modifyInjectionPoint(String value, String target); diff --git a/core/src/main/java/org/sinytra/adapter/transform/patch/MethodPatchBuilderImpl.java b/core/src/main/java/org/sinytra/adapter/transform/patch/MethodPatchBuilderImpl.java index 1e5dd727..30843b67 100644 --- a/core/src/main/java/org/sinytra/adapter/transform/patch/MethodPatchBuilderImpl.java +++ b/core/src/main/java/org/sinytra/adapter/transform/patch/MethodPatchBuilderImpl.java @@ -109,6 +109,21 @@ public MethodPatchBuilder modifyTarget(String method) { return this; } + @Override + public MethodPatchBuilder modifyOrdinal(int ordinal) { + this.config.setProperty(MixinKeys.ORDINAL, ordinal); + return this; + } + + @Override + public MethodPatchBuilder modifyInjectionPointOrdinal(int ordinal) { + this.configCompleter = this.configCompleter.andThen((clean, dirty) -> { + AtData at = dirty.getAtData() == null ? clean.getAtData() : dirty.getAtData(); + dirty.setAtData(at.withOrdinal(ordinal)); + }); + return this; + } + @Override public MethodPatchBuilder modifyInjectionPoint(String target) { // AtData requires value which must be copied at transform time diff --git a/test/src/test/java/org/sinytra/adapter/patch/test/MethodPatchExtensionTest.java b/test/src/test/java/org/sinytra/adapter/patch/test/MethodPatchExtensionTest.java new file mode 100644 index 00000000..6ac56d50 --- /dev/null +++ b/test/src/test/java/org/sinytra/adapter/patch/test/MethodPatchExtensionTest.java @@ -0,0 +1,39 @@ +package org.sinytra.adapter.patch.test; + +import org.junit.jupiter.api.Test; +import org.sinytra.adapter.env.ann.AtData; +import org.sinytra.adapter.patch.config.MutableConfiguration; +import org.sinytra.adapter.patch.config.key.MixinKeys; +import org.sinytra.adapter.patch.mixin.MixinTypes; +import org.sinytra.adapter.transform.patch.MethodPatch; +import org.spongepowered.asm.mixin.injection.ModifyConstant; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; + +class MethodPatchExtensionTest { + @Test + void recognizesModifyConstantMixins() { + assertSame( + MixinTypes.MODIFY_CONST, + MixinTypes.getMixinType(ModifyConstant.class.getName().replace('.', '/')) + ); + } + + @Test + void updatesMethodAndInjectionPointOrdinalsTogether() { + MethodPatch patch = MethodPatch.builder() + .modifyOrdinal(3) + .modifyInjectionPointOrdinal(1) + .build(); + MutableConfiguration clean = MutableConfiguration.create() + .setAtData(AtData.builder("INVOKE").target("Lexample/Target;call()V").build()); + MutableConfiguration dirty = MutableConfiguration.create(); + + patch.configCompleter().accept(clean, dirty); + + assertEquals(3, patch.configuration().getProperty(MixinKeys.ORDINAL).orElseThrow()); + assertEquals(1, dirty.getAtData().getOrdinal().orElseThrow()); + assertEquals("Lexample/Target;call()V", dirty.getAtData().getTargetOrThrow()); + } +} From c04d7f93ce5bccfba40d678b06cae1f8316a6c0f Mon Sep 17 00:00:00 2001 From: DominicBytes Date: Fri, 17 Jul 2026 02:19:27 -0500 Subject: [PATCH 2/2] test: cover ModifyConstant dynamic patches --- .../mixin/DynamicMixinPatchTest.java | 27 ++++++++++++++++- .../test/mc_26_1_2/mixin/AnvilMenuMixin.java | 29 +++++++++++++++++++ .../patch/test/MethodPatchExtensionTest.java | 11 +++++++ 3 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/AnvilMenuMixin.java diff --git a/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java b/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java index 8d4cf1aa..0fe4f17c 100644 --- a/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java +++ b/test/mc-26.1.2/src/test/java/org/sinytra/adapter/patch/test/mc_26_1_2/mixin/DynamicMixinPatchTest.java @@ -10,6 +10,7 @@ import org.sinytra.adapter.patch.DynamicPatches; import org.sinytra.adapter.patch.Patcher; import org.sinytra.adapter.patch.test_main.mixin.MinecraftMixinPatchTest; +import org.sinytra.adapter.transform.patch.MethodPatch; import org.sinytra.adapter.types.FieldTypeUsageTransformer; import org.sinytra.adapter.util.provider.ClassLookup; import org.slf4j.Logger; @@ -17,6 +18,8 @@ import java.util.List; +import static org.sinytra.adapter.env.util.MixinAnnotations.MODIFY_CONST; + public class DynamicMixinPatchTest extends MinecraftMixinPatchTest { private static final Logger LOGGER = LogUtils.getLogger(); @@ -45,7 +48,14 @@ public void copyEntries(String from, String to) { ); patcher = Patcher.builder(patchEnvironment) .classTransformer(new FieldTypeUsageTransformer()) - .methodTransformers(DynamicPatches.methodTransformers(List.of())) + .methodTransformers(DynamicPatches.methodTransformers(List.of( + MethodPatch.builder() + .targetClass("net/minecraft/world/inventory/AnvilMenu") + .targetMethod("createResult") + .targetMixinType(MODIFY_CONST) + .modifyTarget("createResultInternal") + .build() + ))) .build(); } @@ -64,6 +74,21 @@ void testExtractUsingCodePath() throws Exception { ); } + /** + * Reproduces Origins Classes' efficient-repairs mixin, whose integer + * {@code @ModifyConstant} target moves from {@code AnvilMenu#createResult} + * to NeoForge's {@code AnvilMenu#createResultInternal}. + */ + @Test + void testModifyConstantTarget() throws Exception { + assertSameCode( + "org/sinytra/adapter/test/mc_26_1_2/mixin/AnvilMenuMixin", + "halfRepairMaterialCost", + assertTargetMethod(), + assertTargetsConstant() + ); + } + @Override protected LoadResult load(String className, List allowedMethods) throws Exception { ClassNode patched = loadClass(className); diff --git a/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/AnvilMenuMixin.java b/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/AnvilMenuMixin.java new file mode 100644 index 00000000..904abe65 --- /dev/null +++ b/test/mc-26.1.2/src/testClasses/java/org/sinytra/adapter/test/mc_26_1_2/mixin/AnvilMenuMixin.java @@ -0,0 +1,29 @@ +package org.sinytra.adapter.test.mc_26_1_2.mixin; + +import net.minecraft.world.inventory.AnvilMenu; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.Constant; +import org.spongepowered.asm.mixin.injection.ModifyConstant; + +/** + * Equivalent of the Origins Classes efficient-repairs mixin used to verify + * retargeting an integer {@link ModifyConstant} injector for NeoForge. + */ +@Mixin(AnvilMenu.class) +public class AnvilMenuMixin { + @ModifyConstant( + method = "createResult", + constant = @Constant(intValue = 4, ordinal = 0) + ) + private int halfRepairMaterialCost(int original) { + return original / 2; + } + + @ModifyConstant( + method = "createResultInternal", + constant = @Constant(intValue = 4) + ) + private int halfRepairMaterialCostExpected(int original) { + return original / 2; + } +} diff --git a/test/src/test/java/org/sinytra/adapter/patch/test/MethodPatchExtensionTest.java b/test/src/test/java/org/sinytra/adapter/patch/test/MethodPatchExtensionTest.java index 6ac56d50..73c110f3 100644 --- a/test/src/test/java/org/sinytra/adapter/patch/test/MethodPatchExtensionTest.java +++ b/test/src/test/java/org/sinytra/adapter/patch/test/MethodPatchExtensionTest.java @@ -11,7 +11,14 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; +/** + * Tests extension points used by explicit platform compatibility patches. + */ class MethodPatchExtensionTest { + /** + * Verifies that standard {@link ModifyConstant} injectors are dispatched to + * the configuration model that resolves their nested {@code @Constant} target. + */ @Test void recognizesModifyConstantMixins() { assertSame( @@ -20,6 +27,10 @@ void recognizesModifyConstantMixins() { ); } + /** + * Verifies that an injector ordinal and its nested injection-point ordinal can + * be changed independently without either update discarding the other. + */ @Test void updatesMethodAndInjectionPointOrdinalsTogether() { MethodPatch patch = MethodPatch.builder()