From 986ce538f123cdec7e0da12ed89ba7225539df0a Mon Sep 17 00:00:00 2001 From: Linnea Gräf Date: Wed, 10 Jul 2024 01:34:37 +0200 Subject: Add interactive storage overlay --- .../moe/nea/firmament/init/ClientPlayerRiser.java | 77 +++++++++++++++++++ .../java/moe/nea/firmament/init/EarlyRiser.java | 67 +---------------- .../moe/nea/firmament/init/HandledScreenRiser.java | 86 ++++++++++++++++++++++ .../java/moe/nea/firmament/init/RiserUtils.java | 32 ++++++++ 4 files changed, 197 insertions(+), 65 deletions(-) create mode 100644 src/main/java/moe/nea/firmament/init/ClientPlayerRiser.java create mode 100644 src/main/java/moe/nea/firmament/init/HandledScreenRiser.java create mode 100644 src/main/java/moe/nea/firmament/init/RiserUtils.java (limited to 'src/main/java/moe/nea/firmament/init') diff --git a/src/main/java/moe/nea/firmament/init/ClientPlayerRiser.java b/src/main/java/moe/nea/firmament/init/ClientPlayerRiser.java new file mode 100644 index 0000000..1d80a9e --- /dev/null +++ b/src/main/java/moe/nea/firmament/init/ClientPlayerRiser.java @@ -0,0 +1,77 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * 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, "") && Type.getMethodType(method.desc).equals(constructorDescriptor)) { + modifyConstructor(method, superClass); + return; + } + } + var node = new MethodNode(Opcodes.ASM9, "", 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(), "", 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))); + } +} diff --git a/src/main/java/moe/nea/firmament/init/EarlyRiser.java b/src/main/java/moe/nea/firmament/init/EarlyRiser.java index 268e3f3..6ea0cd4 100644 --- a/src/main/java/moe/nea/firmament/init/EarlyRiser.java +++ b/src/main/java/moe/nea/firmament/init/EarlyRiser.java @@ -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, "") && Type.getMethodType(method.desc).equals(constructorDescriptor)) { - modifyConstructor(method, superClass); - return; - } - } - var node = new MethodNode(Opcodes.ASM9, "", 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(), "", constructorDescriptor.getDescriptor(), false)); - - // Return - method.instructions.add(new InsnNode(Opcodes.RETURN)); + new ClientPlayerRiser().addTinkerers(); + new HandledScreenRiser().addTinkerers(); } } diff --git a/src/main/java/moe/nea/firmament/init/HandledScreenRiser.java b/src/main/java/moe/nea/firmament/init/HandledScreenRiser.java new file mode 100644 index 0000000..3222a91 --- /dev/null +++ b/src/main/java/moe/nea/firmament/init/HandledScreenRiser.java @@ -0,0 +1,86 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * 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); + } + +} diff --git a/src/main/java/moe/nea/firmament/init/RiserUtils.java b/src/main/java/moe/nea/firmament/init/RiserUtils.java new file mode 100644 index 0000000..4c68dd4 --- /dev/null +++ b/src/main/java/moe/nea/firmament/init/RiserUtils.java @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * 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; + } + +} -- cgit