From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: violetc <58360096+s-yh-china@users.noreply.github.com> Date: Tue, 26 Sep 2023 19:00:41 +0800 Subject: [PATCH] Leaves: Protocol Core Original license: GPLv3 Original project: https://github.com/LeavesMC/Leaves diff --git a/src/main/java/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java b/src/main/java/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java index af86f752c33a2990405fea058b7c41c437ba9d46..bada9fae1e7178162429e1f5a1608b9c4a680a6c 100644 --- a/src/main/java/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java +++ b/src/main/java/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java @@ -20,7 +20,12 @@ public record ServerboundCustomPayloadPacket(CustomPacketPayload payload) implem private static CustomPacketPayload readPayload(ResourceLocation id, FriendlyByteBuf buf) { FriendlyByteBuf.Reader packetdataserializer_a = (FriendlyByteBuf.Reader) ServerboundCustomPayloadPacket.KNOWN_TYPES.get(id); - + // Leaves start - protocol + CustomPacketPayload leavesPayload = top.leavesmc.leaves.protocol.core.LeavesProtocolManager.getPayload(id, buf); + if (leavesPayload != null) { + return leavesPayload; + } + // Leaves end - protocol return (CustomPacketPayload) (packetdataserializer_a != null ? (CustomPacketPayload) packetdataserializer_a.apply(buf) : readUnknownPayload(id, buf)); } diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index 86bb3df2915495387c43cf425e3183682c9e0344..f2cef17007767a148d4957e654af8a655920790e 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -1711,6 +1711,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop>> KNOWN_TYPES = new HashMap<>(); + private static final Map> KNOW_RECEIVERS = new HashMap<>(); + + private static final List TICKERS = new ArrayList<>(); + private static final List PLAYER_JOIN = new ArrayList<>(); + private static final List PLAYER_LEAVE = new ArrayList<>(); + private static final List RELOAD_SERVER = new ArrayList<>(); + private static final Map> MINECRAFT_REGISTER = new HashMap<>(); + + public static void init() { + for (Class clazz : getClasses("top.leavesmc.leaves.protocol")) { + final LeavesProtocol protocol = clazz.getAnnotation(LeavesProtocol.class); + if (protocol != null) { + Set methods; + try { + Method[] publicMethods = clazz.getMethods(); + Method[] privateMethods = clazz.getDeclaredMethods(); + methods = new HashSet<>(publicMethods.length + privateMethods.length, 1.0f); + Collections.addAll(methods, publicMethods); + Collections.addAll(methods, privateMethods); + } catch (NoClassDefFoundError e) { + e.printStackTrace(); + return; + } + + Map> map = KNOWN_TYPES.getOrDefault(protocol, new HashMap<>()); + for (final Method method : methods) { + if (method.isBridge() || method.isSynthetic() || !Modifier.isStatic(method.getModifiers())) { + continue; + } + + method.setAccessible(true); + + final ProtocolHandler.Init init = method.getAnnotation(ProtocolHandler.Init.class); + if (init != null) { + try { + method.invoke(null); + } catch (InvocationTargetException | IllegalAccessException e) { + e.printStackTrace(); + } + continue; + } + + final ProtocolHandler.PayloadReceiver receiver = method.getAnnotation(ProtocolHandler.PayloadReceiver.class); + if (receiver != null) { + try { + map.put(receiver, receiver.payload().getConstructor(ResourceLocation.class, FriendlyByteBuf.class)); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + continue; + } + + if (!KNOW_RECEIVERS.containsKey(protocol)) { + KNOW_RECEIVERS.put(protocol, new HashMap<>()); + } + + KNOW_RECEIVERS.get(protocol).put(receiver, method); + continue; + } + + final ProtocolHandler.Ticker ticker = method.getAnnotation(ProtocolHandler.Ticker.class); + if (ticker != null) { + TICKERS.add(method); + continue; + } + + final ProtocolHandler.PlayerJoin playerJoin = method.getAnnotation(ProtocolHandler.PlayerJoin.class); + if (playerJoin != null) { + PLAYER_JOIN.add(method); + continue; + } + + final ProtocolHandler.PlayerLeave playerLeave = method.getAnnotation(ProtocolHandler.PlayerLeave.class); + if (playerLeave != null) { + PLAYER_LEAVE.add(method); + continue; + } + + final ProtocolHandler.ReloadServer reloadServer = method.getAnnotation(ProtocolHandler.ReloadServer.class); + if (reloadServer != null) { + RELOAD_SERVER.add(method); + continue; + } + + final ProtocolHandler.MinecraftRegister minecraftRegister = method.getAnnotation(ProtocolHandler.MinecraftRegister.class); + if (minecraftRegister != null) { + if (!MINECRAFT_REGISTER.containsKey(protocol)) { + MINECRAFT_REGISTER.put(protocol, new HashMap<>()); + } + + MINECRAFT_REGISTER.get(protocol).put(minecraftRegister, method); + } + } + KNOWN_TYPES.put(protocol, map); + } + } + } + + public static CustomPacketPayload getPayload(ResourceLocation id, FriendlyByteBuf buf) { + for (LeavesProtocol protocol : KNOWN_TYPES.keySet()) { + if (!ArrayUtils.contains(protocol.namespace(), id.getNamespace())) { + continue; + } + + Map> map = KNOWN_TYPES.get(protocol); + for (ProtocolHandler.PayloadReceiver receiver : map.keySet()) { + if (receiver.ignoreId() || ArrayUtils.contains(receiver.payloadId(), id.getPath())) { + try { + return map.get(receiver).newInstance(id, buf); + } catch (InvocationTargetException | InstantiationException | IllegalAccessException e) { + e.printStackTrace(); + buf.readBytes(buf.readableBytes()); + return new ErrorPayload(id, protocol.namespace(), receiver.payloadId()); + } + } + } + } + return null; + } + + public static void handlePayload(ServerPlayer player, CustomPacketPayload payload) { + if (payload instanceof ServerboundCustomPayloadPacket.UnknownPayload) { + return; + } + + if (payload instanceof ErrorPayload errorPayload) { + player.connection.disconnect("Payload " + Arrays.toString(errorPayload.packetID) + " from " + Arrays.toString(errorPayload.protocolID) + " error", PlayerKickEvent.Cause.INVALID_PAYLOAD); + return; + } + + for (LeavesProtocol protocol : KNOW_RECEIVERS.keySet()) { + if (!ArrayUtils.contains(protocol.namespace(), payload.id().getNamespace())) { + continue; + } + + Map map = KNOW_RECEIVERS.get(protocol); + for (ProtocolHandler.PayloadReceiver receiver : map.keySet()) { + if (payload.getClass() == receiver.payload()) { + if (receiver.ignoreId() || ArrayUtils.contains(receiver.payloadId(), payload.id().getPath())) { + try { + map.get(receiver).invoke(null, player, payload); + } catch (InvocationTargetException | IllegalAccessException e) { + e.printStackTrace(); + } + } + } + } + } + } + + public static void handleTick() { + if (!TICKERS.isEmpty()) { + try { + for (Method method : TICKERS) { + method.invoke(null); + } + } catch (InvocationTargetException | IllegalAccessException e) { + e.printStackTrace(); + } + } + } + + public static void handlePlayerJoin(ServerPlayer player) { + if (!PLAYER_JOIN.isEmpty()) { + try { + for (Method method : PLAYER_JOIN) { + method.invoke(null, player); + } + } catch (InvocationTargetException | IllegalAccessException e) { + e.printStackTrace(); + } + } + } + + public static void handlePlayerLeave(ServerPlayer player) { + if (!PLAYER_LEAVE.isEmpty()) { + try { + for (Method method : PLAYER_LEAVE) { + method.invoke(null, player); + } + } catch (InvocationTargetException | IllegalAccessException e) { + e.printStackTrace(); + } + } + } + + public static void handleServerReload() { + if (!RELOAD_SERVER.isEmpty()) { + try { + for (Method method : RELOAD_SERVER) { + method.invoke(null); + } + } catch (InvocationTargetException | IllegalAccessException e) { + e.printStackTrace(); + } + } + } + + public static void handleMinecraftRegister(String channelId, ServerPlayer player) { + for (LeavesProtocol protocol : MINECRAFT_REGISTER.keySet()) { + String[] channel = channelId.split(":"); + if (!ArrayUtils.contains(protocol.namespace(), channel[0])) { + continue; + } + + Map map = MINECRAFT_REGISTER.get(protocol); + for (ProtocolHandler.MinecraftRegister register : map.keySet()) { + if (register.ignoreId() || register.channelId().equals(channel[1]) || + ArrayUtils.contains(register.channelIds(), channel[1])) { + try { + map.get(register).invoke(null, player); + } catch (InvocationTargetException | IllegalAccessException e) { + e.printStackTrace(); + } + } + } + } + } + + public static Set> getClasses(String pack) { + Set> classes = new LinkedHashSet>(); + String packageDirName = pack.replace('.', '/'); + Enumeration dirs; + try { + dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName); + while (dirs.hasMoreElements()) { + URL url = dirs.nextElement(); + String protocol = url.getProtocol(); + if ("file".equals(protocol)) { + String filePath = URLDecoder.decode(url.getFile(), StandardCharsets.UTF_8); + findClassesInPackageByFile(pack, filePath, classes); + } else if ("jar".equals(protocol)) { + JarFile jar; + try { + jar = ((JarURLConnection) url.openConnection()).getJarFile(); + Enumeration entries = jar.entries(); + findClassesInPackageByJar(pack, entries, packageDirName, classes); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } catch (IOException e) { + e.printStackTrace(); + } + return classes; + } + + private static void findClassesInPackageByFile(String packageName, String packagePath, Set> classes) { + File dir = new File(packagePath); + if (!dir.exists() || !dir.isDirectory()) { + return; + } + File[] dirfiles = dir.listFiles((file) -> file.isDirectory() || file.getName().endsWith(".class")); + if (dirfiles != null) { + for (File file : dirfiles) { + if (file.isDirectory()) { + findClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), classes); + } else { + String className = file.getName().substring(0, file.getName().length() - 6); + try { + classes.add(Class.forName(packageName + '.' + className)); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + } + } + } + } + + private static void findClassesInPackageByJar(String packageName, Enumeration entries, String packageDirName, Set> classes) { + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String name = entry.getName(); + if (name.charAt(0) == '/') { + name = name.substring(1); + } + if (name.startsWith(packageDirName)) { + int idx = name.lastIndexOf('/'); + if (idx != -1) { + packageName = name.substring(0, idx).replace('/', '.'); + } + if (name.endsWith(".class") && !entry.isDirectory()) { + String className = name.substring(packageName.length() + 1, name.length() - 6); + try { + classes.add(Class.forName(packageName + '.' + className)); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + } + } + } + } + + public record ErrorPayload(ResourceLocation id, String[] protocolID, + String[] packetID) implements CustomPacketPayload { + @Override + public void write(@NotNull FriendlyByteBuf buf) { + } + } + + public record EmptyPayload(ResourceLocation id) implements CustomPacketPayload { + + public EmptyPayload(ResourceLocation location, FriendlyByteBuf buf) { + this(location); + } + + @Override + public void write(@NotNull FriendlyByteBuf buf) { + } + } + + public record LeavesPayload(FriendlyByteBuf data, ResourceLocation id) implements CustomPacketPayload { + + public LeavesPayload(ResourceLocation location, FriendlyByteBuf buf) { + this(new FriendlyByteBuf(buf.readBytes(buf.readableBytes())), location); + } + + @Override + public void write(FriendlyByteBuf buf) { + buf.writeBytes(data); + } + } +} diff --git a/src/main/java/top/leavesmc/leaves/protocol/core/ProtocolHandler.java b/src/main/java/top/leavesmc/leaves/protocol/core/ProtocolHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..92ad6e9b1c0d9640b80c1ebe739c613d989eec21 --- /dev/null +++ b/src/main/java/top/leavesmc/leaves/protocol/core/ProtocolHandler.java @@ -0,0 +1,63 @@ +package top.leavesmc.leaves.protocol.core; + +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +public class ProtocolHandler { + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface Init { + + } + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface PayloadReceiver { + + Class payload(); + + String[] payloadId(); + + boolean ignoreId() default false; + } + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface Ticker { + int delay() default 0; + } + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface PlayerJoin { + + } + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface PlayerLeave { + + } + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface ReloadServer { + + } + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface MinecraftRegister { + + String channelId() default ""; + + String[] channelIds() default {}; + + boolean ignoreId() default false; + } +} diff --git a/src/main/java/top/leavesmc/leaves/protocol/core/ProtocolUtils.java b/src/main/java/top/leavesmc/leaves/protocol/core/ProtocolUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..5282c5ad3d26d06ab685ddaaf6fd9a4d49559717 --- /dev/null +++ b/src/main/java/top/leavesmc/leaves/protocol/core/ProtocolUtils.java @@ -0,0 +1,36 @@ +package top.leavesmc.leaves.protocol.core; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.NotNull; + +import java.util.function.Consumer; + +public class ProtocolUtils { + + public static void sendEmptyPayloadPacket(ServerPlayer player, ResourceLocation id) { + player.connection.send(new ClientboundCustomPayloadPacket(new LeavesProtocolManager.EmptyPayload(id))); + } + + public static void sendPayloadPacket(ServerPlayer player, ResourceLocation id, Consumer consumer) { + player.connection.send(new ClientboundCustomPayloadPacket(new CustomPacketPayload() { + @Override + public void write(@NotNull FriendlyByteBuf buf) { + consumer.accept(buf); + } + + @Override + @NotNull + public ResourceLocation id() { + return id; + } + })); + } + + public static void sendPayloadPacket(ServerPlayer player, CustomPacketPayload payload) { + player.connection.send(new ClientboundCustomPayloadPacket(payload)); + } +}