Restore patch to vanilla command node (#464)

This commit is contained in:
IzzelAliz 2022-02-07 11:37:56 +08:00
parent d07354e23d
commit b547770edb
No known key found for this signature in database
GPG Key ID: EE50E123A11D8338
3 changed files with 78 additions and 2 deletions

View File

@ -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<Boolean> cir) {
CommandNode currentCommand = bridge$getCurrentCommand();
if (currentCommand != null) {
cir.setReturnValue(hasPermission(level, VanillaCommandWrapper.getPermission(currentCommand)));
}
@ -47,8 +50,12 @@ public abstract class CommandSourceStackMixin implements CommandSourceBridge {
@Override
public CommandNode<?> bridge$getCurrentCommand() {
if (currentCommand == null) {
return CommandNodeHooks.getCurrent();
} else {
return currentCommand;
}
}
@Override
public void bridge$setCurrentCommand(CommandNode<?> node) {

View File

@ -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<String, ?>) Unsafe.getObject(node, ARGUMENTS)).remove(command);
}
public static CommandNode<?> getCurrent() {
return (CommandNode<?>) Unsafe.getObjectVolatile(CURRENT_BASE, CURRENT);
}
public static <S> boolean canUse(CommandNode<S> node, S source) {
if (source instanceof CommandSourceBridge s) {
try {

View File

@ -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 {