From 3c7e6b6177de6ef3cff8a46bb1726466a299cdde Mon Sep 17 00:00:00 2001
From: Linnea Gräf <nea@nea.moe>
Date: Sat, 10 Aug 2024 01:59:34 +0200
Subject: Add indigo support to custom block textures

---
 .../moe/nea/firmament/init/ClientPlayerRiser.java  |  13 ++-
 .../java/moe/nea/firmament/init/EarlyRiser.java    |   1 +
 .../moe/nea/firmament/init/HandledScreenRiser.java |   8 +-
 .../java/moe/nea/firmament/init/Intermediary.java  |  63 +++++++++++
 .../moe/nea/firmament/init/IntermediaryName.java   |  21 ++++
 .../nea/firmament/init/SectionBuilderRiser.java    | 116 +++++++++++++++++++++
 6 files changed, 215 insertions(+), 7 deletions(-)
 create mode 100644 src/main/java/moe/nea/firmament/init/Intermediary.java
 create mode 100644 src/main/java/moe/nea/firmament/init/IntermediaryName.java
 create mode 100644 src/main/java/moe/nea/firmament/init/SectionBuilderRiser.java

(limited to 'src/main/java/moe')

diff --git a/src/main/java/moe/nea/firmament/init/ClientPlayerRiser.java b/src/main/java/moe/nea/firmament/init/ClientPlayerRiser.java
index 18dfa17..0a5bedd 100644
--- a/src/main/java/moe/nea/firmament/init/ClientPlayerRiser.java
+++ b/src/main/java/moe/nea/firmament/init/ClientPlayerRiser.java
@@ -1,4 +1,3 @@
-
 package moe.nea.firmament.init;
 
 import me.shedaniel.mm.api.ClassTinkerers;
@@ -14,11 +13,15 @@ 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");
+    @IntermediaryName(net.minecraft.entity.player.PlayerEntity.class)
+    String PlayerEntity;
+    @IntermediaryName(net.minecraft.world.World.class)
+    String World;
     String GameProfile = "com.mojang.authlib.GameProfile";
-    String BlockPos = remapper.mapClassName("intermediary", "net.minecraft.class_2338");
-    String AbstractClientPlayerEntity = remapper.mapClassName("intermediary", "net.minecraft.class_742");
+    @IntermediaryName(net.minecraft.util.math.BlockPos.class)
+    String BlockPos;
+    @IntermediaryName(net.minecraft.client.network.AbstractClientPlayerEntity.class)
+    String AbstractClientPlayerEntity;
     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));
diff --git a/src/main/java/moe/nea/firmament/init/EarlyRiser.java b/src/main/java/moe/nea/firmament/init/EarlyRiser.java
index 77c044d..5eab563 100644
--- a/src/main/java/moe/nea/firmament/init/EarlyRiser.java
+++ b/src/main/java/moe/nea/firmament/init/EarlyRiser.java
@@ -6,5 +6,6 @@ public class EarlyRiser implements Runnable {
     public void run() {
         new ClientPlayerRiser().addTinkerers();
         new HandledScreenRiser().addTinkerers();
+        new SectionBuilderRiser().addTinkerers();
     }
 }
diff --git a/src/main/java/moe/nea/firmament/init/HandledScreenRiser.java b/src/main/java/moe/nea/firmament/init/HandledScreenRiser.java
index 0215523..baa0501 100644
--- a/src/main/java/moe/nea/firmament/init/HandledScreenRiser.java
+++ b/src/main/java/moe/nea/firmament/init/HandledScreenRiser.java
@@ -2,6 +2,8 @@
 package moe.nea.firmament.init;
 
 import me.shedaniel.mm.api.ClassTinkerers;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.gui.screen.ingame.HandledScreen;
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.Type;
 import org.objectweb.asm.tree.ClassNode;
@@ -17,8 +19,10 @@ 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");
+    @IntermediaryName(net.minecraft.client.gui.screen.Screen.class)
+    String Screen;
+    @IntermediaryName(net.minecraft.client.gui.screen.ingame.HandledScreen.class)
+    String HandledScreen;
     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());
