From b547770edb720c0d904886a63bbec7d3c8ca7deb Mon Sep 17 00:00:00 2001 From: IzzelAliz Date: Mon, 7 Feb 2022 11:37:56 +0800 Subject: [PATCH] Restore patch to vanilla command node (#464) --- .../commands/CommandSourceStackMixin.java | 9 ++- .../common/mod/compat/CommandNodeHooks.java | 9 ++- .../arclight/boot/AbstractBootstrap.java | 62 +++++++++++++++++++ 3 files changed, 78 insertions(+), 2 deletions(-) diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/commands/CommandSourceStackMixin.java b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/commands/CommandSourceStackMixin.java index 41bfcf41..8ca4f400 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/commands/CommandSourceStackMixin.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/commands/CommandSourceStackMixin.java @@ -3,6 +3,7 @@ package io.izzel.arclight.common.mixin.core.commands; import com.mojang.brigadier.tree.CommandNode; import io.izzel.arclight.common.bridge.core.command.CommandSourceBridge; import io.izzel.arclight.common.bridge.core.command.ICommandSourceBridge; +import io.izzel.arclight.common.mod.compat.CommandNodeHooks; import net.minecraft.commands.CommandSource; import net.minecraft.commands.CommandSourceStack; import net.minecraft.server.level.ServerLevel; @@ -28,8 +29,10 @@ public abstract class CommandSourceStackMixin implements CommandSourceBridge { public CommandNode currentCommand; + @SuppressWarnings({"rawtypes", "unchecked"}) @Inject(method = "hasPermission", cancellable = true, at = @At("HEAD")) public void arclight$checkPermission(int level, CallbackInfoReturnable cir) { + CommandNode currentCommand = bridge$getCurrentCommand(); if (currentCommand != null) { cir.setReturnValue(hasPermission(level, VanillaCommandWrapper.getPermission(currentCommand))); } @@ -47,7 +50,11 @@ public abstract class CommandSourceStackMixin implements CommandSourceBridge { @Override public CommandNode bridge$getCurrentCommand() { - return currentCommand; + if (currentCommand == null) { + return CommandNodeHooks.getCurrent(); + } else { + return currentCommand; + } } @Override diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mod/compat/CommandNodeHooks.java b/arclight-common/src/main/java/io/izzel/arclight/common/mod/compat/CommandNodeHooks.java index 130c64d0..1b25f869 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/mod/compat/CommandNodeHooks.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mod/compat/CommandNodeHooks.java @@ -8,13 +8,16 @@ import java.util.Map; public class CommandNodeHooks { - private static final long CHILDREN, LITERALS, ARGUMENTS; + private static final long CHILDREN, LITERALS, ARGUMENTS, CURRENT; + private static final Object CURRENT_BASE; static { try { CHILDREN = Unsafe.objectFieldOffset(CommandNode.class.getDeclaredField("children")); LITERALS = Unsafe.objectFieldOffset(CommandNode.class.getDeclaredField("literals")); ARGUMENTS = Unsafe.objectFieldOffset(CommandNode.class.getDeclaredField("arguments")); + CURRENT_BASE = Unsafe.staticFieldBase(CommandNode.class.getDeclaredField("CURRENT_COMMAND")); + CURRENT = Unsafe.staticFieldOffset(CommandNode.class.getDeclaredField("CURRENT_COMMAND")); } catch (Throwable t) { throw new RuntimeException(t); } @@ -27,6 +30,10 @@ public class CommandNodeHooks { ((Map) Unsafe.getObject(node, ARGUMENTS)).remove(command); } + public static CommandNode getCurrent() { + return (CommandNode) Unsafe.getObjectVolatile(CURRENT_BASE, CURRENT); + } + public static boolean canUse(CommandNode node, S source) { if (source instanceof CommandSourceBridge s) { try { diff --git a/arclight-forge/src/main/java/io/izzel/arclight/boot/AbstractBootstrap.java b/arclight-forge/src/main/java/io/izzel/arclight/boot/AbstractBootstrap.java index 00228c7a..77c7e2bc 100644 --- a/arclight-forge/src/main/java/io/izzel/arclight/boot/AbstractBootstrap.java +++ b/arclight-forge/src/main/java/io/izzel/arclight/boot/AbstractBootstrap.java @@ -5,13 +5,27 @@ import com.google.gson.reflect.TypeToken; import io.izzel.arclight.api.ArclightVersion; import io.izzel.arclight.api.Unsafe; import io.izzel.arclight.i18n.ArclightLocale; +import net.minecraftforge.forgespi.locating.IModLocator; import org.apache.logging.log4j.LogManager; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.InsnList; +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.io.InputStream; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Map; import java.util.jar.Attributes; import java.util.jar.Manifest; import java.util.stream.Collectors; @@ -24,6 +38,54 @@ public class AbstractBootstrap { Object base = Unsafe.staticFieldBase(field); long offset = Unsafe.staticFieldOffset(field); Unsafe.putObjectVolatile(base, offset, new EnumTypeFactory()); + try (var in = getClass().getClassLoader().getResourceAsStream("com/mojang/brigadier/tree/CommandNode.class")) { + var node = new ClassNode(); + new ClassReader(in).accept(node, 0); + { + FieldNode fieldNode = new FieldNode(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_VOLATILE, "CURRENT_COMMAND", "Lcom/mojang/brigadier/tree/CommandNode;", null, null); + node.fields.add(fieldNode); + for (var method : node.methods) { + if (method.name.equals("canUse")) { + for (var instruction : method.instructions) { + if (instruction.getOpcode() == Opcodes.INVOKEINTERFACE || instruction.getOpcode() == Opcodes.INVOKEVIRTUAL) { + var assign = new InsnList(); + assign.add(new VarInsnNode(Opcodes.ALOAD, 0)); + assign.add(new FieldInsnNode(Opcodes.PUTSTATIC, "com/mojang/brigadier/tree/CommandNode", fieldNode.name, fieldNode.desc)); + method.instructions.insertBefore(instruction, assign); + var reset = new InsnList(); + reset.add(new InsnNode(Opcodes.ACONST_NULL)); + reset.add(new FieldInsnNode(Opcodes.PUTSTATIC, "com/mojang/brigadier/tree/CommandNode", fieldNode.name, fieldNode.desc)); + method.instructions.insert(instruction, assign); + break; + } + } + } + } + } + { + var removeCommand = new MethodNode(); + removeCommand.access = Opcodes.ACC_PUBLIC; + removeCommand.name = "removeCommand"; + removeCommand.desc = Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String.class)); + removeCommand.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + removeCommand.instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); + removeCommand.instructions.add(new FieldInsnNode(Opcodes.GETFIELD, "com/mojang/brigadier/tree/CommandNode", "children", Type.getDescriptor(Map.class))); + removeCommand.instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Type.getInternalName(Map.class), "remove", "(Ljava/lang/Object;)Ljava/lang/Object;", true)); + removeCommand.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + removeCommand.instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); + removeCommand.instructions.add(new FieldInsnNode(Opcodes.GETFIELD, "com/mojang/brigadier/tree/CommandNode", "literals", Type.getDescriptor(Map.class))); + removeCommand.instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Type.getInternalName(Map.class), "remove", "(Ljava/lang/Object;)Ljava/lang/Object;", true)); + removeCommand.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + removeCommand.instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); + removeCommand.instructions.add(new FieldInsnNode(Opcodes.GETFIELD, "com/mojang/brigadier/tree/CommandNode", "arguments", Type.getDescriptor(Map.class))); + removeCommand.instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Type.getInternalName(Map.class), "remove", "(Ljava/lang/Object;)Ljava/lang/Object;", true)); + node.methods.add(removeCommand); + } + var cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); + node.accept(cw); + byte[] bytes = cw.toByteArray(); + Unsafe.defineClass("com.mojang.brigadier.tree.CommandNode", bytes, 0, bytes.length, IModLocator.class.getClassLoader() /* MC-BOOTSTRAP */ , getClass().getProtectionDomain()); + } } protected void setupMod() throws Exception {