Fabric/Quilt fertilization support

This commit is contained in:
Zoë
2022-07-11 21:49:16 -07:00
parent ba6d4649c6
commit 50377a1b89
20 changed files with 342 additions and 32 deletions
@@ -11,6 +11,11 @@ dependencies {
annotationProcessor("net.fabricmc:sponge-mixin:${Versions.Mod.mixin}")
annotationProcessor("dev.architectury:architectury-loom:${Versions.Mod.architecuryLoom}")
modImplementation("com.github.the-glitch-network:minecraft-gudasm:${Versions.Mod.minecraftGudAsm}") {
exclude("net.fabricmc")
exclude("net.fabricmc.fabric-api")
}
implementation(project(path = ":platforms:mixin-common", configuration = "namedElements")) { isTransitive = false }
minecraft("com.mojang:minecraft:${Versions.Mod.minecraft}")
@@ -0,0 +1,81 @@
package com.dfsek.terra.lifecycle.asm;
import com.dfsek.terra.mod.util.FertilizableUtil;
import net.gudenau.minecraft.asm.api.v1.Identifier;
import net.gudenau.minecraft.asm.api.v1.Transformer;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import java.io.IOException;
import com.dfsek.terra.lifecycle.util.ASMUtil;
import com.dfsek.terra.lifecycle.util.LoaderUtil;
import org.objectweb.asm.tree.VarInsnNode;
import static org.objectweb.asm.Opcodes.ACC_STATIC;
public class FertilizableASM implements Transformer {
private static String fertilizableClassName = LoaderUtil.INSTANCE.mapClassName("intermediary", "net.minecraft.class_2256");
private static String fertilizableGrowMethodSignatureBase = String.format("(L%1$s;L%2$s;L%3$s;L%4$s;)",
LoaderUtil.INSTANCE.mapClassName("intermediary", "net.minecraft.class_3218").replace(".", "/"),
LoaderUtil.INSTANCE.mapClassName("intermediary", "net.minecraft.class_5819").replace(".", "/"),
LoaderUtil.INSTANCE.mapClassName("intermediary", "net.minecraft.class_2338").replace(".", "/"),
LoaderUtil.INSTANCE.mapClassName("intermediary", "net.minecraft.class_2680").replace(".", "/"));
private static String fertilizableGrowMethodSignature = fertilizableGrowMethodSignatureBase + "V";
private static String fertilizableGrowMethodName = LoaderUtil.INSTANCE.mapMethodName("intermediary", "net.minecraft.class_2256", "method_9652", "(Lnet/minecraft/class_3218;Lnet/minecraft/class_5819;Lnet/minecraft/class_2338;Lnet/minecraft/class_2680;)V");
@Override
public Identifier getName() {
return new Identifier("terra", "asm_test");
}
@Override
public boolean handlesClass(String name, String transformedName) {
return true;
}
@Override
public boolean transform(ClassNode classNode, Flags flags) {
try {
if (ASMUtil.inheritsFrom(classNode, fertilizableClassName.replace(".", "/"))) {
for (MethodNode method : classNode.methods) {
if (method.name.equals(fertilizableGrowMethodName)) {
if ((method.access & ACC_STATIC) == 0) {
if(method.desc.equals(fertilizableGrowMethodSignature)) {
InsnList list = new InsnList();
list.add(new VarInsnNode(Opcodes.ALOAD, 1));
list.add(new VarInsnNode(Opcodes.ALOAD, 2));
list.add(new VarInsnNode(Opcodes.ALOAD, 3));
list.add(new VarInsnNode(Opcodes.ALOAD, 4));
list.add(new MethodInsnNode(Opcodes.INVOKESTATIC, FertilizableUtil.class.getName().replace(".", "/"), "grow", fertilizableGrowMethodSignatureBase + "Z", false));
LabelNode jmp = new LabelNode();
list.add(new JumpInsnNode(Opcodes.IFNE, jmp));
list.add(new InsnNode(Opcodes.RETURN));
list.add(jmp);
method.instructions.insertBefore(method.instructions.getFirst(), list);
flags.requestFrames();
return true;
}
}
}
}
}
} catch(IOException e) {
throw new RuntimeException(e);
}
return false;
}
}
@@ -0,0 +1,56 @@
package com.dfsek.terra.lifecycle.util;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.tree.ClassNode;
import java.io.IOException;
import java.util.WeakHashMap;
import static org.objectweb.asm.ClassReader.SKIP_CODE;
import static org.objectweb.asm.ClassReader.SKIP_DEBUG;
import static org.objectweb.asm.ClassReader.SKIP_FRAMES;
public class ASMUtil {
static final ThreadLocal<WeakHashMap<String, WeakHashMap<String, Boolean>>> INHERITANCE_CACHE = new ThreadLocal<>();
public static boolean inheritsFrom(ClassNode classNode, String interfaceName) throws IOException {
if (INHERITANCE_CACHE.get() == null) {
INHERITANCE_CACHE.set(new WeakHashMap<>());
}
return inheritsFromInternal(classNode, interfaceName);
}
protected static boolean inheritsFromInternal(ClassNode classNode, String interfaceName) throws IOException {
if (INHERITANCE_CACHE.get().containsKey(classNode.name)) {
if (INHERITANCE_CACHE.get().get(classNode.name).containsKey(interfaceName)) {
return INHERITANCE_CACHE.get().get(classNode.name).get(interfaceName);
}
}
for (String parent : classNode.interfaces) {
if (checkClass(parent, interfaceName)) {
return true;
}
}
if (checkClass(classNode.superName, interfaceName)) {
return true;
}
INHERITANCE_CACHE.get().getOrDefault(classNode.name, new WeakHashMap<>()).put(interfaceName, false);
return false;
}
protected static boolean checkClass(String name, String interfaceName) throws IOException {
if (name.startsWith("java/")) {
return false;
}
boolean isClass = name.equals(interfaceName);
INHERITANCE_CACHE.get().getOrDefault(name, new WeakHashMap<>()).put(interfaceName, isClass);
if (isClass) {
return true;
}
ClassNode node = new ClassNode();
new ClassReader(name).accept(node, SKIP_CODE | SKIP_DEBUG | SKIP_FRAMES);
return inheritsFromInternal(node, interfaceName);
}
}
@@ -0,0 +1,9 @@
package com.dfsek.terra.lifecycle.util;
public abstract class LoaderUtil {
public static LoaderUtil INSTANCE;
public abstract String mapClassName(String namespace, String className);
public abstract String mapMethodName(String namespace, String owner, String name, String descriptor);
}