Add interactive storage overlay

This commit is contained in:
Linnea Gräf
2024-07-10 01:34:37 +02:00
parent 4e1cda1d64
commit 986ce538f1
39 changed files with 1141 additions and 228 deletions

View File

@@ -0,0 +1,77 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package moe.nea.firmament.init;
import me.shedaniel.mm.api.ClassTinkerers;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.VarInsnNode;
import java.lang.reflect.Modifier;
import java.util.Objects;
public class ClientPlayerRiser extends RiserUtils {
String PlayerEntity = remapper.mapClassName("intermediary", "net.minecraft.class_1657");
String World = remapper.mapClassName("intermediary", "net.minecraft.class_1937");
String GameProfile = "com.mojang.authlib.GameProfile";
String BlockPos = remapper.mapClassName("intermediary", "net.minecraft.class_2338");
String AbstractClientPlayerEntity = remapper.mapClassName("intermediary", "net.minecraft.class_742");
String GuiPlayer = "moe.nea.firmament.gui.entity.GuiPlayer";
// World world, BlockPos pos, float yaw, GameProfile gameProfile
Type constructorDescriptor = Type.getMethodType(Type.VOID_TYPE, getTypeForClassName(World), getTypeForClassName(BlockPos), Type.FLOAT_TYPE, getTypeForClassName(GameProfile));
private void mapClassNode(ClassNode classNode, Type superClass) {
for (MethodNode method : classNode.methods) {
if (Objects.equals(method.name, "<init>") && Type.getMethodType(method.desc).equals(constructorDescriptor)) {
modifyConstructor(method, superClass);
return;
}
}
var node = new MethodNode(Opcodes.ASM9, "<init>", constructorDescriptor.getDescriptor(), null, null);
classNode.methods.add(node);
modifyConstructor(node, superClass);
}
private void modifyConstructor(MethodNode method, Type superClass) {
method.access = (method.access | Modifier.PUBLIC) & ~Modifier.PRIVATE & ~Modifier.PROTECTED;
if (method.instructions.size() != 0) return; // Some other mod has already made a constructor here
// World world, BlockPos pos, float yaw, GameProfile gameProfile
// ALOAD this
method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
// ALOAD World
method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 1));
// ALOAD BlockPos
method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 2));
// ALOAD yaw
method.instructions.add(new VarInsnNode(Opcodes.FLOAD, 3));
// ALOAD gameProfile
method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 4));
// Call super
method.instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, superClass.getInternalName(), "<init>", constructorDescriptor.getDescriptor(), false));
// Return
method.instructions.add(new InsnNode(Opcodes.RETURN));
}
@Override
public void addTinkerers() {
ClassTinkerers.addTransformation(AbstractClientPlayerEntity, it -> mapClassNode(it, getTypeForClassName(PlayerEntity)));
ClassTinkerers.addTransformation(GuiPlayer, it -> mapClassNode(it, getTypeForClassName(AbstractClientPlayerEntity)));
}
}

View File

@@ -6,73 +6,10 @@
package moe.nea.firmament.init;
import me.shedaniel.mm.api.ClassTinkerers;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.MappingResolver;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.*;
import java.lang.reflect.Modifier;
import java.util.Objects;
public class EarlyRiser implements Runnable {
MappingResolver remapper = FabricLoader.getInstance().getMappingResolver();
String PlayerEntity = remapper.mapClassName("intermediary", "net.minecraft.class_1657");
String World = remapper.mapClassName("intermediary", "net.minecraft.class_1937");
String GameProfile = "com.mojang.authlib.GameProfile";
String BlockPos = remapper.mapClassName("intermediary", "net.minecraft.class_2338");
String AbstractClientPlayerEntity = remapper.mapClassName("intermediary", "net.minecraft.class_742");
String GuiPlayer = "moe.nea.firmament.gui.entity.GuiPlayer";
// World world, BlockPos pos, float yaw, GameProfile gameProfile
Type constructorDescriptor = Type.getMethodType(Type.VOID_TYPE, getTypeForClassName(World), getTypeForClassName(BlockPos), Type.FLOAT_TYPE, getTypeForClassName(GameProfile));
@Override
public void run() {
ClassTinkerers.addTransformation(AbstractClientPlayerEntity, it -> mapClassNode(it, getTypeForClassName(PlayerEntity)));
ClassTinkerers.addTransformation(GuiPlayer, it -> mapClassNode(it, getTypeForClassName(AbstractClientPlayerEntity)));
}
private void mapClassNode(ClassNode classNode, Type superClass) {
for (MethodNode method : classNode.methods) {
if (Objects.equals(method.name, "<init>") && Type.getMethodType(method.desc).equals(constructorDescriptor)) {
modifyConstructor(method, superClass);
return;
}
}
var node = new MethodNode(Opcodes.ASM9, "<init>", constructorDescriptor.getDescriptor(), null, null);
classNode.methods.add(node);
modifyConstructor(node, superClass);
}
private Type getTypeForClassName(String className) {
return Type.getObjectType(className.replace('.', '/'));
}
private void modifyConstructor(MethodNode method, Type superClass) {
method.access = (method.access | Modifier.PUBLIC) & ~Modifier.PRIVATE & ~Modifier.PROTECTED;
if (method.instructions.size() != 0) return; // Some other mod has already made a constructor here
// World world, BlockPos pos, float yaw, GameProfile gameProfile
// ALOAD this
method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
// ALOAD World
method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 1));
// ALOAD BlockPos
method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 2));
// ALOAD yaw
method.instructions.add(new VarInsnNode(Opcodes.FLOAD, 3));
// ALOAD gameProfile
method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 4));
// Call super
method.instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, superClass.getInternalName(), "<init>", constructorDescriptor.getDescriptor(), false));
// Return
method.instructions.add(new InsnNode(Opcodes.RETURN));
new ClientPlayerRiser().addTinkerers();
new HandledScreenRiser().addTinkerers();
}
}