diff --git a/src/main/java/moe/nea/firmament/init/Intermediary.java b/src/main/java/moe/nea/firmament/init/Intermediary.java
new file mode 100644
index 0000000..61494d7
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/init/Intermediary.java
@@ -0,0 +1,63 @@
+package moe.nea.firmament.init;
+
+import net.fabricmc.loader.api.FabricLoader;
+import net.fabricmc.loader.api.MappingResolver;
+import org.objectweb.asm.Type;
+
+import java.util.List;
+
+public class Intermediary {
+    private static final MappingResolver RESOLVER = FabricLoader.getInstance().getMappingResolver();
+
+    static String methodName(Object object) {
+        throw new AssertionError("Cannot be called at runtime");
+    }
+
+    static <T> String className() {
+        throw new AssertionError("Cannot be called at runtime");
+    }
+
+    static String id(String source) {
+        return source;
+    }
+
+//    public record Class(
+//        Type intermediaryClass
+//    ) {
+//        public Class(String intermediaryClass) {
+//            this(Type.getObjectType(intermediaryClass.replace('.', '/')));
+//        }
+//
+//        public String getMappedName() {
+//            return RESOLVER.mapClassName("intermediary", intermediaryClass.getInternalName()
+//                                                                          .replace('/', '.'));
+//        }
+//    }
+//
+//    public record Method(
+//        Type intermediaryClassName,
+//        String intermediaryMethodName,
+//        Type intermediaryReturnType,
+//        List<Type> intermediaryArgumentTypes
+//    ) {
+//        public Method(
+//            String intermediaryClassName,
+//            String intermediaryMethodName,
+//            String intermediaryReturnType,
+//            String... intermediaryArgumentTypes
+//        ) {
+//            this(intermediaryClassName, intermediaryMethodName, intermediaryReturnType, List.of(intermediaryArgumentTypes));
+//        }
+//
+//        public String getMappedMethodName() {
+//            return RESOLVER.mapMethodName("intermediary",
+//                                          intermediaryClassName.getInternalName().replace('/', '.'));
+//        }
+//
+//        public Type getIntermediaryDescriptor() {
+//            return Type.getMethodType(intermediaryReturnType, intermediaryArgumentTypes.toArray(Type[]::new));
+//        }
+//
+//
+//    }
+}
diff --git a/src/main/java/moe/nea/firmament/init/IntermediaryName.java b/src/main/java/moe/nea/firmament/init/IntermediaryName.java
new file mode 100644
index 0000000..a22ad0f
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/init/IntermediaryName.java
@@ -0,0 +1,21 @@
+package moe.nea.firmament.init;
+
+import net.fabricmc.loader.api.MappingResolver;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Injects the intermediary name of the given field into this field by replacing its initializer with a call to
+ * {@link MappingResolver#mapClassName(String, String)}
+ */
+@Retention(RetentionPolicy.SOURCE)
+@Target(ElementType.FIELD)
+public @interface IntermediaryName {
+    //    String method() default "";
+//
+//    String field() default "";
+    Class<?> value();
+}
diff --git a/src/main/java/moe/nea/firmament/init/SectionBuilderRiser.java b/src/main/java/moe/nea/firmament/init/SectionBuilderRiser.java
new file mode 100644
index 0000000..2be11a6
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/init/SectionBuilderRiser.java
@@ -0,0 +1,116 @@
+package moe.nea.firmament.init;
+
+import me.shedaniel.mm.api.ClassTinkerers;
+import net.fabricmc.loader.api.FabricLoader;
+import net.minecraft.block.BlockState;
+import net.minecraft.client.render.block.BlockModels;
+import net.minecraft.client.render.block.BlockRenderManager;
+import net.minecraft.client.render.chunk.SectionBuilder;
+import net.minecraft.client.render.model.BakedModel;
+import net.minecraft.util.math.BlockPos;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.InsnList;
+import org.objectweb.asm.tree.LocalVariableNode;
+import org.objectweb.asm.tree.MethodInsnNode;
+import org.objectweb.asm.tree.MethodNode;
+import org.objectweb.asm.tree.VarInsnNode;
+
+public class SectionBuilderRiser extends RiserUtils {
+
+    @IntermediaryName(SectionBuilder.class)
+    String SectionBuilder;
+    @IntermediaryName(BlockPos.class)
+    String BlockPos;
+    @IntermediaryName(BlockRenderManager.class)
+    String BlockRenderManager;
+    @IntermediaryName(BlockState.class)
+    String BlockState;
+    @IntermediaryName(BakedModel.class)
+    String BakedModel;
+    String CustomBlockTextures = "moe.nea.firmament.features.texturepack.CustomBlockTextures";
+
+    Type getModelDesc = Type.getMethodType(
+        getTypeForClassName(BlockRenderManager),
+        getTypeForClassName(BlockState)
+    );
+    String getModel = remapper.mapMethodName(
+        "intermediary",
+        Intermediary.<BlockRenderManager>className(),
+        Intermediary.methodName(net.minecraft.client.render.block.BlockRenderManager::getModel),
+        Type.getMethodDescriptor(
+            getTypeForClassName(Intermediary.<BakedModel>className()),
+            getTypeForClassName(Intermediary.<BlockState>className())
+        )
+    );
+
+    @Override
+    public void addTinkerers() {
+        if (FabricLoader.getInstance().isModLoaded("fabric-renderer-indigo"))
+            ClassTinkerers.addTransformation(SectionBuilder, this::handle, true);
+    }
+
+    private void handle(ClassNode classNode) {
+        for (MethodNode method : classNode.methods) {
+            if (method.name.endsWith("$fabric-renderer-indigo$hookChunkBuildTessellate") &&
+                method.name.startsWith("redirect$")) {
+                handleIndigo(method);
+                return;
+            }
+        }
+        new RuntimeException("Could not inject tesselation hook despite fabric renderer indigo being loaded").printStackTrace();
+    }
+
+    private void handleIndigo(MethodNode method) {
+        LocalVariableNode blockPosVar = null, blockStateVar = null;
+        for (LocalVariableNode localVariable : method.localVariables) {
+            if (Type.getType(localVariable.desc).equals(getTypeForClassName(BlockPos))) {
+                blockPosVar = localVariable;
+            }
+            if (Type.getType(localVariable.desc).equals(getTypeForClassName(BlockState))) {
+                blockStateVar = localVariable;
+            }
+        }
+        if (blockPosVar == null || blockStateVar == null) {
+            System.err.println("Firmament could inject into indigo: missing either block pos or blockstate");
+            return;
+        }
+        for (AbstractInsnNode instruction : method.instructions) {
+            if (instruction.getOpcode() != Opcodes.INVOKEVIRTUAL) continue;
+            var methodInsn = (MethodInsnNode) instruction;
+            if (!(methodInsn.name.equals(getModel) && Type.getObjectType(methodInsn.owner).equals(getTypeForClassName(BlockRenderManager))))
+                continue;
+            method.instructions.insertBefore(
+                methodInsn,
+                new MethodInsnNode(
+                    Opcodes.INVOKESTATIC,
+                    getTypeForClassName(CustomBlockTextures).getInternalName(),
+                    "enterFallbackCall",
+                    Type.getMethodDescriptor(Type.VOID_TYPE)
+                ));
+
+            var insnList = new InsnList();
+            insnList.add(new MethodInsnNode(
+                Opcodes.INVOKESTATIC,
+                getTypeForClassName(CustomBlockTextures).getInternalName(),
+                "exitFallbackCall",
+                Type.getMethodDescriptor(Type.VOID_TYPE)
+            ));
+            insnList.add(new VarInsnNode(Opcodes.ALOAD, blockPosVar.index));
+            insnList.add(new VarInsnNode(Opcodes.ALOAD, blockStateVar.index));
+            insnList.add(new MethodInsnNode(
+                Opcodes.INVOKESTATIC,
+                getTypeForClassName(CustomBlockTextures).getInternalName(),
+                "patchIndigo",
+                Type.getMethodDescriptor(getTypeForClassName(BakedModel),
+                                         getTypeForClassName(BakedModel),
+                                         getTypeForClassName(BlockPos),
+                                         getTypeForClassName(BlockState)),
+                false
+            ));
+            method.instructions.insert(methodInsn, insnList);
+        }
+    }
+}
-- 
cgit