Skip to content

Commit

Permalink
Paper 1.19 compat via dynamic code gen
Browse files Browse the repository at this point in the history
Java byte code is a special flavor of utterly cursed
  • Loading branch information
mcmonkey4eva committed Aug 6, 2022
1 parent ef8312b commit f6b02a7
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 3 deletions.
@@ -0,0 +1,123 @@
package com.denizenscript.denizen.utilities.packets;

import com.denizenscript.denizen.nms.NMSHandler;
import com.denizenscript.denizen.utilities.debugging.Debug;
import com.denizenscript.denizencore.utilities.codegen.CodeGenUtil;
import org.objectweb.asm.*;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

public class NetworkInterceptCodeGen {

public static MethodHandle generateInstance = null;

public static void generateClass(Class<?> denClass, Class<?> abstractClass, Class<?> nmsClass) {
try {
Constructor<?> origConstructor = denClass.getConstructors()[0];
// ====== Build class ======
String className = "com/denizenscript/denizen/network_intercept_codegen/GeneratedInterceptor";
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, className, null, Type.getInternalName(denClass), new String[0]);
cw.visitSource("GENERATED_INTERCEPTOR", null);
// ====== Build constructor ======
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", Type.getConstructorDescriptor(origConstructor), null, null);
mv.visitCode();
Label startLabel = new Label();
mv.visitLabel(startLabel);
mv.visitLineNumber(0, startLabel);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitVarInsn(Opcodes.ALOAD, 2);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(denClass), "<init>", Type.getConstructorDescriptor(origConstructor), false);
mv.visitInsn(Opcodes.RETURN);
mv.visitLocalVariable("this", "L" + className + ";", null, startLabel, startLabel, 0);
mv.visitMaxs(0, 0);
mv.visitEnd();
// ====== Find public methods ======
for (Method method : nmsClass.getDeclaredMethods()) {
int modifier = method.getModifiers();
if (Modifier.isPublic(modifier) && !Modifier.isFinal(modifier) && !Modifier.isStatic(modifier)) {
boolean hasMethod = false;
try {
abstractClass.getDeclaredMethod(method.getName(), method.getParameterTypes());
hasMethod = true;
}
catch (NoSuchMethodException ignore) {}
if (!hasMethod) {
if (NMSHandler.debugPackets) {
Debug.log("Must override " + method + " --- " + method.getName() + ", returns " + method.getReturnType() + " is " + modifier
+ ", Public=" + Modifier.isPublic(modifier) + ", final=" + Modifier.isFinal(modifier));
}
mv = cw.visitMethod(Opcodes.ACC_PUBLIC, method.getName(), Type.getMethodDescriptor(method), null, null);
mv.visitCode();
startLabel = new Label();
mv.visitLabel(startLabel);
mv.visitLineNumber(0, startLabel);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(Opcodes.GETFIELD, Type.getInternalName(abstractClass), "oldListener", Type.getDescriptor(nmsClass));
int id = 1;
for (Class<?> type : method.getParameterTypes()) {
if (NMSHandler.debugPackets) {
Debug.log("Var " + id + " is type " + type.getName());
}
int index = id++;
if (type == int.class || type == boolean.class || type == short.class || type == char.class) { mv.visitVarInsn(Opcodes.ILOAD, index); } // Everything sub-integer-width is secretly integers
else if (type == long.class) { mv.visitVarInsn(Opcodes.LLOAD, index); id++; }
else if (type == float.class) { mv.visitVarInsn(Opcodes.FLOAD, index); }
else if (type == double.class) { mv.visitVarInsn(Opcodes.DLOAD, index); id++; } // Doubles and longs have two vars secretly for some reason
else { mv.visitVarInsn(Opcodes.ALOAD, index); }
}
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(nmsClass), method.getName(), Type.getMethodDescriptor(method), false);
int returnCode = Opcodes.ARETURN;
Class<?> returnType = method.getReturnType();
if (returnType == int.class || returnType == boolean.class || returnType == short.class || returnType == char.class) { returnCode = Opcodes.IRETURN; }
else if (returnType == long.class) { returnCode = Opcodes.LRETURN; }
else if (returnType == float.class) { returnCode = Opcodes.FRETURN; }
else if (returnType == double.class) { returnCode = Opcodes.DRETURN; }
else if (returnType == void.class) { returnCode = Opcodes.RETURN; }
mv.visitInsn(returnCode);
mv.visitLocalVariable("this", "L" + className + ";", null, startLabel, startLabel, 0);
id = 1;
for (Class<?> type : method.getParameterTypes()) {
mv.visitLocalVariable("var" + id, Type.getDescriptor(type), null, startLabel, startLabel, id);
if (type == double.class || type == long.class) {
id++;
}
id++;
}
mv.visitMaxs(0, 0);
mv.visitEnd();
}
}
}
// ====== Compile and return ======
cw.visitEnd();
byte[] compiled = cw.toByteArray();
Class<?> generatedClass = CodeGenUtil.loader.define(className.replace('/', '.'), compiled);
Constructor<?> ctor = generatedClass.getDeclaredConstructor(origConstructor.getParameterTypes());
ctor.setAccessible(true);
generateInstance = MethodHandles.lookup().unreflectConstructor(ctor);
}
catch (Throwable ex) {
Debug.echoError(ex);
}
}