View File

@@ -0,0 +1,86 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package moe.nea.firmament.init;
import me.shedaniel.mm.api.ClassTinkerers;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
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.LdcInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.VarInsnNode;
import java.lang.reflect.Modifier;
public class HandledScreenRiser extends RiserUtils {
String Screen = remapper.mapClassName("intermediary", "net.minecraft.class_437");
String HandledScreen = remapper.mapClassName("intermediary", "net.minecraft.class_465");
Type mouseScrolledDesc = Type.getMethodType(Type.BOOLEAN_TYPE, Type.DOUBLE_TYPE, Type.DOUBLE_TYPE, Type.DOUBLE_TYPE, Type.DOUBLE_TYPE);
String mouseScrolled = remapper.mapMethodName("intermediary", "net.minecraft.class_364", "method_25401",
mouseScrolledDesc.getDescriptor());
@Override
public void addTinkerers() {
ClassTinkerers.addTransformation(HandledScreen, this::handle);
}
void handle(ClassNode classNode) {
MethodNode mouseScrolledNode = findMethod(classNode, mouseScrolled, mouseScrolledDesc);
if (mouseScrolledNode == null) {
mouseScrolledNode = new MethodNode(
Modifier.PUBLIC,
mouseScrolled,
mouseScrolledDesc.getDescriptor(),
null,
new String[0]
);
var insns = mouseScrolledNode.instructions;
// ALOAD 0, load this
insns.add(new VarInsnNode(Opcodes.ALOAD, 0));
// DLOAD 1-4, load the 4 argument doubles. Note that since doubles are two entries wide we skip 2 each time.
insns.add(new VarInsnNode(Opcodes.DLOAD, 1));
insns.add(new VarInsnNode(Opcodes.DLOAD, 3));
insns.add(new VarInsnNode(Opcodes.DLOAD, 5));
insns.add(new VarInsnNode(Opcodes.DLOAD, 7));
// INVOKESPECIAL call super method
insns.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, getTypeForClassName(Screen).getInternalName(), mouseScrolled, mouseScrolledDesc.getDescriptor()));
// IRETURN return int on stack (booleans are int at runtime)
insns.add(new InsnNode(Opcodes.IRETURN));
classNode.methods.add(mouseScrolledNode);
}
var insns = new InsnList();
// ALOAD 0, load this
insns.add(new VarInsnNode(Opcodes.ALOAD, 0));
// DLOAD 1-4, load the 4 argument doubles. Note that since doubles are two entries wide we skip 2 each time.
insns.add(new VarInsnNode(Opcodes.DLOAD, 1));
insns.add(new VarInsnNode(Opcodes.DLOAD, 3));
insns.add(new VarInsnNode(Opcodes.DLOAD, 5));
insns.add(new VarInsnNode(Opcodes.DLOAD, 7));
// INVOKEVIRTUAL call custom handler
insns.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
getTypeForClassName(HandledScreen).getInternalName(),
"mouseScrolled_firmament",
mouseScrolledDesc.getDescriptor()));
// Create jump target (but not insert it yet)
var jumpIfFalse = new LabelNode();
// IFEQ (if returned boolean == 0), jump to jumpIfFalse
insns.add(new JumpInsnNode(Opcodes.IFEQ, jumpIfFalse));
// LDC 1 (as int, which is what booleans are at runtime)
insns.add(new LdcInsnNode(1));
// IRETURN return int on stack (booleans are int at runtime)
insns.add(new InsnNode(Opcodes.IRETURN));
insns.add(jumpIfFalse);
mouseScrolledNode.instructions.insert(insns);
}
}

View File

@@ -0,0 +1,32 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package moe.nea.firmament.init;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.MappingResolver;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;
public abstract class RiserUtils {
protected Type getTypeForClassName(String className) {
return Type.getObjectType(className.replace('.', '/'));
}
protected MappingResolver remapper = FabricLoader.getInstance().getMappingResolver();
public abstract void addTinkerers();
protected MethodNode findMethod(ClassNode classNode, String name, Type desc) {
for (MethodNode method : classNode.methods) {
if (method.name.equals(name) && desc.getDescriptor().equals(method.desc))
return method;
}
return null;
}
}