9
0
mirror of https://github.com/Winds-Studio/Leaf.git synced 2025-12-20 15:39:37 +00:00
Files
Leaf/patches/server/0031-Leaves-Protocol-Core.patch
2024-05-21 19:21:31 +08:00

654 lines
30 KiB
Diff

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
TODO - Dreeam: Configurable leaves protocol listening
Original license: GPLv3
Original project: https://github.com/LeavesMC/Leaves
Commit: 87bfa2d2bbc597c8351ec8776b14c5a6166ed01c
diff --git a/src/main/java/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java b/src/main/java/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java
index 663b3b12d9a7cdc04b7f86ccfe6bc6fcfd5028bc..a58ad6f41fc0eacf020e9ab6c8e5f7dfc4977f8d 100644
--- a/src/main/java/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java
+++ b/src/main/java/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java
@@ -40,13 +40,23 @@ public interface CustomPacketPayload {
@Override
public void encode(B friendlyByteBuf, CustomPacketPayload customPacketPayload) {
+ // Leaves start - protocol core
+ if (customPacketPayload instanceof org.leavesmc.leaves.protocol.core.LeavesCustomPayload<?> leavesCustomPayload) {
+ friendlyByteBuf.writeResourceLocation(leavesCustomPayload.id());
+ leavesCustomPayload.write(friendlyByteBuf);
+ return;
+ }
+ // Leaves end - protocol core
this.writeCap(friendlyByteBuf, customPacketPayload.type(), customPacketPayload);
}
@Override
public CustomPacketPayload decode(B friendlyByteBuf) {
ResourceLocation resourceLocation = friendlyByteBuf.readResourceLocation();
- return (CustomPacketPayload)this.findCodec(resourceLocation).decode(friendlyByteBuf);
+ // Leaves start - protocol core
+ var leavesCustomPayload = org.leavesmc.leaves.protocol.core.LeavesProtocolManager.decode(resourceLocation, friendlyByteBuf);
+ return java.util.Objects.requireNonNullElseGet(leavesCustomPayload, () -> (CustomPacketPayload) this.findCodec(resourceLocation).decode(friendlyByteBuf));
+ // Leaves end - protocol core
}
};
}
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 50a275352bdd3a1deef925930b57acf176ddb2ed..c7f44250b1801c581420f93d357d64f615949691 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -1788,6 +1788,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
GameTestTicker.SINGLETON.tick();
}
+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handleTick(); // Leaves - protocol
+
for (int i = 0; i < this.tickables.size(); ++i) {
((Runnable) this.tickables.get(i)).run();
}
diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
index bc14f7ae7c5d3dab3a3fc1ce56c975c32943076c..9fa1cb2146bb8173a246981c0f73cd8c1bc44abe 100644
--- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
@@ -168,6 +168,11 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
@Override
public void handleCustomPayload(ServerboundCustomPayloadPacket packet) {
+ // Leaves start - protocol
+ if (packet.payload() instanceof org.leavesmc.leaves.protocol.core.LeavesCustomPayload<?> leavesPayload) {
+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePayload(player, leavesPayload);
+ }
+ // Leaves end - protocol
// Paper start - Brand support
if (packet.payload() instanceof net.minecraft.network.protocol.common.custom.BrandPayload brandPayload) {
this.player.clientBrandName = brandPayload.brand();
@@ -185,6 +190,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
String channels = payload.toString(com.google.common.base.Charsets.UTF_8);
for (String channel : channels.split("\0")) {
this.getCraftPlayer().addChannel(channel);
+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handleMinecraftRegister(channel, player); // Leaves - protocol
}
} catch (Exception ex) {
ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t register custom payload", ex);
diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
index 2e98c6598e589b498991c745058db3c0efb9cbf6..0d65a53b23c82cbc4539afd28c52b5fd2d2ff5b4 100644
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
@@ -365,6 +365,8 @@ public abstract class PlayerList {
player.didPlayerJoinEvent = true; // Gale - EMC - do not process chat/commands before player has joined
+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePlayerJoin(player); // Leaves - protocol
+
final net.kyori.adventure.text.Component jm = playerJoinEvent.joinMessage();
if (jm != null && !jm.equals(net.kyori.adventure.text.Component.empty())) { // Paper - Adventure
@@ -618,6 +620,7 @@ public abstract class PlayerList {
return this.remove(entityplayer, net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? entityplayer.getBukkitEntity().displayName() : io.papermc.paper.adventure.PaperAdventure.asAdventure(entityplayer.getDisplayName())));
}
public net.kyori.adventure.text.Component remove(ServerPlayer entityplayer, net.kyori.adventure.text.Component leaveMessage) {
+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePlayerLeave(entityplayer); // Leaves - protocol
// Paper end - Fix kick event leave message not being sent
org.purpurmc.purpur.task.BossBarTask.removeFromAll(entityplayer.getBukkitEntity()); // Purpur
ServerLevel worldserver = entityplayer.serverLevel();
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index 5a5619667e68379b810bf8c2912a8c64caa38991..e9475b7761af84b97f522698a304f0b1ccc134a2 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -485,6 +485,7 @@ public final class CraftServer implements Server {
}
this.potionBrewer = new io.papermc.paper.potion.PaperPotionBrewer(console); // Paper - custom potion mixes
datapackManager = new io.papermc.paper.datapack.PaperDatapackManager(console.getPackRepository()); // Paper
+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.init(); // Leaves - protocol
}
public boolean getCommandBlockOverride(String command) {
@@ -1087,6 +1088,7 @@ public final class CraftServer implements Server {
org.purpurmc.purpur.PurpurConfig.registerCommands(); // Purpur
this.overrideAllCommandBlockCommands = this.commandsConfiguration.getStringList("command-block-overrides").contains("*");
this.ignoreVanillaPermissions = this.commandsConfiguration.getBoolean("ignore-vanilla-permissions");
+ org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handleServerReload(); // Leaves - protocol
int pollCount = 0;
diff --git a/src/main/java/org/leavesmc/leaves/protocol/core/LeavesCustomPayload.java b/src/main/java/org/leavesmc/leaves/protocol/core/LeavesCustomPayload.java
new file mode 100644
index 0000000000000000000000000000000000000000..243a8d77d67b9634d6442b60e481d18831367a74
--- /dev/null
+++ b/src/main/java/org/leavesmc/leaves/protocol/core/LeavesCustomPayload.java
@@ -0,0 +1,26 @@
+package org.leavesmc.leaves.protocol.core;
+
+import net.minecraft.network.FriendlyByteBuf;
+import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
+import net.minecraft.resources.ResourceLocation;
+import org.jetbrains.annotations.NotNull;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+public interface LeavesCustomPayload<T extends LeavesCustomPayload<T>> extends CustomPacketPayload {
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface New {
+ }
+
+ void write(FriendlyByteBuf buf);
+
+ ResourceLocation id();
+
+ @Override
+ @NotNull
+ default Type<T> type() {
+ return new CustomPacketPayload.Type<>(id());
+ }
+}
diff --git a/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocol.java b/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocol.java
new file mode 100644
index 0000000000000000000000000000000000000000..986d2a6641ff8017dddf3e5f2655adfc2866c119
--- /dev/null
+++ b/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocol.java
@@ -0,0 +1,12 @@
+package org.leavesmc.leaves.protocol.core;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface LeavesProtocol {
+ String[] namespace();
+}
diff --git a/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocolManager.java b/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocolManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..2356bb3bc68d2a33329b7c4a41b48f1a52576ccb
--- /dev/null
+++ b/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocolManager.java
@@ -0,0 +1,368 @@
+package org.leavesmc.leaves.protocol.core;
+
+import net.minecraft.network.FriendlyByteBuf;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.server.level.ServerPlayer;
+import org.apache.commons.lang.ArrayUtils;
+import org.bukkit.event.player.PlayerKickEvent;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+public class LeavesProtocolManager {
+
+ private static final Logger LOGGER = LogManager.getLogger("Leaves");
+
+ private static final Map<LeavesProtocol, Map<ProtocolHandler.PayloadReceiver, Constructor<? extends LeavesCustomPayload<?>>>> KNOWN_TYPES = new HashMap<>();
+ private static final Map<LeavesProtocol, Map<ProtocolHandler.PayloadReceiver, Method>> KNOW_RECEIVERS = new HashMap<>();
+
+ private static final List<Method> TICKERS = new ArrayList<>();
+ private static final List<Method> PLAYER_JOIN = new ArrayList<>();
+ private static final List<Method> PLAYER_LEAVE = new ArrayList<>();
+ private static final List<Method> RELOAD_SERVER = new ArrayList<>();
+ private static final Map<LeavesProtocol, Map<ProtocolHandler.MinecraftRegister, Method>> MINECRAFT_REGISTER = new HashMap<>();
+
+ public static void init() {
+ for (Class<?> clazz : getClasses("org.leavesmc.leaves.protocol")) {
+ final LeavesProtocol protocol = clazz.getAnnotation(LeavesProtocol.class);
+ if (protocol != null) {
+ Set<Method> 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 error) {
+ LOGGER.fatal("Failed to load class {} due to missing dependencies, {}", clazz.getName(), error.getMessage());
+ return;
+ }
+
+ Map<ProtocolHandler.PayloadReceiver, Constructor<? extends LeavesCustomPayload<?>>> 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 exception) {
+ LOGGER.fatal("Failed to invoke init method {} in {}, {}", method.getName(), clazz.getName(), exception.getMessage());
+ }
+ continue;
+ }
+
+ final ProtocolHandler.PayloadReceiver receiver = method.getAnnotation(ProtocolHandler.PayloadReceiver.class);
+ if (receiver != null) {
+ try {
+ Constructor<? extends LeavesCustomPayload<?>> constructor = receiver.payload().getConstructor(ResourceLocation.class, FriendlyByteBuf.class);
+ constructor.setAccessible(true);
+
+ if (constructor.getAnnotation(LeavesCustomPayload.New.class) == null) {
+ LOGGER.warn("Failed to find new annotation for {}", receiver.payload().getName());
+ }
+
+ map.put(receiver, constructor);
+ } catch (NoSuchMethodException exception) {
+ LOGGER.fatal("Failed to find constructor for {}, {}", receiver.payload().getName(), exception.getMessage());
+ 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 LeavesCustomPayload<?> decode(ResourceLocation id, FriendlyByteBuf buf) {
+ for (LeavesProtocol protocol : KNOWN_TYPES.keySet()) {
+ if (!ArrayUtils.contains(protocol.namespace(), id.getNamespace())) {
+ continue;
+ }
+
+ Map<ProtocolHandler.PayloadReceiver, Constructor<? extends LeavesCustomPayload<?>>> 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 exception) {
+ LOGGER.warn("Failed to create payload for {} in {}, {}", id, ArrayUtils.toString(protocol.namespace()), exception.getMessage());
+ buf.readBytes(buf.readableBytes());
+ return new ErrorPayload(id, protocol.namespace(), receiver.payloadId());
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ public static void handlePayload(ServerPlayer player, LeavesCustomPayload<?> payload) {
+ 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.type().id().getNamespace())) {
+ continue;
+ }
+
+ Map<ProtocolHandler.PayloadReceiver, Method> map = KNOW_RECEIVERS.get(protocol);
+ for (ProtocolHandler.PayloadReceiver receiver : map.keySet()) {
+ if (payload.getClass() == receiver.payload()) {
+ if (receiver.ignoreId() || ArrayUtils.contains(receiver.payloadId(), payload.type().id().getPath())) {
+ try {
+ map.get(receiver).invoke(null, player, payload);
+ } catch (InvocationTargetException | IllegalAccessException exception) {
+ LOGGER.warn("Failed to handle payload {} in {}, {}", payload.type().id(), ArrayUtils.toString(protocol.namespace()), exception.getMessage());
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public static void handleTick() {
+ if (!TICKERS.isEmpty()) {
+ try {
+ for (Method method : TICKERS) {
+ method.invoke(null);
+ }
+ } catch (InvocationTargetException | IllegalAccessException exception) {
+ LOGGER.warn("Failed to tick, {}", exception.getMessage());
+ }
+ }
+ }
+
+ public static void handlePlayerJoin(ServerPlayer player) {
+ if (!PLAYER_JOIN.isEmpty()) {
+ try {
+ for (Method method : PLAYER_JOIN) {
+ method.invoke(null, player);
+ }
+ } catch (InvocationTargetException | IllegalAccessException exception) {
+ LOGGER.warn("Failed to handle player join, {}", exception.getMessage());
+ }
+ }
+ }
+
+ public static void handlePlayerLeave(ServerPlayer player) {
+ if (!PLAYER_LEAVE.isEmpty()) {
+ try {
+ for (Method method : PLAYER_LEAVE) {
+ method.invoke(null, player);
+ }
+ } catch (InvocationTargetException | IllegalAccessException exception) {
+ LOGGER.warn("Failed to handle player leave, {}", exception.getMessage());
+ }
+ }
+ }
+
+ public static void handleServerReload() {
+ if (!RELOAD_SERVER.isEmpty()) {
+ try {
+ for (Method method : RELOAD_SERVER) {
+ method.invoke(null);
+ }
+ } catch (InvocationTargetException | IllegalAccessException exception) {
+ LOGGER.warn("Failed to handle server reload, {}", exception.getMessage());
+ }
+ }
+ }
+
+ 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<ProtocolHandler.MinecraftRegister, Method> 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 exception) {
+ LOGGER.warn("Failed to handle minecraft register, {}", exception.getMessage());
+ }
+ }
+ }
+ }
+ }
+
+ public static Set<Class<?>> getClasses(String pack) {
+ Set<Class<?>> classes = new LinkedHashSet<>();
+ String packageDirName = pack.replace('.', '/');
+ Enumeration<URL> 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<JarEntry> entries = jar.entries();
+ findClassesInPackageByJar(pack, entries, packageDirName, classes);
+ } catch (IOException exception) {
+ LOGGER.warn("Failed to load jar file, {}", exception.getMessage());
+ }
+ }
+ }
+ } catch (IOException exception) {
+ LOGGER.warn("Failed to load classes, {}", exception.getMessage());
+ }
+ return classes;
+ }
+
+ private static void findClassesInPackageByFile(String packageName, String packagePath, Set<Class<?>> 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 exception) {
+ LOGGER.warn("Failed to load class {}, {}", className, exception.getMessage());
+ }
+ }
+ }
+ }
+ }
+
+ private static void findClassesInPackageByJar(String packageName, Enumeration<JarEntry> entries, String packageDirName, Set<Class<?>> 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 exception) {
+ LOGGER.warn("Failed to load class {}, {}", className, exception.getMessage());
+ }
+ }
+ }
+ }
+ }
+
+ public record ErrorPayload(ResourceLocation id, String[] protocolID,
+ String[] packetID) implements LeavesCustomPayload<ErrorPayload> {
+ @Override
+ public void write(@NotNull FriendlyByteBuf buf) {
+ }
+ }
+
+ public record EmptyPayload(ResourceLocation id) implements LeavesCustomPayload<EmptyPayload> {
+
+ @LeavesCustomPayload.New
+ public EmptyPayload(ResourceLocation location, FriendlyByteBuf buf) {
+ this(location);
+ }
+
+ @Override
+ public void write(@NotNull FriendlyByteBuf buf) {
+ }
+ }
+
+ public record LeavesPayload(FriendlyByteBuf data,
+ ResourceLocation id) implements LeavesCustomPayload<LeavesPayload> {
+
+ @LeavesCustomPayload.New
+ 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/org/leavesmc/leaves/protocol/core/ProtocolHandler.java b/src/main/java/org/leavesmc/leaves/protocol/core/ProtocolHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..f9dc0a60ca4287e5ec91dd3fc1ae315e2826da34
--- /dev/null
+++ b/src/main/java/org/leavesmc/leaves/protocol/core/ProtocolHandler.java
@@ -0,0 +1,61 @@
+package org.leavesmc.leaves.protocol.core;
+
+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<? extends LeavesCustomPayload<?>> 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/org/leavesmc/leaves/protocol/core/ProtocolUtils.java b/src/main/java/org/leavesmc/leaves/protocol/core/ProtocolUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..562f3d515679965571597945e8682712e1e1f0b9
--- /dev/null
+++ b/src/main/java/org/leavesmc/leaves/protocol/core/ProtocolUtils.java
@@ -0,0 +1,37 @@
+package org.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)));
+ }
+
+ @SuppressWarnings("all")
+ public static void sendPayloadPacket(@NotNull ServerPlayer player, ResourceLocation id, Consumer<FriendlyByteBuf> consumer) {
+ player.connection.send(new ClientboundCustomPayloadPacket(new LeavesCustomPayload() {
+ @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));
+ }
+}