From 9b2ad0bbfdddf1301ca43fb8d96ecf65e57b022b Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 13 Jul 2025 22:03:53 +0200 Subject: [PATCH 1/4] Initial draft of the Geyser Networking API --- .../api/connection/GeyserConnection.java | 10 + .../SessionDefineNetworkChannelsEvent.java | 61 +++++ .../ServerReceiveNetworkMessageEvent.java | 99 +++++++ .../api/network/ExtensionNetworkChannel.java | 91 +++++++ .../api/network/ExternalNetworkChannel.java | 92 +++++++ .../geyser/api/network/MessageDirection.java | 41 +++ .../geyser/api/network/NetworkChannel.java | 96 +++++++ .../geyser/api/network/NetworkManager.java | 59 +++++ .../geyser/api/network/PacketChannel.java | 92 +++++++ .../geyser/api/network/message/DataType.java | 234 +++++++++++++++++ .../geyser/api/network/message/Message.java | 79 ++++++ .../api/network/message/MessageBuffer.java | 63 +++++ .../api/network/message/MessageCodec.java | 247 ++++++++++++++++++ .../api/network/message/MessageFactory.java | 46 ++++ .../geyser/network/GeyserNetworkManager.java | 221 ++++++++++++++++ .../geyser/network/UpstreamPacketHandler.java | 5 + .../network/message/BedrockPacketMessage.java | 57 ++++ .../geyser/network/message/ByteBufCodec.java | 178 +++++++++++++ .../network/message/ByteBufCodecLE.java | 176 +++++++++++++ .../network/message/ByteBufMessageBuffer.java | 58 ++++ .../network/message/JavaPacketMessage.java | 38 +++ .../loader/ProviderRegistryLoader.java | 20 ++ .../geyser/session/GeyserSession.java | 17 +- .../geyser/session/UpstreamSession.java | 12 +- .../java/JavaCustomPayloadTranslator.java | 21 ++ .../protocol/java/JavaLoginTranslator.java | 13 + 26 files changed, 2122 insertions(+), 4 deletions(-) create mode 100644 api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionDefineNetworkChannelsEvent.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/event/java/ServerReceiveNetworkMessageEvent.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/network/ExtensionNetworkChannel.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/network/ExternalNetworkChannel.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/network/MessageDirection.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/network/NetworkChannel.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/network/NetworkManager.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/network/PacketChannel.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/network/message/DataType.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/network/message/Message.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/network/message/MessageBuffer.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/network/message/MessageCodec.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/network/message/MessageFactory.java create mode 100644 core/src/main/java/org/geysermc/geyser/network/GeyserNetworkManager.java create mode 100644 core/src/main/java/org/geysermc/geyser/network/message/BedrockPacketMessage.java create mode 100644 core/src/main/java/org/geysermc/geyser/network/message/ByteBufCodec.java create mode 100644 core/src/main/java/org/geysermc/geyser/network/message/ByteBufCodecLE.java create mode 100644 core/src/main/java/org/geysermc/geyser/network/message/ByteBufMessageBuffer.java create mode 100644 core/src/main/java/org/geysermc/geyser/network/message/JavaPacketMessage.java diff --git a/api/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java b/api/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java index b7a678aa6b1..b95c4198a67 100644 --- a/api/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java +++ b/api/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java @@ -35,6 +35,7 @@ import org.geysermc.geyser.api.entity.EntityData; import org.geysermc.geyser.api.entity.type.GeyserEntity; import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity; +import org.geysermc.geyser.api.network.NetworkManager; import java.util.Set; import java.util.concurrent.CompletableFuture; @@ -118,6 +119,15 @@ public interface GeyserConnection extends Connection, CommandSource { */ void sendCommand(String command); + /** + * Gets the {@link NetworkManager} used for handling + * network channels and sending messages. + * + * @return the network manager + */ + @NonNull + NetworkManager networkManager(); + /** * @param javaId the Java entity ID to look up. * @return a {@link GeyserEntity} if present in this connection's entity tracker. diff --git a/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionDefineNetworkChannelsEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionDefineNetworkChannelsEvent.java new file mode 100644 index 00000000000..1148345aeec --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionDefineNetworkChannelsEvent.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.event.bedrock; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.connection.GeyserConnection; +import org.geysermc.geyser.api.event.connection.ConnectionEvent; +import org.geysermc.geyser.api.network.NetworkChannel; +import org.geysermc.geyser.api.network.message.MessageBuffer; +import org.geysermc.geyser.api.network.message.MessageCodec; +import org.geysermc.geyser.api.network.message.MessageFactory; + +/** + * Called whenever Geyser is registering network channels. + * @since 2.8.2 + */ +public abstract class SessionDefineNetworkChannelsEvent extends ConnectionEvent { + + public SessionDefineNetworkChannelsEvent(@NonNull GeyserConnection connection) { + super(connection); + } + + /** + * Registers a new network channel with a message factory. + * + * @param channel the channel to register + * @param messageFactory the factory to create messages from the buffer + */ + public abstract void register(@NonNull NetworkChannel channel, @NonNull MessageFactory messageFactory); + + /** + * Registers a new network channel with a message factory. + * + * @param channel the channel to register + * @param messageFactory the factory to create messages from the buffer + */ + public abstract void register(@NonNull NetworkChannel channel, @NonNull MessageCodec codec, @NonNull MessageFactory messageFactory); +} diff --git a/api/src/main/java/org/geysermc/geyser/api/event/java/ServerReceiveNetworkMessageEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/java/ServerReceiveNetworkMessageEvent.java new file mode 100644 index 00000000000..8e9f68332cb --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/event/java/ServerReceiveNetworkMessageEvent.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.event.java; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.event.Cancellable; +import org.geysermc.geyser.api.connection.GeyserConnection; +import org.geysermc.geyser.api.event.connection.ConnectionEvent; +import org.geysermc.geyser.api.network.MessageDirection; +import org.geysermc.geyser.api.network.NetworkChannel; +import org.geysermc.geyser.api.network.message.Message; + +/** + * Called when the server receives a network message. + * @since 2.8.2 + */ +public class ServerReceiveNetworkMessageEvent extends ConnectionEvent implements Cancellable { + private final NetworkChannel channel; + private final Message message; + private final MessageDirection direction; + private boolean cancelled = false; + + public ServerReceiveNetworkMessageEvent(@NonNull GeyserConnection connection, @NonNull NetworkChannel channel, @NonNull Message message, @NonNull MessageDirection direction) { + super(connection); + + this.channel = channel; + this.message = message; + this.direction = direction; + } + + /** + * Gets the channel that received the message. + * + * @return the channel that received the message + */ + @NonNull + public NetworkChannel channel() { + return this.channel; + } + + /** + * Gets the message that was received. + * + * @return the received message + */ + @NonNull + public Message message() { + return this.message; + } + + /** + * Gets the direction of the message. + * + * @return the direction of the message + */ + @NonNull + public MessageDirection direction() { + return this.direction; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isCancelled() { + return this.cancelled; + } + + /** + * {@inheritDoc} + */ + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } +} diff --git a/api/src/main/java/org/geysermc/geyser/api/network/ExtensionNetworkChannel.java b/api/src/main/java/org/geysermc/geyser/api/network/ExtensionNetworkChannel.java new file mode 100644 index 00000000000..44b2becdee7 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/network/ExtensionNetworkChannel.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.network; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.extension.Extension; + +import java.util.Objects; + +/** + * Represents a network channel associated with an extension. + * @since 2.8.2 + */ +public class ExtensionNetworkChannel implements NetworkChannel { + private final Extension extension; + private final String channel; + + protected ExtensionNetworkChannel(@NonNull Extension extension, @NonNull String channel) { + this.extension = extension; + this.channel = channel; + } + + /** + * {@inheritDoc} + */ + @Override + @NonNull + public String key() { + return this.extension.description().id(); + } + + /** + * {@inheritDoc} + */ + @Override + @NonNull + public String channel() { + return this.channel; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isPacket() { + return false; + } + + @Override + public boolean equals(Object o) { + if (o == null || !NetworkChannel.class.isAssignableFrom(o.getClass())) return false; + NetworkChannel that = (NetworkChannel) o; + return Objects.equals(this.key(), that.key()) && Objects.equals(this.channel(), that.channel()); + } + + @Override + public int hashCode() { + return Objects.hash(this.key(), this.channel()); + } + + @Override + public String toString() { + return "ExtensionNetworkChannel{" + + "extension=" + this.extension.description().id() + + ", channel='" + this.channel + '\'' + + '}'; + } +} diff --git a/api/src/main/java/org/geysermc/geyser/api/network/ExternalNetworkChannel.java b/api/src/main/java/org/geysermc/geyser/api/network/ExternalNetworkChannel.java new file mode 100644 index 00000000000..a22693a8fed --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/network/ExternalNetworkChannel.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.network; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.Objects; + +/** + * Represents a network channel not associated with any specific extension. + *

+ * This can be used for external communication channels, like mods or plugins. + * @since 2.8.2 + */ +public class ExternalNetworkChannel implements NetworkChannel { + private final String key; + private final String channel; + + protected ExternalNetworkChannel(@NonNull String key, @NonNull String channel) { + this.key = key; + this.channel = channel; + } + + /** + * {@inheritDoc} + */ + @Override + @NonNull + public String key() { + return this.key; + } + + /** + * {@inheritDoc} + */ + @Override + @NonNull + public String channel() { + return this.channel; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isPacket() { + return false; + } + + @Override + public boolean equals(Object o) { + if (o == null || !NetworkChannel.class.isAssignableFrom(o.getClass())) return false; + NetworkChannel that = (NetworkChannel) o; + return Objects.equals(this.key(), that.key()) && Objects.equals(this.channel(), that.channel()); + } + + @Override + public int hashCode() { + return Objects.hash(this.key(), this.channel()); + } + + @Override + public String toString() { + return "ExternalNetworkChannel{" + + "key='" + this.key + '\'' + + ", channel='" + this.channel + '\'' + + '}'; + } +} diff --git a/api/src/main/java/org/geysermc/geyser/api/network/MessageDirection.java b/api/src/main/java/org/geysermc/geyser/api/network/MessageDirection.java new file mode 100644 index 00000000000..56af6f2d6bc --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/network/MessageDirection.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.network; + +/** + * Represents the direction of a message. + * @since 2.8.2 + */ +public enum MessageDirection { + /** + * Indicates that the message is sent from the server to the client. + */ + CLIENTBOUND, + /** + * Indicates that the message is sent from the client to the server. + */ + SERVERBOUND +} diff --git a/api/src/main/java/org/geysermc/geyser/api/network/NetworkChannel.java b/api/src/main/java/org/geysermc/geyser/api/network/NetworkChannel.java new file mode 100644 index 00000000000..741a9118d84 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/network/NetworkChannel.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.network; + +import org.checkerframework.checker.index.qual.NonNegative; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.extension.Extension; + +/** + * Represents a channel used for network communication. + * @since 2.8.2 + */ +public interface NetworkChannel { + + /** + * Gets the key that owns this channel. + * + * @return the key that owns this channel + */ + @NonNull + String key(); + + /** + * Gets the name of the channel. + * + * @return the channel name + */ + @NonNull + String channel(); + + /** + * Checks if this channel is a packet channel. + * + * @return true if this channel is a packet channel, false otherwise + */ + boolean isPacket(); + + /** + * Creates a new {@link NetworkChannel} instance. + * + * @param extension the extension that registered this channel + * @param channel the name of the channel + * @return a new {@link NetworkChannel} instance + */ + @NonNull + static NetworkChannel of(@NonNull Extension extension, @NonNull String channel) { + return new ExtensionNetworkChannel(extension, channel); + } + + /** + * Creates a new {@link NetworkChannel} instance. + * + * @param id the channel id + * @param channel the name of the channel + * @return a new {@link NetworkChannel} instance + */ + @NonNull + static NetworkChannel of(@NonNull String id, @NonNull String channel) { + return new ExternalNetworkChannel(id, channel); + } + + /** + * Creates a new {@link PacketChannel} instance for a packet channel. + * + * @param key the packet key + * @param packetId the packet ID + * @param packetType the type of the packet + * @return a new {@link PacketChannel} instance for a packet channel + */ + static NetworkChannel packet(@NonNull String key, @NonNegative int packetId, @NonNull Class packetType) { + return new PacketChannel(key, packetId, packetType); + } +} diff --git a/api/src/main/java/org/geysermc/geyser/api/network/NetworkManager.java b/api/src/main/java/org/geysermc/geyser/api/network/NetworkManager.java new file mode 100644 index 00000000000..546a63bef95 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/network/NetworkManager.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.network; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.connection.GeyserConnection; +import org.geysermc.geyser.api.network.message.Message; +import org.geysermc.geyser.api.network.message.MessageBuffer; + +import java.util.Set; + +/** + * Represents the network manager responsible for handling network operations + * for a {@link GeyserConnection}. + * + * @since 2.8.2 + */ +public interface NetworkManager { + + /** + * Gets the registered network channels. + * + * @return the registered network channels + */ + @NonNull + Set getRegisteredChannels(); + + /** + * Sends a message to this connection on the specified channel. + * + * @param channel the channel to send the message on + * @param message the message to send + * @param direction the direction of the message (clientbound or serverbound) + */ + void send(@NonNull NetworkChannel channel, @NonNull Message message, @NonNull MessageDirection direction); +} diff --git a/api/src/main/java/org/geysermc/geyser/api/network/PacketChannel.java b/api/src/main/java/org/geysermc/geyser/api/network/PacketChannel.java new file mode 100644 index 00000000000..d21f93dbd36 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/network/PacketChannel.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.network; + +import org.checkerframework.checker.index.qual.NonNegative; +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.Objects; + +/** + * Represents a network channel associated with a packet. + *

+ * This channel is used for communication of packets between the server and client. + * @since 2.8.2 + */ +public class PacketChannel extends ExternalNetworkChannel { + private static final String PACKET_CHANNEL_KEY = "packet"; + + private final int packetId; + private final Class packetType; + + protected PacketChannel(@NonNull String key, @NonNegative int packetId, @NonNull Class packetType) { + super(PACKET_CHANNEL_KEY, key); + + this.packetId = packetId; + this.packetType = packetType; + } + + /** + * Gets the packet ID associated with this channel. + * + * @return the packet ID + */ + @NonNegative + public int packetId() { + return this.packetId; + } + + /** + * Gets the type of the packet associated with this channel. + * + * @return the class of the packet type + */ + @NonNull + public Class packetType() { + return this.packetType; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isPacket() { + return true; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + PacketChannel that = (PacketChannel) o; + return this.packetId == that.packetId; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), this.packetId); + } +} diff --git a/api/src/main/java/org/geysermc/geyser/api/network/message/DataType.java b/api/src/main/java/org/geysermc/geyser/api/network/message/DataType.java new file mode 100644 index 00000000000..6bbd8675358 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/network/message/DataType.java @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.network.message; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * Represents a data type that can be sent or received over the network. + * + * @param the type + * @since 2.8.2 + */ +public final class DataType { + /** + * A DataType for reading and writing boolean values. + */ + public static final DataType BOOLEAN = of(MessageCodec::readBoolean, MessageCodec::writeBoolean); + /** + * A DataType for reading and writing byte values. + */ + public static final DataType BYTE = of(MessageCodec::readByte, MessageCodec::writeByte); + /** + * A DataType for reading and writing short values. + */ + public static final DataType SHORT = of(MessageCodec::readShort, MessageCodec::writeShort); + /** + * A DataType for reading and writing integer values. + */ + public static final DataType INT = of(MessageCodec::readInt, MessageCodec::writeInt); + /** + * A DataType for reading and writing float values. + */ + public static final DataType FLOAT = of(MessageCodec::readFloat, MessageCodec::writeFloat); + /** + * A DataType for reading and writing double values. + */ + public static final DataType DOUBLE = of(MessageCodec::readDouble, MessageCodec::writeDouble); + /** + * A DataType for reading and writing long values. + */ + public static final DataType LONG = of(MessageCodec::readLong, MessageCodec::writeLong); + /** + * A DataType for reading and writing variable-length integers. + */ + public static final DataType VAR_INT = of(MessageCodec::readVarInt, MessageCodec::writeVarInt); + /** + * A DataType for reading and writing unsigned variable-length integers. + */ + public static final DataType UNSIGNED_VAR_INT = of(MessageCodec::readUnsignedVarInt, MessageCodec::writeUnsignedVarInt); + /** + * A DataType for reading and writing variable-length long values. + */ + public static final DataType VAR_LONG = of(MessageCodec::readVarLong, MessageCodec::writeVarLong); + /** + * A DataType for reading and writing unsigned variable-length long values. + */ + public static final DataType UNSIGNED_VAR_LONG = of(MessageCodec::readUnsignedVarLong, MessageCodec::writeUnsignedVarLong); + /** + * A DataType for reading and writing strings. + *

+ * Note: Strings are encoded in UTF-8 format. + */ + public static final DataType STRING = of(MessageCodec::readString, MessageCodec::writeString); + + private final Reader reader; + private final Writer writer; + + private DataType(@NonNull Reader reader, @NonNull Writer writer) { + this.reader = reader; + this.writer = writer; + } + + /** + * Reads a value of this data type from the given buffer using the specified codec. + * + * @param codec the codec to use for reading + * @param buffer the buffer to read from + * @return the read value + */ + @NonNull + public > T read(@NonNull C codec, @NonNull B buffer) { + return this.reader.read(codec, buffer); + } + + /** + * Writes a value of this data type to the given buffer using the specified codec. + * + * @param codec the codec to use for writing + * @param buffer the buffer to write to + * @param value the value to write + */ + public > void write(@NonNull C codec, @NonNull B buffer, @NonNull T value) { + this.writer.write(codec, buffer, value); + } + + /** + * Creates a new DataType with the specified reader and writer. + * + * @param reader the reader to use for reading values of this type + * @param writer the writer to use for writing values of this type + * @param the type of values this DataType handles + * @return a new DataType instance + */ + @NonNull + public static DataType of(@NonNull Reader reader, @NonNull Writer writer) { + return new DataType<>(reader, writer); + } + + /** + * Creates an optional DataType based on the provided type. + * + * @param type the underlying data type to be wrapped in an Optional + * @return a DataType that reads and writes Optional values + * @param the type of the value contained in the Optional + */ + @NonNull + public static DataType> optional(@NonNull DataType type) { + return new DataType<>(new Reader<>() { + + @Override + @NonNull + public > Optional read(@NonNull C codec, @NonNull B buffer) { + if (codec.readBoolean(buffer)) { + return Optional.of(type.read(codec, buffer)); + } else { + return Optional.empty(); + } + } + }, new Writer<>() { + + @Override + public > void write(@NonNull C codec, @NonNull B buffer, @NonNull Optional value) { + codec.writeBoolean(buffer, value.isPresent()); + value.ifPresent(t -> type.write(codec, buffer, t)); + } + }); + } + + /** + * Creates a DataType that represents a list of values of the specified type. + * + * @param type the underlying data type of the list elements + * @return a DataType that reads and writes lists of the specified type + * @param the type of the elements in the list + */ + @NonNull + public static DataType> list(@NonNull DataType type) { + return new DataType<>(new Reader<>() { + + @Override + @NonNull + public > List read(@NonNull C codec, @NonNull B buffer) { + int size = codec.readUnsignedVarInt(buffer); + List list = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + list.add(type.read(codec, buffer)); + } + + return list; + } + }, new Writer<>() { + + @Override + public > void write(@NonNull C codec, @NonNull B buffer, @NonNull List value) { + codec.writeUnsignedVarInt(buffer, value.size()); + for (T element : value) { + type.write(codec, buffer, element); + } + } + }); + } + + /** + * Represents a reader that can read values of a {@link DataType} from a buffer. + * + * @param the type of value to read + */ + public interface Reader { + + /** + * Reads a value of type {@link T} from the given buffer using the specified {@link C codec}. + * + * @param codec the codec to use for reading + * @param buffer the buffer to read from + * @return the read value + */ + @NonNull + > T read(@NonNull C codec, @NonNull B buffer); + } + + /** + * Represents a writer that can write values of a {@link DataType} to a buffer. + * + * @param the type of value to write + */ + public interface Writer { + + /** + * Writes a value of type {@link T} to the given buffer using the specified {@link C codec}. + * + * @param codec the codec to use for writing + * @param buffer the buffer to write to + * @param value the value to write + */ + > void write(@NonNull C codec, @NonNull B buffer, @NonNull T value); + } +} diff --git a/api/src/main/java/org/geysermc/geyser/api/network/message/Message.java b/api/src/main/java/org/geysermc/geyser/api/network/message/Message.java new file mode 100644 index 00000000000..bcbe5b0f709 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/network/message/Message.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.network.message; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.GeyserApi; + +/** + * Represents a message that can be sent over the network. + * @since 2.8.2 + */ +public interface Message { + + /** + * Reads the message from the provided buffer. + * + * @param buffer the buffer to read from + */ + void encode(@NonNull T buffer); + + /** + * Represents a simple message using the built-in {@link MessageBuffer} implementation. + */ + interface Simple extends Message { + } + + interface PacketBase extends Message { + } + + /** + * Represents a packet message that includes a packet ID. + */ + interface Packet extends PacketBase { + + /** + * Creates a new packet message from the given packet object and direction. + * + * @param packet the packet object to create the message from + * @return a new packet message + */ + static PacketWrapped of(@NonNull Object packet) { + return GeyserApi.api().provider(PacketWrapped.class, packet); + } + } + + interface PacketWrapped extends PacketBase { + + /** + * Gets the packet associated with this message. + * + * @return the packet + */ + @NonNull + Object packet(); + } +} diff --git a/api/src/main/java/org/geysermc/geyser/api/network/message/MessageBuffer.java b/api/src/main/java/org/geysermc/geyser/api/network/message/MessageBuffer.java new file mode 100644 index 00000000000..d4234d08860 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/network/message/MessageBuffer.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.network.message; + +import org.checkerframework.checker.nullness.qual.NonNull; + +/** + * A buffer for messages that can be sent over the network. + * @since 2.8.2 + */ +public interface MessageBuffer { + + /** + * Reads a {@link T value} from the buffer using the + * provided {@link DataType}. + * + * @param type the type of message to read + * @return the read message + * @param the type of message to read + */ + @NonNull + T read(@NonNull DataType type); + + /** + * Writes a {@link T value} to the buffer using the + * provided {@link DataType}. + * + * @param type the type of message to write + * @param value the value to write + * @param the type of message to write + */ + void write(@NonNull DataType type, @NonNull T value); + + /** + * Serializes the buffer to a byte array. + * + * @return the serialized byte array + */ + byte @NonNull [] serialize(); +} diff --git a/api/src/main/java/org/geysermc/geyser/api/network/message/MessageCodec.java b/api/src/main/java/org/geysermc/geyser/api/network/message/MessageCodec.java new file mode 100644 index 00000000000..b3f43c335ed --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/network/message/MessageCodec.java @@ -0,0 +1,247 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.network.message; + +import org.checkerframework.checker.nullness.qual.NonNull; + +/** + * A codec for encoding and decoding messages. + * + * @param the type of {@link MessageBuffer} used for encoding and decoding + * @since 2.8.2 + */ +public interface MessageCodec { + + /** + * Reads a boolean value from the {@link T buffer}. + * + * @param buffer the buffer to read from + * @return the boolean value read + */ + boolean readBoolean(@NonNull T buffer); + + /** + * Reads a byte value from the {@link T buffer}. + * + * @param buffer the buffer to read from + * @return the byte value read + */ + byte readByte(@NonNull T buffer); + + /** + * Reads a short value from the {@link T buffer}. + * + * @param buffer the buffer to read from + * @return the short value read + */ + short readShort(@NonNull T buffer); + + /** + * Reads an integer value from the {@link T buffer}. + * + * @param buffer the buffer to read from + * @return the integer value read + */ + int readInt(@NonNull T buffer); + + /** + * Reads a float value from the {@link T buffer}. + * + * @param buffer the buffer to read from + * @return the float value read + */ + float readFloat(@NonNull T buffer); + + /** + * Reads a double value from the {@link T buffer}. + * + * @param buffer the buffer to read from + * @return the double value read + */ + double readDouble(@NonNull T buffer); + + /** + * Reads a long value from the {@link T buffer}. + * + * @param buffer the buffer to read from + * @return the long value read + */ + long readLong(@NonNull T buffer); + + /** + * Reads a variable-length integer from the {@link T buffer}. + * + * @param buffer the buffer to read from + * @return the variable-length integer read + */ + int readVarInt(@NonNull T buffer); + + /** + * Reads an unsigned variable-length integer from the {@link T buffer}. + * + * @param buffer the buffer to read from + * @return the unsigned variable-length integer read + */ + int readUnsignedVarInt(@NonNull T buffer); + + /** + * Reads a variable-length long from the {@link T buffer}. + * + * @param buffer the buffer to read from + * @return the variable-length long read + */ + long readVarLong(@NonNull T buffer); + + /** + * Reads an unsigned variable-length long from the {@link T buffer}. + * + * @param buffer the buffer to read from + * @return the unsigned variable-length long read + */ + long readUnsignedVarLong(@NonNull T buffer); + + /** + * Reads a string from the {@link T buffer}. + * + * @param buffer the buffer to read from + * @return the string read + */ + @NonNull + String readString(@NonNull T buffer); + + /** + * Writes a boolean value to the {@link T buffer}. + * + * @param buffer the buffer to write to + * @param value the boolean value to write + */ + void writeBoolean(@NonNull T buffer, boolean value); + + /** + * Writes a byte value to the {@link T buffer}. + * + * @param buffer the buffer to write to + * @param value the byte value to write + */ + void writeByte(@NonNull T buffer, byte value); + + /** + * Writes a short value to the {@link T buffer}. + * + * @param buffer the buffer to write to + * @param value the short value to write + */ + void writeShort(@NonNull T buffer, short value); + + /** + * Writes an integer value to the {@link T buffer}. + * + * @param buffer the buffer to write to + * @param value the integer value to write + */ + void writeInt(@NonNull T buffer, int value); + + /** + * Writes a float value to the {@link T buffer}. + * + * @param buffer the buffer to write to + * @param value the float value to write + */ + void writeFloat(@NonNull T buffer, float value); + + /** + * Writes a double value to the {@link T buffer}. + * + * @param buffer the buffer to write to + * @param value the double value to write + */ + void writeDouble(@NonNull T buffer, double value); + + /** + * Writes a long value to the {@link T buffer}. + * + * @param buffer the buffer to write to + * @param value the long value to write + */ + void writeLong(@NonNull T buffer, long value); + + /** + * Writes a variable-length integer to the {@link T buffer}. + * + * @param buffer the buffer to write to + * @param value the variable-length integer to write + */ + void writeVarInt(@NonNull T buffer, int value); + + /** + * Writes an unsigned variable-length integer to the {@link T buffer}. + * + * @param buffer the buffer to write to + * @param value the unsigned variable-length integer to write + */ + void writeUnsignedVarInt(@NonNull T buffer, int value); + + /** + * Writes a variable-length long to the {@link T buffer}. + * + * @param buffer the buffer to write to + * @param value the variable-length long to write + */ + void writeVarLong(@NonNull T buffer, long value); + + /** + * Writes an unsigned variable-length long to the {@link T buffer}. + * + * @param buffer the buffer to write to + * @param value the unsigned variable-length long to write + */ + void writeUnsignedVarLong(@NonNull T buffer, long value); + + /** + * Writes a string to the {@link T buffer}. + * + * @param buffer the buffer to write to + * @param value the string to write + */ + void writeString(@NonNull T buffer, @NonNull String value); + + /** + * Creates a new {@link T buffer} instance. + * + * @return a new instance of {@link MessageBuffer} + */ + @NonNull + T createBuffer(); + + /** + * Creates a new {@link T buffer} instance with the given data. + * + * @param data the byte array to initialize the buffer with + * @return a new instance of {@link MessageBuffer} initialized with the provided data + */ + @NonNull + T createBuffer(byte @NonNull [] data); +} diff --git a/api/src/main/java/org/geysermc/geyser/api/network/message/MessageFactory.java b/api/src/main/java/org/geysermc/geyser/api/network/message/MessageFactory.java new file mode 100644 index 00000000000..6c8e7c5084b --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/network/message/MessageFactory.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.network.message; + +import org.checkerframework.checker.nullness.qual.NonNull; + +/** + * A factory interface for creating messages from a given message buffer. + * + * @param the type of the message buffer + * @since 2.8.2 + */ +public interface MessageFactory { + + /** + * Creates a new message from the provided buffer. + * + * @param buffer the buffer to create the message from + * @return a new message created from the buffer + */ + @NonNull + Message create(@NonNull T buffer); +} diff --git a/core/src/main/java/org/geysermc/geyser/network/GeyserNetworkManager.java b/core/src/main/java/org/geysermc/geyser/network/GeyserNetworkManager.java new file mode 100644 index 00000000000..e2abd692f07 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/network/GeyserNetworkManager.java @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.network; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import net.kyori.adventure.key.Key; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec; +import org.cloudburstmc.protocol.bedrock.codec.BedrockCodecHelper; +import org.cloudburstmc.protocol.bedrock.codec.BedrockPacketDefinition; +import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket; +import org.geysermc.geyser.api.GeyserApi; +import org.geysermc.geyser.api.event.bedrock.SessionDefineNetworkChannelsEvent; +import org.geysermc.geyser.api.event.java.ServerReceiveNetworkMessageEvent; +import org.geysermc.geyser.api.network.MessageDirection; +import org.geysermc.geyser.api.network.NetworkChannel; +import org.geysermc.geyser.api.network.NetworkManager; +import org.geysermc.geyser.api.network.PacketChannel; +import org.geysermc.geyser.api.network.message.Message; +import org.geysermc.geyser.api.network.message.MessageBuffer; +import org.geysermc.geyser.api.network.message.MessageCodec; +import org.geysermc.geyser.api.network.message.MessageFactory; +import org.geysermc.geyser.network.message.BedrockPacketMessage; +import org.geysermc.geyser.network.message.ByteBufCodec; +import org.geysermc.geyser.network.message.ByteBufMessageBuffer; +import org.geysermc.geyser.network.message.JavaPacketMessage; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.mcprotocollib.protocol.packet.common.serverbound.ServerboundCustomPayloadPacket; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + +public class GeyserNetworkManager implements NetworkManager { + private final GeyserSession session; + + private final Map> definitions = new HashMap<>(); + private final Int2ObjectMap packetChannels = new Int2ObjectOpenHashMap<>(); + + public GeyserNetworkManager(GeyserSession session) { + this.session = session; + + SessionDefineNetworkChannelsEvent event = new SessionDefineNetworkChannelsEvent(session) { + + @Override + public void register(@NonNull NetworkChannel channel, @NonNull MessageFactory messageFactory) { + GeyserNetworkManager.this.registerMessage(channel, new MessageDefinition<>(ByteBufCodec.INSTANCE, messageFactory)); + } + + @Override + public void register(@NonNull NetworkChannel channel, @NonNull MessageCodec codec, @NonNull MessageFactory messageFactory) { + GeyserNetworkManager.this.registerMessage(channel, new MessageDefinition<>(codec, messageFactory)); + } + }; + + GeyserApi.api().eventBus().fire(event); + } + + @Override + public @NonNull Set getRegisteredChannels() { + return Set.copyOf(this.definitions.keySet()); + } + + @SuppressWarnings("unchecked") + @Override + public void send(@NonNull NetworkChannel channel, @NonNull Message message, @NonNull MessageDirection direction) { + if (channel.isPacket() && message instanceof Message.PacketBase packetBase) { + if (packetBase instanceof BedrockPacketMessage packetMessage) { + this.session.sendUpstreamPacket(packetMessage.packet()); + } else if (packetBase instanceof JavaPacketMessage packetMessage) { + this.session.sendDownstreamPacket(packetMessage.packet()); + } else if (packetBase instanceof Message.Packet packet) { + PacketChannel packetChannel = (PacketChannel) channel; + int packetId = packetChannel.packetId(); + + ByteBufMessageBuffer buffer = ByteBufCodec.INSTANCE_LE.createBuffer(); + packet.encode(buffer); + + BedrockCodec codec = this.session.getUpstream().getSession().getCodec(); + BedrockCodecHelper helper = this.session.getUpstream().getCodecHelper(); + + BedrockPacket bedrockPacket = codec.tryDecode(helper, buffer.buffer(), packetId); + if (bedrockPacket == null) { + throw new IllegalArgumentException("No Bedrock packet definition found for packet ID: " + packetId); + } + + // Clientbound packets are sent upstream, serverbound packets are sent downstream + if (direction == MessageDirection.CLIENTBOUND) { + this.session.sendUpstreamPacket(bedrockPacket); + } else { + this.session.getUpstream().getSession().getPacketHandler().handlePacket(bedrockPacket); + } + } + + return; + } + + MessageDefinition definition = (MessageDefinition) this.definitions.get(channel); + if (definition == null) { + throw new IllegalArgumentException("No message definition registered for channel: " + channel); + } + + T buffer = definition.codec.createBuffer(); + message.encode(buffer); + + ServerboundCustomPayloadPacket packet = new ServerboundCustomPayloadPacket( + Key.key(channel.key(), channel.channel()), + buffer.serialize() + ); + + this.session.sendDownstreamPacket(packet); + } + + public Message createMessage(@NonNull NetworkChannel channel, byte @NotNull[] data) { + return this.createMessage0(channel, definition -> definition.createBuffer(data)); + } + + public Message createMessage(@NonNull NetworkChannel channel, @NonNull T buffer) { + return this.createMessage0(channel, def -> buffer); + } + + @SuppressWarnings("unchecked") + private Message createMessage0(@NonNull NetworkChannel channel, @NonNull Function, T> creator) { + MessageDefinition definition = (MessageDefinition) this.definitions.get(channel); + if (definition == null) { + throw new IllegalArgumentException("No message definition registered for channel: " + channel); + } + + T buffer = creator.apply(definition); + Message message = definition.createMessage(buffer); + if (message instanceof BedrockPacketMessage packetMessage) { + packetMessage.postProcess(this.session, (ByteBufMessageBuffer) buffer); + } + return message; + } + + public PacketChannel getPacketChannel(int packetId) { + return this.packetChannels.get(packetId); + } + + @SuppressWarnings("unchecked") + public boolean handlePacket(BedrockPacket packet, MessageDirection direction) { + if (this.packetChannels.isEmpty()) { + return true; // Avoid processing anything if we have nothing to handle + } + + BedrockCodec codec = this.session.getUpstream().getSession().getCodec(); + BedrockPacketDefinition definition = codec.getPacketDefinition((Class) packet.getClass()); + PacketChannel channel = this.getPacketChannel(definition.getId()); + if (channel == null) { + return true; + } + + Message message; + if (channel.packetType().isInstance(packet)) { + message = new BedrockPacketMessage(packet); + } else { + ByteBuf buffer = Unpooled.buffer(); + definition.getSerializer().serialize(buffer, this.session.getUpstream().getCodecHelper(), packet); + message = this.createMessage(channel, new ByteBufMessageBuffer(ByteBufCodec.INSTANCE_LE, buffer)); + } + + ServerReceiveNetworkMessageEvent event = new ServerReceiveNetworkMessageEvent(this.session, channel, message, direction); + this.session.getGeyser().eventBus().fire(event); + + // If the event is canceled, we do not want to process the packet further + return !event.isCancelled(); + } + + private void registerMessage(@NonNull NetworkChannel channel, @NonNull MessageDefinition codec) { + if (this.definitions.containsKey(channel)) { + throw new IllegalArgumentException("Channel is already registered: " + channel); + } + + this.definitions.put(channel, codec); + if (channel.isPacket()) { + PacketChannel packetChannel = (PacketChannel) channel; + int packetId = packetChannel.packetId(); + this.packetChannels.put(packetId, packetChannel); + } + } + + public record MessageDefinition(MessageCodec codec, MessageFactory messageFactory) { + + public T createBuffer(byte @NotNull[] data) { + return this.codec.createBuffer(data); + } + + public Message createMessage(@NonNull T buffer) { + return this.messageFactory.create(buffer); + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java index e04b23e96c4..8e0c4e4fb5a 100644 --- a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java @@ -57,6 +57,7 @@ import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.event.bedrock.SessionInitializeEvent; import org.geysermc.geyser.api.network.AuthType; +import org.geysermc.geyser.api.network.MessageDirection; import org.geysermc.geyser.api.pack.PackCodec; import org.geysermc.geyser.api.pack.ResourcePack; import org.geysermc.geyser.api.pack.ResourcePackManifest; @@ -108,6 +109,10 @@ public UpstreamPacketHandler(GeyserImpl geyser, GeyserSession session) { } private PacketSignal translateAndDefault(BedrockPacket packet) { + if (!this.session.getNetworkManager().handlePacket(packet, MessageDirection.SERVERBOUND)) { + return PacketSignal.HANDLED; + } + Registries.BEDROCK_PACKET_TRANSLATORS.translate(packet.getClass(), packet, session, false); return PacketSignal.HANDLED; // PacketSignal.UNHANDLED will log a WARN publicly } diff --git a/core/src/main/java/org/geysermc/geyser/network/message/BedrockPacketMessage.java b/core/src/main/java/org/geysermc/geyser/network/message/BedrockPacketMessage.java new file mode 100644 index 00000000000..776514d8e2a --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/network/message/BedrockPacketMessage.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.network.message; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.cloudburstmc.protocol.bedrock.codec.BedrockPacketDefinition; +import org.cloudburstmc.protocol.bedrock.codec.BedrockPacketSerializer; +import org.cloudburstmc.protocol.bedrock.codec.PacketSerializeException; +import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket; +import org.geysermc.geyser.api.network.message.Message; +import org.geysermc.geyser.session.GeyserSession; + +public record BedrockPacketMessage(@NonNull BedrockPacket packet) implements Message.PacketWrapped { + + @SuppressWarnings("unchecked") + public void postProcess(@NonNull GeyserSession session, @NonNull ByteBufMessageBuffer buffer) { + BedrockPacketDefinition definition = session.getUpstream().getSession().getCodec().getPacketDefinition(this.packet.getClass()); + if (definition == null) { + throw new IllegalArgumentException("Packet definition for " + this.packet.getClass().getSimpleName() + " not found!"); + } + + BedrockPacketSerializer serializer = (BedrockPacketSerializer) definition.getSerializer(); + try { + serializer.deserialize(buffer.buffer(), session.getUpstream().getCodecHelper(), this.packet); + } catch (Exception e) { + throw new PacketSerializeException("Error whilst deserializing " + this.packet, e); + } + } + + @Override + public void encode(@NonNull ByteBufMessageBuffer buffer) { + throw new UnsupportedOperationException("BedrockPacketMessage does not support encoding directly to a MessageBuffe."); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/network/message/ByteBufCodec.java b/core/src/main/java/org/geysermc/geyser/network/message/ByteBufCodec.java new file mode 100644 index 00000000000..d73e768cf89 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/network/message/ByteBufCodec.java @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.network.message; + +import io.netty.buffer.Unpooled; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.cloudburstmc.protocol.common.util.VarInts; +import org.geysermc.geyser.api.network.message.MessageCodec; +import org.jetbrains.annotations.NotNull; + +import java.nio.charset.StandardCharsets; + +public class ByteBufCodec implements MessageCodec { + public static final ByteBufCodec INSTANCE = new ByteBufCodec(); + public static final ByteBufCodecLE INSTANCE_LE = new ByteBufCodecLE(); + + private ByteBufCodec() { + } + + @Override + public boolean readBoolean(@NotNull ByteBufMessageBuffer buffer) { + return buffer.buffer().readBoolean(); + } + + @Override + public byte readByte(@NotNull ByteBufMessageBuffer buffer) { + return buffer.buffer().readByte(); + } + + @Override + public short readShort(@NotNull ByteBufMessageBuffer buffer) { + return buffer.buffer().readShort(); + } + + @Override + public int readInt(@NotNull ByteBufMessageBuffer buffer) { + return buffer.buffer().readInt(); + } + + @Override + public float readFloat(@NotNull ByteBufMessageBuffer buffer) { + return buffer.buffer().readFloat(); + } + + @Override + public double readDouble(@NotNull ByteBufMessageBuffer buffer) { + return buffer.buffer().readDouble(); + } + + @Override + public long readLong(@NotNull ByteBufMessageBuffer buffer) { + return buffer.buffer().readLong(); + } + + @Override + public int readVarInt(@NotNull ByteBufMessageBuffer buffer) { + return VarInts.readInt(buffer.buffer()); + } + + @Override + public int readUnsignedVarInt(@NotNull ByteBufMessageBuffer buffer) { + return VarInts.readUnsignedInt(buffer.buffer()); + } + + @Override + public long readVarLong(@NotNull ByteBufMessageBuffer buffer) { + return VarInts.readLong(buffer.buffer()); + } + + @Override + public long readUnsignedVarLong(@NotNull ByteBufMessageBuffer buffer) { + return VarInts.readUnsignedLong(buffer.buffer()); + } + + @Override + public @NotNull String readString(@NotNull ByteBufMessageBuffer buffer) { + int size = VarInts.readUnsignedInt(buffer.buffer()); + byte[] bytes = new byte[size]; + buffer.buffer().readBytes(bytes); + + return new String(bytes, StandardCharsets.UTF_8); + } + + @Override + public void writeBoolean(@NotNull ByteBufMessageBuffer buffer, boolean value) { + buffer.buffer().writeBoolean(value); + } + + @Override + public void writeByte(@NotNull ByteBufMessageBuffer buffer, byte value) { + buffer.buffer().writeByte(value); + } + + @Override + public void writeShort(@NotNull ByteBufMessageBuffer buffer, short value) { + buffer.buffer().writeShort(value); + } + + @Override + public void writeInt(@NotNull ByteBufMessageBuffer buffer, int value) { + buffer.buffer().writeInt(value); + } + + @Override + public void writeFloat(@NotNull ByteBufMessageBuffer buffer, float value) { + buffer.buffer().writeFloat(value); + } + + @Override + public void writeDouble(@NotNull ByteBufMessageBuffer buffer, double value) { + buffer.buffer().writeDouble(value); + } + + @Override + public void writeLong(@NotNull ByteBufMessageBuffer buffer, long value) { + buffer.buffer().writeLong(value); + } + + @Override + public void writeVarInt(@NotNull ByteBufMessageBuffer buffer, int value) { + VarInts.writeInt(buffer.buffer(), value); + } + + @Override + public void writeUnsignedVarInt(@NotNull ByteBufMessageBuffer buffer, int value) { + VarInts.writeUnsignedInt(buffer.buffer(), value); + } + + @Override + public void writeVarLong(@NotNull ByteBufMessageBuffer buffer, long value) { + VarInts.writeLong(buffer.buffer(), value); + } + + @Override + public void writeUnsignedVarLong(@NotNull ByteBufMessageBuffer buffer, long value) { + VarInts.writeUnsignedLong(buffer.buffer(), value); + } + + @Override + public void writeString(@NotNull ByteBufMessageBuffer buffer, @NonNull String value) { + byte[] bytes = value.getBytes(StandardCharsets.UTF_8); + VarInts.writeUnsignedInt(buffer.buffer(), bytes.length); + buffer.buffer().writeBytes(bytes); + } + + @Override + public @NotNull ByteBufMessageBuffer createBuffer() { + return new ByteBufMessageBuffer(this); + } + + @Override + public @NotNull ByteBufMessageBuffer createBuffer(byte @NotNull [] data) { + return new ByteBufMessageBuffer(this, Unpooled.wrappedBuffer(data)); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/network/message/ByteBufCodecLE.java b/core/src/main/java/org/geysermc/geyser/network/message/ByteBufCodecLE.java new file mode 100644 index 00000000000..98e401e7344 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/network/message/ByteBufCodecLE.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.network.message; + +import io.netty.buffer.Unpooled; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.cloudburstmc.protocol.common.util.VarInts; +import org.geysermc.geyser.api.network.message.MessageCodec; +import org.jetbrains.annotations.NotNull; + +import java.nio.charset.StandardCharsets; + +public class ByteBufCodecLE implements MessageCodec { + + ByteBufCodecLE() { + } + + @Override + public boolean readBoolean(@NotNull ByteBufMessageBuffer buffer) { + return buffer.buffer().readBoolean(); + } + + @Override + public byte readByte(@NotNull ByteBufMessageBuffer buffer) { + return buffer.buffer().readByte(); + } + + @Override + public short readShort(@NotNull ByteBufMessageBuffer buffer) { + return buffer.buffer().readShortLE(); + } + + @Override + public int readInt(@NotNull ByteBufMessageBuffer buffer) { + return buffer.buffer().readIntLE(); + } + + @Override + public float readFloat(@NotNull ByteBufMessageBuffer buffer) { + return buffer.buffer().readFloatLE(); + } + + @Override + public double readDouble(@NotNull ByteBufMessageBuffer buffer) { + return buffer.buffer().readDoubleLE(); + } + + @Override + public long readLong(@NotNull ByteBufMessageBuffer buffer) { + return buffer.buffer().readLongLE(); + } + + @Override + public int readVarInt(@NotNull ByteBufMessageBuffer buffer) { + return VarInts.readInt(buffer.buffer()); + } + + @Override + public int readUnsignedVarInt(@NotNull ByteBufMessageBuffer buffer) { + return VarInts.readUnsignedInt(buffer.buffer()); + } + + @Override + public long readVarLong(@NotNull ByteBufMessageBuffer buffer) { + return VarInts.readLong(buffer.buffer()); + } + + @Override + public long readUnsignedVarLong(@NotNull ByteBufMessageBuffer buffer) { + return VarInts.readUnsignedLong(buffer.buffer()); + } + + @Override + public @NotNull String readString(@NotNull ByteBufMessageBuffer buffer) { + int size = VarInts.readUnsignedInt(buffer.buffer()); + byte[] bytes = new byte[size]; + buffer.buffer().readBytes(bytes); + + return new String(bytes, StandardCharsets.UTF_8); + } + + @Override + public void writeBoolean(@NotNull ByteBufMessageBuffer buffer, boolean value) { + buffer.buffer().writeBoolean(value); + } + + @Override + public void writeByte(@NotNull ByteBufMessageBuffer buffer, byte value) { + buffer.buffer().writeByte(value); + } + + @Override + public void writeShort(@NotNull ByteBufMessageBuffer buffer, short value) { + buffer.buffer().writeShortLE(value); + } + + @Override + public void writeInt(@NotNull ByteBufMessageBuffer buffer, int value) { + buffer.buffer().writeIntLE(value); + } + + @Override + public void writeFloat(@NotNull ByteBufMessageBuffer buffer, float value) { + buffer.buffer().writeFloatLE(value); + } + + @Override + public void writeDouble(@NotNull ByteBufMessageBuffer buffer, double value) { + buffer.buffer().writeDoubleLE(value); + } + + @Override + public void writeLong(@NotNull ByteBufMessageBuffer buffer, long value) { + buffer.buffer().writeLongLE(value); + } + + @Override + public void writeVarInt(@NotNull ByteBufMessageBuffer buffer, int value) { + VarInts.writeInt(buffer.buffer(), value); + } + + @Override + public void writeUnsignedVarInt(@NotNull ByteBufMessageBuffer buffer, int value) { + VarInts.writeUnsignedInt(buffer.buffer(), value); + } + + @Override + public void writeVarLong(@NotNull ByteBufMessageBuffer buffer, long value) { + VarInts.writeLong(buffer.buffer(), value); + } + + @Override + public void writeUnsignedVarLong(@NotNull ByteBufMessageBuffer buffer, long value) { + VarInts.writeUnsignedLong(buffer.buffer(), value); + } + + @Override + public void writeString(@NotNull ByteBufMessageBuffer buffer, @NonNull String value) { + byte[] bytes = value.getBytes(StandardCharsets.UTF_8); + VarInts.writeUnsignedInt(buffer.buffer(), bytes.length); + buffer.buffer().writeBytes(bytes); + } + + @Override + public @NotNull ByteBufMessageBuffer createBuffer() { + return new ByteBufMessageBuffer(this); + } + + @Override + public @NotNull ByteBufMessageBuffer createBuffer(byte @NotNull [] data) { + return new ByteBufMessageBuffer(this, Unpooled.wrappedBuffer(data)); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/network/message/ByteBufMessageBuffer.java b/core/src/main/java/org/geysermc/geyser/network/message/ByteBufMessageBuffer.java new file mode 100644 index 00000000000..adff86fb3ee --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/network/message/ByteBufMessageBuffer.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.network.message; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.network.message.MessageBuffer; +import org.geysermc.geyser.api.network.message.MessageCodec; +import org.geysermc.geyser.api.network.message.DataType; + +public record ByteBufMessageBuffer(MessageCodec codec, ByteBuf buffer) implements MessageBuffer { + + public ByteBufMessageBuffer(MessageCodec codec) { + this(codec, Unpooled.buffer()); + } + + @Override + public @NonNull T read(@NonNull DataType type) { + return type.read(this.codec, this); + } + + @Override + public void write(@NonNull DataType type, @NonNull T value) { + type.write(this.codec, this, value); + } + + @Override + public byte @NonNull [] serialize() { + byte[] bytes = new byte[this.buffer.readableBytes()]; + this.buffer.readBytes(bytes); + this.buffer.clear(); // Clear the buffer after serialization + return bytes; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/network/message/JavaPacketMessage.java b/core/src/main/java/org/geysermc/geyser/network/message/JavaPacketMessage.java new file mode 100644 index 00000000000..36d75ff4fb2 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/network/message/JavaPacketMessage.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.network.message; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.network.message.Message; +import org.geysermc.mcprotocollib.protocol.codec.MinecraftPacket; + +public record JavaPacketMessage(MinecraftPacket packet) implements Message.PacketWrapped { + + @Override + public void encode(@NonNull ByteBufMessageBuffer buffer) { + throw new UnsupportedOperationException("JavaPacketMessage does not support encoding directly to a MessageBuffe."); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java index b1f62ca9984..416923fdc97 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java @@ -25,6 +25,7 @@ package org.geysermc.geyser.registry.loader; +import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket; import org.geysermc.geyser.api.bedrock.camera.CameraFade; import org.geysermc.geyser.api.bedrock.camera.CameraPosition; import org.geysermc.geyser.api.block.custom.CustomBlockData; @@ -39,6 +40,7 @@ import org.geysermc.geyser.api.item.custom.CustomItemData; import org.geysermc.geyser.api.item.custom.CustomItemOptions; import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; +import org.geysermc.geyser.api.network.message.Message; import org.geysermc.geyser.api.pack.PathPackCodec; import org.geysermc.geyser.api.pack.UrlPackCodec; import org.geysermc.geyser.api.pack.option.PriorityOption; @@ -57,12 +59,15 @@ import org.geysermc.geyser.level.block.GeyserJavaBlockState; import org.geysermc.geyser.level.block.GeyserMaterialInstance; import org.geysermc.geyser.level.block.GeyserNonVanillaCustomBlockData; +import org.geysermc.geyser.network.message.BedrockPacketMessage; +import org.geysermc.geyser.network.message.JavaPacketMessage; import org.geysermc.geyser.pack.option.GeyserPriorityOption; import org.geysermc.geyser.pack.option.GeyserSubpackOption; import org.geysermc.geyser.pack.option.GeyserUrlFallbackOption; import org.geysermc.geyser.pack.path.GeyserPathPackCodec; import org.geysermc.geyser.pack.url.GeyserUrlPackCodec; import org.geysermc.geyser.registry.provider.ProviderSupplier; +import org.geysermc.mcprotocollib.protocol.codec.MinecraftPacket; import java.nio.file.Path; import java.util.Map; @@ -104,6 +109,21 @@ public Map, ProviderSupplier> load(Map, ProviderSupplier> prov providers.put(CameraFade.Builder.class, args -> new GeyserCameraFade.Builder()); providers.put(CameraPosition.Builder.class, args -> new GeyserCameraPosition.Builder()); + // network api + providers.put(Message.PacketWrapped.class, args -> { + if (args.length < 1) { + throw new IllegalArgumentException("Message.PacketWrapped requires at least one argument, got " + args.length); + } + + if (args[0] instanceof BedrockPacket bedrockPacket) { + return new BedrockPacketMessage(bedrockPacket); + } else if (args[0] instanceof MinecraftPacket javaPacket) { + return new JavaPacketMessage(javaPacket); + } else { + throw new IllegalArgumentException("Unsupported packet type: " + args[0].getClass().getName()); + } + }); + return providers; } } diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index dceb5707a5f..3a2fec4f391 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -119,6 +119,7 @@ import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity; import org.geysermc.geyser.api.event.bedrock.SessionDisconnectEvent; import org.geysermc.geyser.api.event.bedrock.SessionLoginEvent; +import org.geysermc.geyser.api.network.NetworkManager; import org.geysermc.geyser.api.network.RemoteServer; import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.command.GeyserCommandSource; @@ -152,6 +153,7 @@ import org.geysermc.geyser.level.JavaDimension; import org.geysermc.geyser.level.physics.CollisionManager; import org.geysermc.geyser.network.GameProtocol; +import org.geysermc.geyser.network.GeyserNetworkManager; import org.geysermc.geyser.network.netty.LocalSession; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.BlockMappings; @@ -174,11 +176,11 @@ import org.geysermc.geyser.session.cache.StructureBlockCache; import org.geysermc.geyser.session.cache.TagCache; import org.geysermc.geyser.session.cache.TeleportCache; -import org.geysermc.geyser.session.cache.waypoint.WaypointCache; import org.geysermc.geyser.session.cache.WorldBorder; import org.geysermc.geyser.session.cache.WorldCache; import org.geysermc.geyser.session.cache.registry.JavaRegistries; import org.geysermc.geyser.session.cache.tags.DialogTag; +import org.geysermc.geyser.session.cache.waypoint.WaypointCache; import org.geysermc.geyser.session.dialog.BuiltInDialog; import org.geysermc.geyser.session.dialog.Dialog; import org.geysermc.geyser.session.dialog.DialogManager; @@ -235,10 +237,10 @@ import java.util.Queue; import java.util.Set; import java.util.UUID; -import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -732,9 +734,11 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { @Setter private boolean allowVibrantVisuals = true; + private final GeyserNetworkManager networkManager; + public GeyserSession(GeyserImpl geyser, BedrockServerSession bedrockServerSession, EventLoop tickEventLoop) { this.geyser = geyser; - this.upstream = new UpstreamSession(bedrockServerSession); + this.upstream = new UpstreamSession(this, bedrockServerSession); this.tickEventLoop = tickEventLoop; this.erosionHandler = new GeyserboundHandshakePacketHandler(this); @@ -783,6 +787,7 @@ public GeyserSession(GeyserImpl geyser, BedrockServerSession bedrockServerSessio } this.remoteServer = geyser.defaultRemoteServer(); + this.networkManager = new GeyserNetworkManager(this); } /** @@ -2330,6 +2335,12 @@ public boolean hasFormOpen() { return formCache.hasFormOpen(); } + @Override + @NonNull + public NetworkManager networkManager() { + return this.networkManager; + } + @Override public void closeForm() { sendUpstreamPacket(new ClientboundCloseFormPacket()); diff --git a/core/src/main/java/org/geysermc/geyser/session/UpstreamSession.java b/core/src/main/java/org/geysermc/geyser/session/UpstreamSession.java index 35ede56a18b..d40bd10f84b 100644 --- a/core/src/main/java/org/geysermc/geyser/session/UpstreamSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/UpstreamSession.java @@ -32,6 +32,7 @@ import org.cloudburstmc.protocol.bedrock.BedrockServerSession; import org.cloudburstmc.protocol.bedrock.codec.BedrockCodecHelper; import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket; +import org.geysermc.geyser.api.network.MessageDirection; import org.geysermc.geyser.network.GeyserBedrockPeer; import java.net.InetSocketAddress; @@ -40,18 +41,27 @@ @RequiredArgsConstructor public class UpstreamSession { + private final GeyserSession geyserSession; @Getter private final BedrockServerSession session; @Getter @Setter private boolean initialized = false; private Queue postStartGamePackets = new ArrayDeque<>(); public void sendPacket(@NonNull BedrockPacket packet) { + if (!this.geyserSession.getNetworkManager().handlePacket(packet, MessageDirection.CLIENTBOUND)) { + return; + } + if (!isClosed()) { session.sendPacket(packet); } } public void sendPacketImmediately(@NonNull BedrockPacket packet) { + if (!this.geyserSession.getNetworkManager().handlePacket(packet, MessageDirection.CLIENTBOUND)) { + return; + } + if (!isClosed()) { session.sendPacketImmediately(packet); } @@ -75,7 +85,7 @@ public void sendPostStartGamePackets() { BedrockPacket packet; while ((packet = postStartGamePackets.poll()) != null) { - session.sendPacket(packet); + this.sendPacket(packet); } postStartGamePackets = null; } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCustomPayloadTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCustomPayloadTranslator.java index d5c3c246c5a..fed4d1a156e 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCustomPayloadTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCustomPayloadTranslator.java @@ -40,6 +40,13 @@ import org.geysermc.floodgate.pluginmessage.PluginMessageChannels; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; +import org.geysermc.geyser.api.event.EventBus; +import org.geysermc.geyser.api.event.java.ServerReceiveNetworkMessageEvent; +import org.geysermc.geyser.api.network.MessageDirection; +import org.geysermc.geyser.api.network.NetworkChannel; +import org.geysermc.geyser.api.network.message.Message; +import org.geysermc.geyser.api.network.message.MessageBuffer; +import org.geysermc.geyser.network.GeyserNetworkManager; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; @@ -143,6 +150,20 @@ public void translate(GeyserSession session, ClientboundCustomPayloadPacket pack session.sendUpstreamPacket(toSend); }); + } else { + session.ensureInEventLoop(() -> { + GeyserNetworkManager networkManager = session.getNetworkManager(); + NetworkChannel networkChannel = NetworkChannel.of(packet.getChannel().namespace(), packet.getChannel().value()); + if (!networkManager.getRegisteredChannels().contains(networkChannel)) { + logger.debug("Received a custom payload for an unregistered channel: " + networkChannel.channel()); + return; + } + + Message message = networkManager.createMessage(networkChannel, packet.getData()); + + EventBus eventBus = session.getGeyser().getEventBus(); + eventBus.fire(new ServerReceiveNetworkMessageEvent(session, networkChannel, message, MessageDirection.CLIENTBOUND)); + }); } } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java index 622b2d23b65..d4efa7577dd 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java @@ -32,6 +32,7 @@ import org.geysermc.erosion.Constants; import org.geysermc.floodgate.pluginmessage.PluginMessageChannels; import org.geysermc.geyser.api.network.AuthType; +import org.geysermc.geyser.api.network.NetworkChannel; import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; import org.geysermc.geyser.level.BedrockDimension; import org.geysermc.geyser.level.JavaDimension; @@ -50,6 +51,8 @@ import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; @Translator(packet = ClientboundLoginPacket.class) public class JavaLoginTranslator extends PacketTranslator { @@ -130,6 +133,16 @@ public void translate(GeyserSession session, ClientboundLoginPacket packet) { } session.sendDownstreamPacket(new ServerboundCustomPayloadPacket(register, Constants.PLUGIN_MESSAGE.getBytes(StandardCharsets.UTF_8))); + Set registeredChannels = session.getNetworkManager().getRegisteredChannels(); + if (!registeredChannels.isEmpty()) { + String channels = registeredChannels + .stream() + .map(channel -> channel.key() + ":" + channel.channel()) + .collect(Collectors.joining("\0")); + + session.sendDownstreamPacket(new ServerboundCustomPayloadPacket(register, channels.getBytes(StandardCharsets.UTF_8))); + } + if (session.getBedrockDimension().bedrockId() != newDimension.bedrockId()) { DimensionUtils.switchDimension(session, newDimension); } else if (BedrockDimension.isCustomBedrockNetherId() && newDimension.isNetherLike()) { From 43d37477535c5b294e1374102fa92ca1e0ffa212 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sun, 13 Jul 2025 22:48:16 +0200 Subject: [PATCH 2/4] Add additional packet constructors for use in channel defining --- .../geyser/api/network/message/Message.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/api/src/main/java/org/geysermc/geyser/api/network/message/Message.java b/api/src/main/java/org/geysermc/geyser/api/network/message/Message.java index bcbe5b0f709..adac7599cd0 100644 --- a/api/src/main/java/org/geysermc/geyser/api/network/message/Message.java +++ b/api/src/main/java/org/geysermc/geyser/api/network/message/Message.java @@ -28,6 +28,9 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.GeyserApi; +import java.util.function.Function; +import java.util.function.Supplier; + /** * Represents a message that can be sent over the network. * @since 2.8.2 @@ -64,6 +67,29 @@ interface Packet extends PacketBase { static PacketWrapped of(@NonNull Object packet) { return GeyserApi.api().provider(PacketWrapped.class, packet); } + + /** + * Creates a new packet message from the given packet object and direction. + * + * @param packetSupplier a supplier that provides the packet object to create the message from + * @return a new packet message + */ + @NonNull + static MessageFactory of(@NonNull Supplier packetSupplier) { + return buffer -> of(packetSupplier.get()); + } + + /** + * Creates a new packet message from the given substitutor and packet supplier. + * + * @param substitutor a function that applies to the buffer to get the packet object + * @param packetSupplier a function that provides the packet object + * @return a new packet message factory + */ + @NonNull + static MessageFactory of(@NonNull Function substitutor, @NonNull Function packetSupplier) { + return buffer -> of(packetSupplier.apply(substitutor.apply(buffer))); + } } interface PacketWrapped extends PacketBase { From 3049c77975d0f45ac5f627766d6b33012724eb25 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sat, 25 Oct 2025 21:59:25 +0100 Subject: [PATCH 3/4] Address comments --- .../SessionDefineNetworkChannelsEvent.java | 1 + .../ServerReceiveNetworkMessageEvent.java | 4 +- .../api/network/ExtensionNetworkChannel.java | 18 ++--- .../api/network/ExternalNetworkChannel.java | 29 +++---- .../geyser/api/network/MessageDirection.java | 6 ++ .../geyser/api/network/NetworkChannel.java | 76 ++++++++++++++++--- .../geyser/api/network/NetworkManager.java | 2 +- .../geyser/api/network/PacketChannel.java | 7 +- .../geyser/api/network/message/Message.java | 10 +++ .../api/network/message/MessageBuffer.java | 7 ++ .../api/network/message/MessageFactory.java | 1 + .../geyser/network/GeyserNetworkManager.java | 7 +- .../network/message/BedrockPacketMessage.java | 2 +- .../network/message/ByteBufMessageBuffer.java | 7 +- .../network/message/JavaPacketMessage.java | 2 +- .../java/JavaCustomPayloadTranslator.java | 2 +- .../protocol/java/JavaLoginTranslator.java | 2 +- 17 files changed, 125 insertions(+), 58 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionDefineNetworkChannelsEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionDefineNetworkChannelsEvent.java index 1148345aeec..29b91702596 100644 --- a/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionDefineNetworkChannelsEvent.java +++ b/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionDefineNetworkChannelsEvent.java @@ -55,6 +55,7 @@ public SessionDefineNetworkChannelsEvent(@NonNull GeyserConnection connection) { * Registers a new network channel with a message factory. * * @param channel the channel to register + * @param codec the codec to use to encode/decode the buffer * @param messageFactory the factory to create messages from the buffer */ public abstract void register(@NonNull NetworkChannel channel, @NonNull MessageCodec codec, @NonNull MessageFactory messageFactory); diff --git a/api/src/main/java/org/geysermc/geyser/api/event/java/ServerReceiveNetworkMessageEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/java/ServerReceiveNetworkMessageEvent.java index 8e9f68332cb..e6b61b51ff7 100644 --- a/api/src/main/java/org/geysermc/geyser/api/event/java/ServerReceiveNetworkMessageEvent.java +++ b/api/src/main/java/org/geysermc/geyser/api/event/java/ServerReceiveNetworkMessageEvent.java @@ -37,7 +37,7 @@ * Called when the server receives a network message. * @since 2.8.2 */ -public class ServerReceiveNetworkMessageEvent extends ConnectionEvent implements Cancellable { +public final class ServerReceiveNetworkMessageEvent extends ConnectionEvent implements Cancellable { private final NetworkChannel channel; private final Message message; private final MessageDirection direction; @@ -53,6 +53,8 @@ public ServerReceiveNetworkMessageEvent(@NonNull GeyserConnection connection, @N /** * Gets the channel that received the message. + *

+ * See {@link NetworkChannel} for more information. * * @return the channel that received the message */ diff --git a/api/src/main/java/org/geysermc/geyser/api/network/ExtensionNetworkChannel.java b/api/src/main/java/org/geysermc/geyser/api/network/ExtensionNetworkChannel.java index 44b2becdee7..9cc3413d867 100644 --- a/api/src/main/java/org/geysermc/geyser/api/network/ExtensionNetworkChannel.java +++ b/api/src/main/java/org/geysermc/geyser/api/network/ExtensionNetworkChannel.java @@ -27,6 +27,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.extension.Extension; +import org.geysermc.geyser.api.util.Identifier; import java.util.Objects; @@ -48,17 +49,8 @@ protected ExtensionNetworkChannel(@NonNull Extension extension, @NonNull String */ @Override @NonNull - public String key() { - return this.extension.description().id(); - } - - /** - * {@inheritDoc} - */ - @Override - @NonNull - public String channel() { - return this.channel; + public Identifier identifier() { + return Identifier.of(this.extension.description().id(), this.channel); } /** @@ -73,12 +65,12 @@ public boolean isPacket() { public boolean equals(Object o) { if (o == null || !NetworkChannel.class.isAssignableFrom(o.getClass())) return false; NetworkChannel that = (NetworkChannel) o; - return Objects.equals(this.key(), that.key()) && Objects.equals(this.channel(), that.channel()); + return Objects.equals(this.identifier(), that.identifier()); } @Override public int hashCode() { - return Objects.hash(this.key(), this.channel()); + return Objects.hash(this.identifier()); } @Override diff --git a/api/src/main/java/org/geysermc/geyser/api/network/ExternalNetworkChannel.java b/api/src/main/java/org/geysermc/geyser/api/network/ExternalNetworkChannel.java index a22693a8fed..479727a15f1 100644 --- a/api/src/main/java/org/geysermc/geyser/api/network/ExternalNetworkChannel.java +++ b/api/src/main/java/org/geysermc/geyser/api/network/ExternalNetworkChannel.java @@ -26,6 +26,7 @@ package org.geysermc.geyser.api.network; import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.util.Identifier; import java.util.Objects; @@ -36,12 +37,10 @@ * @since 2.8.2 */ public class ExternalNetworkChannel implements NetworkChannel { - private final String key; - private final String channel; + private final Identifier identifier; - protected ExternalNetworkChannel(@NonNull String key, @NonNull String channel) { - this.key = key; - this.channel = channel; + protected ExternalNetworkChannel(@NonNull Identifier identifier) { + this.identifier = identifier; } /** @@ -49,17 +48,8 @@ protected ExternalNetworkChannel(@NonNull String key, @NonNull String channel) { */ @Override @NonNull - public String key() { - return this.key; - } - - /** - * {@inheritDoc} - */ - @Override - @NonNull - public String channel() { - return this.channel; + public Identifier identifier() { + return this.identifier; } /** @@ -74,19 +64,18 @@ public boolean isPacket() { public boolean equals(Object o) { if (o == null || !NetworkChannel.class.isAssignableFrom(o.getClass())) return false; NetworkChannel that = (NetworkChannel) o; - return Objects.equals(this.key(), that.key()) && Objects.equals(this.channel(), that.channel()); + return Objects.equals(this.identifier(), that.identifier()); } @Override public int hashCode() { - return Objects.hash(this.key(), this.channel()); + return Objects.hash(this.identifier()); } @Override public String toString() { return "ExternalNetworkChannel{" + - "key='" + this.key + '\'' + - ", channel='" + this.channel + '\'' + + "identifier='" + this.identifier + '\'' + '}'; } } diff --git a/api/src/main/java/org/geysermc/geyser/api/network/MessageDirection.java b/api/src/main/java/org/geysermc/geyser/api/network/MessageDirection.java index 56af6f2d6bc..16d4886b4ab 100644 --- a/api/src/main/java/org/geysermc/geyser/api/network/MessageDirection.java +++ b/api/src/main/java/org/geysermc/geyser/api/network/MessageDirection.java @@ -32,10 +32,16 @@ public enum MessageDirection { /** * Indicates that the message is sent from the server to the client. + *

+ * Note that extensions may also send messages in this direction, meaning + * that not every clientbound message is necessarily from the server itself. */ CLIENTBOUND, /** * Indicates that the message is sent from the client to the server. + *

+ * Note that extensions may also send messages in this direction, meaning + * that not every serverbound message is necessarily from the client itself. */ SERVERBOUND } diff --git a/api/src/main/java/org/geysermc/geyser/api/network/NetworkChannel.java b/api/src/main/java/org/geysermc/geyser/api/network/NetworkChannel.java index 741a9118d84..3c627d7ebde 100644 --- a/api/src/main/java/org/geysermc/geyser/api/network/NetworkChannel.java +++ b/api/src/main/java/org/geysermc/geyser/api/network/NetworkChannel.java @@ -28,28 +28,60 @@ import org.checkerframework.checker.index.qual.NonNegative; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.extension.Extension; +import org.geysermc.geyser.api.util.Identifier; /** * Represents a channel used for network communication. + *

+ * A network channel can either be an external channel or a + * packet channel. External channels are identified by a unique + * key and are used for custom payloads over the network. Packet + * channels will represent data from packets, identified by a packet + * ID and type. + *

+ * For constructing an external NetworkChannel, the following + * can be done: + * + *

+ * {@code
+ *     private final NetworkChannel myChannel = NetworkChannel.of("example", "my_channel");
+ * }
+ * 
+ * Or when inside an extension, with 'this' being the extension instance: + *
+ * {@code
+ *     private final NetworkChannel myChannel = NetworkChannel.of(this, "my_channel");
+ * }
+ * 
+ * + *

+ * For packet channels, it can get slightly more complex as you need to + * know the packet ID alongside having a constructed message type. The + * following example demonstrates this with the animate packet, assuming + * the AnimateMessage class represents the correct packet structure: + *

+ * {@code
+ *    private final NetworkChannel animateChannel = NetworkChannel.packet("animate", 44, AnimateMessage.class);
+ * }
+ * 
+ * + *

+ * Packet channels can also be registered against packet objects from + * exterbak protocol libraries, such as the ones provided in Geyser. For + * an example on how to do this, please see the + * Networking API documentation. + * * @since 2.8.2 */ public interface NetworkChannel { /** - * Gets the key that owns this channel. - * - * @return the key that owns this channel - */ - @NonNull - String key(); - - /** - * Gets the name of the channel. + * Gets the identifier that owns this channel. * - * @return the channel name + * @return the identifier that owns this channel */ @NonNull - String channel(); + Identifier identifier(); /** * Checks if this channel is a packet channel. @@ -60,6 +92,9 @@ public interface NetworkChannel { /** * Creates a new {@link NetworkChannel} instance. + *

+ * Extensions should use this method to register + * their own channels for more robust identification. * * @param extension the extension that registered this channel * @param channel the name of the channel @@ -72,6 +107,9 @@ static NetworkChannel of(@NonNull Extension extension, @NonNull String channel) /** * Creates a new {@link NetworkChannel} instance. + *

+ * This method is used for external channels provided + * by third parties, such as plugins or mods. * * @param id the channel id * @param channel the name of the channel @@ -79,7 +117,21 @@ static NetworkChannel of(@NonNull Extension extension, @NonNull String channel) */ @NonNull static NetworkChannel of(@NonNull String id, @NonNull String channel) { - return new ExternalNetworkChannel(id, channel); + return of(Identifier.of(id, channel)); + } + + /** + * Creates a new {@link NetworkChannel} instance. + *

+ * This method is used for external channels provided + * by third parties, such as plugins or mods. + * + * @param identifier the {@link Identifier} of the channel + * @return a new {@link NetworkChannel} instance + */ + @NonNull + static NetworkChannel of(@NonNull Identifier identifier) { + return new ExternalNetworkChannel(identifier); } /** diff --git a/api/src/main/java/org/geysermc/geyser/api/network/NetworkManager.java b/api/src/main/java/org/geysermc/geyser/api/network/NetworkManager.java index 546a63bef95..9aa29115560 100644 --- a/api/src/main/java/org/geysermc/geyser/api/network/NetworkManager.java +++ b/api/src/main/java/org/geysermc/geyser/api/network/NetworkManager.java @@ -46,7 +46,7 @@ public interface NetworkManager { * @return the registered network channels */ @NonNull - Set getRegisteredChannels(); + Set registeredChannels(); /** * Sends a message to this connection on the specified channel. diff --git a/api/src/main/java/org/geysermc/geyser/api/network/PacketChannel.java b/api/src/main/java/org/geysermc/geyser/api/network/PacketChannel.java index d21f93dbd36..8067cc59d9e 100644 --- a/api/src/main/java/org/geysermc/geyser/api/network/PacketChannel.java +++ b/api/src/main/java/org/geysermc/geyser/api/network/PacketChannel.java @@ -27,13 +27,16 @@ import org.checkerframework.checker.index.qual.NonNegative; import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.util.Identifier; import java.util.Objects; /** * Represents a network channel associated with a packet. *

- * This channel is used for communication of packets between the server and client. + * This channel is used for listening to communication over + * packets between the server and client and can be used to + * send or receive packets. * @since 2.8.2 */ public class PacketChannel extends ExternalNetworkChannel { @@ -43,7 +46,7 @@ public class PacketChannel extends ExternalNetworkChannel { private final Class packetType; protected PacketChannel(@NonNull String key, @NonNegative int packetId, @NonNull Class packetType) { - super(PACKET_CHANNEL_KEY, key); + super(Identifier.of(PACKET_CHANNEL_KEY, key)); this.packetId = packetId; this.packetType = packetType; diff --git a/api/src/main/java/org/geysermc/geyser/api/network/message/Message.java b/api/src/main/java/org/geysermc/geyser/api/network/message/Message.java index adac7599cd0..e09e43b7de7 100644 --- a/api/src/main/java/org/geysermc/geyser/api/network/message/Message.java +++ b/api/src/main/java/org/geysermc/geyser/api/network/message/Message.java @@ -50,6 +50,11 @@ public interface Message { interface Simple extends Message { } + /** + * Represents a packet message with an unknown packet ID. + * + * @param the type of message buffer + */ interface PacketBase extends Message { } @@ -92,6 +97,11 @@ static MessageFactory of(@NonNull Function } } + /** + * Represents a packet message that wraps an underlying packet object. + * + * @param the type of message buffer + */ interface PacketWrapped extends PacketBase { /** diff --git a/api/src/main/java/org/geysermc/geyser/api/network/message/MessageBuffer.java b/api/src/main/java/org/geysermc/geyser/api/network/message/MessageBuffer.java index d4234d08860..7b47ecaf198 100644 --- a/api/src/main/java/org/geysermc/geyser/api/network/message/MessageBuffer.java +++ b/api/src/main/java/org/geysermc/geyser/api/network/message/MessageBuffer.java @@ -60,4 +60,11 @@ public interface MessageBuffer { * @return the serialized byte array */ byte @NonNull [] serialize(); + + /** + * Gets the length of the buffer. + * + * @return the length of the buffer + */ + int length(); } diff --git a/api/src/main/java/org/geysermc/geyser/api/network/message/MessageFactory.java b/api/src/main/java/org/geysermc/geyser/api/network/message/MessageFactory.java index 6c8e7c5084b..3d205823a3a 100644 --- a/api/src/main/java/org/geysermc/geyser/api/network/message/MessageFactory.java +++ b/api/src/main/java/org/geysermc/geyser/api/network/message/MessageFactory.java @@ -33,6 +33,7 @@ * @param the type of the message buffer * @since 2.8.2 */ +@FunctionalInterface public interface MessageFactory { /** diff --git a/core/src/main/java/org/geysermc/geyser/network/GeyserNetworkManager.java b/core/src/main/java/org/geysermc/geyser/network/GeyserNetworkManager.java index e2abd692f07..312958060bc 100644 --- a/core/src/main/java/org/geysermc/geyser/network/GeyserNetworkManager.java +++ b/core/src/main/java/org/geysermc/geyser/network/GeyserNetworkManager.java @@ -85,7 +85,7 @@ public void register(@NonNull NetworkChannel channel, } @Override - public @NonNull Set getRegisteredChannels() { + public @NonNull Set registeredChannels() { return Set.copyOf(this.definitions.keySet()); } @@ -132,7 +132,7 @@ public void send(@NonNull NetworkChannel channel, @Non message.encode(buffer); ServerboundCustomPayloadPacket packet = new ServerboundCustomPayloadPacket( - Key.key(channel.key(), channel.channel()), + Key.key(channel.identifier().toString()), buffer.serialize() ); @@ -201,8 +201,7 @@ private void registerMessage(@NonNull NetworkChannel c } this.definitions.put(channel, codec); - if (channel.isPacket()) { - PacketChannel packetChannel = (PacketChannel) channel; + if (channel.isPacket() && channel instanceof PacketChannel packetChannel) { int packetId = packetChannel.packetId(); this.packetChannels.put(packetId, packetChannel); } diff --git a/core/src/main/java/org/geysermc/geyser/network/message/BedrockPacketMessage.java b/core/src/main/java/org/geysermc/geyser/network/message/BedrockPacketMessage.java index 776514d8e2a..3a09d648aef 100644 --- a/core/src/main/java/org/geysermc/geyser/network/message/BedrockPacketMessage.java +++ b/core/src/main/java/org/geysermc/geyser/network/message/BedrockPacketMessage.java @@ -52,6 +52,6 @@ public void postProcess(@NonNull GeyserSession session, @NonNull ByteBufMessageB @Override public void encode(@NonNull ByteBufMessageBuffer buffer) { - throw new UnsupportedOperationException("BedrockPacketMessage does not support encoding directly to a MessageBuffe."); + throw new UnsupportedOperationException("BedrockPacketMessage does not support encoding directly to a MessageBuffer."); } } diff --git a/core/src/main/java/org/geysermc/geyser/network/message/ByteBufMessageBuffer.java b/core/src/main/java/org/geysermc/geyser/network/message/ByteBufMessageBuffer.java index adff86fb3ee..8b414d3033e 100644 --- a/core/src/main/java/org/geysermc/geyser/network/message/ByteBufMessageBuffer.java +++ b/core/src/main/java/org/geysermc/geyser/network/message/ByteBufMessageBuffer.java @@ -28,9 +28,9 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.geyser.api.network.message.DataType; import org.geysermc.geyser.api.network.message.MessageBuffer; import org.geysermc.geyser.api.network.message.MessageCodec; -import org.geysermc.geyser.api.network.message.DataType; public record ByteBufMessageBuffer(MessageCodec codec, ByteBuf buffer) implements MessageBuffer { @@ -55,4 +55,9 @@ public void write(@NonNull DataType type, @NonNull T value) { this.buffer.clear(); // Clear the buffer after serialization return bytes; } + + @Override + public int length() { + return this.buffer.readableBytes(); + } } diff --git a/core/src/main/java/org/geysermc/geyser/network/message/JavaPacketMessage.java b/core/src/main/java/org/geysermc/geyser/network/message/JavaPacketMessage.java index 36d75ff4fb2..6a2fd336f5b 100644 --- a/core/src/main/java/org/geysermc/geyser/network/message/JavaPacketMessage.java +++ b/core/src/main/java/org/geysermc/geyser/network/message/JavaPacketMessage.java @@ -33,6 +33,6 @@ public record JavaPacketMessage(MinecraftPacket packet) implements Message.Packe @Override public void encode(@NonNull ByteBufMessageBuffer buffer) { - throw new UnsupportedOperationException("JavaPacketMessage does not support encoding directly to a MessageBuffe."); + throw new UnsupportedOperationException("JavaPacketMessage does not support encoding directly to a MessageBuffer."); } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCustomPayloadTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCustomPayloadTranslator.java index fed4d1a156e..64a46c89e69 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCustomPayloadTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCustomPayloadTranslator.java @@ -154,7 +154,7 @@ public void translate(GeyserSession session, ClientboundCustomPayloadPacket pack session.ensureInEventLoop(() -> { GeyserNetworkManager networkManager = session.getNetworkManager(); NetworkChannel networkChannel = NetworkChannel.of(packet.getChannel().namespace(), packet.getChannel().value()); - if (!networkManager.getRegisteredChannels().contains(networkChannel)) { + if (!networkManager.registeredChannels().contains(networkChannel)) { logger.debug("Received a custom payload for an unregistered channel: " + networkChannel.channel()); return; } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java index d4efa7577dd..eb94afdb8ec 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java @@ -133,7 +133,7 @@ public void translate(GeyserSession session, ClientboundLoginPacket packet) { } session.sendDownstreamPacket(new ServerboundCustomPayloadPacket(register, Constants.PLUGIN_MESSAGE.getBytes(StandardCharsets.UTF_8))); - Set registeredChannels = session.getNetworkManager().getRegisteredChannels(); + Set registeredChannels = session.getNetworkManager().registeredChannels(); if (!registeredChannels.isEmpty()) { String channels = registeredChannels .stream() From 86b1ac979f47005eb10f7bee9709dcbf1290e7a0 Mon Sep 17 00:00:00 2001 From: RednedEpic Date: Sat, 25 Oct 2025 22:01:32 +0100 Subject: [PATCH 4/4] Missed this --- .../geyser/api/event/java/ServerReceiveNetworkMessageEvent.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/event/java/ServerReceiveNetworkMessageEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/java/ServerReceiveNetworkMessageEvent.java index e6b61b51ff7..70035462c50 100644 --- a/api/src/main/java/org/geysermc/geyser/api/event/java/ServerReceiveNetworkMessageEvent.java +++ b/api/src/main/java/org/geysermc/geyser/api/event/java/ServerReceiveNetworkMessageEvent.java @@ -34,7 +34,7 @@ import org.geysermc.geyser.api.network.message.Message; /** - * Called when the server receives a network message. + * Called when Geyser receives a network message from the server. * @since 2.8.2 */ public final class ServerReceiveNetworkMessageEvent extends ConnectionEvent implements Cancellable {