diff --git a/build.gradle.kts b/build.gradle.kts index e0acfc7..238cd2a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,58 +12,66 @@ val versionMc: String by project val versionForge: String by project val timestamp: String = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd.HHmmss")) -group = "org.sinytra.adapter" version = "${AdapterPlugin.getDefinitionVersion()?.let { "$it-" } ?: ""}$versionMc-$timestamp" -base { - archivesName.set(project.name.lowercase()) -} println("Data version: $version") -java { - toolchain { - languageVersion.set(JavaLanguageVersion.of(17)) - } -} +allprojects { + apply(plugin = "net.neoforged.gradle") + apply(plugin = "maven-publish") -minecraft { - mappings("official", versionMc) -} + group = "org.sinytra.adapter" -repositories { - mavenCentral() - maven { - name = "MinecraftForge" - url = uri("https://maven.minecraftforge.net/") + base { + archivesName.set(project.name.lowercase()) } -} - -dependencies { - minecraft(group = "net.minecraftforge", name = "forge", version = "$versionMc-$versionForge") -} -tasks { - jar { - from(generateAdapterData) + java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } + withSourcesJar() } -} -publishing { - publications { - create("mavenJava") { - from(components["java"]) - fg.component(this) - artifactId = project.base.archivesName.get() - } + minecraft { + mappings("official", versionMc) } + repositories { + mavenCentral() maven { - name = "Su5eD" - url = uri("https://maven.su5ed.dev/releases") - credentials { - username = System.getenv("MAVEN_USER") ?: "not" - password = System.getenv("MAVEN_PASSWORD") ?: "set" + name = "MinecraftForge" + url = uri("https://maven.minecraftforge.net/") + } + } + + dependencies { + minecraft(group = "net.minecraftforge", name = "forge", version = "$versionMc-$versionForge") + } + + publishing { + publications { + create("mavenJava") { + from(components["java"]) + fg.component(this) + artifactId = project.base.archivesName.get() + } + } + repositories { + maven { + name = "Su5eD" + url = uri("https://maven.su5ed.dev/releases") + credentials { + username = System.getenv("MAVEN_USER") ?: "not" + password = System.getenv("MAVEN_PASSWORD") ?: "set" + } } } } } + +tasks { + jar { + from(generateAdapterData) + } +} diff --git a/definition/src/main/java/org/sinytra/adapter/patch/api/MixinConstants.java b/definition/src/main/java/org/sinytra/adapter/patch/api/MixinConstants.java index 60a632f..28d75e5 100644 --- a/definition/src/main/java/org/sinytra/adapter/patch/api/MixinConstants.java +++ b/definition/src/main/java/org/sinytra/adapter/patch/api/MixinConstants.java @@ -24,6 +24,8 @@ public class MixinConstants { public static final String OPERATION_INTERNAL_NAME = "com/llamalad7/mixinextras/injector/wrapoperation/Operation"; public static final String LOCAL = "Lcom/llamalad7/mixinextras/sugar/Local;"; public static final String SHARE = "Lcom/llamalad7/mixinextras/sugar/Share;"; + // Adapter custom mixin injectors + public static final String MODIFY_INSTANCEOF_VAL = "Lorg/sinytra/adapter/runtime/inject/ModifyInstanceofValue;"; // Misc public static final String MIXIN = "Lorg/spongepowered/asm/mixin/Mixin;"; public static final String AT = "Lorg/spongepowered/asm/mixin/injection/At;"; diff --git a/definition/src/main/java/org/sinytra/adapter/patch/transformer/dynamic/DynamicSyntheticInstanceofPatch.java b/definition/src/main/java/org/sinytra/adapter/patch/transformer/dynamic/DynamicSyntheticInstanceofPatch.java index 1a510d6..6989fe4 100644 --- a/definition/src/main/java/org/sinytra/adapter/patch/transformer/dynamic/DynamicSyntheticInstanceofPatch.java +++ b/definition/src/main/java/org/sinytra/adapter/patch/transformer/dynamic/DynamicSyntheticInstanceofPatch.java @@ -1,11 +1,13 @@ package org.sinytra.adapter.patch.transformer.dynamic; +import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.*; import org.sinytra.adapter.patch.analysis.InsnComparator; import org.sinytra.adapter.patch.analysis.InstructionMatcher; import org.sinytra.adapter.patch.analysis.MethodCallAnalyzer; import org.sinytra.adapter.patch.api.*; import org.sinytra.adapter.patch.transformer.DisableMixin; +import org.sinytra.adapter.patch.transformer.ModifyMixinType; import java.util.*; @@ -19,9 +21,11 @@ * Reference: stack.isOf(Items.CROSSBOW) -> stack.getItem() instanceof CrossbowItem in HeldItemRenderer#renderFirstPersonItem */ public class DynamicSyntheticInstanceofPatch implements MethodTransform { + private static final int RANGE = 4; + @Override public Collection getAcceptedAnnotations() { - return Set.of(MixinConstants.REDIRECT); + return Set.of(MixinConstants.REDIRECT, MixinConstants.MODIFY_EXPR_VAL); } @Override @@ -43,14 +47,28 @@ public Patch.Result apply(ClassNode classNode, MethodNode methodNode, MethodCont if (!(jumpInsn instanceof JumpInsnNode)) { return Patch.Result.PASS; } - InstructionMatcher cleanMatcher = MethodCallAnalyzer.findForwardInstructions(targetInsn, 5, true); + InstructionMatcher cleanMatcher = MethodCallAnalyzer.findForwardInstructions(targetInsn, RANGE, true); int firstOp = cleanMatcher.after().get(0).getOpcode(); // Find equivalent dirty code point - for (AbstractInsnNode insn : methodContext.findDirtyInjectionTarget().methodNode().instructions) { + InsnList dirtyInsns = methodContext.findDirtyInjectionTarget().methodNode().instructions; + for (AbstractInsnNode insn : dirtyInsns) { if (insn.getOpcode() == firstOp) { AbstractInsnNode nextLabel = findInsnAfterLabel(insn); - InstructionMatcher dirtyMatcher = MethodCallAnalyzer.findForwardInstructions(nextLabel, 5, true); + InstructionMatcher dirtyMatcher = MethodCallAnalyzer.findForwardInstructions(nextLabel, RANGE, true); if (cleanMatcher.test(dirtyMatcher)) { + // ModifyExpressionValue doesn't include the original instanceof call, so we can skip comparing instructions + if (methodContext.methodAnnotation().matchesDesc(MixinConstants.MODIFY_EXPR_VAL)) { + TypeInsnNode instanceOfInsn = (TypeInsnNode) findLabelInsns(insn).stream().filter(i -> i.getOpcode() == Opcodes.INSTANCEOF).findFirst().orElseThrow(); + MethodTransform transform = new ModifyMixinType(MixinConstants.MODIFY_INSTANCEOF_VAL, b -> { + b.sameTarget().injectionPoint("sinytra:INSTANCEOF", instanceOfInsn.desc); + int ordinal = getInstanceofOrdinal(dirtyInsns, instanceOfInsn); + if (ordinal != 0) { + b.putValue("ordinal", ordinal); + } + }); + return transform.apply(classNode, methodNode, methodContext, context); + } + // Found the code point, now determine the contents of the updated if statement List dirtyLabelInsns = findLabelInsns(insn); @@ -120,4 +138,14 @@ private static List findLabelInsns(AbstractInsnNode insn) { } return list; } + + private static int getInstanceofOrdinal(InsnList insns, AbstractInsnNode insn) { + List instanceOfInsns = new ArrayList<>(); + for (AbstractInsnNode node : insns) { + if (node.getOpcode() == Opcodes.INSTANCEOF) { + instanceOfInsns.add(insn); + } + } + return instanceOfInsns.indexOf(insn); + } } diff --git a/gradle.properties b/gradle.properties index 2e566ac..4f9b06e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,4 +5,6 @@ org.gradle.daemon=true #org.gradle.caching=true versionMc=1.20.1 -versionForge=47.1.3 \ No newline at end of file +versionForge=47.1.3 + +runtimeVersion=1.0.0 \ No newline at end of file diff --git a/runtime/build.gradle.kts b/runtime/build.gradle.kts new file mode 100644 index 0000000..f1046a0 --- /dev/null +++ b/runtime/build.gradle.kts @@ -0,0 +1,20 @@ +val runtimeVersion: String by project +val versionMc: String by project + +version = "$runtimeVersion+$versionMc" + +println("Runtime version: $version") + +dependencies { + implementation(annotationProcessor("io.github.llamalad7:mixinextras-common:0.3.6")!!) +} + +tasks.jar { + manifest { + manifest.attributes( + "Implementation-Version" to project.version, + "MixinConfigs" to "adapter.init.mixins.json", + "FMLModType" to "GAMELIBRARY" + ) + } +} diff --git a/runtime/src/main/java/org/sinytra/adapter/runtime/AdapterMixinPlugin.java b/runtime/src/main/java/org/sinytra/adapter/runtime/AdapterMixinPlugin.java new file mode 100644 index 0000000..19dac98 --- /dev/null +++ b/runtime/src/main/java/org/sinytra/adapter/runtime/AdapterMixinPlugin.java @@ -0,0 +1,30 @@ +package org.sinytra.adapter.runtime; + +import org.objectweb.asm.tree.ClassNode; +import org.sinytra.adapter.runtime.inject.InstanceOfInjectionPoint; +import org.sinytra.adapter.runtime.inject.ModifyInstanceofValueInjectionInfo; +import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; +import org.spongepowered.asm.mixin.extensibility.IMixinInfo; +import org.spongepowered.asm.mixin.injection.InjectionPoint; +import org.spongepowered.asm.mixin.injection.struct.InjectionInfo; + +import java.util.List; +import java.util.Set; + +public class AdapterMixinPlugin implements IMixinConfigPlugin { + + static { + InjectionPoint.register(InstanceOfInjectionPoint.class, null); + InjectionInfo.register(ModifyInstanceofValueInjectionInfo.class); + } + + //@formatter:off + @Override public void onLoad(String mixinPackage) {} + @Override public String getRefMapperConfig() {return "";} + @Override public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {return true;} + @Override public void acceptTargets(Set myTargets, Set otherTargets) {} + @Override public List getMixins() {return List.of();} + @Override public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {} + @Override public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {} + //@formatter:on +} diff --git a/runtime/src/main/java/org/sinytra/adapter/runtime/inject/InstanceOfInjectionPoint.java b/runtime/src/main/java/org/sinytra/adapter/runtime/inject/InstanceOfInjectionPoint.java new file mode 100644 index 0000000..aafd3a1 --- /dev/null +++ b/runtime/src/main/java/org/sinytra/adapter/runtime/inject/InstanceOfInjectionPoint.java @@ -0,0 +1,38 @@ +package org.sinytra.adapter.runtime.inject; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.TypeInsnNode; +import org.spongepowered.asm.mixin.injection.InjectionPoint; +import org.spongepowered.asm.mixin.injection.selectors.ITargetSelectorByName; +import org.spongepowered.asm.mixin.injection.struct.InjectionPointData; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +@InjectionPoint.AtCode(value = "INSTANCEOF", namespace = "sinytra") +public class InstanceOfInjectionPoint extends InjectionPoint { + private final String target; + + public InstanceOfInjectionPoint(InjectionPointData data) { + super(data); + + this.target = ((ITargetSelectorByName) data.getTarget()).getOwner(); + } + + @Override + public boolean find(String s, InsnList insns, Collection nodes) { + List found = new ArrayList<>(); + + for (AbstractInsnNode insn : insns) { + if (insn instanceof TypeInsnNode type && insn.getOpcode() == Opcodes.INSTANCEOF && type.desc.equals(this.target)) { + found.add(insn); + } + } + + nodes.addAll(found); + return !found.isEmpty(); + } +} diff --git a/runtime/src/main/java/org/sinytra/adapter/runtime/inject/ModifyInstanceofValue.java b/runtime/src/main/java/org/sinytra/adapter/runtime/inject/ModifyInstanceofValue.java new file mode 100644 index 0000000..5c1437c --- /dev/null +++ b/runtime/src/main/java/org/sinytra/adapter/runtime/inject/ModifyInstanceofValue.java @@ -0,0 +1,27 @@ +package org.sinytra.adapter.runtime.inject; + +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Slice; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface ModifyInstanceofValue { + String[] method(); + + At[] at(); + + Slice[] slice() default {}; + + boolean remap() default true; + + int require() default -1; + + int expect() default 1; + + int allow() default -1; +} diff --git a/runtime/src/main/java/org/sinytra/adapter/runtime/inject/ModifyInstanceofValueInjectionInfo.java b/runtime/src/main/java/org/sinytra/adapter/runtime/inject/ModifyInstanceofValueInjectionInfo.java new file mode 100644 index 0000000..d851644 --- /dev/null +++ b/runtime/src/main/java/org/sinytra/adapter/runtime/inject/ModifyInstanceofValueInjectionInfo.java @@ -0,0 +1,22 @@ +package org.sinytra.adapter.runtime.inject; + +import com.llamalad7.mixinextras.injector.MixinExtrasInjectionInfo; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.MethodNode; +import org.spongepowered.asm.mixin.injection.code.Injector; +import org.spongepowered.asm.mixin.injection.struct.InjectionInfo; +import org.spongepowered.asm.mixin.transformer.MixinTargetContext; + +@InjectionInfo.AnnotationType(ModifyInstanceofValue.class) +@InjectionInfo.HandlerPrefix("modifyInstanceofValue") +public class ModifyInstanceofValueInjectionInfo extends MixinExtrasInjectionInfo { + + public ModifyInstanceofValueInjectionInfo(MixinTargetContext mixin, MethodNode method, AnnotationNode annotation) { + super(mixin, method, annotation); + } + + @Override + protected Injector parseInjector(AnnotationNode injectAnnotation) { + return new ModifyInstanceofValueInjector(this); + } +} diff --git a/runtime/src/main/java/org/sinytra/adapter/runtime/inject/ModifyInstanceofValueInjector.java b/runtime/src/main/java/org/sinytra/adapter/runtime/inject/ModifyInstanceofValueInjector.java new file mode 100644 index 0000000..a3d9612 --- /dev/null +++ b/runtime/src/main/java/org/sinytra/adapter/runtime/inject/ModifyInstanceofValueInjector.java @@ -0,0 +1,47 @@ +package org.sinytra.adapter.runtime.inject; + +import com.llamalad7.mixinextras.injector.StackExtension; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.InsnNode; +import org.objectweb.asm.tree.VarInsnNode; +import org.spongepowered.asm.mixin.injection.code.Injector; +import org.spongepowered.asm.mixin.injection.struct.InjectionInfo; +import org.spongepowered.asm.mixin.injection.struct.InjectionNodes; +import org.spongepowered.asm.mixin.injection.struct.Target; + +public class ModifyInstanceofValueInjector extends Injector { + + public ModifyInstanceofValueInjector(InjectionInfo info) { + super(info, "@ModifyInstanceofValue"); + } + + @Override + protected void inject(Target target, InjectionNodes.InjectionNode node) { + AbstractInsnNode valueNode = node.getCurrentTarget(); + StackExtension stack = new StackExtension(target); + + InjectorData handler = new InjectorData(target, "instanceof value modifier"); + validateParams(handler, Type.BOOLEAN_TYPE, Type.BOOLEAN_TYPE); + + InsnList insns = new InsnList(); + + if (!this.isStatic) { + insns.add(new VarInsnNode(Opcodes.ALOAD, 0)); + insns.add(new InsnNode(Opcodes.SWAP)); + } + + if (handler.captureTargetArgs > 0) { + pushArgs(target.arguments, insns, target.getArgIndices(), 0, handler.captureTargetArgs); + } + + stack.receiver(this.isStatic); + stack.capturedArgs(target.arguments, handler.captureTargetArgs); + + invokeHandler(insns); + + target.insns.insert(valueNode, insns); + } +} diff --git a/runtime/src/main/resources/adapter.init.mixins.json b/runtime/src/main/resources/adapter.init.mixins.json new file mode 100644 index 0000000..1b02b28 --- /dev/null +++ b/runtime/src/main/resources/adapter.init.mixins.json @@ -0,0 +1,5 @@ +{ + "minVersion": "0.8.5", + "plugin": "org.sinytra.adapter.runtime.AdapterMixinPlugin", + "package": "org.sinytra.adapter.runtime.mixin" +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 7a0f8e9..0bba50b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -18,4 +18,5 @@ pluginManagement { rootProject.name = "Adapter" -includeBuild("plugin") \ No newline at end of file +includeBuild("plugin") +include("runtime") \ No newline at end of file