From e60e2063a87c8904b5ad89680aa51698b1ef8a0c Mon Sep 17 00:00:00 2001 From: Adrian <68704415+4drian3d@users.noreply.github.com> Date: Sun, 16 Jun 2024 10:56:09 -0500 Subject: [PATCH] Add Configuration State API (#1261) * Implement Configuration State events * Implement PlayerFinishedConfigurationEvent * Fixed PlayerFinishConfigurationEvent execution * Apply suggestions * Fix keep alive when blocking PlayerFinishConfigurationEvent * Add ServerConnection to configuration events * Separate PlayPacketQueueHandler to fix AutoReadHolderHandler --------- Co-authored-by: Gero --- .../PlayerEnterConfigurationEvent.java | 27 ++++++ .../PlayerFinishConfigurationEvent.java | 26 +++++ .../PlayerFinishedConfigurationEvent.java | 26 +++++ .../velocitypowered/proxy/VelocityServer.java | 2 +- .../proxy/connection/MinecraftConnection.java | 29 +++--- .../backend/BackendPlaySessionHandler.java | 4 +- .../backend/BungeeCordMessageResponder.java | 8 +- .../backend/ConfigSessionHandler.java | 28 +++--- .../backend/LoginSessionHandler.java | 7 +- .../client/ClientConfigSessionHandler.java | 10 +- .../client/ClientPlaySessionHandler.java | 4 +- .../connection/client/ConnectedPlayer.java | 39 ++++---- .../bundle}/BundleDelimiterHandler.java | 3 +- .../VelocityResourcePackInfo.java | 2 +- .../Legacy117ResourcePackHandler.java | 2 +- .../LegacyResourcePackHandler.java | 3 +- .../ModernResourcePackHandler.java | 3 +- .../{ => handler}/ResourcePackHandler.java | 5 +- .../proxy/network/Connections.java | 3 +- .../protocol/netty/MinecraftDecoder.java | 7 +- .../netty/PlayPacketQueueInboundHandler.java | 94 +++++++++++++++++++ ...va => PlayPacketQueueOutboundHandler.java} | 20 ++-- .../packet/ResourcePackRequestPacket.java | 2 +- 23 files changed, 264 insertions(+), 90 deletions(-) create mode 100644 api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerEnterConfigurationEvent.java create mode 100644 api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerFinishConfigurationEvent.java create mode 100644 api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerFinishedConfigurationEvent.java rename proxy/src/main/java/com/velocitypowered/proxy/connection/{client => player/bundle}/BundleDelimiterHandler.java (95%) rename proxy/src/main/java/com/velocitypowered/proxy/connection/player/{ => resourcepack}/VelocityResourcePackInfo.java (98%) rename proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/{ => handler}/Legacy117ResourcePackHandler.java (99%) rename proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/{ => handler}/LegacyResourcePackHandler.java (98%) rename proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/{ => handler}/ModernResourcePackHandler.java (98%) rename proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/{ => handler}/ResourcePackHandler.java (97%) create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/PlayPacketQueueInboundHandler.java rename proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/{PlayPacketQueueHandler.java => PlayPacketQueueOutboundHandler.java} (86%) diff --git a/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerEnterConfigurationEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerEnterConfigurationEvent.java new file mode 100644 index 000000000..3b108c6a7 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerEnterConfigurationEvent.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2024 Velocity Contributors + * + * The Velocity API is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package com.velocitypowered.api.event.player.configuration; + +import com.velocitypowered.api.network.ProtocolState; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ServerConnection; +import org.jetbrains.annotations.NotNull; + +/** + * This event is executed when a player with version 1.20.2 or higher enters the configuration phase. + *

From this moment on, until the {@link PlayerFinishedConfigurationEvent} is executed, + * the {@linkplain Player#getProtocolState()} method is guaranteed + * to return {@link ProtocolState#CONFIGURATION}.

+ * + * @param player The player that has entered the configuration phase. + * @param server The server that will now (re-)configure the player. + * @since 3.3.0 + * @sinceMinecraft 1.20.2 + */ +public record PlayerEnterConfigurationEvent(@NotNull Player player, ServerConnection server) { +} diff --git a/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerFinishConfigurationEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerFinishConfigurationEvent.java new file mode 100644 index 000000000..f6249b897 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerFinishConfigurationEvent.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 Velocity Contributors + * + * The Velocity API is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package com.velocitypowered.api.event.player.configuration; + +import com.velocitypowered.api.event.annotation.AwaitingEvent; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ServerConnection; +import org.jetbrains.annotations.NotNull; + +/** + * This event is executed when the player is about to finish the Configuration state. + *

Velocity will wait for this event to finish the configuration phase on the client.

+ * + * @param player The player who is about to complete the configuration phase. + * @param server The server that is currently (re-)configuring the player. + * @since 3.3.0 + * @sinceMinecraft 1.20.2 + */ +@AwaitingEvent +public record PlayerFinishConfigurationEvent(@NotNull Player player, @NotNull ServerConnection server) { +} diff --git a/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerFinishedConfigurationEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerFinishedConfigurationEvent.java new file mode 100644 index 000000000..09e76104f --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerFinishedConfigurationEvent.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 Velocity Contributors + * + * The Velocity API is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package com.velocitypowered.api.event.player.configuration; + +import com.velocitypowered.api.network.ProtocolState; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ServerConnection; +import org.jetbrains.annotations.NotNull; + +/** + * Event executed when a player of version 1.20.2 or higher finishes the Configuration state. + *

From this moment on, the {@link Player#getProtocolState()} method + * will return {@link ProtocolState#PLAY}.

+ * + * @param player The player who has completed the Configuration state + * @param server The server that has (re-)configured the player. + * @since 3.3.0 + * @sinceMinecraft 1.20.2 + */ +public record PlayerFinishedConfigurationEvent(@NotNull Player player, @NotNull ServerConnection server) { +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index ef8217c69..004243616 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -45,7 +45,7 @@ import com.velocitypowered.proxy.command.builtin.ShutdownCommand; import com.velocitypowered.proxy.command.builtin.VelocityCommand; import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; -import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo; +import com.velocitypowered.proxy.connection.player.resourcepack.VelocityResourcePackInfo; import com.velocitypowered.proxy.connection.util.ServerListPingHandler; import com.velocitypowered.proxy.console.VelocityConsole; import com.velocitypowered.proxy.crypto.EncryptionUtils; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java index b1c91ebdb..c91739078 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java @@ -47,7 +47,8 @@ import com.velocitypowered.proxy.protocol.netty.MinecraftCompressorAndLengthEnco import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder; import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder; -import com.velocitypowered.proxy.protocol.netty.PlayPacketQueueHandler; +import com.velocitypowered.proxy.protocol.netty.PlayPacketQueueInboundHandler; +import com.velocitypowered.proxy.protocol.netty.PlayPacketQueueOutboundHandler; import com.velocitypowered.proxy.protocol.packet.SetCompressionPacket; import com.velocitypowered.proxy.util.except.QuietDecoderException; import io.netty.buffer.ByteBuf; @@ -148,13 +149,11 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { return; } - if (msg instanceof MinecraftPacket) { - MinecraftPacket pkt = (MinecraftPacket) msg; + if (msg instanceof MinecraftPacket pkt) { if (!pkt.handle(activeSessionHandler)) { activeSessionHandler.handleGeneric((MinecraftPacket) msg); } - } else if (msg instanceof HAProxyMessage) { - HAProxyMessage proxyMessage = (HAProxyMessage) msg; + } else if (msg instanceof HAProxyMessage proxyMessage) { this.remoteAddress = new InetSocketAddress(proxyMessage.sourceAddress(), proxyMessage.sourcePort()); } else if (msg instanceof ByteBuf) { @@ -383,9 +382,14 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { if (state == StateRegistry.CONFIG) { // Activate the play packet queue addPlayPacketQueueHandler(); - } else if (this.channel.pipeline().get(Connections.PLAY_PACKET_QUEUE) != null) { + } else { // Remove the queue - this.channel.pipeline().remove(Connections.PLAY_PACKET_QUEUE); + if (this.channel.pipeline().get(Connections.PLAY_PACKET_QUEUE_OUTBOUND) != null) { + this.channel.pipeline().remove(Connections.PLAY_PACKET_QUEUE_OUTBOUND); + } + if (this.channel.pipeline().get(Connections.PLAY_PACKET_QUEUE_INBOUND) != null) { + this.channel.pipeline().remove(Connections.PLAY_PACKET_QUEUE_INBOUND); + } } } @@ -393,10 +397,13 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { * Adds the play packet queue handler. */ public void addPlayPacketQueueHandler() { - if (this.channel.pipeline().get(Connections.PLAY_PACKET_QUEUE) == null) { - this.channel.pipeline().addAfter(Connections.MINECRAFT_ENCODER, Connections.PLAY_PACKET_QUEUE, - new PlayPacketQueueHandler(this.protocolVersion, - channel.pipeline().get(MinecraftEncoder.class).getDirection())); + if (this.channel.pipeline().get(Connections.PLAY_PACKET_QUEUE_OUTBOUND) == null) { + this.channel.pipeline().addAfter(Connections.MINECRAFT_ENCODER, Connections.PLAY_PACKET_QUEUE_OUTBOUND, + new PlayPacketQueueOutboundHandler(this.protocolVersion, channel.pipeline().get(MinecraftEncoder.class).getDirection())); + } + if (this.channel.pipeline().get(Connections.PLAY_PACKET_QUEUE_INBOUND) == null) { + this.channel.pipeline().addAfter(Connections.MINECRAFT_DECODER, Connections.PLAY_PACKET_QUEUE_INBOUND, + new PlayPacketQueueInboundHandler(this.protocolVersion, channel.pipeline().get(MinecraftDecoder.class).getDirection())); } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java index 5b83e6549..14989b1a3 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java @@ -40,8 +40,8 @@ import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; -import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo; -import com.velocitypowered.proxy.connection.player.resourcepack.ResourcePackHandler; +import com.velocitypowered.proxy.connection.player.resourcepack.VelocityResourcePackInfo; +import com.velocitypowered.proxy.connection.player.resourcepack.handler.ResourcePackHandler; import com.velocitypowered.proxy.connection.util.ConnectionMessages; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.StateRegistry; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BungeeCordMessageResponder.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BungeeCordMessageResponder.java index 3c2691781..ea28d2c97 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BungeeCordMessageResponder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BungeeCordMessageResponder.java @@ -37,7 +37,6 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import java.util.Optional; import java.util.StringJoiner; -import net.kyori.adventure.identity.Identity; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.ComponentSerializer; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; @@ -143,7 +142,7 @@ public class BungeeCordMessageResponder { out.writeUTF("PlayerList"); out.writeUTF(info.getServerInfo().getName()); - StringJoiner joiner = new StringJoiner(", "); + final StringJoiner joiner = new StringJoiner(", "); for (Player online : info.getPlayersConnected()) { joiner.add(online.getUsername()); } @@ -187,10 +186,9 @@ public class BungeeCordMessageResponder { Component messageComponent = serializer.deserialize(message); if (target.equals("ALL")) { - proxy.sendMessage(Identity.nil(), messageComponent); + proxy.sendMessage(messageComponent); } else { - proxy.getPlayer(target).ifPresent(player -> player.sendMessage(Identity.nil(), - messageComponent)); + proxy.getPlayer(target).ifPresent(player -> player.sendMessage(messageComponent)); } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java index 3f4325e52..a19854eb9 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java @@ -29,7 +29,7 @@ import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.client.ClientConfigSessionHandler; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; -import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo; +import com.velocitypowered.proxy.connection.player.resourcepack.VelocityResourcePackInfo; import com.velocitypowered.proxy.connection.util.ConnectionMessages; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl; @@ -132,7 +132,8 @@ public class ConfigSessionHandler implements MinecraftSessionHandler { @Override public boolean handle(KeepAlivePacket packet) { - serverConn.ensureConnected().write(packet); + serverConn.getPendingPings().put(packet.getRandomId(), System.nanoTime()); + serverConn.getPlayer().getConnection().write(packet); return true; } @@ -193,30 +194,25 @@ public class ConfigSessionHandler implements MinecraftSessionHandler { @Override public boolean handle(FinishedUpdatePacket packet) { - MinecraftConnection smc = serverConn.ensureConnected(); - ConnectedPlayer player = serverConn.getPlayer(); - ClientConfigSessionHandler configHandler = - (ClientConfigSessionHandler) player.getConnection().getActiveSessionHandler(); + final MinecraftConnection smc = serverConn.ensureConnected(); + final ConnectedPlayer player = serverConn.getPlayer(); + final ClientConfigSessionHandler configHandler = (ClientConfigSessionHandler) player.getConnection().getActiveSessionHandler(); - smc.setAutoReading(false); - // Even when not auto reading messages are still decoded. Decode them with the correct state smc.getChannel().pipeline().get(MinecraftDecoder.class).setState(StateRegistry.PLAY); - configHandler.handleBackendFinishUpdate(serverConn).thenAcceptAsync((unused) -> { + //noinspection DataFlowIssue + configHandler.handleBackendFinishUpdate(serverConn).thenRunAsync(() -> { + smc.write(FinishedUpdatePacket.INSTANCE); if (serverConn == player.getConnectedServer()) { smc.setActiveSessionHandler(StateRegistry.PLAY); - player.sendPlayerListHeaderAndFooter( - player.getPlayerListHeader(), player.getPlayerListFooter()); + player.sendPlayerListHeaderAndFooter(player.getPlayerListHeader(), player.getPlayerListFooter()); // The client cleared the tab list. TODO: Restore changes done via TabList API player.getTabList().clearAllSilent(); } else { - smc.setActiveSessionHandler(StateRegistry.PLAY, - new TransitionSessionHandler(server, serverConn, resultFuture)); + smc.setActiveSessionHandler(StateRegistry.PLAY, new TransitionSessionHandler(server, serverConn, resultFuture)); } - if (player.resourcePackHandler().getFirstAppliedPack() == null - && resourcePackToApply != null) { + if (player.resourcePackHandler().getFirstAppliedPack() == null && resourcePackToApply != null) { player.resourcePackHandler().queueResourcePack(resourcePackToApply); } - smc.setAutoReading(true); }, smc.eventLoop()); return true; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java index a672c9174..a2d2205ef 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java @@ -166,12 +166,9 @@ public class LoginSessionHandler implements MinecraftSessionHandler { if (player.getClientSettingsPacket() != null) { smc.write(player.getClientSettingsPacket()); } - if (player.getConnection().getActiveSessionHandler() instanceof ClientPlaySessionHandler) { + if (player.getConnection().getActiveSessionHandler() instanceof ClientPlaySessionHandler clientPlaySessionHandler) { smc.setAutoReading(false); - ((ClientPlaySessionHandler) player.getConnection() - .getActiveSessionHandler()).doSwitch().thenAcceptAsync((unused) -> { - smc.setAutoReading(true); - }, smc.eventLoop()); + clientPlaySessionHandler.doSwitch().thenAcceptAsync((unused) -> smc.setAutoReading(true), smc.eventLoop()); } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java index 52b67f7b5..3d955e903 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java @@ -19,6 +19,8 @@ package com.velocitypowered.proxy.connection.client; import com.velocitypowered.api.event.player.CookieReceiveEvent; import com.velocitypowered.api.event.player.PlayerClientBrandEvent; +import com.velocitypowered.api.event.player.configuration.PlayerFinishConfigurationEvent; +import com.velocitypowered.api.event.player.configuration.PlayerFinishedConfigurationEvent; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; @@ -246,13 +248,11 @@ public class ClientConfigSessionHandler implements MinecraftSessionHandler { smc.write(brandPacket); } - player.getConnection().eventLoop().execute(() -> { + server.getEventManager().fire(new PlayerFinishConfigurationEvent(player, serverConn)).thenAcceptAsync(event -> { player.getConnection().write(FinishedUpdatePacket.INSTANCE); player.getConnection().getChannel().pipeline().get(MinecraftEncoder.class).setState(StateRegistry.PLAY); - }); - - smc.write(FinishedUpdatePacket.INSTANCE); - smc.getChannel().pipeline().get(MinecraftEncoder.class).setState(StateRegistry.PLAY); + server.getEventManager().fireAndForget(new PlayerFinishedConfigurationEvent(player, serverConn)); + }, player.getConnection().eventLoop()); return configSwitchFuture; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java index 2a595b167..a76f054e7 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java @@ -27,6 +27,7 @@ import com.velocitypowered.api.event.player.CookieReceiveEvent; import com.velocitypowered.api.event.player.PlayerChannelRegisterEvent; import com.velocitypowered.api.event.player.PlayerClientBrandEvent; import com.velocitypowered.api.event.player.TabCompleteEvent; +import com.velocitypowered.api.event.player.configuration.PlayerEnterConfigurationEvent; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.proxy.messages.ChannelIdentifier; import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier; @@ -406,6 +407,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { // Complete client switch player.getConnection().setActiveSessionHandler(StateRegistry.CONFIG); VelocityServerConnection serverConnection = player.getConnectedServer(); + server.getEventManager().fireAndForget(new PlayerEnterConfigurationEvent(player, serverConnection)); if (serverConnection != null) { MinecraftConnection smc = serverConnection.ensureConnected(); CompletableFuture.runAsync(() -> { @@ -512,7 +514,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { * @return a future that completes when the switch is complete */ public CompletableFuture doSwitch() { - VelocityServerConnection existingConnection = player.getConnectedServer(); + final VelocityServerConnection existingConnection = player.getConnectedServer(); if (existingConnection != null) { // Shut down the existing server connection. diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index a471e2b53..de828382e 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -37,6 +37,7 @@ import com.velocitypowered.api.event.player.KickedFromServerEvent.ServerKickResu import com.velocitypowered.api.event.player.PlayerModInfoEvent; import com.velocitypowered.api.event.player.PlayerSettingsChangedEvent; import com.velocitypowered.api.event.player.ServerPreConnectEvent; +import com.velocitypowered.api.event.player.configuration.PlayerEnterConfigurationEvent; import com.velocitypowered.api.network.ProtocolState; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.permission.PermissionFunction; @@ -59,8 +60,9 @@ import com.velocitypowered.proxy.adventure.VelocityBossBarImplementation; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation; import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; -import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo; -import com.velocitypowered.proxy.connection.player.resourcepack.ResourcePackHandler; +import com.velocitypowered.proxy.connection.player.bundle.BundleDelimiterHandler; +import com.velocitypowered.proxy.connection.player.resourcepack.VelocityResourcePackInfo; +import com.velocitypowered.proxy.connection.player.resourcepack.handler.ResourcePackHandler; import com.velocitypowered.proxy.connection.util.ConnectionMessages; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl; import com.velocitypowered.proxy.connection.util.VelocityInboundConnection; @@ -806,7 +808,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, }, connection.eventLoop()); } else if (event.getResult() instanceof final Notify res) { if (event.kickedDuringServerConnect() && previousConnection != null) { - sendMessage(Identity.nil(), res.getMessageComponent()); + sendMessage(res.getMessageComponent()); } else { disconnect(res.getMessageComponent()); } @@ -1224,11 +1226,12 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, CompletableFuture.runAsync(() -> { connection.write(StartUpdatePacket.INSTANCE); connection.getChannel().pipeline() - .get(MinecraftEncoder.class).setState(StateRegistry.CONFIG); + .get(MinecraftEncoder.class).setState(StateRegistry.CONFIG); // Make sure we don't send any play packets to the player after update start connection.addPlayPacketQueueHandler(); + server.getEventManager().fireAndForget(new PlayerEnterConfigurationEvent(this, connectionInFlight)); }, connection.eventLoop()).exceptionally((ex) -> { - logger.error("Error switching player connection to config state:", ex); + logger.error("Error switching player connection to config state", ex); return null; }); } @@ -1363,24 +1366,20 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, } switch (status.getStatus()) { - case ALREADY_CONNECTED: - sendMessage(Identity.nil(), ConnectionMessages.ALREADY_CONNECTED); - break; - case CONNECTION_IN_PROGRESS: - sendMessage(Identity.nil(), ConnectionMessages.IN_PROGRESS); - break; - case CONNECTION_CANCELLED: + case ALREADY_CONNECTED -> sendMessage(ConnectionMessages.ALREADY_CONNECTED); + case CONNECTION_IN_PROGRESS -> sendMessage(ConnectionMessages.IN_PROGRESS); + case CONNECTION_CANCELLED -> { // Ignored; the plugin probably already handled this. - break; - case SERVER_DISCONNECTED: - Component reason = status.getReasonComponent() - .orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR); + } + case SERVER_DISCONNECTED -> { + final Component reason = status.getReasonComponent() + .orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR); handleConnectionException(toConnect, - DisconnectPacket.create(reason, getProtocolVersion(), connection.getState()), status.isSafe()); - break; - default: + DisconnectPacket.create(reason, getProtocolVersion(), connection.getState()), status.isSafe()); + } + default -> { // The only remaining value is successful (no need to do anything!) - break; + } } }, connection.eventLoop()).thenApply(Result::isSuccessful); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/BundleDelimiterHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/bundle/BundleDelimiterHandler.java similarity index 95% rename from proxy/src/main/java/com/velocitypowered/proxy/connection/client/BundleDelimiterHandler.java rename to proxy/src/main/java/com/velocitypowered/proxy/connection/player/bundle/BundleDelimiterHandler.java index e92d10587..d5d52ef03 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/BundleDelimiterHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/bundle/BundleDelimiterHandler.java @@ -15,10 +15,11 @@ * along with this program. If not, see . */ -package com.velocitypowered.proxy.connection.client; +package com.velocitypowered.proxy.connection.player.bundle; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; +import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket; import java.util.concurrent.CompletableFuture; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/VelocityResourcePackInfo.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/VelocityResourcePackInfo.java similarity index 98% rename from proxy/src/main/java/com/velocitypowered/proxy/connection/player/VelocityResourcePackInfo.java rename to proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/VelocityResourcePackInfo.java index 67f90fd15..4292710e0 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/VelocityResourcePackInfo.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/VelocityResourcePackInfo.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.velocitypowered.proxy.connection.player; +package com.velocitypowered.proxy.connection.player.resourcepack; import com.google.common.base.Preconditions; import com.velocitypowered.api.proxy.player.ResourcePackInfo; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/Legacy117ResourcePackHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/Legacy117ResourcePackHandler.java similarity index 99% rename from proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/Legacy117ResourcePackHandler.java rename to proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/Legacy117ResourcePackHandler.java index 385bdce4c..a1af567dd 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/Legacy117ResourcePackHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/Legacy117ResourcePackHandler.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.velocitypowered.proxy.connection.player.resourcepack; +package com.velocitypowered.proxy.connection.player.resourcepack.handler; import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent; import com.velocitypowered.proxy.VelocityServer; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/LegacyResourcePackHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/LegacyResourcePackHandler.java similarity index 98% rename from proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/LegacyResourcePackHandler.java rename to proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/LegacyResourcePackHandler.java index b1b325484..53fa2421c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/LegacyResourcePackHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/LegacyResourcePackHandler.java @@ -15,13 +15,14 @@ * along with this program. If not, see . */ -package com.velocitypowered.proxy.connection.player.resourcepack; +package com.velocitypowered.proxy.connection.player.resourcepack.handler; import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.proxy.player.ResourcePackInfo; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; +import com.velocitypowered.proxy.connection.player.resourcepack.ResourcePackResponseBundle; import java.util.ArrayDeque; import java.util.Arrays; import java.util.Collection; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/ModernResourcePackHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/ModernResourcePackHandler.java similarity index 98% rename from proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/ModernResourcePackHandler.java rename to proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/ModernResourcePackHandler.java index 5ba74a273..077ce701d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/ModernResourcePackHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/ModernResourcePackHandler.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.velocitypowered.proxy.connection.player.resourcepack; +package com.velocitypowered.proxy.connection.player.resourcepack.handler; import com.google.common.collect.ListMultimap; import com.google.common.collect.Multimaps; @@ -23,6 +23,7 @@ import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent; import com.velocitypowered.api.proxy.player.ResourcePackInfo; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; +import com.velocitypowered.proxy.connection.player.resourcepack.ResourcePackResponseBundle; import java.util.Arrays; import java.util.Collection; import java.util.LinkedList; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/ResourcePackHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/ResourcePackHandler.java similarity index 97% rename from proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/ResourcePackHandler.java rename to proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/ResourcePackHandler.java index 4e6e72505..e5df9f659 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/ResourcePackHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/ResourcePackHandler.java @@ -15,14 +15,15 @@ * along with this program. If not, see . */ -package com.velocitypowered.proxy.connection.player.resourcepack; +package com.velocitypowered.proxy.connection.player.resourcepack.handler; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.proxy.player.ResourcePackInfo; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; -import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo; +import com.velocitypowered.proxy.connection.player.resourcepack.ResourcePackResponseBundle; +import com.velocitypowered.proxy.connection.player.resourcepack.VelocityResourcePackInfo; import com.velocitypowered.proxy.protocol.packet.ResourcePackRequestPacket; import com.velocitypowered.proxy.protocol.packet.ResourcePackResponsePacket; import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/Connections.java b/proxy/src/main/java/com/velocitypowered/proxy/network/Connections.java index 27ec4ba8b..ca4e6b1b2 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/network/Connections.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/Connections.java @@ -35,7 +35,8 @@ public class Connections { public static final String MINECRAFT_DECODER = "minecraft-decoder"; public static final String MINECRAFT_ENCODER = "minecraft-encoder"; public static final String READ_TIMEOUT = "read-timeout"; - public static final String PLAY_PACKET_QUEUE = "play-packet-queue"; + public static final String PLAY_PACKET_QUEUE_OUTBOUND = "play-packet-queue-outbound"; + public static final String PLAY_PACKET_QUEUE_INBOUND = "play-packet-queue-inbound"; private Connections() { throw new AssertionError(); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftDecoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftDecoder.java index f8362cc09..d75cb46ca 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftDecoder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftDecoder.java @@ -56,8 +56,7 @@ public class MinecraftDecoder extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - if (msg instanceof ByteBuf) { - ByteBuf buf = (ByteBuf) msg; + if (msg instanceof ByteBuf buf) { tryDecode(ctx, buf); } else { ctx.fireChannelRead(msg); @@ -147,4 +146,8 @@ public class MinecraftDecoder extends ChannelInboundHandlerAdapter { this.state = state; this.setProtocolVersion(registry.version); } + + public ProtocolUtils.Direction getDirection() { + return direction; + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/PlayPacketQueueInboundHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/PlayPacketQueueInboundHandler.java new file mode 100644 index 000000000..fe553f76a --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/PlayPacketQueueInboundHandler.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2023 Velocity Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.velocitypowered.proxy.protocol.netty; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.protocol.StateRegistry; +import io.netty.channel.ChannelDuplexHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.util.ReferenceCountUtil; +import io.netty.util.internal.PlatformDependent; +import java.util.Queue; +import org.jetbrains.annotations.NotNull; + +/** + * Queues up any pending PLAY packets while the client is in the CONFIG state. + * + *

Much of the Velocity API (i.e. chat messages) utilize PLAY packets, however the client is + * incapable of receiving these packets during the CONFIG state. Certain events such as the + * ServerPreConnectEvent may be called during this time, and we need to ensure that any API that + * uses these packets will work as expected. + * + *

This handler will queue up any packets that are sent to the client during this time, and send + * them once the client has (re)entered the PLAY state. + */ +public class PlayPacketQueueInboundHandler extends ChannelDuplexHandler { + + private final StateRegistry.PacketRegistry.ProtocolRegistry registry; + private final Queue queue = PlatformDependent.newMpscQueue(); + + /** + * Provides registries for client & server bound packets. + * + * @param version the protocol version + */ + public PlayPacketQueueInboundHandler(ProtocolVersion version, ProtocolUtils.Direction direction) { + this.registry = StateRegistry.CONFIG.getProtocolRegistry(direction, version); + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + if (msg instanceof final MinecraftPacket packet) { + // If the packet exists in the CONFIG state, we want to always + // ensure that it gets handled by the current handler + if (this.registry.containsPacket(packet)) { + ctx.fireChannelRead(msg); + return; + } + } + + // Otherwise, queue the packet + this.queue.offer(msg); + } + + @Override + public void channelInactive(@NotNull ChannelHandlerContext ctx) throws Exception { + this.releaseQueue(ctx, false); + + super.channelInactive(ctx); + } + + @Override + public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { + this.releaseQueue(ctx, ctx.channel().isActive()); + } + + private void releaseQueue(ChannelHandlerContext ctx, boolean active) { + // Handle all the queued packets + Object msg; + while ((msg = this.queue.poll()) != null) { + if (active) { + ctx.fireChannelRead(msg); + } else { + ReferenceCountUtil.release(msg); + } + } + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/PlayPacketQueueHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/PlayPacketQueueOutboundHandler.java similarity index 86% rename from proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/PlayPacketQueueHandler.java rename to proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/PlayPacketQueueOutboundHandler.java index 990985c25..d5764ef6a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/PlayPacketQueueHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/PlayPacketQueueOutboundHandler.java @@ -40,7 +40,7 @@ import org.jetbrains.annotations.NotNull; *

This handler will queue up any packets that are sent to the client during this time, and send * them once the client has (re)entered the PLAY state. */ -public class PlayPacketQueueHandler extends ChannelDuplexHandler { +public class PlayPacketQueueOutboundHandler extends ChannelDuplexHandler { private final StateRegistry.PacketRegistry.ProtocolRegistry registry; private final Queue queue = PlatformDependent.newMpscQueue(); @@ -50,28 +50,26 @@ public class PlayPacketQueueHandler extends ChannelDuplexHandler { * * @param version the protocol version */ - public PlayPacketQueueHandler(ProtocolVersion version, ProtocolUtils.Direction direction) { - this.registry = - StateRegistry.CONFIG.getProtocolRegistry(direction, version); + public PlayPacketQueueOutboundHandler(ProtocolVersion version, ProtocolUtils.Direction direction) { + this.registry = StateRegistry.CONFIG.getProtocolRegistry(direction, version); } @Override - public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) - throws Exception { - if (!(msg instanceof MinecraftPacket)) { + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + if (!(msg instanceof final MinecraftPacket packet)) { ctx.write(msg, promise); return; } // If the packet exists in the CONFIG state, we want to always // ensure that it gets sent out to the client - if (this.registry.containsPacket(((MinecraftPacket) msg))) { + if (this.registry.containsPacket(packet)) { ctx.write(msg, promise); return; } // Otherwise, queue the packet - this.queue.offer((MinecraftPacket) msg); + this.queue.offer(packet); } @Override @@ -87,10 +85,6 @@ public class PlayPacketQueueHandler extends ChannelDuplexHandler { } private void releaseQueue(ChannelHandlerContext ctx, boolean active) { - if (this.queue.isEmpty()) { - return; - } - // Send out all the queued packets MinecraftPacket packet; while ((packet = this.queue.poll()) != null) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ResourcePackRequestPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ResourcePackRequestPacket.java index 07f9f776a..a0f86aed1 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ResourcePackRequestPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ResourcePackRequestPacket.java @@ -21,7 +21,7 @@ import com.google.common.base.Preconditions; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.proxy.player.ResourcePackInfo; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; -import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo; +import com.velocitypowered.proxy.connection.player.resourcepack.VelocityResourcePackInfo; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;