Skip to content

Commit

Permalink
Introduce ModifyInstanceofValue injector
Browse files Browse the repository at this point in the history
  • Loading branch information
Su5eD committed May 30, 2024
1 parent 503aa2c commit 04b3137
Show file tree
Hide file tree
Showing 12 changed files with 274 additions and 44 deletions.
84 changes: 46 additions & 38 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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<MavenPublication>("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<MavenPublication>("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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;";
Expand Down
Original file line number Diff line number Diff line change
@@ -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.*;

Expand All @@ -19,9 +21,11 @@
* Reference: <code>stack.isOf(Items.CROSSBOW)</code> -> <code>stack.getItem() instanceof CrossbowItem</code> in <code>HeldItemRenderer#renderFirstPersonItem</code>
*/
public class DynamicSyntheticInstanceofPatch implements MethodTransform {
private static final int RANGE = 4;

@Override
public Collection<String> getAcceptedAnnotations() {
return Set.of(MixinConstants.REDIRECT);
return Set.of(MixinConstants.REDIRECT, MixinConstants.MODIFY_EXPR_VAL);
}

@Override
Expand All @@ -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<AbstractInsnNode> dirtyLabelInsns = findLabelInsns(insn);

Expand Down Expand Up @@ -120,4 +138,14 @@ private static List<AbstractInsnNode> findLabelInsns(AbstractInsnNode insn) {
}
return list;
}

private static int getInstanceofOrdinal(InsnList insns, AbstractInsnNode insn) {
List<AbstractInsnNode> instanceOfInsns = new ArrayList<>();
for (AbstractInsnNode node : insns) {
if (node.getOpcode() == Opcodes.INSTANCEOF) {
instanceOfInsns.add(insn);
}
}
return instanceOfInsns.indexOf(insn);
}
}
4 changes: 3 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ org.gradle.daemon=true
#org.gradle.caching=true

versionMc=1.20.1
versionForge=47.1.3
versionForge=47.1.3

runtimeVersion=1.0.0
20 changes: 20 additions & 0 deletions runtime/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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"
)
}
}
Original file line number Diff line number Diff line change
@@ -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<String> myTargets, Set<String> otherTargets) {}
@Override public List<String> 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
}
Original file line number Diff line number Diff line change
@@ -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<AbstractInsnNode> nodes) {
List<AbstractInsnNode> 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();
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
5 changes: 5 additions & 0 deletions runtime/src/main/resources/adapter.init.mixins.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"minVersion": "0.8.5",
"plugin": "org.sinytra.adapter.runtime.AdapterMixinPlugin",
"package": "org.sinytra.adapter.runtime.mixin"
}
Loading

0 comments on commit 04b3137

Please sign in to comment.