public static Object generateAppropriateInterceptor(Object netMan, Object player, Class<?> denClass, Class<?> abstractClass, Class<?> nmsClass) {
if (generateInstance == null) {
generateClass(denClass, abstractClass, nmsClass);
}
try {
return generateInstance.invoke(netMan, player);
}
catch (Throwable ex) {
Debug.echoError(ex);
return null;
}
}
}
Expand Up @@ -74,6 +74,8 @@ public class ReflectionMappingsInfo {
public static String ServerGamePacketListenerImpl_aboveGroundTickCount = "G";
public static String ServerGamePacketListenerImpl_aboveGroundVehicleTickCount = "I";
public static String ServerGamePacketListenerImpl_connection = "b";
public static String ServerGamePacketListenerImpl_awaitingPositionFromClient = "C";
public static String ServerGamePacketListenerImpl_awaitingTeleport = "D";

// net.minecraft.network.protocol.game.ClientboundPlayerAbilitiesPacket
public static String ClientboundPlayerAbilitiesPacket_walkingSpeed = "j";
Expand Down
Expand Up @@ -2,6 +2,9 @@

import com.denizenscript.denizen.events.player.PlayerSendPacketScriptEvent;
import com.denizenscript.denizen.nms.NMSHandler;
import com.denizenscript.denizen.nms.v1_19.ReflectionMappingsInfo;
import com.denizenscript.denizencore.utilities.ReflectionHelper;
import com.denizenscript.denizencore.utilities.debugging.Debug;
import net.minecraft.network.Connection;
import net.minecraft.network.PacketSendListener;
import net.minecraft.network.chat.Component;
Expand All @@ -12,10 +15,12 @@
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import net.minecraft.world.phys.Vec3;
import org.bukkit.Location;
import org.bukkit.craftbukkit.v1_19_R1.entity.CraftPlayer;
import org.bukkit.event.player.PlayerTeleportEvent;

import java.lang.reflect.Field;
import java.util.Set;

public class AbstractListenerPlayInImpl extends ServerGamePacketListenerImpl {
Expand Down Expand Up @@ -119,10 +124,37 @@ public void send(Packet<?> packet, PacketSendListener listener) {
oldListener.send(packet, listener);
}

public static Field AWAITING_POS_FIELD = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get(ReflectionMappingsInfo.ServerGamePacketListenerImpl_awaitingPositionFromClient, Vec3.class);
public static Field AWAITING_TELEPORT_FIELD = ReflectionHelper.getFields(ServerGamePacketListenerImpl.class).get(ReflectionMappingsInfo.ServerGamePacketListenerImpl_awaitingTeleport, int.class);

public void debugPacketOutput(Packet<ServerGamePacketListener> packet) {
try {
if (packet instanceof ServerboundMovePlayerPacket) {
ServerboundMovePlayerPacket movePacket = (ServerboundMovePlayerPacket) packet;
DenizenNetworkManagerImpl.doPacketOutput("Packet ServerboundMovePlayerPacket sent from " + player.getScoreboardName() + " with XYZ="
+ movePacket.x + ", " + movePacket.y + ", " + movePacket.z + ", yRot=" + movePacket.yRot + ", xRot=" + movePacket.xRot
+ ", onGround=" + movePacket.isOnGround() + ", hasPos=" + movePacket.hasPos + ", hasRot=" + movePacket.hasRot);
}
else if (packet instanceof ServerboundAcceptTeleportationPacket) {
Vec3 awaitPos = (Vec3) AWAITING_POS_FIELD.get(oldListener);
int awaitTeleportId = AWAITING_TELEPORT_FIELD.getInt(oldListener);
ServerboundAcceptTeleportationPacket acceptPacket = (ServerboundAcceptTeleportationPacket) packet;
DenizenNetworkManagerImpl.doPacketOutput("Packet ServerboundAcceptTeleportationPacket sent from " + player.getScoreboardName()
+ " with ID=" + acceptPacket.getId() + ", awaitingTeleport=" + awaitTeleportId + ", awaitPos=" + awaitPos);
}
else {
DenizenNetworkManagerImpl.doPacketOutput("Packet: " + packet.getClass().getCanonicalName() + " sent from " + player.getScoreboardName());
}
}
catch (Throwable ex) {
Debug.echoError(ex);
}
}

public boolean handlePacketIn(Packet<ServerGamePacketListener> packet) {
denizenNetworkManager.packetsReceived++;
if (NMSHandler.debugPackets) {
DenizenNetworkManagerImpl.doPacketOutput("Packet: " + packet.getClass().getCanonicalName() + " sent from " + player.getScoreboardName());
debugPacketOutput(packet);
}
if (PlayerSendPacketScriptEvent.enabled) {
if (PlayerSendPacketScriptEvent.fireFor(player.getBukkitEntity(), packet)) {
Expand Down
Expand Up @@ -23,6 +23,7 @@
import com.denizenscript.denizen.utilities.entity.HideEntitiesHelper;
import com.denizenscript.denizen.utilities.packets.DenizenPacketHandler;
import com.denizenscript.denizen.utilities.packets.HideParticles;
import com.denizenscript.denizen.utilities.packets.NetworkInterceptCodeGen;
import com.denizenscript.denizencore.objects.core.ElementTag;
import com.denizenscript.denizencore.utilities.CoreConfiguration;
import com.denizenscript.denizencore.utilities.CoreUtilities;
Expand All @@ -40,7 +41,6 @@
import net.minecraft.core.NonNullList;
import net.minecraft.core.SectionPos;
import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.data.BuiltinRegistries;
import net.minecraft.network.*;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
Expand Down Expand Up @@ -100,7 +100,7 @@ public DenizenNetworkManagerImpl(ServerPlayer entityPlayer, Connection oldManage
super(getProtocolDirection(oldManager));
this.oldManager = oldManager;
this.channel = oldManager.channel;
this.packetListener = new DenizenPacketListenerImpl(this, entityPlayer);
this.packetListener = (DenizenPacketListenerImpl) NetworkInterceptCodeGen.generateAppropriateInterceptor(this, entityPlayer, DenizenPacketListenerImpl.class, AbstractListenerPlayInImpl.class, ServerGamePacketListenerImpl.class);
oldManager.setListener(packetListener);
this.player = this.packetListener.player;
}
Expand Down

0 comments on commit f6b02a7

Please sign in to comment.