From 287a9b28f1f3be0ae794380aec0b8b2af94c8282 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sat, 18 Oct 2025 22:05:18 -0400 Subject: [PATCH] Make packets immutable --- .../VelocityBossBarImplementation.java | 2 +- .../command/builtin/VelocityCommand.java | 15 +- .../backend/BackendPlaySessionHandler.java | 2 +- .../backend/ConfigSessionHandler.java | 2 +- .../backend/LoginSessionHandler.java | 2 +- .../backend/VelocityServerConnection.java | 22 +- .../connection/client/AuthSessionHandler.java | 7 +- .../client/ClientPlaySessionHandler.java | 52 +- .../client/ClientSettingsWrapper.java | 18 +- .../connection/client/ConnectedPlayer.java | 73 +- .../client/HandshakeSessionHandler.java | 30 +- .../client/InitialLoginSessionHandler.java | 11 +- .../forge/legacy/LegacyForgeUtil.java | 8 +- .../handler/ResourcePackHandler.java | 20 +- .../proxy/protocol/MinecraftPacket.java | 34 +- .../proxy/protocol/PacketCodec.java | 27 + .../proxy/protocol/PacketDecoder.java | 67 ++ .../proxy/protocol/PacketEncoder.java | 54 ++ .../proxy/protocol/StateRegistry.java | 222 ++--- .../protocol/netty/LegacyPingDecoder.java | 2 +- .../protocol/netty/MinecraftDecoder.java | 47 +- .../protocol/netty/MinecraftEncoder.java | 20 +- .../netty/MinecraftVarintFrameDecoder.java | 23 +- .../packet/AvailableCommandsPacket.java | 302 +++---- .../proxy/protocol/packet/BossBarPacket.java | 295 +++---- .../packet/BundleDelimiterPacket.java | 24 +- .../protocol/packet/ClientSettingsPacket.java | 252 +++--- .../ClientboundCookieRequestPacket.java | 36 +- .../packet/ClientboundSoundEntityPacket.java | 88 +- .../packet/ClientboundStopSoundPacket.java | 105 +-- .../packet/ClientboundStoreCookiePacket.java | 50 +- .../protocol/packet/DialogClearPacket.java | 26 +- .../protocol/packet/DialogShowPacket.java | 68 +- .../protocol/packet/DisconnectPacket.java | 60 +- .../packet/EncryptionRequestPacket.java | 117 ++- .../packet/EncryptionResponsePacket.java | 169 ++-- .../protocol/packet/HandshakePacket.java | 148 ++-- .../packet/HeaderAndFooterPacket.java | 53 +- .../proxy/protocol/packet/JoinGamePacket.java | 794 +++++++++--------- .../protocol/packet/KeepAlivePacket.java | 59 +- .../packet/LegacyHandshakePacket.java | 26 +- .../protocol/packet/LegacyPingPacket.java | 31 +- .../packet/LegacyPlayerListItemPacket.java | 259 +++--- .../packet/LoginAcknowledgedPacket.java | 37 +- .../packet/LoginPluginMessagePacket.java | 73 +- .../packet/LoginPluginResponsePacket.java | 78 +- .../protocol/packet/PingIdentifyPacket.java | 34 +- .../protocol/packet/PluginMessagePacket.java | 101 +-- .../packet/RemovePlayerInfoPacket.java | 60 +- .../packet/RemoveResourcePackPacket.java | 54 +- .../packet/ResourcePackRequestPacket.java | 137 ++- .../packet/ResourcePackResponsePacket.java | 87 +- .../proxy/protocol/packet/RespawnPacket.java | 347 ++++---- .../protocol/packet/ServerDataPacket.java | 139 ++- .../protocol/packet/ServerLoginPacket.java | 251 +++--- .../packet/ServerLoginSuccessPacket.java | 151 ++-- .../ServerboundCookieResponsePacket.java | 57 +- .../ServerboundCustomClickActionPacket.java | 78 +- .../protocol/packet/SetCompressionPacket.java | 40 +- .../protocol/packet/StatusPingPacket.java | 45 +- .../protocol/packet/StatusRequestPacket.java | 34 +- .../protocol/packet/StatusResponsePacket.java | 52 +- .../packet/TabCompleteRequestPacket.java | 163 ++-- .../packet/TabCompleteResponsePacket.java | 146 ++-- .../proxy/protocol/packet/TransferPacket.java | 40 +- .../packet/UpsertPlayerInfoPacket.java | 125 ++- .../chat/ChatAcknowledgementPacket.java | 51 +- .../chat/PlayerChatCompletionPacket.java | 51 +- .../packet/chat/SystemChatPacket.java | 78 +- .../packet/chat/keyed/KeyedChatBuilder.java | 10 +- .../chat/keyed/KeyedPlayerChatPacket.java | 200 +++-- .../chat/keyed/KeyedPlayerCommandPacket.java | 239 +++--- .../packet/chat/legacy/LegacyChatBuilder.java | 4 +- .../packet/chat/legacy/LegacyChatPacket.java | 114 +-- .../chat/session/SessionChatBuilder.java | 23 +- .../chat/session/SessionPlayerChatPacket.java | 91 +- .../session/SessionPlayerCommandPacket.java | 71 +- .../session/UnsignedPlayerCommandPacket.java | 29 +- .../packet/config/ActiveFeaturesPacket.java | 42 +- .../ClientboundCustomReportDetailsPacket.java | 55 +- .../config/ClientboundServerLinksPacket.java | 57 +- .../config/CodeOfConductAcceptPacket.java | 24 +- .../packet/config/CodeOfConductPacket.java | 77 +- .../packet/config/FinishedUpdatePacket.java | 38 +- .../packet/config/KnownPacksPacket.java | 69 +- .../packet/config/RegistrySyncPacket.java | 38 +- .../packet/config/StartUpdatePacket.java | 38 +- .../packet/config/TagsUpdatePacket.java | 112 +-- .../packet/title/GenericTitlePacket.java | 96 ++- .../packet/title/LegacyTitlePacket.java | 113 ++- .../packet/title/TitleActionbarPacket.java | 36 +- .../packet/title/TitleClearPacket.java | 39 +- .../packet/title/TitleSubtitlePacket.java | 36 +- .../packet/title/TitleTextPacket.java | 36 +- .../packet/title/TitleTimesPacket.java | 56 +- .../protocol/util/DeferredByteBufHolder.java | 153 ---- .../proxy/server/PingSessionHandler.java | 10 +- .../proxy/protocol/PacketRegistryTest.java | 63 +- 98 files changed, 4132 insertions(+), 4068 deletions(-) create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/protocol/PacketCodec.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/protocol/PacketDecoder.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/protocol/PacketEncoder.java delete mode 100644 proxy/src/main/java/com/velocitypowered/proxy/protocol/util/DeferredByteBufHolder.java diff --git a/proxy/src/main/java/com/velocitypowered/proxy/adventure/VelocityBossBarImplementation.java b/proxy/src/main/java/com/velocitypowered/proxy/adventure/VelocityBossBarImplementation.java index e2451188a..a6acc6484 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/adventure/VelocityBossBarImplementation.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/adventure/VelocityBossBarImplementation.java @@ -69,7 +69,7 @@ public final class VelocityBossBarImplementation implements BossBar.Listener, public boolean viewerRemove(final ConnectedPlayer viewer) { if (this.viewers.remove(viewer)) { - viewer.getBossBarManager().remove(this, BossBarPacket.createRemovePacket(this.id, this.bar)); + viewer.getBossBarManager().remove(this, BossBarPacket.createRemovePacket(this.id)); return true; } return false; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java index 6b233b874..b768555c8 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java @@ -55,6 +55,7 @@ import java.util.function.Consumer; import java.util.function.Predicate; import java.util.stream.Collectors; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.JoinConfiguration; import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.TranslatableComponent; import net.kyori.adventure.text.event.ClickEvent; @@ -70,6 +71,7 @@ import org.apache.logging.log4j.Logger; */ public final class VelocityCommand { private static final String USAGE = "/velocity <%s>"; + private static final JoinConfiguration COMMA_JOINER = JoinConfiguration.commas(true); @SuppressWarnings("checkstyle:MissingJavadocMethod") public static BrigadierCommand create(final VelocityServer server) { @@ -208,19 +210,14 @@ public final class VelocityCommand { return Command.SINGLE_SUCCESS; } - final TextComponent.Builder listBuilder = Component.text(); - for (int i = 0; i < pluginCount; i++) { - final PluginContainer plugin = plugins.get(i); - listBuilder.append(componentForPlugin(plugin.getDescription())); - if (i + 1 < pluginCount) { - listBuilder.append(Component.text(", ")); - } - } + final Component pluginListComponents = plugins.stream() + .map(container -> componentForPlugin(container.getDescription())) + .collect(Component.toComponent(Component.text(", "))); final TranslatableComponent output = Component.translatable() .key("velocity.command.plugins-list") .color(NamedTextColor.YELLOW) - .arguments(listBuilder.build()) + .arguments(pluginListComponents) .build(); source.sendMessage(output); return Command.SINGLE_SUCCESS; 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 ed30057a4..1d5219c26 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 @@ -420,7 +420,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { @Override public boolean handle(ClientboundStoreCookiePacket packet) { server.getEventManager() - .fire(new CookieStoreEvent(serverConn.getPlayer(), packet.getKey(), packet.getPayload())) + .fire(new CookieStoreEvent(serverConn.getPlayer(), packet.key(), packet.payload())) .thenAcceptAsync(event -> { if (event.getResult().isAllowed()) { final Key resultedKey = event.getResult().getKey() == null 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 90bb1a301..baa5e4add 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 @@ -328,7 +328,7 @@ public class ConfigSessionHandler implements MinecraftSessionHandler { @Override public boolean handle(ClientboundStoreCookiePacket packet) { server.getEventManager() - .fire(new CookieStoreEvent(serverConn.getPlayer(), packet.getKey(), packet.getPayload())) + .fire(new CookieStoreEvent(serverConn.getPlayer(), packet.key(), packet.payload())) .thenAcceptAsync(event -> { if (event.getResult().isAllowed()) { final Key resultedKey = event.getResult().getKey() == null 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 612e9c25a..9d3690b1d 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 @@ -157,7 +157,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler { if (smc.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_20_2)) { smc.setActiveSessionHandler(StateRegistry.PLAY, new TransitionSessionHandler(server, serverConn, resultFuture)); } else { - smc.write(new LoginAcknowledgedPacket()); + smc.write(LoginAcknowledgedPacket.INSTANCE); smc.setActiveSessionHandler(StateRegistry.CONFIG, new ConfigSessionHandler(server, serverConn, resultFuture)); ConnectedPlayer player = serverConn.getPlayer(); if (player.getClientSettingsPacket() != null) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java index 71ebd7bc7..eac9bcf68 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java @@ -170,26 +170,26 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, .orElseGet(() -> registeredServer.getServerInfo().getAddress()) .getHostString(); - HandshakePacket handshake = new HandshakePacket(); - handshake.setIntent(HandshakeIntent.LOGIN); - handshake.setProtocolVersion(protocolVersion); + String serverAddress; if (forwardingMode == PlayerInfoForwarding.LEGACY) { - handshake.setServerAddress(createLegacyForwardingAddress()); + serverAddress = createLegacyForwardingAddress(); } else if (forwardingMode == PlayerInfoForwarding.BUNGEEGUARD) { byte[] secret = server.getConfiguration().getForwardingSecret(); - handshake.setServerAddress(createBungeeGuardForwardingAddress(secret)); + serverAddress = createBungeeGuardForwardingAddress(secret); } else if (proxyPlayer.getConnection().getType() == ConnectionTypes.LEGACY_FORGE) { - handshake.setServerAddress(playerVhost + HANDSHAKE_HOSTNAME_TOKEN); + serverAddress = playerVhost + HANDSHAKE_HOSTNAME_TOKEN; } else if (proxyPlayer.getConnection().getType() instanceof ModernForgeConnectionType) { - handshake.setServerAddress(playerVhost + ((ModernForgeConnectionType) proxyPlayer - .getConnection().getType()).getModernToken()); + serverAddress = playerVhost + ((ModernForgeConnectionType) proxyPlayer + .getConnection().getType()).getModernToken(); } else { - handshake.setServerAddress(playerVhost); + serverAddress = playerVhost; } - handshake.setPort(proxyPlayer.getVirtualHost() + int port = proxyPlayer.getVirtualHost() .orElseGet(() -> registeredServer.getServerInfo().getAddress()) - .getPort()); + .getPort(); + + HandshakePacket handshake = new HandshakePacket(protocolVersion, serverAddress, port, HandshakeIntent.LOGIN); mc.delayedWrite(handshake); mc.setProtocolVersion(protocolVersion); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/AuthSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/AuthSessionHandler.java index 0aea8d778..60c7ac7d2 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/AuthSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/AuthSessionHandler.java @@ -230,10 +230,9 @@ public class AuthSessionHandler implements MinecraftSessionHandler { return; } - ServerLoginSuccessPacket success = new ServerLoginSuccessPacket(); - success.setUsername(player.getUsername()); - success.setProperties(player.getGameProfileProperties()); - success.setUuid(player.getUniqueId()); + ServerLoginSuccessPacket success = new ServerLoginSuccessPacket( + player.getUniqueId(), player.getUsername(), player.getGameProfileProperties() + ); mcConnection.write(success); loginState = State.SUCCESS_SENT; 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 17eb708b3..c9ced6274 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 @@ -387,8 +387,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { public boolean handle(ResourcePackResponsePacket packet) { return player.resourcePackHandler().onResourcePackResponse( new ResourcePackResponseBundle(packet.getId(), - packet.getHash(), - packet.getStatus())); + packet.hash(), + packet.status())); } @Override @@ -428,7 +428,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { @Override public boolean handle(ServerboundCookieResponsePacket packet) { server.getEventManager() - .fire(new CookieReceiveEvent(player, packet.getKey(), packet.getPayload())) + .fire(new CookieReceiveEvent(player, packet.key(), packet.payload())) .thenAcceptAsync(event -> { if (event.getResult().isAllowed()) { final VelocityServerConnection serverConnection = player.getConnectedServer(); @@ -586,10 +586,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { // Remove previous boss bars. These don't get cleared when sending JoinGame (up until 1.20.2), // thus the need to track them. for (UUID serverBossBar : serverBossBars) { - BossBarPacket deletePacket = new BossBarPacket(); - deletePacket.setUuid(serverBossBar); - deletePacket.setAction(BossBarPacket.REMOVE); - player.getConnection().delayedWrite(deletePacket); + player.getConnection().delayedWrite(BossBarPacket.createRemovePacket(serverBossBar)); } serverBossBars.clear(); } @@ -615,7 +612,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { // Clear any title from the previous server. if (player.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_8)) { player.getConnection().delayedWrite( - GenericTitlePacket.constructTitlePacket(GenericTitlePacket.ActionType.RESET, + GenericTitlePacket.createClearTitlePacket(GenericTitlePacket.ActionType.RESET, player.getProtocolVersion())); } @@ -634,14 +631,15 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { // Most notably, by having the client accept the join game packet, we can work around the need // to perform entity ID rewrites, eliminating potential issues from rewriting packets and // improving compatibility with mods. - final RespawnPacket respawn = RespawnPacket.fromJoinGame(joinGame); - + int dim = joinGame.getDimension(); if (player.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_16)) { // Before Minecraft 1.16, we could not switch to the same dimension without sending an // additional respawn. On older versions of Minecraft this forces the client to perform // garbage collection which adds additional latency. - joinGame.setDimension(joinGame.getDimension() == 0 ? -1 : 0); + dim = joinGame.getDimension() == 0 ? -1 : 0; } + final RespawnPacket respawn = RespawnPacket.fromJoinGame(joinGame, dim); + player.getConnection().delayedWrite(joinGame); player.getConnection().delayedWrite(respawn); } @@ -655,8 +653,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { player.getConnection().delayedWrite(joinGame); // Send a respawn packet in a different dimension. - final RespawnPacket fakeSwitchPacket = RespawnPacket.fromJoinGame(joinGame); - fakeSwitchPacket.setDimension(joinGame.getDimension() == 0 ? -1 : 0); + final RespawnPacket fakeSwitchPacket = RespawnPacket.fromJoinGame(joinGame, joinGame.getDimension() == 0 ? -1 : 0); player.getConnection().delayedWrite(fakeSwitchPacket); // Now send a respawn packet in the correct dimension. @@ -728,11 +725,9 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { offers.add(new Offer(offer, tooltip)); } - TabCompleteResponsePacket resp = new TabCompleteResponsePacket(); - resp.setTransactionId(packet.getTransactionId()); - resp.setStart(startPos + 1); - resp.setLength(packet.getCommand().length() - startPos - 1); - resp.getOffers().addAll(offers); + TabCompleteResponsePacket resp = new TabCompleteResponsePacket( + packet.transactionId(), startPos + 1, packet.getCommand().length() - startPos - 1, offers + ); player.getConnection().write(resp); } }, player.getConnection().eventLoop()).exceptionally((ex) -> { @@ -778,6 +773,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { .thenAcceptAsync(offers -> { boolean legacy = player.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_13); + List extendedOffers = new ArrayList<>(response.offers()); try { for (Suggestion suggestion : offers.getList()) { String offer = suggestion.getText(); @@ -791,10 +787,10 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { } else if (suggestion.getTooltip() != null) { tooltip = new ComponentHolder(player.getProtocolVersion(), Component.text(suggestion.getTooltip().getString())); } - response.getOffers().add(new Offer(offer, tooltip)); + extendedOffers.add(new Offer(offer, tooltip)); } - response.getOffers().sort(null); - player.getConnection().write(response); + extendedOffers.sort(null); + player.getConnection().write(response.withOffers(extendedOffers)); } catch (Exception e) { logger.error("Unable to provide tab list completions for {} for command '{}'", player.getUsername(), command, @@ -811,17 +807,17 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { private void finishRegularTabComplete(TabCompleteRequestPacket request, TabCompleteResponsePacket response) { - List offers = new ArrayList<>(); - for (Offer offer : response.getOffers()) { - offers.add(offer.getText()); + List textOffers = new ArrayList<>(); + for (Offer offer : response.offers()) { + textOffers.add(offer.getText()); } - server.getEventManager().fire(new TabCompleteEvent(player, request.getCommand(), offers)) + server.getEventManager().fire(new TabCompleteEvent(player, request.getCommand(), textOffers)) .thenAcceptAsync(e -> { - response.getOffers().clear(); + List newOffers = new ArrayList<>(); for (String s : e.getSuggestions()) { - response.getOffers().add(new Offer(s)); + newOffers.add(new Offer(s)); } - player.getConnection().write(response); + player.getConnection().write(response.withOffers(newOffers)); }, player.getConnection().eventLoop()).exceptionally((ex) -> { logger.error( "Exception while finishing regular tab completion," diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientSettingsWrapper.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientSettingsWrapper.java index f8d3107cc..e0726f140 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientSettingsWrapper.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientSettingsWrapper.java @@ -38,25 +38,25 @@ public class ClientSettingsWrapper implements PlayerSettings { ClientSettingsWrapper(ClientSettingsPacket settings) { this.settings = settings; - this.parts = new SkinParts((byte) settings.getSkinParts()); + this.parts = new SkinParts((byte) settings.skinParts()); } @Override public Locale getLocale() { if (locale == null) { - locale = Locale.forLanguageTag(settings.getLocale().replaceAll("_", "-")); + locale = Locale.forLanguageTag(settings.locale().replaceAll("_", "-")); } return locale; } @Override public byte getViewDistance() { - return settings.getViewDistance(); + return settings.viewDistance(); } @Override public ChatMode getChatMode() { - return switch (settings.getChatVisibility()) { + return switch (settings.chatVisibility()) { case 1 -> ChatMode.COMMANDS_ONLY; case 2 -> ChatMode.HIDDEN; default -> ChatMode.SHOWN; @@ -65,7 +65,7 @@ public class ClientSettingsWrapper implements PlayerSettings { @Override public boolean hasChatColors() { - return settings.isChatColors(); + return settings.chatColors(); } @Override @@ -75,22 +75,22 @@ public class ClientSettingsWrapper implements PlayerSettings { @Override public MainHand getMainHand() { - return settings.getMainHand() == 1 ? MainHand.RIGHT : MainHand.LEFT; + return settings.mainHand() == 1 ? MainHand.RIGHT : MainHand.LEFT; } @Override public boolean isClientListingAllowed() { - return settings.isClientListingAllowed(); + return settings.clientListingAllowed(); } @Override public boolean isTextFilteringEnabled() { - return settings.isTextFilteringEnabled(); + return settings.textFilteringEnabled(); } @Override public ParticleStatus getParticleStatus() { - return switch (settings.getParticleStatus()) { + return switch (settings.particleStatus()) { case 1 -> ParticleStatus.DECREASED; case 2 -> ParticleStatus.MINIMAL; default -> ParticleStatus.ALL; 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 17f4fb5ce..9dc6c67ec 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 @@ -451,9 +451,9 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, ProtocolVersion playerVersion = getProtocolVersion(); if (playerVersion.noLessThan(ProtocolVersion.MINECRAFT_1_11)) { // Use the title packet instead. - GenericTitlePacket pkt = GenericTitlePacket.constructTitlePacket( - GenericTitlePacket.ActionType.SET_ACTION_BAR, playerVersion); - pkt.setComponent(new ComponentHolder(playerVersion, translated)); + GenericTitlePacket pkt = GenericTitlePacket.createComponentTitlePacket( + GenericTitlePacket.ActionType.SET_ACTION_BAR, + new ComponentHolder(playerVersion, translated), playerVersion); connection.write(pkt); } else { // Due to issues with action bar packets, we'll need to convert the text message into a @@ -461,9 +461,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, JsonObject object = new JsonObject(); object.addProperty("text", LegacyComponentSerializer.legacySection() .serialize(translated)); - LegacyChatPacket legacyChat = new LegacyChatPacket(); - legacyChat.setMessage(object.toString()); - legacyChat.setType(LegacyChatPacket.GAME_INFO_TYPE); + LegacyChatPacket legacyChat = new LegacyChatPacket(object.toString(), LegacyChatPacket.GAME_INFO_TYPE, null); connection.write(legacyChat); } } @@ -504,26 +502,26 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, @Override public void showTitle(net.kyori.adventure.title.@NonNull Title title) { if (this.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_8)) { - GenericTitlePacket timesPkt = GenericTitlePacket.constructTitlePacket( - GenericTitlePacket.ActionType.SET_TIMES, this.getProtocolVersion()); net.kyori.adventure.title.Title.Times times = title.times(); if (times != null) { - timesPkt.setFadeIn((int) DurationUtils.toTicks(times.fadeIn())); - timesPkt.setStay((int) DurationUtils.toTicks(times.stay())); - timesPkt.setFadeOut((int) DurationUtils.toTicks(times.fadeOut())); + GenericTitlePacket timesPkt = GenericTitlePacket.createTimesTitlePacket( + (int) DurationUtils.toTicks(times.fadeIn()), + (int) DurationUtils.toTicks(times.stay()), + (int) DurationUtils.toTicks(times.fadeOut()), + this.getProtocolVersion()); + connection.delayedWrite(timesPkt); } - connection.delayedWrite(timesPkt); - GenericTitlePacket subtitlePkt = GenericTitlePacket.constructTitlePacket( - GenericTitlePacket.ActionType.SET_SUBTITLE, this.getProtocolVersion()); - subtitlePkt.setComponent(new ComponentHolder( - this.getProtocolVersion(), translateMessage(title.subtitle()))); + GenericTitlePacket subtitlePkt = GenericTitlePacket.createComponentTitlePacket( + GenericTitlePacket.ActionType.SET_SUBTITLE, + new ComponentHolder(this.getProtocolVersion(), translateMessage(title.subtitle())), + this.getProtocolVersion()); connection.delayedWrite(subtitlePkt); - GenericTitlePacket titlePkt = GenericTitlePacket.constructTitlePacket( - GenericTitlePacket.ActionType.SET_TITLE, this.getProtocolVersion()); - titlePkt.setComponent(new ComponentHolder( - this.getProtocolVersion(), translateMessage(title.title()))); + GenericTitlePacket titlePkt = GenericTitlePacket.createComponentTitlePacket( + GenericTitlePacket.ActionType.SET_TITLE, + new ComponentHolder(this.getProtocolVersion(), translateMessage(title.title())), + this.getProtocolVersion()); connection.delayedWrite(titlePkt); connection.flush(); @@ -545,24 +543,24 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, } if (part == TitlePart.TITLE) { - GenericTitlePacket titlePkt = GenericTitlePacket.constructTitlePacket( - GenericTitlePacket.ActionType.SET_TITLE, this.getProtocolVersion()); - titlePkt.setComponent(new ComponentHolder( - this.getProtocolVersion(), translateMessage((Component) value))); + GenericTitlePacket titlePkt = GenericTitlePacket.createComponentTitlePacket( + GenericTitlePacket.ActionType.SET_TITLE, + new ComponentHolder(this.getProtocolVersion(), translateMessage((Component) value)), + this.getProtocolVersion()); connection.write(titlePkt); } else if (part == TitlePart.SUBTITLE) { - GenericTitlePacket titlePkt = GenericTitlePacket.constructTitlePacket( - GenericTitlePacket.ActionType.SET_SUBTITLE, this.getProtocolVersion()); - titlePkt.setComponent(new ComponentHolder( - this.getProtocolVersion(), translateMessage((Component) value))); + GenericTitlePacket titlePkt = GenericTitlePacket.createComponentTitlePacket( + GenericTitlePacket.ActionType.SET_SUBTITLE, + new ComponentHolder(this.getProtocolVersion(), translateMessage((Component) value)), + this.getProtocolVersion()); connection.write(titlePkt); } else if (part == TitlePart.TIMES) { Times times = (Times) value; - GenericTitlePacket timesPkt = GenericTitlePacket.constructTitlePacket( - GenericTitlePacket.ActionType.SET_TIMES, this.getProtocolVersion()); - timesPkt.setFadeIn((int) DurationUtils.toTicks(times.fadeIn())); - timesPkt.setStay((int) DurationUtils.toTicks(times.stay())); - timesPkt.setFadeOut((int) DurationUtils.toTicks(times.fadeOut())); + GenericTitlePacket timesPkt = GenericTitlePacket.createTimesTitlePacket( + (int) DurationUtils.toTicks(times.fadeIn()), + (int) DurationUtils.toTicks(times.stay()), + (int) DurationUtils.toTicks(times.fadeOut()), + this.getProtocolVersion()); connection.write(timesPkt); } else { throw new IllegalArgumentException("Title part " + part + " is not valid"); @@ -572,7 +570,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, @Override public void clearTitle() { if (this.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_8)) { - connection.write(GenericTitlePacket.constructTitlePacket( + connection.write(GenericTitlePacket.createClearTitlePacket( GenericTitlePacket.ActionType.HIDE, this.getProtocolVersion())); } } @@ -580,7 +578,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, @Override public void resetTitle() { if (this.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_8)) { - connection.write(GenericTitlePacket.constructTitlePacket( + connection.write(GenericTitlePacket.createClearTitlePacket( GenericTitlePacket.ActionType.RESET, this.getProtocolVersion())); } } @@ -1262,7 +1260,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, @Override public void clearResourcePacks() { if (this.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) { - connection.write(new RemoveResourcePackPacket()); + connection.write(new RemoveResourcePackPacket(null)); this.resourcePackHandler.clearAppliedResourcePacks(); } } @@ -1333,8 +1331,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, public void sendKeepAlive() { if (connection.getState() == StateRegistry.PLAY || connection.getState() == StateRegistry.CONFIG) { - KeepAlivePacket keepAlive = new KeepAlivePacket(); - keepAlive.setRandomId(ThreadLocalRandom.current().nextLong()); + KeepAlivePacket keepAlive = new KeepAlivePacket(ThreadLocalRandom.current().nextLong()); connection.write(keepAlive); } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/HandshakeSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/HandshakeSessionHandler.java index 80260d2b4..ab6d948a8 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/HandshakeSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/HandshakeSessionHandler.java @@ -87,19 +87,19 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler { @Override public boolean handle(final HandshakePacket handshake) { - final StateRegistry nextState = getStateForProtocol(handshake.getNextStatus()); + final StateRegistry nextState = getStateForProtocol(handshake.nextStatus()); if (nextState == null) { - LOGGER.error("{} provided invalid protocol {}", this, handshake.getNextStatus()); + LOGGER.error("{} provided invalid protocol {}", this, handshake.nextStatus()); connection.close(true); } else { final InitialInboundConnection ic = new InitialInboundConnection(connection, - cleanVhost(handshake.getServerAddress()), handshake); - if (handshake.getIntent() == HandshakeIntent.TRANSFER + cleanVhost(handshake.serverAddress()), handshake); + if (handshake.intent() == HandshakeIntent.TRANSFER && !server.getConfiguration().isAcceptTransfers()) { ic.disconnect(Component.translatable("multiplayer.disconnect.transfers_disabled")); return true; } - connection.setProtocolVersion(handshake.getProtocolVersion()); + connection.setProtocolVersion(handshake.protocolVersion()); connection.setAssociation(ic); switch (nextState) { @@ -124,7 +124,7 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler { } private void handleLogin(HandshakePacket handshake, InitialInboundConnection ic) { - if (!handshake.getProtocolVersion().isSupported()) { + if (!handshake.protocolVersion().isSupported()) { // Bump connection into correct protocol state so that we can send the disconnect packet. connection.setState(StateRegistry.LOGIN); ic.disconnectQuietly(Component.translatable() @@ -147,7 +147,7 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler { // If the proxy is configured for modern forwarding, we must deny connections from 1.12.2 // and lower, otherwise IP information will never get forwarded. if (server.getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN - && handshake.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_13)) { + && handshake.protocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_13)) { // Bump connection into correct protocol state so that we can send the disconnect packet. connection.setState(StateRegistry.LOGIN); ic.disconnectQuietly( @@ -157,21 +157,23 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler { final LoginInboundConnection lic = new LoginInboundConnection(ic); server.getEventManager().fireAndForget( - new ConnectionHandshakeEvent(lic, handshake.getIntent())); + new ConnectionHandshakeEvent(lic, handshake.intent())); connection.setActiveSessionHandler(StateRegistry.LOGIN, new InitialLoginSessionHandler(server, connection, lic)); } private ConnectionType getHandshakeConnectionType(HandshakePacket handshake) { - if (handshake.getServerAddress().contains(ModernForgeConstants.MODERN_FORGE_TOKEN) - && handshake.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) { - return new ModernForgeConnectionType(handshake.getServerAddress()); + final String serverAddress = handshake.serverAddress(); + final ProtocolVersion protocolVersion = handshake.protocolVersion(); + if (serverAddress.contains(ModernForgeConstants.MODERN_FORGE_TOKEN) + && protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) { + return new ModernForgeConnectionType(serverAddress); } // Determine if we're using Forge (1.8 to 1.12, may not be the case in 1.13). - if (handshake.getServerAddress().endsWith(LegacyForgeConstants.HANDSHAKE_HOSTNAME_TOKEN) - && handshake.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_13)) { + if (serverAddress.endsWith(LegacyForgeConstants.HANDSHAKE_HOSTNAME_TOKEN) + && protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_13)) { return ConnectionTypes.LEGACY_FORGE; - } else if (handshake.getProtocolVersion().noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6)) { + } else if (protocolVersion.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6)) { // 1.7 Forge will not notify us during handshake. UNDETERMINED will listen for incoming // forge handshake attempts. Also sends a reset handshake packet on every transition. return ConnectionTypes.UNDETERMINED_17; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java index 00187d347..4cb81fe9f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java @@ -91,7 +91,7 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler { public boolean handle(ServerLoginPacket packet) { assertState(LoginState.LOGIN_PACKET_EXPECTED); this.currentState = LoginState.LOGIN_PACKET_RECEIVED; - IdentifiedKey playerKey = packet.getPlayerKey(); + IdentifiedKey playerKey = packet.playerKey(); if (playerKey != null) { if (playerKey.hasExpired()) { inbound.disconnect( @@ -102,7 +102,7 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler { boolean isKeyValid; if (playerKey.getKeyRevision() == IdentifiedKey.Revision.LINKED_V2 && playerKey instanceof final IdentifiedKeyImpl keyImpl) { - isKeyValid = keyImpl.internalAddHolder(packet.getHolderUuid()); + isKeyValid = keyImpl.internalAddHolder(packet.holderUuid()); } else { isKeyValid = playerKey.isSignatureValid(); } @@ -120,7 +120,7 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler { inbound.setPlayerKey(playerKey); this.login = packet; - final PreLoginEvent event = new PreLoginEvent(inbound, login.getUsername(), login.getHolderUuid()); + final PreLoginEvent event = new PreLoginEvent(inbound, login.getUsername(), login.holderUuid()); server.getEventManager().fire(event).thenRunAsync(() -> { if (mcConnection.isClosed()) { // The player was disconnected @@ -289,9 +289,8 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler { byte[] verify = new byte[4]; ThreadLocalRandom.current().nextBytes(verify); - EncryptionRequestPacket request = new EncryptionRequestPacket(); - request.setPublicKey(server.getServerKeyPair().getPublic().getEncoded()); - request.setVerifyToken(verify); + EncryptionRequestPacket request = new EncryptionRequestPacket("", + server.getServerKeyPair().getPublic().getEncoded(), verify, true); return request; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeUtil.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeUtil.java index 854a52d74..7b065d23a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeUtil.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeUtil.java @@ -84,9 +84,9 @@ class LegacyForgeUtil { * @return A copy of the reset packet */ static PluginMessagePacket resetPacket() { - PluginMessagePacket msg = new PluginMessagePacket(); - msg.setChannel(FORGE_LEGACY_HANDSHAKE_CHANNEL); - msg.replace(Unpooled.wrappedBuffer(FORGE_LEGACY_HANDSHAKE_RESET_DATA.clone())); - return msg; + return new PluginMessagePacket( + FORGE_LEGACY_HANDSHAKE_CHANNEL, + Unpooled.wrappedBuffer(FORGE_LEGACY_HANDSHAKE_RESET_DATA.clone()) + ); } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/ResourcePackHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/ResourcePackHandler.java index b38438853..c99585fd2 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/ResourcePackHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/ResourcePackHandler.java @@ -101,17 +101,15 @@ public abstract sealed class ResourcePackHandler } protected void sendResourcePackRequestPacket(final @NotNull ResourcePackInfo queued) { - final ResourcePackRequestPacket request = new ResourcePackRequestPacket(); - request.setId(queued.getId()); - request.setUrl(queued.getUrl()); - if (queued.getHash() != null) { - request.setHash(ByteBufUtil.hexDump(queued.getHash())); - } else { - request.setHash(""); - } - request.setRequired(queued.getShouldForce()); - request.setPrompt(queued.getPrompt() == null ? null : - new ComponentHolder(player.getProtocolVersion(), player.translateMessage(queued.getPrompt()))); + final String hash = queued.getHash() != null ? ByteBufUtil.hexDump(queued.getHash()) : ""; + final ComponentHolder prompt = queued.getPrompt() == null ? null : + new ComponentHolder(player.getProtocolVersion(), player.translateMessage(queued.getPrompt())); + final ResourcePackRequestPacket request = new ResourcePackRequestPacket( + queued.getId(), + queued.getUrl(), + hash, + queued.getShouldForce(), + prompt); player.getConnection().write(request); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/MinecraftPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/MinecraftPacket.java index 1722f42bf..6f3a8a078 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/MinecraftPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/MinecraftPacket.java @@ -17,33 +17,23 @@ package com.velocitypowered.proxy.protocol; -import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; -import io.netty.buffer.ByteBuf; /** - * Represents a Minecraft packet. + * Represents a Minecraft packet. Packets are immutable data holders that use separate + * {@link PacketCodec} implementations for encoding and decoding. + * + * @see PacketCodec + * @see PacketEncoder + * @see PacketDecoder */ public interface MinecraftPacket { - void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion); - - void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion); - + /** + * Handles this packet using the visitor pattern. + * + * @param handler the session handler + * @return true if the packet was handled + */ boolean handle(MinecraftSessionHandler handler); - - default int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion version) { - return -1; - } - - default int decodeExpectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion version) { - return 0; - } - - default int encodeSizeHint(ProtocolUtils.Direction direction, - ProtocolVersion version) { - return -1; - } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/PacketCodec.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/PacketCodec.java new file mode 100644 index 000000000..6abf58f31 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/PacketCodec.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2018-2025 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; + +/** + * A combined codec that can both encode and decode packets. + * + * @param the packet type + */ +public interface PacketCodec + extends PacketEncoder, PacketDecoder { +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/PacketDecoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/PacketDecoder.java new file mode 100644 index 000000000..b0c517939 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/PacketDecoder.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2018-2025 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; + +import com.velocitypowered.api.network.ProtocolVersion; +import io.netty.buffer.ByteBuf; + +/** + * Decodes a packet from a ByteBuf. + * + * @param the packet type + */ +public interface PacketDecoder { + + /** + * Decodes a packet from the provided ByteBuf. + * + * @param buf the buffer to read from + * @param direction the direction of the packet + * @param protocolVersion the protocol version + * @return the decoded packet instance + */ + T decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion); + + /** + * Returns the expected maximum length of the packet. This is used for validation during + * decoding. Return -1 if no maximum is enforced. + * + * @param buf the buffer containing the packet + * @param direction the direction of the packet + * @param version the protocol version + * @return the maximum expected length, or -1 if no limit + */ + default int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + return -1; + } + + /** + * Returns the expected minimum length of the packet. This is used for validation during + * decoding. Return 0 if no minimum is enforced. + * + * @param buf the buffer containing the packet + * @param direction the direction of the packet + * @param version the protocol version + * @return the minimum expected length + */ + default int decodeExpectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + return 0; + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/PacketEncoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/PacketEncoder.java new file mode 100644 index 000000000..7009aa9eb --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/PacketEncoder.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2018-2025 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; + +import com.velocitypowered.api.network.ProtocolVersion; +import io.netty.buffer.ByteBuf; + +/** + * Encodes a packet into a ByteBuf. + * + * @param the packet type + */ +public interface PacketEncoder { + + /** + * Encodes the given packet into the provided ByteBuf. + * + * @param packet the packet to encode + * @param buf the buffer to write to + * @param direction the direction of the packet + * @param protocolVersion the protocol version + */ + void encode(T packet, ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion); + + /** + * Returns a hint for the expected size of the encoded packet. This is used to pre-allocate + * buffers for better performance. Return -1 if no hint is available. + * + * @param packet the packet to encode + * @param direction the direction of the packet + * @param version the protocol version + * @return the size hint, or -1 if unknown + */ + default int encodeSizeHint(T packet, ProtocolUtils.Direction direction, + ProtocolVersion version) { + return -1; + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java index 67b010879..d4bd8dbc9 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -128,9 +128,9 @@ import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import java.util.Collections; import java.util.EnumMap; import java.util.EnumSet; +import java.util.HashMap; import java.util.Map; import java.util.Objects; -import java.util.function.Supplier; import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -140,122 +140,122 @@ public enum StateRegistry { HANDSHAKE { { - serverbound.register(HandshakePacket.class, HandshakePacket::new, + serverbound.register(HandshakePacket.class, new HandshakePacket.Codec(), map(0x00, MINECRAFT_1_7_2, false)); } }, STATUS { { serverbound.register( - StatusRequestPacket.class, () -> StatusRequestPacket.INSTANCE, + StatusRequestPacket.class, new StatusRequestPacket.Codec(), map(0x00, MINECRAFT_1_7_2, false)); - serverbound.register(StatusPingPacket.class, StatusPingPacket::new, + serverbound.register(StatusPingPacket.class, new StatusPingPacket.Codec(), map(0x01, MINECRAFT_1_7_2, false)); clientbound.register( - StatusResponsePacket.class, StatusResponsePacket::new, + StatusResponsePacket.class, new StatusResponsePacket.Codec(), map(0x00, MINECRAFT_1_7_2, false)); - clientbound.register(StatusPingPacket.class, StatusPingPacket::new, + clientbound.register(StatusPingPacket.class, new StatusPingPacket.Codec(), map(0x01, MINECRAFT_1_7_2, false)); } }, CONFIG { { serverbound.register( - ClientSettingsPacket.class, ClientSettingsPacket::new, + ClientSettingsPacket.class, new ClientSettingsPacket.Codec(), map(0x00, MINECRAFT_1_20_2, false)); serverbound.register( - ServerboundCookieResponsePacket.class, ServerboundCookieResponsePacket::new, + ServerboundCookieResponsePacket.class, new ServerboundCookieResponsePacket.Codec(), map(0x01, MINECRAFT_1_20_5, false)); serverbound.register( - PluginMessagePacket.class, PluginMessagePacket::new, + PluginMessagePacket.class, new PluginMessagePacket.Codec(), map(0x01, MINECRAFT_1_20_2, false), map(0x02, MINECRAFT_1_20_5, false)); serverbound.register( - FinishedUpdatePacket.class, () -> FinishedUpdatePacket.INSTANCE, + FinishedUpdatePacket.class, new FinishedUpdatePacket.Codec(), map(0x02, MINECRAFT_1_20_2, false), map(0x03, MINECRAFT_1_20_5, false)); - serverbound.register(KeepAlivePacket.class, KeepAlivePacket::new, + serverbound.register(KeepAlivePacket.class, new KeepAlivePacket.Codec(), map(0x03, MINECRAFT_1_20_2, false), map(0x04, MINECRAFT_1_20_5, false)); serverbound.register( - PingIdentifyPacket.class, PingIdentifyPacket::new, + PingIdentifyPacket.class, new PingIdentifyPacket.Codec(), map(0x04, MINECRAFT_1_20_2, false), map(0x05, MINECRAFT_1_20_5, false)); serverbound.register( ResourcePackResponsePacket.class, - ResourcePackResponsePacket::new, + new ResourcePackResponsePacket.Codec(), map(0x05, MINECRAFT_1_20_2, false), map(0x06, MINECRAFT_1_20_5, false)); serverbound.register( KnownPacksPacket.class, - KnownPacksPacket::new, + new KnownPacksPacket.Codec(), map(0x07, MINECRAFT_1_20_5, false)); - serverbound.register(ServerboundCustomClickActionPacket.class, ServerboundCustomClickActionPacket::new, + serverbound.register(ServerboundCustomClickActionPacket.class, new ServerboundCustomClickActionPacket.Codec(), map(0x08, MINECRAFT_1_21_6, false)); serverbound.register( CodeOfConductAcceptPacket.class, - () -> CodeOfConductAcceptPacket.INSTANCE, + new CodeOfConductAcceptPacket.Codec(), map(0x09, MINECRAFT_1_21_9, false)); clientbound.register( - ClientboundCookieRequestPacket.class, ClientboundCookieRequestPacket::new, + ClientboundCookieRequestPacket.class, new ClientboundCookieRequestPacket.Codec(), map(0x00, MINECRAFT_1_20_5, false)); clientbound.register( - PluginMessagePacket.class, PluginMessagePacket::new, + PluginMessagePacket.class, new PluginMessagePacket.Codec(), map(0x00, MINECRAFT_1_20_2, false), map(0x01, MINECRAFT_1_20_5, false)); clientbound.register( - DisconnectPacket.class, () -> new DisconnectPacket(this), + DisconnectPacket.class, new DisconnectPacket.Codec(this), map(0x01, MINECRAFT_1_20_2, false), map(0x02, MINECRAFT_1_20_5, false)); clientbound.register( - FinishedUpdatePacket.class, () -> FinishedUpdatePacket.INSTANCE, + FinishedUpdatePacket.class, new FinishedUpdatePacket.Codec(), map(0x02, MINECRAFT_1_20_2, false), map(0x03, MINECRAFT_1_20_5, false)); - clientbound.register(KeepAlivePacket.class, KeepAlivePacket::new, + clientbound.register(KeepAlivePacket.class, new KeepAlivePacket.Codec(), map(0x03, MINECRAFT_1_20_2, false), map(0x04, MINECRAFT_1_20_5, false)); clientbound.register( - PingIdentifyPacket.class, PingIdentifyPacket::new, + PingIdentifyPacket.class, new PingIdentifyPacket.Codec(), map(0x04, MINECRAFT_1_20_2, false), map(0x05, MINECRAFT_1_20_5, false)); clientbound.register( - RegistrySyncPacket.class, RegistrySyncPacket::new, + RegistrySyncPacket.class, new RegistrySyncPacket.Codec(), map(0x05, MINECRAFT_1_20_2, false), map(0x07, MINECRAFT_1_20_5, false)); clientbound.register( - RemoveResourcePackPacket.class, RemoveResourcePackPacket::new, + RemoveResourcePackPacket.class, new RemoveResourcePackPacket.Codec(), map(0x06, MINECRAFT_1_20_3, false), map(0x08, MINECRAFT_1_20_5, false)); - clientbound.register(ResourcePackRequestPacket.class, ResourcePackRequestPacket::new, + clientbound.register(ResourcePackRequestPacket.class, new ResourcePackRequestPacket.Codec(), map(0x06, MINECRAFT_1_20_2, false), map(0x07, MINECRAFT_1_20_3, false), map(0x09, MINECRAFT_1_20_5, false)); clientbound.register( - ClientboundStoreCookiePacket.class, ClientboundStoreCookiePacket::new, + ClientboundStoreCookiePacket.class, new ClientboundStoreCookiePacket.Codec(), map(0x0A, MINECRAFT_1_20_5, false)); - clientbound.register(TransferPacket.class, TransferPacket::new, + clientbound.register(TransferPacket.class, new TransferPacket.Codec(), map(0x0B, MINECRAFT_1_20_5, false)); - clientbound.register(ActiveFeaturesPacket.class, ActiveFeaturesPacket::new, + clientbound.register(ActiveFeaturesPacket.class, new ActiveFeaturesPacket.Codec(), map(0x07, MINECRAFT_1_20_2, false), map(0x08, MINECRAFT_1_20_3, false), map(0x0C, MINECRAFT_1_20_5, false)); - clientbound.register(TagsUpdatePacket.class, TagsUpdatePacket::new, + clientbound.register(TagsUpdatePacket.class, new TagsUpdatePacket.Codec(), map(0x08, MINECRAFT_1_20_2, false), map(0x09, MINECRAFT_1_20_3, false), map(0x0D, MINECRAFT_1_20_5, false)); - clientbound.register(KnownPacksPacket.class, KnownPacksPacket::new, + clientbound.register(KnownPacksPacket.class, new KnownPacksPacket.Codec(), map(0x0E, MINECRAFT_1_20_5, false)); - clientbound.register(ClientboundCustomReportDetailsPacket.class, ClientboundCustomReportDetailsPacket::new, + clientbound.register(ClientboundCustomReportDetailsPacket.class, new ClientboundCustomReportDetailsPacket.Codec(), map(0x0F, MINECRAFT_1_21, false)); - clientbound.register(ClientboundServerLinksPacket.class, ClientboundServerLinksPacket::new, + clientbound.register(ClientboundServerLinksPacket.class, new ClientboundServerLinksPacket.Codec(), map(0x10, MINECRAFT_1_21, false)); - clientbound.register(DialogClearPacket.class, () -> DialogClearPacket.INSTANCE, + clientbound.register(DialogClearPacket.class, new DialogClearPacket.Codec(), map(0x11, MINECRAFT_1_21_6, false)); - clientbound.register(DialogShowPacket.class, () -> new DialogShowPacket(this), + clientbound.register(DialogShowPacket.class, new DialogShowPacket.Codec(this), map(0x12, MINECRAFT_1_21_6, false)); - clientbound.register(CodeOfConductPacket.class, CodeOfConductPacket::new, + clientbound.register(CodeOfConductPacket.class, new CodeOfConductPacket.Codec(), map(0x13, MINECRAFT_1_21_9, false)); } }, @@ -264,7 +264,7 @@ public enum StateRegistry { serverbound.fallback = false; clientbound.fallback = false; - serverbound.register(TabCompleteRequestPacket.class, TabCompleteRequestPacket::new, + serverbound.register(TabCompleteRequestPacket.class, new TabCompleteRequestPacket.Codec(), map(0x14, MINECRAFT_1_7_2, false), map(0x01, MINECRAFT_1_9, false), map(0x02, MINECRAFT_1_12, false), @@ -281,7 +281,7 @@ public enum StateRegistry { map(0x0E, MINECRAFT_1_21_6, false)); serverbound.register( LegacyChatPacket.class, - LegacyChatPacket::new, + new LegacyChatPacket.Codec(), map(0x01, MINECRAFT_1_7_2, false), map(0x02, MINECRAFT_1_9, false), map(0x03, MINECRAFT_1_12, false), @@ -289,35 +289,35 @@ public enum StateRegistry { map(0x03, MINECRAFT_1_14, MINECRAFT_1_18_2, false)); serverbound.register( ChatAcknowledgementPacket.class, - ChatAcknowledgementPacket::new, + new ChatAcknowledgementPacket.Codec(), map(0x03, MINECRAFT_1_19_3, false), map(0x04, MINECRAFT_1_21_2, false), map(0x05, MINECRAFT_1_21_6, false)); - serverbound.register(KeyedPlayerCommandPacket.class, KeyedPlayerCommandPacket::new, + serverbound.register(KeyedPlayerCommandPacket.class, new KeyedPlayerCommandPacket.Codec(), map(0x03, MINECRAFT_1_19, false), map(0x04, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false)); - serverbound.register(KeyedPlayerChatPacket.class, KeyedPlayerChatPacket::new, + serverbound.register(KeyedPlayerChatPacket.class, new KeyedPlayerChatPacket.Codec(), map(0x04, MINECRAFT_1_19, false), map(0x05, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false)); - serverbound.register(SessionPlayerCommandPacket.class, SessionPlayerCommandPacket::new, + serverbound.register(SessionPlayerCommandPacket.class, new SessionPlayerCommandPacket.Codec(), map(0x04, MINECRAFT_1_19_3, false), map(0x05, MINECRAFT_1_20_5, false), map(0x06, MINECRAFT_1_21_2, false), map(0x07, MINECRAFT_1_21_6, false)); - serverbound.register(UnsignedPlayerCommandPacket.class, UnsignedPlayerCommandPacket::new, + serverbound.register(UnsignedPlayerCommandPacket.class, new UnsignedPlayerCommandPacket.Codec(), map(0x04, MINECRAFT_1_20_5, false), map(0x05, MINECRAFT_1_21_2, false), map(0x06, MINECRAFT_1_21_6, false)); serverbound.register( SessionPlayerChatPacket.class, - SessionPlayerChatPacket::new, + new SessionPlayerChatPacket.Codec(), map(0x05, MINECRAFT_1_19_3, false), map(0x06, MINECRAFT_1_20_5, false), map(0x07, MINECRAFT_1_21_2, false), map(0x08, MINECRAFT_1_21_6, false)); serverbound.register( ClientSettingsPacket.class, - ClientSettingsPacket::new, + new ClientSettingsPacket.Codec(), map(0x15, MINECRAFT_1_7_2, false), map(0x04, MINECRAFT_1_9, false), map(0x05, MINECRAFT_1_12, false), @@ -332,13 +332,13 @@ public enum StateRegistry { map(0x0C, MINECRAFT_1_21_2, false), map(0x0D, MINECRAFT_1_21_6, false)); serverbound.register( - ServerboundCookieResponsePacket.class, ServerboundCookieResponsePacket::new, + ServerboundCookieResponsePacket.class, new ServerboundCookieResponsePacket.Codec(), map(0x11, MINECRAFT_1_20_5, false), map(0x13, MINECRAFT_1_21_2, false), map(0x14, MINECRAFT_1_21_6, false)); serverbound.register( PluginMessagePacket.class, - PluginMessagePacket::new, + new PluginMessagePacket.Codec(), map(0x17, MINECRAFT_1_7_2, false), map(0x09, MINECRAFT_1_9, false), map(0x0A, MINECRAFT_1_12, false), @@ -357,7 +357,7 @@ public enum StateRegistry { map(0x15, MINECRAFT_1_21_6, false)); serverbound.register( KeepAlivePacket.class, - KeepAlivePacket::new, + new KeepAlivePacket.Codec(), map(0x00, MINECRAFT_1_7_2, false), map(0x0B, MINECRAFT_1_9, false), map(0x0C, MINECRAFT_1_12, false), @@ -377,7 +377,7 @@ public enum StateRegistry { map(0x1B, MINECRAFT_1_21_6, false)); serverbound.register( ResourcePackResponsePacket.class, - ResourcePackResponsePacket::new, + new ResourcePackResponsePacket.Codec(), map(0x19, MINECRAFT_1_8, false), map(0x16, MINECRAFT_1_9, false), map(0x18, MINECRAFT_1_12, false), @@ -394,7 +394,7 @@ public enum StateRegistry { map(0x2F, MINECRAFT_1_21_4, false), map(0x30, MINECRAFT_1_21_6, false)); serverbound.register( - FinishedUpdatePacket.class, () -> FinishedUpdatePacket.INSTANCE, + FinishedUpdatePacket.class, new FinishedUpdatePacket.Codec(), map(0x0B, MINECRAFT_1_20_2, false), map(0x0C, MINECRAFT_1_20_5, false), map(0x0E, MINECRAFT_1_21_2, false), @@ -402,7 +402,7 @@ public enum StateRegistry { clientbound.register( BossBarPacket.class, - BossBarPacket::new, + new BossBarPacket.Codec(), map(0x0C, MINECRAFT_1_9, false), map(0x0D, MINECRAFT_1_15, false), map(0x0C, MINECRAFT_1_16, false), @@ -413,14 +413,14 @@ public enum StateRegistry { map(0x09, MINECRAFT_1_21_5, false)); clientbound.register( LegacyChatPacket.class, - LegacyChatPacket::new, + new LegacyChatPacket.Codec(), map(0x02, MINECRAFT_1_7_2, true), map(0x0F, MINECRAFT_1_9, true), map(0x0E, MINECRAFT_1_13, true), map(0x0F, MINECRAFT_1_15, true), map(0x0E, MINECRAFT_1_16, true), map(0x0F, MINECRAFT_1_17, MINECRAFT_1_18_2, true)); - clientbound.register(TabCompleteResponsePacket.class, TabCompleteResponsePacket::new, + clientbound.register(TabCompleteResponsePacket.class, new TabCompleteResponsePacket.Codec(), map(0x3A, MINECRAFT_1_7_2, false), map(0x0E, MINECRAFT_1_9, false), map(0x10, MINECRAFT_1_13, false), @@ -435,7 +435,7 @@ public enum StateRegistry { map(0x0F, MINECRAFT_1_21_5, false)); clientbound.register( AvailableCommandsPacket.class, - AvailableCommandsPacket::new, + new AvailableCommandsPacket.Codec(), map(0x11, MINECRAFT_1_13, false), map(0x12, MINECRAFT_1_15, false), map(0x11, MINECRAFT_1_16, false), @@ -447,11 +447,11 @@ public enum StateRegistry { map(0x11, MINECRAFT_1_20_2, false), map(0x10, MINECRAFT_1_21_5, false)); clientbound.register( - ClientboundCookieRequestPacket.class, ClientboundCookieRequestPacket::new, + ClientboundCookieRequestPacket.class, new ClientboundCookieRequestPacket.Codec(), map(0x16, MINECRAFT_1_20_5, false), map(0x15, MINECRAFT_1_21_5, false)); clientbound.register( - ClientboundSoundEntityPacket.class, ClientboundSoundEntityPacket::new, + ClientboundSoundEntityPacket.class, new ClientboundSoundEntityPacket.Codec(), map(0x5D, MINECRAFT_1_19_3, true), map(0x61, MINECRAFT_1_19_4, true), map(0x63, MINECRAFT_1_20_2, true), @@ -461,7 +461,7 @@ public enum StateRegistry { map(0x6D, MINECRAFT_1_21_5, true), map(0x72, MINECRAFT_1_21_9, true)); clientbound.register( - ClientboundStopSoundPacket.class, ClientboundStopSoundPacket::new, + ClientboundStopSoundPacket.class, new ClientboundStopSoundPacket.Codec(), map(0x5F, MINECRAFT_1_19_3, true), map(0x63, MINECRAFT_1_19_4, true), map(0x66, MINECRAFT_1_20_2, true), @@ -472,7 +472,7 @@ public enum StateRegistry { map(0x75, MINECRAFT_1_21_9, true)); clientbound.register( PluginMessagePacket.class, - PluginMessagePacket::new, + new PluginMessagePacket.Codec(), map(0x3F, MINECRAFT_1_7_2, false), map(0x18, MINECRAFT_1_9, false), map(0x19, MINECRAFT_1_13, false), @@ -490,7 +490,7 @@ public enum StateRegistry { map(0x18, MINECRAFT_1_21_5, false)); clientbound.register( DisconnectPacket.class, - () -> new DisconnectPacket(this), + new DisconnectPacket.Codec(this), map(0x40, MINECRAFT_1_7_2, false), map(0x1A, MINECRAFT_1_9, false), map(0x1B, MINECRAFT_1_13, false), @@ -509,7 +509,7 @@ public enum StateRegistry { map(0x20, MINECRAFT_1_21_9, false)); clientbound.register( KeepAlivePacket.class, - KeepAlivePacket::new, + new KeepAlivePacket.Codec(), map(0x00, MINECRAFT_1_7_2, false), map(0x1F, MINECRAFT_1_9, false), map(0x21, MINECRAFT_1_13, false), @@ -529,7 +529,7 @@ public enum StateRegistry { map(0x2B, MINECRAFT_1_21_9, false)); clientbound.register( JoinGamePacket.class, - JoinGamePacket::new, + new JoinGamePacket.Codec(), map(0x01, MINECRAFT_1_7_2, false), map(0x23, MINECRAFT_1_9, false), map(0x25, MINECRAFT_1_13, false), @@ -549,7 +549,7 @@ public enum StateRegistry { map(0x30, MINECRAFT_1_21_9, false)); clientbound.register( RespawnPacket.class, - RespawnPacket::new, + new RespawnPacket.Codec(), map(0x07, MINECRAFT_1_7_2, true), map(0x33, MINECRAFT_1_9, true), map(0x34, MINECRAFT_1_12, true), @@ -572,7 +572,7 @@ public enum StateRegistry { map(0x50, MINECRAFT_1_21_9, true)); clientbound.register( RemoveResourcePackPacket.class, - RemoveResourcePackPacket::new, + new RemoveResourcePackPacket.Codec(), map(0x43, MINECRAFT_1_20_3, false), map(0x45, MINECRAFT_1_20_5, false), map(0x4A, MINECRAFT_1_21_2, false), @@ -580,7 +580,7 @@ public enum StateRegistry { map(0x4E, MINECRAFT_1_21_9, false)); clientbound.register( ResourcePackRequestPacket.class, - ResourcePackRequestPacket::new, + new ResourcePackRequestPacket.Codec(), map(0x48, MINECRAFT_1_8, false), map(0x32, MINECRAFT_1_9, false), map(0x33, MINECRAFT_1_12, false), @@ -603,7 +603,7 @@ public enum StateRegistry { map(0x4F, MINECRAFT_1_21_9, false)); clientbound.register( HeaderAndFooterPacket.class, - HeaderAndFooterPacket::new, + new HeaderAndFooterPacket.Codec(), map(0x47, MINECRAFT_1_8, true), map(0x48, MINECRAFT_1_9, true), map(0x47, MINECRAFT_1_9_4, true), @@ -627,7 +627,7 @@ public enum StateRegistry { map(0x78, MINECRAFT_1_21_9, true)); clientbound.register( LegacyTitlePacket.class, - LegacyTitlePacket::new, + new LegacyTitlePacket.Codec(), map(0x45, MINECRAFT_1_8, true), map(0x45, MINECRAFT_1_9, true), map(0x47, MINECRAFT_1_12, true), @@ -636,7 +636,7 @@ public enum StateRegistry { map(0x4F, MINECRAFT_1_14, true), map(0x50, MINECRAFT_1_15, true), map(0x4F, MINECRAFT_1_16, MINECRAFT_1_16_4, true)); - clientbound.register(TitleSubtitlePacket.class, TitleSubtitlePacket::new, + clientbound.register(TitleSubtitlePacket.class, new TitleSubtitlePacket.Codec(), map(0x57, MINECRAFT_1_17, true), map(0x58, MINECRAFT_1_18, true), map(0x5B, MINECRAFT_1_19_1, true), @@ -650,7 +650,7 @@ public enum StateRegistry { map(0x6E, MINECRAFT_1_21_9, true)); clientbound.register( TitleTextPacket.class, - TitleTextPacket::new, + new TitleTextPacket.Codec(), map(0x59, MINECRAFT_1_17, true), map(0x5A, MINECRAFT_1_18, true), map(0x5D, MINECRAFT_1_19_1, true), @@ -664,7 +664,7 @@ public enum StateRegistry { map(0x70, MINECRAFT_1_21_9, true)); clientbound.register( TitleActionbarPacket.class, - TitleActionbarPacket::new, + new TitleActionbarPacket.Codec(), map(0x41, MINECRAFT_1_17, true), map(0x40, MINECRAFT_1_19, true), map(0x43, MINECRAFT_1_19_1, true), @@ -678,7 +678,7 @@ public enum StateRegistry { map(0x55, MINECRAFT_1_21_9, true)); clientbound.register( TitleTimesPacket.class, - TitleTimesPacket::new, + new TitleTimesPacket.Codec(), map(0x5A, MINECRAFT_1_17, true), map(0x5B, MINECRAFT_1_18, true), map(0x5E, MINECRAFT_1_19_1, true), @@ -692,7 +692,7 @@ public enum StateRegistry { map(0x71, MINECRAFT_1_21_9, true)); clientbound.register( TitleClearPacket.class, - TitleClearPacket::new, + new TitleClearPacket.Codec(), map(0x10, MINECRAFT_1_17, true), map(0x0D, MINECRAFT_1_19, true), map(0x0C, MINECRAFT_1_19_3, true), @@ -701,7 +701,7 @@ public enum StateRegistry { map(0x0E, MINECRAFT_1_21_5, true)); clientbound.register( LegacyPlayerListItemPacket.class, - LegacyPlayerListItemPacket::new, + new LegacyPlayerListItemPacket.Codec(), map(0x38, MINECRAFT_1_7_2, false), map(0x2D, MINECRAFT_1_9, false), map(0x2E, MINECRAFT_1_12_1, false), @@ -713,7 +713,7 @@ public enum StateRegistry { map(0x36, MINECRAFT_1_17, false), map(0x34, MINECRAFT_1_19, false), map(0x37, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false)); - clientbound.register(RemovePlayerInfoPacket.class, RemovePlayerInfoPacket::new, + clientbound.register(RemovePlayerInfoPacket.class, new RemovePlayerInfoPacket.Codec(), map(0x35, MINECRAFT_1_19_3, false), map(0x39, MINECRAFT_1_19_4, false), map(0x3B, MINECRAFT_1_20_2, false), @@ -723,7 +723,7 @@ public enum StateRegistry { map(0x43, MINECRAFT_1_21_9, false)); clientbound.register( UpsertPlayerInfoPacket.class, - UpsertPlayerInfoPacket::new, + new UpsertPlayerInfoPacket.Codec(), map(0x36, MINECRAFT_1_19_3, false), map(0x3A, MINECRAFT_1_19_4, false), map(0x3C, MINECRAFT_1_20_2, false), @@ -732,14 +732,14 @@ public enum StateRegistry { map(0x3F, MINECRAFT_1_21_5, false), map(0x44, MINECRAFT_1_21_9, false)); clientbound.register( - ClientboundStoreCookiePacket.class, ClientboundStoreCookiePacket::new, + ClientboundStoreCookiePacket.class, new ClientboundStoreCookiePacket.Codec(), map(0x6B, MINECRAFT_1_20_5, false), map(0x72, MINECRAFT_1_21_2, false), map(0x71, MINECRAFT_1_21_5, false), map(0x76, MINECRAFT_1_21_9, false)); clientbound.register( SystemChatPacket.class, - SystemChatPacket::new, + new SystemChatPacket.Codec(), map(0x5F, MINECRAFT_1_19, true), map(0x62, MINECRAFT_1_19_1, true), map(0x60, MINECRAFT_1_19_3, true), @@ -752,7 +752,7 @@ public enum StateRegistry { map(0x77, MINECRAFT_1_21_9, true)); clientbound.register( PlayerChatCompletionPacket.class, - PlayerChatCompletionPacket::new, + new PlayerChatCompletionPacket.Codec(), map(0x15, MINECRAFT_1_19_1, true), map(0x14, MINECRAFT_1_19_3, true), map(0x16, MINECRAFT_1_19_4, true), @@ -761,7 +761,7 @@ public enum StateRegistry { map(0x17, MINECRAFT_1_21_5, true)); clientbound.register( ServerDataPacket.class, - ServerDataPacket::new, + new ServerDataPacket.Codec(), map(0x3F, MINECRAFT_1_19, false), map(0x42, MINECRAFT_1_19_1, false), map(0x41, MINECRAFT_1_19_3, false), @@ -774,7 +774,7 @@ public enum StateRegistry { map(0x54, MINECRAFT_1_21_9, false)); clientbound.register( StartUpdatePacket.class, - () -> StartUpdatePacket.INSTANCE, + new StartUpdatePacket.Codec(), map(0x65, MINECRAFT_1_20_2, false), map(0x67, MINECRAFT_1_20_3, false), map(0x69, MINECRAFT_1_20_5, false), @@ -783,23 +783,23 @@ public enum StateRegistry { map(0x74, MINECRAFT_1_21_9, false)); clientbound.register( BundleDelimiterPacket.class, - () -> BundleDelimiterPacket.INSTANCE, + new BundleDelimiterPacket.Codec(), map(0x00, MINECRAFT_1_19_4, false)); clientbound.register( TransferPacket.class, - TransferPacket::new, + new TransferPacket.Codec(), map(0x73, MINECRAFT_1_20_5, false), map(0x7A, MINECRAFT_1_21_2, false), map(0x7F, MINECRAFT_1_21_9, false)); clientbound.register( ClientboundCustomReportDetailsPacket.class, - ClientboundCustomReportDetailsPacket::new, + new ClientboundCustomReportDetailsPacket.Codec(), map(0x7A, MINECRAFT_1_21, false), map(0x81, MINECRAFT_1_21_2, false), map(0x86, MINECRAFT_1_21_9, false)); clientbound.register( ClientboundServerLinksPacket.class, - ClientboundServerLinksPacket::new, + new ClientboundServerLinksPacket.Codec(), map(0x7B, MINECRAFT_1_21, false), map(0x82, MINECRAFT_1_21_2, false), map(0x87, MINECRAFT_1_21_9, false)); @@ -808,39 +808,39 @@ public enum StateRegistry { LOGIN { { serverbound.register(ServerLoginPacket.class, - ServerLoginPacket::new, + new ServerLoginPacket.Codec(), map(0x00, MINECRAFT_1_7_2, false)); serverbound.register( - EncryptionResponsePacket.class, EncryptionResponsePacket::new, + EncryptionResponsePacket.class, new EncryptionResponsePacket.Codec(), map(0x01, MINECRAFT_1_7_2, false)); serverbound.register( - LoginPluginResponsePacket.class, LoginPluginResponsePacket::new, + LoginPluginResponsePacket.class, new LoginPluginResponsePacket.Codec(), map(0x02, MINECRAFT_1_13, false)); serverbound.register( - LoginAcknowledgedPacket.class, LoginAcknowledgedPacket::new, + LoginAcknowledgedPacket.class, new LoginAcknowledgedPacket.Codec(), map(0x03, MINECRAFT_1_20_2, false)); serverbound.register( - ServerboundCookieResponsePacket.class, ServerboundCookieResponsePacket::new, + ServerboundCookieResponsePacket.class, new ServerboundCookieResponsePacket.Codec(), map(0x04, MINECRAFT_1_20_5, false)); clientbound.register( - DisconnectPacket.class, () -> new DisconnectPacket(this), + DisconnectPacket.class, new DisconnectPacket.Codec(this), map(0x00, MINECRAFT_1_7_2, false)); clientbound.register( - EncryptionRequestPacket.class, EncryptionRequestPacket::new, + EncryptionRequestPacket.class, new EncryptionRequestPacket.Codec(), map(0x01, MINECRAFT_1_7_2, false)); clientbound.register( - ServerLoginSuccessPacket.class, ServerLoginSuccessPacket::new, + ServerLoginSuccessPacket.class, new ServerLoginSuccessPacket.Codec(), map(0x02, MINECRAFT_1_7_2, false)); clientbound.register( - SetCompressionPacket.class, SetCompressionPacket::new, + SetCompressionPacket.class, new SetCompressionPacket.Codec(), map(0x03, MINECRAFT_1_8, false)); clientbound.register( LoginPluginMessagePacket.class, - LoginPluginMessagePacket::new, + new LoginPluginMessagePacket.Codec(), map(0x04, MINECRAFT_1_13, false)); clientbound.register( - ClientboundCookieRequestPacket.class, ClientboundCookieRequestPacket::new, + ClientboundCookieRequestPacket.class, new ClientboundCookieRequestPacket.Codec(), map(0x05, MINECRAFT_1_20_5, false)); } }; @@ -906,7 +906,7 @@ public enum StateRegistry { return registry; } -

void register(Class

clazz, Supplier

packetSupplier, +

void register(Class

clazz, PacketCodec

codec, PacketMapping... mappings) { if (mappings.length == 0) { throw new IllegalArgumentException("At least one mapping must be provided."); @@ -947,7 +947,7 @@ public enum StateRegistry { "Unknown protocol version " + current.protocolVersion); } - if (registry.packetIdToSupplier.containsKey(current.id)) { + if (registry.packetIdToCodec.containsKey(current.id)) { throw new IllegalArgumentException( "Can not register class " + clazz.getSimpleName() @@ -964,8 +964,9 @@ public enum StateRegistry { } if (!current.encodeOnly) { - registry.packetIdToSupplier.put(current.id, packetSupplier); + registry.packetIdToCodec.put(current.id, codec); } + registry.packetClassToCodec.put(clazz, codec); registry.packetClassToId.put(clazz, current.id); } } @@ -977,8 +978,11 @@ public enum StateRegistry { public class ProtocolRegistry { public final ProtocolVersion version; - final IntObjectMap> packetIdToSupplier = + final IntObjectMap> packetIdToCodec = new IntObjectHashMap<>(16, 0.5f); + + final Map, PacketCodec> packetClassToCodec = + new HashMap<>(16, 0.5f); final Object2IntMap> packetClassToId = new Object2IntOpenHashMap<>(16, 0.5f); @@ -988,17 +992,25 @@ public enum StateRegistry { } /** - * Attempts to create a packet from the specified {@code id}. + * Gets the codec for the specified packet {@code id}. * * @param id the packet ID - * @return the packet instance, or {@code null} if the ID is not registered + * @return the packet codec, or {@code null} if the ID is not registered */ - public @Nullable MinecraftPacket createPacket(final int id) { - final Supplier supplier = this.packetIdToSupplier.get(id); - if (supplier == null) { - return null; - } - return supplier.get(); + public @Nullable PacketCodec getCodec(final int id) { + return this.packetIdToCodec.get(id); + } + + /** + * Gets the codec for the specified packet class. + * + * @param packetClass the packet class + * @return the packet codec, or {@code null} if the class is not registered + */ + @SuppressWarnings("unchecked") + public @Nullable PacketCodec getCodec( + final Class packetClass) { + return (PacketCodec) this.packetClassToCodec.get(packetClass); } /** diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/LegacyPingDecoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/LegacyPingDecoder.java index a6c2c065f..ae99ed4cf 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/LegacyPingDecoder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/LegacyPingDecoder.java @@ -66,7 +66,7 @@ public class LegacyPingDecoder extends ByteToMessageDecoder { out.add(readExtended16Data(in)); } else if (first == 0x02 && in.isReadable()) { in.skipBytes(in.readableBytes()); - out.add(new LegacyHandshakePacket()); + out.add(LegacyHandshakePacket.INSTANCE); } else { in.readerIndex(originalReaderIndex); ctx.pipeline().remove(this); 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 35ddccd5e..45b79b3dc 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 @@ -71,18 +71,20 @@ public class MinecraftDecoder extends ChannelInboundHandlerAdapter { int originalReaderIndex = buf.readerIndex(); int packetId = ProtocolUtils.readVarInt(buf); - MinecraftPacket packet = this.registry.createPacket(packetId); - if (packet == null) { + com.velocitypowered.proxy.protocol.PacketCodec codec = + this.registry.getCodec(packetId); + if (codec == null) { buf.readerIndex(originalReaderIndex); ctx.fireChannelRead(buf); } else { try { - doLengthSanityChecks(buf, packet); + doLengthSanityChecks(buf, codec); + MinecraftPacket packet; try { - packet.decode(buf, direction, registry.version); + packet = codec.decode(buf, direction, registry.version); } catch (Exception e) { - throw handleDecodeFailure(e, packet, packetId); + throw handleDecodeFailure(e, codec, packetId); } if (buf.isReadable()) { @@ -95,14 +97,16 @@ public class MinecraftDecoder extends ChannelInboundHandlerAdapter { } } - private void doLengthSanityChecks(ByteBuf buf, MinecraftPacket packet) throws Exception { - int expectedMinLen = packet.decodeExpectedMinLength(buf, direction, registry.version); - int expectedMaxLen = packet.decodeExpectedMaxLength(buf, direction, registry.version); + private void doLengthSanityChecks(ByteBuf buf, + com.velocitypowered.proxy.protocol.PacketCodec codec) + throws Exception { + int expectedMinLen = codec.decodeExpectedMinLength(buf, direction, registry.version); + int expectedMaxLen = codec.decodeExpectedMaxLength(buf, direction, registry.version); if (expectedMaxLen != -1 && buf.readableBytes() > expectedMaxLen) { - throw handleOverflow(packet, expectedMaxLen, buf.readableBytes()); + throw handleOverflow(codec, expectedMaxLen, buf.readableBytes()); } if (buf.readableBytes() < expectedMinLen) { - throw handleUnderflow(packet, expectedMaxLen, buf.readableBytes()); + throw handleUnderflow(codec, expectedMaxLen, buf.readableBytes()); } } @@ -115,19 +119,34 @@ public class MinecraftDecoder extends ChannelInboundHandlerAdapter { } } - private Exception handleUnderflow(MinecraftPacket packet, int expected, int actual) { + private Exception handleOverflow( + com.velocitypowered.proxy.protocol.PacketCodec codec, + int expected, int actual) { if (DEBUG) { - return new CorruptedFrameException("Packet sent for " + packet.getClass() + " was too " + return new CorruptedFrameException("Packet sent for " + codec.getClass() + " was too " + + "big (expected " + expected + " bytes, got " + actual + " bytes)"); + } else { + return DECODE_FAILED; + } + } + + private Exception handleUnderflow( + com.velocitypowered.proxy.protocol.PacketCodec codec, + int expected, int actual) { + if (DEBUG) { + return new CorruptedFrameException("Packet sent for " + codec.getClass() + " was too " + "small (expected " + expected + " bytes, got " + actual + " bytes)"); } else { return DECODE_FAILED; } } - private Exception handleDecodeFailure(Exception cause, MinecraftPacket packet, int packetId) { + private Exception handleDecodeFailure(Exception cause, + com.velocitypowered.proxy.protocol.PacketCodec codec, + int packetId) { if (DEBUG) { return new CorruptedFrameException( - "Error decoding " + packet.getClass() + " " + getExtraConnectionDetail(packetId), cause); + "Error decoding " + codec.getClass() + " " + getExtraConnectionDetail(packetId), cause); } else { return DECODE_FAILED; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftEncoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftEncoder.java index 4af366cec..9e7344eb5 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftEncoder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftEncoder.java @@ -20,6 +20,7 @@ package com.velocitypowered.proxy.protocol.netty; import com.google.common.base.Preconditions; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.StateRegistry; import io.netty.buffer.ByteBuf; @@ -48,16 +49,31 @@ public class MinecraftEncoder extends MessageToByteEncoder { } @Override + @SuppressWarnings("unchecked") protected void encode(ChannelHandlerContext ctx, MinecraftPacket msg, ByteBuf out) { + PacketCodec codec = (PacketCodec) this.registry.getCodec(msg.getClass()); + if (codec == null) { + throw new IllegalArgumentException("No codec found for packet: " + msg.getClass()); + } + int packetId = this.registry.getPacketId(msg); ProtocolUtils.writeVarInt(out, packetId); - msg.encode(out, direction, registry.version); + codec.encode(msg, out, direction, registry.version); } @Override + @SuppressWarnings("unchecked") protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, MinecraftPacket msg, boolean preferDirect) throws Exception { - int hint = msg.encodeSizeHint(direction, registry.version); + PacketCodec codec = + (PacketCodec) + this.registry.getCodec(msg.getClass()); + + int hint = -1; + if (codec != null) { + hint = codec.encodeSizeHint(msg, direction, registry.version); + } + if (hint < 0) { return super.allocateBuffer(ctx, msg, preferDirect); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftVarintFrameDecoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftVarintFrameDecoder.java index 7d4d8d6d9..e7af83458 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftVarintFrameDecoder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftVarintFrameDecoder.java @@ -20,7 +20,7 @@ package com.velocitypowered.proxy.protocol.netty; import static io.netty.util.ByteProcessor.FIND_NON_NUL; import com.velocitypowered.api.network.ProtocolVersion; -import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.util.except.QuietDecoderException; @@ -122,22 +122,21 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder { } final int payloadLength = length - ProtocolUtils.varIntBytes(packetId); - MinecraftPacket packet = registry.createPacket(packetId); - + PacketCodec codec = registry.getCodec(packetId); // We handle every packet in this phase, if you said something we don't know, something is really wrong - if (packet == null) { + if (codec == null) { throw UNKNOWN_PACKET; } // We 'technically' have the incoming bytes of a payload here, and so, these can actually parse // the packet if needed, so, we'll take advantage of the existing methods - int expectedMinLen = packet.decodeExpectedMinLength(in, direction, registry.version); - int expectedMaxLen = packet.decodeExpectedMaxLength(in, direction, registry.version); + int expectedMinLen = codec.decodeExpectedMinLength(in, direction, registry.version); + int expectedMaxLen = codec.decodeExpectedMaxLength(in, direction, registry.version); if (expectedMaxLen != -1 && payloadLength > expectedMaxLen) { - throw handleOverflow(packet, expectedMaxLen, in.readableBytes()); + throw handleOverflow(expectedMaxLen, in.readableBytes()); } if (payloadLength < expectedMinLen) { - throw handleUnderflow(packet, expectedMaxLen, in.readableBytes()); + throw handleUnderflow(expectedMaxLen, in.readableBytes()); } in.readerIndex(index); @@ -224,18 +223,18 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder { return result | (tmp & 0x7F) << 14; } - private Exception handleOverflow(MinecraftPacket packet, int expected, int actual) { + private Exception handleOverflow(int expected, int actual) { if (MinecraftDecoder.DEBUG) { - return new CorruptedFrameException("Packet sent for " + packet.getClass() + " was too " + return new CorruptedFrameException("Packet sent for handshake was too " + "big (expected " + expected + " bytes, got " + actual + " bytes)"); } else { return FRAME_DECODER_FAILED; } } - private Exception handleUnderflow(MinecraftPacket packet, int expected, int actual) { + private Exception handleUnderflow(int expected, int actual) { if (MinecraftDecoder.DEBUG) { - return new CorruptedFrameException("Packet sent for " + packet.getClass() + " was too " + return new CorruptedFrameException("Packet sent for handshake was too " + "small (expected " + expected + " bytes, got " + actual + " bytes)"); } else { return FRAME_DECODER_FAILED; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/AvailableCommandsPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/AvailableCommandsPacket.java index d3fc3a829..abe70d7e0 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/AvailableCommandsPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/AvailableCommandsPacket.java @@ -37,6 +37,7 @@ import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; import com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentPropertyRegistry; @@ -54,7 +55,7 @@ import java.util.function.Predicate; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.Nullable; -public class AvailableCommandsPacket implements MinecraftPacket { +public final class AvailableCommandsPacket implements MinecraftPacket { private static final Command PLACEHOLDER_COMMAND = source -> 0; private static final Predicate PLACEHOLDER_REQUIREMENT = source -> true; @@ -69,160 +70,169 @@ public class AvailableCommandsPacket implements MinecraftPacket { private static final byte FLAG_HAS_SUGGESTIONS = 0x10; private static final byte FLAG_IS_RESTRICTED = 0x20; - private @MonotonicNonNull RootCommandNode rootNode; + private final RootCommandNode rootNode; + + public AvailableCommandsPacket(RootCommandNode rootNode) { + this.rootNode = rootNode; + } - /** - * Returns the root node. - * - * @return the root node - */ public RootCommandNode getRootNode() { - if (rootNode == null) { - throw new IllegalStateException("Packet not yet deserialized"); - } return rootNode; } - @Override - public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { - int commands = ProtocolUtils.readVarInt(buf); - WireNode[] wireNodes = new WireNode[commands]; - for (int i = 0; i < commands; i++) { - wireNodes[i] = deserializeNode(buf, i, protocolVersion); - } - - // Iterate over the deserialized nodes and attempt to form a graph. We also resolve any cycles - // that exist. - Queue nodeQueue = new ArrayDeque<>(Arrays.asList(wireNodes)); - while (!nodeQueue.isEmpty()) { - boolean cycling = false; - - for (Iterator it = nodeQueue.iterator(); it.hasNext(); ) { - WireNode node = it.next(); - if (node.toNode(wireNodes)) { - cycling = true; - it.remove(); - } - } - - if (!cycling) { - // Uh-oh. We can't cycle. This is bad. - throw new IllegalStateException("Stopped cycling; the root node can't be built."); - } - } - - int rootIdx = ProtocolUtils.readVarInt(buf); - rootNode = (RootCommandNode) wireNodes[rootIdx].built; - } - - @Override - public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { - // Assign all the children an index. - Deque> childrenQueue = new ArrayDeque<>(ImmutableList.of(rootNode)); - Object2IntMap> idMappings = new Object2IntLinkedOpenCustomHashMap<>( - IdentityHashStrategy.instance()); - while (!childrenQueue.isEmpty()) { - CommandNode child = childrenQueue.poll(); - if (!idMappings.containsKey(child)) { - idMappings.put(child, idMappings.size()); - childrenQueue.addAll(child.getChildren()); - if (child.getRedirect() != null) { - childrenQueue.add(child.getRedirect()); - } - } - } - - // Now serialize the children. - ProtocolUtils.writeVarInt(buf, idMappings.size()); - for (CommandNode child : idMappings.keySet()) { - serializeNode(child, buf, idMappings, protocolVersion); - } - ProtocolUtils.writeVarInt(buf, idMappings.getInt(rootNode)); - } - - private static void serializeNode(CommandNode node, ByteBuf buf, - Object2IntMap> idMappings, ProtocolVersion protocolVersion) { - byte flags = 0; - if (node.getRedirect() != null) { - flags |= FLAG_IS_REDIRECT; - } - if (node.getCommand() != null) { - flags |= FLAG_EXECUTABLE; - } - if (node.getRequirement() == PLACEHOLDER_REQUIREMENT) { - flags |= FLAG_IS_RESTRICTED; - } - - if (node instanceof LiteralCommandNode) { - flags |= NODE_TYPE_LITERAL; - } else if (node instanceof ArgumentCommandNode) { - flags |= NODE_TYPE_ARGUMENT; - if (((ArgumentCommandNode) node).getCustomSuggestions() != null) { - flags |= FLAG_HAS_SUGGESTIONS; - } - } else if (!(node instanceof RootCommandNode)) { - throw new IllegalArgumentException("Unknown node type " + node.getClass().getName()); - } - - buf.writeByte(flags); - ProtocolUtils.writeVarInt(buf, node.getChildren().size()); - for (CommandNode child : node.getChildren()) { - ProtocolUtils.writeVarInt(buf, idMappings.getInt(child)); - } - if (node.getRedirect() != null) { - ProtocolUtils.writeVarInt(buf, idMappings.getInt(node.getRedirect())); - } - - if (node instanceof ArgumentCommandNode) { - ProtocolUtils.writeString(buf, node.getName()); - ArgumentPropertyRegistry.serialize(buf, - ((ArgumentCommandNode) node).getType(), protocolVersion); - - if (((ArgumentCommandNode) node).getCustomSuggestions() != null) { - SuggestionProvider provider = ((ArgumentCommandNode) node) - .getCustomSuggestions(); - String name = "minecraft:ask_server"; - if (provider instanceof ProtocolSuggestionProvider) { - name = ((ProtocolSuggestionProvider) provider).name; - } - ProtocolUtils.writeString(buf, name); - } - } else if (node instanceof LiteralCommandNode) { - ProtocolUtils.writeString(buf, node.getName()); - } - } - @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } - private static WireNode deserializeNode(ByteBuf buf, int idx, ProtocolVersion version) { - byte flags = buf.readByte(); - int[] children = ProtocolUtils.readIntegerArray(buf); - int redirectTo = -1; - if ((flags & FLAG_IS_REDIRECT) > 0) { - redirectTo = ProtocolUtils.readVarInt(buf); + public static class Codec implements PacketCodec { + @Override + public AvailableCommandsPacket decode(ByteBuf buf, Direction direction, + ProtocolVersion protocolVersion) { + int commands = ProtocolUtils.readVarInt(buf); + WireNode[] wireNodes = new WireNode[commands]; + for (int i = 0; i < commands; i++) { + wireNodes[i] = deserializeNode(buf, i, protocolVersion); + } + + // Iterate over the deserialized nodes and attempt to form a graph. We also resolve any cycles + // that exist. + Queue nodeQueue = new ArrayDeque<>(Arrays.asList(wireNodes)); + while (!nodeQueue.isEmpty()) { + boolean cycling = false; + + for (Iterator it = nodeQueue.iterator(); it.hasNext(); ) { + WireNode node = it.next(); + if (node.toNode(wireNodes)) { + cycling = true; + it.remove(); + } + } + + if (!cycling) { + // Uh-oh. We can't cycle. This is bad. + throw new IllegalStateException("Stopped cycling; the root node can't be built."); + } + } + + int rootIdx = ProtocolUtils.readVarInt(buf); + RootCommandNode rootNode = (RootCommandNode) wireNodes[rootIdx].built; + return new AvailableCommandsPacket(rootNode); } - switch (flags & FLAG_NODE_TYPE) { - case NODE_TYPE_ROOT: - return new WireNode(idx, flags, children, redirectTo, null); - case NODE_TYPE_LITERAL: - return new WireNode(idx, flags, children, redirectTo, LiteralArgumentBuilder - .literal(ProtocolUtils.readString(buf))); - case NODE_TYPE_ARGUMENT: - String name = ProtocolUtils.readString(buf); - ArgumentType argumentType = ArgumentPropertyRegistry.deserialize(buf, version); - - RequiredArgumentBuilder argumentBuilder = RequiredArgumentBuilder - .argument(name, argumentType); - if ((flags & FLAG_HAS_SUGGESTIONS) != 0) { - argumentBuilder.suggests(new ProtocolSuggestionProvider(ProtocolUtils.readString(buf))); + @Override + public void encode(AvailableCommandsPacket packet, ByteBuf buf, Direction direction, + ProtocolVersion protocolVersion) { + // Assign all the children an index. + Deque> childrenQueue = new ArrayDeque<>(ImmutableList.of(packet.rootNode)); + Object2IntMap> idMappings = new Object2IntLinkedOpenCustomHashMap<>( + IdentityHashStrategy.instance()); + while (!childrenQueue.isEmpty()) { + CommandNode child = childrenQueue.poll(); + if (!idMappings.containsKey(child)) { + idMappings.put(child, idMappings.size()); + childrenQueue.addAll(child.getChildren()); + if (child.getRedirect() != null) { + childrenQueue.add(child.getRedirect()); + } } - return new WireNode(idx, flags, children, redirectTo, argumentBuilder); - default: - throw new IllegalArgumentException("Unknown node type " + (flags & FLAG_NODE_TYPE)); + } + + // Now serialize the children. + ProtocolUtils.writeVarInt(buf, idMappings.size()); + for (CommandNode child : idMappings.keySet()) { + serializeNode(child, buf, idMappings, protocolVersion); + } + ProtocolUtils.writeVarInt(buf, idMappings.getInt(packet.rootNode)); + } + + private static void serializeNode(CommandNode node, ByteBuf buf, + Object2IntMap> idMappings, ProtocolVersion protocolVersion) { + byte flags = 0; + if (node.getRedirect() != null) { + flags |= FLAG_IS_REDIRECT; + } + if (node.getCommand() != null) { + flags |= FLAG_EXECUTABLE; + } + if (node.getRequirement() == PLACEHOLDER_REQUIREMENT) { + flags |= FLAG_IS_RESTRICTED; + } + + if (node instanceof LiteralCommandNode) { + flags |= NODE_TYPE_LITERAL; + } else if (node instanceof ArgumentCommandNode) { + flags |= NODE_TYPE_ARGUMENT; + if (((ArgumentCommandNode) node).getCustomSuggestions() != null) { + flags |= FLAG_HAS_SUGGESTIONS; + } + } else if (!(node instanceof RootCommandNode)) { + throw new IllegalArgumentException("Unknown node type " + node.getClass().getName()); + } + + buf.writeByte(flags); + ProtocolUtils.writeVarInt(buf, node.getChildren().size()); + for (CommandNode child : node.getChildren()) { + ProtocolUtils.writeVarInt(buf, idMappings.getInt(child)); + } + if (node.getRedirect() != null) { + ProtocolUtils.writeVarInt(buf, idMappings.getInt(node.getRedirect())); + } + + if (node instanceof ArgumentCommandNode) { + ProtocolUtils.writeString(buf, node.getName()); + ArgumentPropertyRegistry.serialize(buf, + ((ArgumentCommandNode) node).getType(), protocolVersion); + + if (((ArgumentCommandNode) node).getCustomSuggestions() != null) { + SuggestionProvider provider = ((ArgumentCommandNode) node) + .getCustomSuggestions(); + String name = "minecraft:ask_server"; + if (provider instanceof ProtocolSuggestionProvider) { + name = ((ProtocolSuggestionProvider) provider).name; + } + ProtocolUtils.writeString(buf, name); + } + } else if (node instanceof LiteralCommandNode) { + ProtocolUtils.writeString(buf, node.getName()); + } + } + + private static WireNode deserializeNode(ByteBuf buf, int idx, ProtocolVersion version) { + byte flags = buf.readByte(); + int[] children = ProtocolUtils.readIntegerArray(buf); + int redirectTo = -1; + if ((flags & FLAG_IS_REDIRECT) > 0) { + redirectTo = ProtocolUtils.readVarInt(buf); + } + + switch (flags & FLAG_NODE_TYPE) { + case NODE_TYPE_ROOT: + return new WireNode(idx, flags, children, redirectTo, null); + case NODE_TYPE_LITERAL: + return new WireNode(idx, flags, children, redirectTo, LiteralArgumentBuilder + .literal(ProtocolUtils.readString(buf))); + case NODE_TYPE_ARGUMENT: + String name = ProtocolUtils.readString(buf); + ArgumentType argumentType = ArgumentPropertyRegistry.deserialize(buf, version); + + RequiredArgumentBuilder argumentBuilder = RequiredArgumentBuilder + .argument(name, argumentType); + if ((flags & FLAG_HAS_SUGGESTIONS) != 0) { + argumentBuilder.suggests(new ProtocolSuggestionProvider(ProtocolUtils.readString(buf))); + } + return new WireNode(idx, flags, children, redirectTo, argumentBuilder); + default: + throw new IllegalArgumentException("Unknown node type " + (flags & FLAG_NODE_TYPE)); + } + } + + @Override + public int encodeSizeHint(AvailableCommandsPacket packet, Direction direction, ProtocolVersion version) { + // This is a very complex packet to encode. Paper 1.21.10 + Velocity with Spark has a size of + // 30,334, but this is likely on the lower side. We'll use 128KiB as a more realistically-sized + // amount. + return 128 * 1024; } } @@ -362,12 +372,4 @@ public class AvailableCommandsPacket implements MinecraftPacket { return builder.buildFuture(); } } - - @Override - public int encodeSizeHint(Direction direction, ProtocolVersion version) { - // This is a very complex packet to encode. Paper 1.21.10 + Velocity with Spark has a size of - // 30,334, but this is likely on the lower side. We'll use 128KiB as a more realistically-sized - // amount. - return 128 * 1024; - } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/BossBarPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/BossBarPacket.java index cc4c26715..b20dccddf 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/BossBarPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/BossBarPacket.java @@ -20,6 +20,7 @@ package com.velocitypowered.proxy.protocol.packet; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import com.velocitypowered.proxy.util.collect.Enum2IntMap; @@ -29,7 +30,8 @@ import java.util.UUID; import net.kyori.adventure.bossbar.BossBar; import org.checkerframework.checker.nullness.qual.Nullable; -public class BossBarPacket implements MinecraftPacket { +public record BossBarPacket(UUID uuid, int action, @Nullable ComponentHolder name, + float percent, int color, int overlay, short flags) implements MinecraftPacket { private static final Enum2IntMap COLORS_TO_PROTOCOL = new Enum2IntMap.Builder<>(BossBar.Color.class) @@ -62,43 +64,31 @@ public class BossBarPacket implements MinecraftPacket { public static final int UPDATE_NAME = 3; public static final int UPDATE_STYLE = 4; public static final int UPDATE_PROPERTIES = 5; - private @Nullable UUID uuid; - private int action; - private @Nullable ComponentHolder name; - private float percent; - private int color; - private int overlay; - private short flags; + + public UUID getUuid() { + return uuid; + } + + public int getAction() { + return action; + } public static BossBarPacket createAddPacket( final UUID id, final BossBar bar, final ComponentHolder name ) { - final BossBarPacket packet = new BossBarPacket(); - packet.setUuid(id); - packet.setAction(BossBarPacket.ADD); - packet.setName(name); - packet.setColor(COLORS_TO_PROTOCOL.get(bar.color())); - packet.setOverlay(OVERLAY_TO_PROTOCOL.get(bar.overlay())); - packet.setPercent(bar.progress()); - packet.setFlags(serializeFlags(bar.flags())); - return packet; + return new BossBarPacket(id, ADD, name, bar.progress(), + COLORS_TO_PROTOCOL.get(bar.color()), OVERLAY_TO_PROTOCOL.get(bar.overlay()), + serializeFlags(bar.flags())); } - public static BossBarPacket createRemovePacket(final UUID id, final BossBar bar) { - final BossBarPacket packet = new BossBarPacket(); - packet.setUuid(id); - packet.setAction(REMOVE); - return packet; + public static BossBarPacket createRemovePacket(final UUID id) { + return new BossBarPacket(id, REMOVE, null, 0, 0, 0, (short) 0); } public static BossBarPacket createUpdateProgressPacket(final UUID id, final BossBar bar) { - final BossBarPacket packet = new BossBarPacket(); - packet.setUuid(id); - packet.setAction(UPDATE_PERCENT); - packet.setPercent(bar.progress()); - return packet; + return new BossBarPacket(id, UPDATE_PERCENT, null, bar.progress(), 0, 0, (short) 0); } public static BossBarPacket createUpdateNamePacket( @@ -106,177 +96,20 @@ public class BossBarPacket implements MinecraftPacket { final BossBar bar, final ComponentHolder name ) { - final BossBarPacket packet = new BossBarPacket(); - packet.setUuid(id); - packet.setAction(UPDATE_NAME); - packet.setName(name); - return packet; + return new BossBarPacket(id, UPDATE_NAME, name, 0, 0, 0, (short) 0); } public static BossBarPacket createUpdateStylePacket(final UUID id, final BossBar bar) { - final BossBarPacket packet = new BossBarPacket(); - packet.setUuid(id); - packet.setAction(UPDATE_STYLE); - packet.setColor(COLORS_TO_PROTOCOL.get(bar.color())); - packet.setOverlay(OVERLAY_TO_PROTOCOL.get(bar.overlay())); - return packet; + return new BossBarPacket(id, UPDATE_STYLE, null, 0, + COLORS_TO_PROTOCOL.get(bar.color()), OVERLAY_TO_PROTOCOL.get(bar.overlay()), (short) 0); } public static BossBarPacket createUpdatePropertiesPacket(final UUID id, final BossBar bar) { - final BossBarPacket packet = new BossBarPacket(); - packet.setUuid(id); - packet.setAction(UPDATE_PROPERTIES); - packet.setFlags(serializeFlags(bar.flags())); - return packet; + return new BossBarPacket(id, UPDATE_PROPERTIES, null, 0, 0, 0, serializeFlags(bar.flags())); } - public UUID getUuid() { - if (uuid == null) { - throw new IllegalStateException("No boss bar UUID specified"); - } - return uuid; - } - - public void setUuid(UUID uuid) { - this.uuid = uuid; - } - - public int getAction() { - return action; - } - - public void setAction(int action) { - this.action = action; - } - - public @Nullable ComponentHolder getName() { - return name; - } - - public void setName(ComponentHolder name) { - this.name = name; - } - - public float getPercent() { - return percent; - } - - public void setPercent(float percent) { - this.percent = percent; - } - - public int getColor() { - return color; - } - - public void setColor(int color) { - this.color = color; - } - - public int getOverlay() { - return overlay; - } - - public void setOverlay(int overlay) { - this.overlay = overlay; - } - - public short getFlags() { - return flags; - } - - public void setFlags(short flags) { - this.flags = flags; - } - - @Override - public String toString() { - return "BossBar{" - + "uuid=" + uuid - + ", action=" + action - + ", name='" + name + '\'' - + ", percent=" + percent - + ", color=" + color - + ", overlay=" + overlay - + ", flags=" + flags - + '}'; - } - - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - this.uuid = ProtocolUtils.readUuid(buf); - this.action = ProtocolUtils.readVarInt(buf); - switch (action) { - case ADD: - this.name = ComponentHolder.read(buf, version); - this.percent = buf.readFloat(); - this.color = ProtocolUtils.readVarInt(buf); - this.overlay = ProtocolUtils.readVarInt(buf); - this.flags = buf.readUnsignedByte(); - break; - case REMOVE: - break; - case UPDATE_PERCENT: - this.percent = buf.readFloat(); - break; - case UPDATE_NAME: - this.name = ComponentHolder.read(buf, version); - break; - case UPDATE_STYLE: - this.color = ProtocolUtils.readVarInt(buf); - this.overlay = ProtocolUtils.readVarInt(buf); - break; - case UPDATE_PROPERTIES: - this.flags = buf.readUnsignedByte(); - break; - default: - throw new UnsupportedOperationException("Unknown action " + action); - } - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - if (uuid == null) { - throw new IllegalStateException("No boss bar UUID specified"); - } - ProtocolUtils.writeUuid(buf, uuid); - ProtocolUtils.writeVarInt(buf, action); - switch (action) { - case ADD: - if (name == null) { - throw new IllegalStateException("No name specified!"); - } - name.write(buf); - buf.writeFloat(percent); - ProtocolUtils.writeVarInt(buf, color); - ProtocolUtils.writeVarInt(buf, overlay); - buf.writeByte(flags); - break; - case REMOVE: - break; - case UPDATE_PERCENT: - buf.writeFloat(percent); - break; - case UPDATE_NAME: - if (name == null) { - throw new IllegalStateException("No name specified!"); - } - name.write(buf); - break; - case UPDATE_STYLE: - ProtocolUtils.writeVarInt(buf, color); - ProtocolUtils.writeVarInt(buf, overlay); - break; - case UPDATE_PROPERTIES: - buf.writeByte(flags); - break; - default: - throw new UnsupportedOperationException("Unknown action " + action); - } - } - - private static byte serializeFlags(Set flags) { - byte val = 0x0; + private static short serializeFlags(Set flags) { + short val = 0x0; for (BossBar.Flag flag : flags) { val |= FLAG_BITS_TO_PROTOCOL.get(flag); } @@ -287,4 +120,86 @@ public class BossBarPacket implements MinecraftPacket { public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + public static class Codec implements PacketCodec { + @Override + public BossBarPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + UUID uuid = ProtocolUtils.readUuid(buf); + int action = ProtocolUtils.readVarInt(buf); + ComponentHolder name = null; + float percent = 0; + int color = 0; + int overlay = 0; + short flags = 0; + + switch (action) { + case ADD: + name = ComponentHolder.read(buf, version); + percent = buf.readFloat(); + color = ProtocolUtils.readVarInt(buf); + overlay = ProtocolUtils.readVarInt(buf); + flags = buf.readUnsignedByte(); + break; + case REMOVE: + break; + case UPDATE_PERCENT: + percent = buf.readFloat(); + break; + case UPDATE_NAME: + name = ComponentHolder.read(buf, version); + break; + case UPDATE_STYLE: + color = ProtocolUtils.readVarInt(buf); + overlay = ProtocolUtils.readVarInt(buf); + break; + case UPDATE_PROPERTIES: + flags = buf.readUnsignedByte(); + break; + default: + throw new UnsupportedOperationException("Unknown action " + action); + } + + return new BossBarPacket(uuid, action, name, percent, color, overlay, flags); + } + + @Override + public void encode(BossBarPacket packet, ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + ProtocolUtils.writeUuid(buf, packet.uuid); + ProtocolUtils.writeVarInt(buf, packet.action); + switch (packet.action) { + case ADD: + if (packet.name == null) { + throw new IllegalStateException("No name specified!"); + } + packet.name.write(buf); + buf.writeFloat(packet.percent); + ProtocolUtils.writeVarInt(buf, packet.color); + ProtocolUtils.writeVarInt(buf, packet.overlay); + buf.writeByte(packet.flags); + break; + case REMOVE: + break; + case UPDATE_PERCENT: + buf.writeFloat(packet.percent); + break; + case UPDATE_NAME: + if (packet.name == null) { + throw new IllegalStateException("No name specified!"); + } + packet.name.write(buf); + break; + case UPDATE_STYLE: + ProtocolUtils.writeVarInt(buf, packet.color); + ProtocolUtils.writeVarInt(buf, packet.overlay); + break; + case UPDATE_PROPERTIES: + buf.writeByte(packet.flags); + break; + default: + throw new UnsupportedOperationException("Unknown action " + packet.action); + } + } + } } \ No newline at end of file diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/BundleDelimiterPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/BundleDelimiterPacket.java index 4da691c76..51339cf8c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/BundleDelimiterPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/BundleDelimiterPacket.java @@ -20,6 +20,7 @@ package com.velocitypowered.proxy.protocol.packet; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; @@ -29,18 +30,21 @@ public final class BundleDelimiterPacket implements MinecraftPacket { private BundleDelimiterPacket() { } - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - - } - @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + public static class Codec implements PacketCodec { + @Override + public BundleDelimiterPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + return INSTANCE; + } + + @Override + public void encode(BundleDelimiterPacket packet, ByteBuf buf, + ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientSettingsPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientSettingsPacket.java index 39e6fde02..91bed4cc0 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientSettingsPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientSettingsPacket.java @@ -20,34 +20,46 @@ package com.velocitypowered.proxy.protocol.packet; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; import java.util.Objects; import org.checkerframework.checker.nullness.qual.Nullable; -public class ClientSettingsPacket implements MinecraftPacket { - private @Nullable String locale; - private byte viewDistance; - private int chatVisibility; - private boolean chatColors; - private byte difficulty; // 1.7 Protocol - private short skinParts; - private int mainHand; - private boolean textFilteringEnabled; // Added in 1.17 - private boolean clientListingAllowed; // Added in 1.18, overwrites server-list "anonymous" mode - private int particleStatus; // Added in 1.21.2 +public final class ClientSettingsPacket implements MinecraftPacket { + private final @Nullable String locale; + private final byte viewDistance; + private final int chatVisibility; + private final boolean chatColors; + private final byte difficulty; // 1.7 Protocol + private final short skinParts; + private final int mainHand; + private final boolean textFilteringEnabled; // Added in 1.17 + private final boolean clientListingAllowed; // Added in 1.18, overwrites server-list "anonymous" mode + private final int particleStatus; // Added in 1.21.2 public ClientSettingsPacket() { + this(null, (byte) 0, 0, false, (byte) 0, (short) 0, 0, false, false, 0); } - public ClientSettingsPacket(String locale, byte viewDistance, int chatVisibility, boolean chatColors, - short skinParts, int mainHand, boolean textFilteringEnabled, boolean clientListingAllowed, - int particleStatus) { + public ClientSettingsPacket(String locale, byte viewDistance, int chatVisibility, + boolean chatColors, short skinParts, int mainHand, + boolean textFilteringEnabled, boolean clientListingAllowed, + int particleStatus) { + this(locale, viewDistance, chatVisibility, chatColors, (byte) 0, skinParts, mainHand, + textFilteringEnabled, clientListingAllowed, particleStatus); + } + + public ClientSettingsPacket(String locale, byte viewDistance, int chatVisibility, + boolean chatColors, byte difficulty, short skinParts, int mainHand, + boolean textFilteringEnabled, boolean clientListingAllowed, + int particleStatus) { this.locale = locale; this.viewDistance = viewDistance; this.chatVisibility = chatVisibility; this.chatColors = chatColors; + this.difficulty = difficulty; this.skinParts = skinParts; this.mainHand = mainHand; this.textFilteringEnabled = textFilteringEnabled; @@ -55,150 +67,51 @@ public class ClientSettingsPacket implements MinecraftPacket { this.particleStatus = particleStatus; } - public String getLocale() { + public String locale() { if (locale == null) { throw new IllegalStateException("No locale specified"); } return locale; } - public void setLocale(String locale) { - this.locale = locale; - } - - public byte getViewDistance() { + public byte viewDistance() { return viewDistance; } - public void setViewDistance(byte viewDistance) { - this.viewDistance = viewDistance; - } - - public int getChatVisibility() { + public int chatVisibility() { return chatVisibility; } - public void setChatVisibility(int chatVisibility) { - this.chatVisibility = chatVisibility; - } - - public boolean isChatColors() { + public boolean chatColors() { return chatColors; } - public void setChatColors(boolean chatColors) { - this.chatColors = chatColors; + public byte difficulty() { + return difficulty; } - public short getSkinParts() { + public short skinParts() { return skinParts; } - public void setSkinParts(short skinParts) { - this.skinParts = skinParts; - } - - public int getMainHand() { + public int mainHand() { return mainHand; } - public void setMainHand(int mainHand) { - this.mainHand = mainHand; - } - - public boolean isTextFilteringEnabled() { + public boolean textFilteringEnabled() { return textFilteringEnabled; } - public void setTextFilteringEnabled(boolean textFilteringEnabled) { - this.textFilteringEnabled = textFilteringEnabled; - } - - public boolean isClientListingAllowed() { + public boolean clientListingAllowed() { return clientListingAllowed; } - public void setClientListingAllowed(boolean clientListingAllowed) { - this.clientListingAllowed = clientListingAllowed; - } - - public int getParticleStatus() { + public int particleStatus() { return particleStatus; } - public void setParticleStatus(int particleStatus) { - this.particleStatus = particleStatus; - } - - @Override - public String toString() { - return "ClientSettings{" + "locale='" + locale + '\'' + ", viewDistance=" + viewDistance + - ", chatVisibility=" + chatVisibility + ", chatColors=" + chatColors + ", skinParts=" + - skinParts + ", mainHand=" + mainHand + ", chatFilteringEnabled=" + textFilteringEnabled + - ", clientListingAllowed=" + clientListingAllowed + ", particleStatus=" + particleStatus + '}'; - } - - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - this.locale = ProtocolUtils.readString(buf, 16); - this.viewDistance = buf.readByte(); - this.chatVisibility = ProtocolUtils.readVarInt(buf); - this.chatColors = buf.readBoolean(); - - if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6)) { - this.difficulty = buf.readByte(); - } - - this.skinParts = buf.readUnsignedByte(); - - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_9)) { - this.mainHand = ProtocolUtils.readVarInt(buf); - - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_17)) { - this.textFilteringEnabled = buf.readBoolean(); - - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_18)) { - this.clientListingAllowed = buf.readBoolean(); - - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) { - this.particleStatus = ProtocolUtils.readVarInt(buf); - } - } - } - } - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - if (locale == null) { - throw new IllegalStateException("No locale specified"); - } - ProtocolUtils.writeString(buf, locale); - buf.writeByte(viewDistance); - ProtocolUtils.writeVarInt(buf, chatVisibility); - buf.writeBoolean(chatColors); - - if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6)) { - buf.writeByte(difficulty); - } - - buf.writeByte(skinParts); - - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_9)) { - ProtocolUtils.writeVarInt(buf, mainHand); - - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_17)) { - buf.writeBoolean(textFilteringEnabled); - - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_18)) { - buf.writeBoolean(clientListingAllowed); - } - - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) { - ProtocolUtils.writeVarInt(buf, particleStatus); - } - } - } + public String getLocale() { + return locale(); } @Override @@ -227,18 +140,79 @@ public class ClientSettingsPacket implements MinecraftPacket { && Objects.equals(locale, that.locale); } - @Override - public int hashCode() { - return Objects.hash( - locale, - viewDistance, - chatVisibility, - chatColors, - difficulty, - skinParts, - mainHand, - textFilteringEnabled, - clientListingAllowed, - particleStatus); + public static class Codec implements PacketCodec { + @Override + public ClientSettingsPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + String locale = ProtocolUtils.readString(buf, 16); + byte viewDistance = buf.readByte(); + int chatVisibility = ProtocolUtils.readVarInt(buf); + boolean chatColors = buf.readBoolean(); + byte difficulty = 0; + + if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6)) { + difficulty = buf.readByte(); + } + + short skinParts = buf.readUnsignedByte(); + int mainHand = 0; + boolean textFilteringEnabled = false; + boolean clientListingAllowed = false; + int particleStatus = 0; + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_9)) { + mainHand = ProtocolUtils.readVarInt(buf); + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_17)) { + textFilteringEnabled = buf.readBoolean(); + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_18)) { + clientListingAllowed = buf.readBoolean(); + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) { + particleStatus = ProtocolUtils.readVarInt(buf); + } + } + } + } + + return new ClientSettingsPacket(locale, viewDistance, chatVisibility, chatColors, + difficulty, skinParts, mainHand, textFilteringEnabled, clientListingAllowed, + particleStatus); + } + + @Override + public void encode(ClientSettingsPacket packet, ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + if (packet.locale == null) { + throw new IllegalStateException("No locale specified"); + } + ProtocolUtils.writeString(buf, packet.locale); + buf.writeByte(packet.viewDistance); + ProtocolUtils.writeVarInt(buf, packet.chatVisibility); + buf.writeBoolean(packet.chatColors); + + if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6)) { + buf.writeByte(packet.difficulty); + } + + buf.writeByte(packet.skinParts); + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_9)) { + ProtocolUtils.writeVarInt(buf, packet.mainHand); + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_17)) { + buf.writeBoolean(packet.textFilteringEnabled); + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_18)) { + buf.writeBoolean(packet.clientListingAllowed); + } + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) { + ProtocolUtils.writeVarInt(buf, packet.particleStatus); + } + } + } + } } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientboundCookieRequestPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientboundCookieRequestPacket.java index fd558b29c..a6a9fd33d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientboundCookieRequestPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientboundCookieRequestPacket.java @@ -20,38 +20,34 @@ package com.velocitypowered.proxy.protocol.packet; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; import io.netty.buffer.ByteBuf; import net.kyori.adventure.key.Key; -public class ClientboundCookieRequestPacket implements MinecraftPacket { - - private Key key; +public record ClientboundCookieRequestPacket(Key key) implements MinecraftPacket { public Key getKey() { return key; } - public ClientboundCookieRequestPacket() { - } - - public ClientboundCookieRequestPacket(final Key key) { - this.key = key; - } - - @Override - public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { - this.key = ProtocolUtils.readKey(buf); - } - - @Override - public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { - ProtocolUtils.writeKey(buf, key); - } - @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + public static class Codec implements PacketCodec { + @Override + public ClientboundCookieRequestPacket decode(ByteBuf buf, Direction direction, + ProtocolVersion protocolVersion) { + return new ClientboundCookieRequestPacket(ProtocolUtils.readKey(buf)); + } + + @Override + public void encode(ClientboundCookieRequestPacket packet, ByteBuf buf, Direction direction, + ProtocolVersion protocolVersion) { + ProtocolUtils.writeKey(buf, packet.key); + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientboundSoundEntityPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientboundSoundEntityPacket.java index 459f14301..daa34a3e8 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientboundSoundEntityPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientboundSoundEntityPacket.java @@ -20,82 +20,50 @@ package com.velocitypowered.proxy.protocol.packet; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; +import java.util.concurrent.ThreadLocalRandom; import net.kyori.adventure.sound.Sound; import org.jetbrains.annotations.Nullable; -import java.util.Random; -public class ClientboundSoundEntityPacket implements MinecraftPacket { - - private static final Random SEEDS_RANDOM = new Random(); - - private Sound sound; - private @Nullable Float fixedRange; - private int emitterEntityId; - - public ClientboundSoundEntityPacket() {} - - public ClientboundSoundEntityPacket(Sound sound, @Nullable Float fixedRange, int emitterEntityId) { - this.sound = sound; - this.fixedRange = fixedRange; - this.emitterEntityId = emitterEntityId; - } - - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { - throw new UnsupportedOperationException("Decode is not implemented"); - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { - ProtocolUtils.writeVarInt(buf, 0); // version-dependent, hardcoded sound ID - - ProtocolUtils.writeMinimalKey(buf, sound.name()); - - buf.writeBoolean(fixedRange != null); - if (fixedRange != null) - buf.writeFloat(fixedRange); - - ProtocolUtils.writeSoundSource(buf, protocolVersion, sound.source()); - - ProtocolUtils.writeVarInt(buf, emitterEntityId); - - buf.writeFloat(sound.volume()); - - buf.writeFloat(sound.pitch()); - - buf.writeLong(sound.seed().orElse(SEEDS_RANDOM.nextLong())); - } +public record ClientboundSoundEntityPacket(Sound sound, @Nullable Float fixedRange, + int emitterEntityId) implements MinecraftPacket { @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } - public Sound getSound() { - return sound; - } + public static class Codec implements PacketCodec { + @Override + public ClientboundSoundEntityPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + throw new UnsupportedOperationException("Decode is not implemented"); + } - public void setSound(Sound sound) { - this.sound = sound; - } + @Override + public void encode(ClientboundSoundEntityPacket packet, ByteBuf buf, + ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + ProtocolUtils.writeVarInt(buf, 0); // version-dependent, hardcoded sound ID - public @Nullable Float getFixedRange() { - return fixedRange; - } + ProtocolUtils.writeMinimalKey(buf, packet.sound.name()); - public void setFixedRange(@Nullable Float fixedRange) { - this.fixedRange = fixedRange; - } + buf.writeBoolean(packet.fixedRange != null); + if (packet.fixedRange != null) { + buf.writeFloat(packet.fixedRange); + } - public int getEmitterEntityId() { - return emitterEntityId; - } + ProtocolUtils.writeSoundSource(buf, protocolVersion, packet.sound.source()); - public void setEmitterEntityId(int emitterEntityId) { - this.emitterEntityId = emitterEntityId; - } + ProtocolUtils.writeVarInt(buf, packet.emitterEntityId); + buf.writeFloat(packet.sound.volume()); + + buf.writeFloat(packet.sound.pitch()); + + buf.writeLong(packet.sound.seed().orElse(ThreadLocalRandom.current().nextLong())); + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientboundStopSoundPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientboundStopSoundPacket.java index 3e085d38f..8ebc46b48 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientboundStopSoundPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientboundStopSoundPacket.java @@ -20,6 +20,7 @@ package com.velocitypowered.proxy.protocol.packet; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; import net.kyori.adventure.key.Key; @@ -28,82 +29,58 @@ import net.kyori.adventure.sound.SoundStop; import javax.annotation.Nullable; -public class ClientboundStopSoundPacket implements MinecraftPacket { - - private @Nullable Sound.Source source; - private @Nullable Key soundName; - - public ClientboundStopSoundPacket() {} +public record ClientboundStopSoundPacket(@Nullable Sound.Source source, + @Nullable Key soundName) implements MinecraftPacket { public ClientboundStopSoundPacket(SoundStop soundStop) { this(soundStop.source(), soundStop.sound()); } - public ClientboundStopSoundPacket(@Nullable Sound.Source source, @Nullable Key soundName) { - this.source = source; - this.soundName = soundName; - } - - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { - int flagsBitmask = buf.readByte(); - - if ((flagsBitmask & 1) != 0) { - source = ProtocolUtils.readSoundSource(buf, protocolVersion); - } else { - source = null; - } - - if ((flagsBitmask & 2) != 0) { - soundName = ProtocolUtils.readKey(buf); - } else { - soundName = null; - } - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { - int flagsBitmask = 0; - if (source != null && soundName == null) { - flagsBitmask |= 1; - } else if (soundName != null && source == null) { - flagsBitmask |= 2; - } else if (source != null /*&& sound != null*/) { - flagsBitmask |= 3; - } - - buf.writeByte(flagsBitmask); - - if (source != null) { - ProtocolUtils.writeSoundSource(buf, protocolVersion, source); - } - - if (soundName != null) { - ProtocolUtils.writeMinimalKey(buf, soundName); - } - } - @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } - @Nullable - public Sound.Source getSource() { - return source; - } + public static class Codec implements PacketCodec { + @Override + public ClientboundStopSoundPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + int flagsBitmask = buf.readByte(); - public void setSource(@Nullable Sound.Source source) { - this.source = source; - } + Sound.Source source = null; + if ((flagsBitmask & 1) != 0) { + source = ProtocolUtils.readSoundSource(buf, protocolVersion); + } - @Nullable - public Key getSoundName() { - return soundName; - } + Key soundName = null; + if ((flagsBitmask & 2) != 0) { + soundName = ProtocolUtils.readKey(buf); + } - public void setSoundName(@Nullable Key soundName) { - this.soundName = soundName; - } + return new ClientboundStopSoundPacket(source, soundName); + } + @Override + public void encode(ClientboundStopSoundPacket packet, ByteBuf buf, + ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + int flagsBitmask = 0; + if (packet.source != null && packet.soundName == null) { + flagsBitmask |= 1; + } else if (packet.soundName != null && packet.source == null) { + flagsBitmask |= 2; + } else if (packet.source != null /*&& sound != null*/) { + flagsBitmask |= 3; + } + + buf.writeByte(flagsBitmask); + + if (packet.source != null) { + ProtocolUtils.writeSoundSource(buf, protocolVersion, packet.source); + } + + if (packet.soundName != null) { + ProtocolUtils.writeMinimalKey(buf, packet.soundName); + } + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientboundStoreCookiePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientboundStoreCookiePacket.java index 7823b5584..47c3bf9c6 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientboundStoreCookiePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientboundStoreCookiePacket.java @@ -20,46 +20,32 @@ package com.velocitypowered.proxy.protocol.packet; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; import io.netty.buffer.ByteBuf; import net.kyori.adventure.key.Key; -public class ClientboundStoreCookiePacket implements MinecraftPacket { - - private Key key; - private byte[] payload; - - public Key getKey() { - return key; - } - - public byte[] getPayload() { - return payload; - } - - public ClientboundStoreCookiePacket() { - } - - public ClientboundStoreCookiePacket(final Key key, final byte[] payload) { - this.key = key; - this.payload = payload; - } - - @Override - public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { - this.key = ProtocolUtils.readKey(buf); - this.payload = ProtocolUtils.readByteArray(buf, 5120); - } - - @Override - public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { - ProtocolUtils.writeKey(buf, key); - ProtocolUtils.writeByteArray(buf, payload); - } +public record ClientboundStoreCookiePacket(Key key, byte[] payload) implements MinecraftPacket { @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + public static class Codec implements PacketCodec { + @Override + public ClientboundStoreCookiePacket decode(ByteBuf buf, Direction direction, + ProtocolVersion protocolVersion) { + return new ClientboundStoreCookiePacket(ProtocolUtils.readKey(buf), + ProtocolUtils.readByteArray(buf, 5120)); + } + + @Override + public void encode(ClientboundStoreCookiePacket packet, ByteBuf buf, Direction direction, + ProtocolVersion protocolVersion) { + ProtocolUtils.writeKey(buf, packet.key); + ProtocolUtils.writeByteArray(buf, packet.payload); + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/DialogClearPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/DialogClearPacket.java index 4188abfdb..8103ccdb2 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/DialogClearPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/DialogClearPacket.java @@ -20,26 +20,32 @@ package com.velocitypowered.proxy.protocol.packet; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; -import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; +import com.velocitypowered.proxy.protocol.PacketCodec; +import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; -public class DialogClearPacket implements MinecraftPacket { +public final class DialogClearPacket implements MinecraftPacket { public static final DialogClearPacket INSTANCE = new DialogClearPacket(); private DialogClearPacket() { } - @Override - public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { - } - - @Override - public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { - } - @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + public static class Codec implements PacketCodec { + @Override + public DialogClearPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + return INSTANCE; + } + + @Override + public void encode(DialogClearPacket packet, ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/DialogShowPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/DialogShowPacket.java index 67d4b8f82..160c092d0 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/DialogShowPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/DialogShowPacket.java @@ -20,45 +20,71 @@ package com.velocitypowered.proxy.protocol.packet; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; -import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; import com.velocitypowered.proxy.protocol.StateRegistry; import io.netty.buffer.ByteBuf; import net.kyori.adventure.nbt.BinaryTag; import net.kyori.adventure.nbt.BinaryTagIO; -public class DialogShowPacket implements MinecraftPacket { +public final class DialogShowPacket implements MinecraftPacket { private final StateRegistry state; - private int id; - private BinaryTag nbt; + private final int id; + private final BinaryTag nbt; - public DialogShowPacket(final StateRegistry state) { + public DialogShowPacket(StateRegistry state, int id, BinaryTag nbt) { this.state = state; + this.id = id; + this.nbt = nbt; } - @Override - public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { - this.id = this.state == StateRegistry.CONFIG ? 0 : ProtocolUtils.readVarInt(buf); - if (this.id == 0) { - this.nbt = ProtocolUtils.readBinaryTag(buf, protocolVersion, BinaryTagIO.reader()); - } + public StateRegistry state() { + return state; } - @Override - public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { - if (this.state == StateRegistry.CONFIG) { - ProtocolUtils.writeBinaryTag(buf, protocolVersion, this.nbt); - } else { - ProtocolUtils.writeVarInt(buf, this.id); - if (this.id == 0) { - ProtocolUtils.writeBinaryTag(buf, protocolVersion, this.nbt); - } - } + public int id() { + return id; + } + + public BinaryTag nbt() { + return nbt; } @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + public static class Codec implements PacketCodec { + private final StateRegistry state; + + public Codec(StateRegistry state) { + this.state = state; + } + + @Override + public DialogShowPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + int id = state == StateRegistry.CONFIG ? 0 : ProtocolUtils.readVarInt(buf); + BinaryTag nbt = null; + if (id == 0) { + nbt = ProtocolUtils.readBinaryTag(buf, protocolVersion, BinaryTagIO.reader()); + } + return new DialogShowPacket(state, id, nbt); + } + + @Override + public void encode(DialogShowPacket packet, ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + if (packet.state == StateRegistry.CONFIG) { + ProtocolUtils.writeBinaryTag(buf, protocolVersion, packet.nbt); + } else { + ProtocolUtils.writeVarInt(buf, packet.id); + if (packet.id == 0) { + ProtocolUtils.writeBinaryTag(buf, protocolVersion, packet.nbt); + } + } + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/DisconnectPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/DisconnectPacket.java index dd16cb61e..d272dd16b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/DisconnectPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/DisconnectPacket.java @@ -21,36 +21,36 @@ import com.google.common.base.Preconditions; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import io.netty.buffer.ByteBuf; import net.kyori.adventure.text.Component; -import org.checkerframework.checker.nullness.qual.Nullable; -public class DisconnectPacket implements MinecraftPacket { +public final class DisconnectPacket implements MinecraftPacket { - private @Nullable ComponentHolder reason; + private final ComponentHolder reason; private final StateRegistry state; - public DisconnectPacket(StateRegistry state) { - this.state = state; - } - - private DisconnectPacket(StateRegistry state, ComponentHolder reason) { + public DisconnectPacket(StateRegistry state, ComponentHolder reason) { this.state = state; this.reason = Preconditions.checkNotNull(reason, "reason"); } - public ComponentHolder getReason() { + public ComponentHolder reason() { if (reason == null) { throw new IllegalStateException("No reason specified"); } return reason; } - public void setReason(@Nullable ComponentHolder reason) { - this.reason = reason; + public ComponentHolder getReason() { + return reason(); + } + + public StateRegistry state() { + return state; } @Override @@ -60,25 +60,37 @@ public class DisconnectPacket implements MinecraftPacket { + '}'; } - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - reason = ComponentHolder.read(buf, state == StateRegistry.LOGIN - ? ProtocolVersion.MINECRAFT_1_20_2 : version); - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - getReason().write(buf); - } - @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } - public static DisconnectPacket create(Component component, ProtocolVersion version, StateRegistry state) { + public static DisconnectPacket create(Component component, ProtocolVersion version, + StateRegistry state) { Preconditions.checkNotNull(component, "component"); return new DisconnectPacket(state, new ComponentHolder(state == StateRegistry.LOGIN ? ProtocolVersion.MINECRAFT_1_20_2 : version, component)); } -} \ No newline at end of file + + public static class Codec implements PacketCodec { + private final StateRegistry state; + + public Codec(StateRegistry state) { + this.state = state; + } + + @Override + public DisconnectPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + ComponentHolder reason = ComponentHolder.read(buf, state == StateRegistry.LOGIN + ? ProtocolVersion.MINECRAFT_1_20_2 : protocolVersion); + return new DisconnectPacket(state, reason); + } + + @Override + public void encode(DisconnectPacket packet, ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + packet.reason().write(buf); + } + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/EncryptionRequestPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/EncryptionRequestPacket.java index 422d5e117..bb15ef366 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/EncryptionRequestPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/EncryptionRequestPacket.java @@ -17,36 +17,52 @@ package com.velocitypowered.proxy.protocol.packet; -import static com.velocitypowered.proxy.connection.VelocityConstants.EMPTY_BYTE_ARRAY; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; import java.util.Arrays; -public class EncryptionRequestPacket implements MinecraftPacket { +public final class EncryptionRequestPacket implements MinecraftPacket { - private String serverId = ""; - private byte[] publicKey = EMPTY_BYTE_ARRAY; - private byte[] verifyToken = EMPTY_BYTE_ARRAY; - private boolean shouldAuthenticate = true; + private final String serverId; + private final byte[] publicKey; + private final byte[] verifyToken; + private final boolean shouldAuthenticate; - public byte[] getPublicKey() { + public EncryptionRequestPacket(String serverId, byte[] publicKey, byte[] verifyToken, + boolean shouldAuthenticate) { + this.serverId = serverId; + this.publicKey = publicKey.clone(); + this.verifyToken = verifyToken.clone(); + this.shouldAuthenticate = shouldAuthenticate; + } + + public String serverId() { + return serverId; + } + + public byte[] publicKey() { return publicKey.clone(); } - public void setPublicKey(byte[] publicKey) { - this.publicKey = publicKey.clone(); - } - - public byte[] getVerifyToken() { + public byte[] verifyToken() { return verifyToken.clone(); } - public void setVerifyToken(byte[] verifyToken) { - this.verifyToken = verifyToken.clone(); + public boolean shouldAuthenticate() { + return shouldAuthenticate; + } + + public byte[] getPublicKey() { + return publicKey(); + } + + public byte[] getVerifyToken() { + return verifyToken(); } @Override @@ -57,40 +73,49 @@ public class EncryptionRequestPacket implements MinecraftPacket { + '}'; } - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - this.serverId = ProtocolUtils.readString(buf, 20); - - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) { - publicKey = ProtocolUtils.readByteArray(buf, 256); - verifyToken = ProtocolUtils.readByteArray(buf, 16); - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { - shouldAuthenticate = buf.readBoolean(); - } - } else { - publicKey = ProtocolUtils.readByteArray17(buf); - verifyToken = ProtocolUtils.readByteArray17(buf); - } - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - ProtocolUtils.writeString(buf, this.serverId); - - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) { - ProtocolUtils.writeByteArray(buf, publicKey); - ProtocolUtils.writeByteArray(buf, verifyToken); - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { - buf.writeBoolean(shouldAuthenticate); - } - } else { - ProtocolUtils.writeByteArray17(publicKey, buf, false); - ProtocolUtils.writeByteArray17(verifyToken, buf, false); - } - } - @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + public static class Codec implements PacketCodec { + @Override + public EncryptionRequestPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + String serverId = ProtocolUtils.readString(buf, 20); + byte[] publicKey; + byte[] verifyToken; + boolean shouldAuthenticate = true; + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) { + publicKey = ProtocolUtils.readByteArray(buf, 256); + verifyToken = ProtocolUtils.readByteArray(buf, 16); + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { + shouldAuthenticate = buf.readBoolean(); + } + } else { + publicKey = ProtocolUtils.readByteArray17(buf); + verifyToken = ProtocolUtils.readByteArray17(buf); + } + + return new EncryptionRequestPacket(serverId, publicKey, verifyToken, shouldAuthenticate); + } + + @Override + public void encode(EncryptionRequestPacket packet, ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + ProtocolUtils.writeString(buf, packet.serverId); + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) { + ProtocolUtils.writeByteArray(buf, packet.publicKey); + ProtocolUtils.writeByteArray(buf, packet.verifyToken); + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { + buf.writeBoolean(packet.shouldAuthenticate); + } + } else { + ProtocolUtils.writeByteArray17(packet.publicKey, buf, false); + ProtocolUtils.writeByteArray17(packet.verifyToken, buf, false); + } + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/EncryptionResponsePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/EncryptionResponsePacket.java index fedb67a5e..ccadb397a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/EncryptionResponsePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/EncryptionResponsePacket.java @@ -17,43 +17,60 @@ package com.velocitypowered.proxy.protocol.packet; -import static com.velocitypowered.proxy.connection.VelocityConstants.EMPTY_BYTE_ARRAY; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; -import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; import com.velocitypowered.proxy.util.except.QuietDecoderException; import io.netty.buffer.ByteBuf; import org.checkerframework.checker.nullness.qual.Nullable; import java.util.Arrays; -public class EncryptionResponsePacket implements MinecraftPacket { +public final class EncryptionResponsePacket implements MinecraftPacket { private static final QuietDecoderException NO_SALT = new QuietDecoderException( "Encryption response didn't contain salt"); - private byte[] sharedSecret = EMPTY_BYTE_ARRAY; - private byte[] verifyToken = EMPTY_BYTE_ARRAY; - private @Nullable Long salt; + private final byte[] sharedSecret; + private final byte[] verifyToken; + private final @Nullable Long salt; - public byte[] getSharedSecret() { + public EncryptionResponsePacket(byte[] sharedSecret, byte[] verifyToken, @Nullable Long salt) { + this.sharedSecret = sharedSecret.clone(); + this.verifyToken = verifyToken.clone(); + this.salt = salt; + } + + public byte[] sharedSecret() { return sharedSecret.clone(); } - public byte[] getVerifyToken() { + public byte[] verifyToken() { return verifyToken.clone(); } - public long getSalt() { + public long salt() { if (salt == null) { throw NO_SALT; } return salt; } + public byte[] getSharedSecret() { + return sharedSecret(); + } + + public byte[] getVerifyToken() { + return verifyToken(); + } + + public long getSalt() { + return salt(); + } + @Override public String toString() { return "EncryptionResponse{" @@ -62,73 +79,85 @@ public class EncryptionResponsePacket implements MinecraftPacket { + '}'; } - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) { - this.sharedSecret = ProtocolUtils.readByteArray(buf, 128); - - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19) - && version.lessThan(ProtocolVersion.MINECRAFT_1_19_3) - && !buf.readBoolean()) { - salt = buf.readLong(); - } - - this.verifyToken = ProtocolUtils.readByteArray(buf, - version.noLessThan(ProtocolVersion.MINECRAFT_1_19) ? 256 : 128); - } else { - this.sharedSecret = ProtocolUtils.readByteArray17(buf); - this.verifyToken = ProtocolUtils.readByteArray17(buf); - } - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) { - ProtocolUtils.writeByteArray(buf, sharedSecret); - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19) - && version.lessThan(ProtocolVersion.MINECRAFT_1_19_3)) { - if (salt != null) { - buf.writeBoolean(false); - buf.writeLong(salt); - } else { - buf.writeBoolean(true); - } - } - ProtocolUtils.writeByteArray(buf, verifyToken); - } else { - ProtocolUtils.writeByteArray17(sharedSecret, buf, false); - ProtocolUtils.writeByteArray17(verifyToken, buf, false); - } - } - @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } - @Override - public int decodeExpectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) { - // It turns out these come out to the same length, whether we're talking >=1.8 or not. - // The length prefix always winds up being 2 bytes. - int base = 256 + 2 + 2; - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_3)) { - return base + 128; - } - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) { - // Verify token is twice as long on 1.19+ - // Additional 1 byte for left <> right and 8 bytes for salt - base += 128 + 8 + 1; - } - return base; - } + public static class Codec implements PacketCodec { + @Override + public EncryptionResponsePacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + byte[] sharedSecret; + byte[] verifyToken; + Long salt = null; - @Override - public int decodeExpectedMinLength(ByteBuf buf, Direction direction, ProtocolVersion version) { - int base = decodeExpectedMaxLength(buf, direction, version); - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) { - // These are "optional" - base -= 128 + 8; + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) { + sharedSecret = ProtocolUtils.readByteArray(buf, 128); + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19) + && version.lessThan(ProtocolVersion.MINECRAFT_1_19_3) + && !buf.readBoolean()) { + salt = buf.readLong(); + } + + verifyToken = ProtocolUtils.readByteArray(buf, + version.noLessThan(ProtocolVersion.MINECRAFT_1_19) ? 256 : 128); + } else { + sharedSecret = ProtocolUtils.readByteArray17(buf); + verifyToken = ProtocolUtils.readByteArray17(buf); + } + + return new EncryptionResponsePacket(sharedSecret, verifyToken, salt); + } + + @Override + public void encode(EncryptionResponsePacket packet, ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) { + ProtocolUtils.writeByteArray(buf, packet.sharedSecret); + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19) + && version.lessThan(ProtocolVersion.MINECRAFT_1_19_3)) { + if (packet.salt != null) { + buf.writeBoolean(false); + buf.writeLong(packet.salt); + } else { + buf.writeBoolean(true); + } + } + ProtocolUtils.writeByteArray(buf, packet.verifyToken); + } else { + ProtocolUtils.writeByteArray17(packet.sharedSecret, buf, false); + ProtocolUtils.writeByteArray17(packet.verifyToken, buf, false); + } + } + + @Override + public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + // It turns out these come out to the same length, whether we're talking >=1.8 or not. + // The length prefix always winds up being 2 bytes. + int base = 256 + 2 + 2; + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_3)) { + return base + 128; + } + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) { + // Verify token is twice as long on 1.19+ + // Additional 1 byte for left <> right and 8 bytes for salt + base += 128 + 8 + 1; + } + return base; + } + + @Override + public int decodeExpectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + int base = decodeExpectedMaxLength(buf, direction, version); + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) { + // These are "optional" + base -= 128 + 8; + } + return base; } - return base; } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HandshakePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HandshakePacket.java index 88cb3688b..38abc1999 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HandshakePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HandshakePacket.java @@ -23,58 +23,71 @@ import com.velocitypowered.api.network.HandshakeIntent; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; -import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; import io.netty.buffer.ByteBuf; -public class HandshakePacket implements MinecraftPacket { +public final class HandshakePacket implements MinecraftPacket { // This size was chosen to ensure Forge clients can still connect even with very long hostnames. // While DNS technically allows any character to be used, in practice ASCII is used. private static final int MAXIMUM_HOSTNAME_LENGTH = 255 + HANDSHAKE_HOSTNAME_TOKEN.length() + 1; - private ProtocolVersion protocolVersion; - private String serverAddress = ""; - private int port; - private HandshakeIntent intent; - private int nextStatus; - public ProtocolVersion getProtocolVersion() { - return protocolVersion; + private final ProtocolVersion protocolVersion; + private final String serverAddress; + private final int port; + private final HandshakeIntent intent; + private final int nextStatus; + + public HandshakePacket() { + this(ProtocolVersion.MINIMUM_VERSION, "", 0, HandshakeIntent.LOGIN); } - public void setProtocolVersion(ProtocolVersion protocolVersion) { + public HandshakePacket(ProtocolVersion protocolVersion, String serverAddress, int port, + HandshakeIntent intent) { this.protocolVersion = protocolVersion; - } - - public String getServerAddress() { - return serverAddress; - } - - public void setServerAddress(String serverAddress) { this.serverAddress = serverAddress; - } - - public int getPort() { - return port; - } - - public void setPort(int port) { this.port = port; - } - - public int getNextStatus() { - return this.nextStatus; - } - - public void setIntent(HandshakeIntent intent) { this.intent = intent; this.nextStatus = intent.id(); } - public HandshakeIntent getIntent() { + public ProtocolVersion protocolVersion() { + return protocolVersion; + } + + public String serverAddress() { + return serverAddress; + } + + public int port() { + return port; + } + + public int nextStatus() { + return this.nextStatus; + } + + public HandshakeIntent intent() { return this.intent; } + public ProtocolVersion getProtocolVersion() { + return protocolVersion(); + } + + public String getServerAddress() { + return serverAddress(); + } + + public int getPort() { + return port(); + } + + public HandshakeIntent getIntent() { + return intent(); + } + @Override public String toString() { return "Handshake{" @@ -85,45 +98,50 @@ public class HandshakePacket implements MinecraftPacket { + '}'; } - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion ignored) { - int realProtocolVersion = ProtocolUtils.readVarInt(buf); - this.protocolVersion = ProtocolVersion.getProtocolVersion(realProtocolVersion); - this.serverAddress = ProtocolUtils.readString(buf, MAXIMUM_HOSTNAME_LENGTH); - this.port = buf.readUnsignedShort(); - this.nextStatus = ProtocolUtils.readVarInt(buf); - this.intent = HandshakeIntent.getById(nextStatus); - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion ignored) { - ProtocolUtils.writeVarInt(buf, this.protocolVersion.getProtocol()); - ProtocolUtils.writeString(buf, this.serverAddress); - buf.writeShort(this.port); - ProtocolUtils.writeVarInt(buf, this.nextStatus); - } - @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } - @Override - public int decodeExpectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion version) { - return 7; - } + public static class Codec implements PacketCodec { + @Override + public HandshakePacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion ignored) { + int realProtocolVersion = ProtocolUtils.readVarInt(buf); + ProtocolVersion protocolVersion = ProtocolVersion.getProtocolVersion(realProtocolVersion); + String serverAddress = ProtocolUtils.readString(buf, MAXIMUM_HOSTNAME_LENGTH); + int port = buf.readUnsignedShort(); + int nextStatus = ProtocolUtils.readVarInt(buf); + HandshakeIntent intent = HandshakeIntent.getById(nextStatus); + return new HandshakePacket(protocolVersion, serverAddress, port, intent); + } - @Override - public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion version) { - return 9 + (MAXIMUM_HOSTNAME_LENGTH * 3); - } + @Override + public void encode(HandshakePacket packet, ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion ignored) { + ProtocolUtils.writeVarInt(buf, packet.protocolVersion.getProtocol()); + ProtocolUtils.writeString(buf, packet.serverAddress); + buf.writeShort(packet.port); + ProtocolUtils.writeVarInt(buf, packet.nextStatus); + } - @Override - public int encodeSizeHint(Direction direction, ProtocolVersion version) { - // We could compute an exact size, but 4KiB ought to be enough to encode all reasonable - // sizes of this packet. - return 4 * 1024; + @Override + public int decodeExpectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + return 7; + } + + @Override + public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + return 9 + (MAXIMUM_HOSTNAME_LENGTH * 3); + } + + @Override + public int encodeSizeHint(HandshakePacket packet, ProtocolUtils.Direction direction, ProtocolVersion version) { + // We could compute an exact size, but 4KiB ought to be enough to encode all reasonable + // sizes of this packet. + return 4 * 1024; + } } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HeaderAndFooterPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HeaderAndFooterPacket.java index 339d026f8..619fed85e 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HeaderAndFooterPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/HeaderAndFooterPacket.java @@ -21,42 +21,18 @@ import com.google.common.base.Preconditions; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import io.netty.buffer.ByteBuf; import net.kyori.adventure.text.Component; -public class HeaderAndFooterPacket implements MinecraftPacket { +public record HeaderAndFooterPacket(ComponentHolder header, + ComponentHolder footer) implements MinecraftPacket { - private final ComponentHolder header; - private final ComponentHolder footer; - - public HeaderAndFooterPacket() { - throw new UnsupportedOperationException("Decode is not implemented"); - } - - public HeaderAndFooterPacket(ComponentHolder header, ComponentHolder footer) { - this.header = Preconditions.checkNotNull(header, "header"); - this.footer = Preconditions.checkNotNull(footer, "footer"); - } - - public ComponentHolder getHeader() { - return header; - } - - public ComponentHolder getFooter() { - return footer; - } - - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - throw new UnsupportedOperationException("Decode is not implemented"); - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - header.write(buf); - footer.write(buf); + public HeaderAndFooterPacket { + Preconditions.checkNotNull(header, "header"); + Preconditions.checkNotNull(footer, "footer"); } @Override @@ -74,4 +50,19 @@ public class HeaderAndFooterPacket implements MinecraftPacket { ComponentHolder empty = new ComponentHolder(version, Component.empty()); return new HeaderAndFooterPacket(empty, empty); } -} \ No newline at end of file + + public static class Codec implements PacketCodec { + @Override + public HeaderAndFooterPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + throw new UnsupportedOperationException("Decode is not implemented"); + } + + @Override + public void encode(HeaderAndFooterPacket packet, ByteBuf buf, + ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + packet.header.write(buf); + packet.footer.write(buf); + } + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGamePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGamePacket.java index 787d858eb..cfaa46524 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGamePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGamePacket.java @@ -22,62 +22,83 @@ import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.registry.DimensionInfo; import com.velocitypowered.proxy.protocol.*; +import com.velocitypowered.proxy.protocol.PacketCodec; import io.netty.buffer.ByteBuf; import it.unimi.dsi.fastutil.Pair; import net.kyori.adventure.nbt.BinaryTagIO; import net.kyori.adventure.nbt.CompoundBinaryTag; import org.checkerframework.checker.nullness.qual.Nullable; -public class JoinGamePacket implements MinecraftPacket { +public final class JoinGamePacket implements MinecraftPacket { private static final BinaryTagIO.Reader JOINGAME_READER = BinaryTagIO.reader(4 * 1024 * 1024); - private int entityId; - private short gamemode; - private int dimension; - private long partialHashedSeed; // 1.15+ - private short difficulty; - private boolean isHardcore; - private int maxPlayers; - private @Nullable String levelType; - private int viewDistance; // 1.14+ - private boolean reducedDebugInfo; - private boolean showRespawnScreen; - private boolean doLimitedCrafting; // 1.20.2+ - private ImmutableSet levelNames; // 1.16+ - private CompoundBinaryTag registry; // 1.16+ - private DimensionInfo dimensionInfo; // 1.16+ - private CompoundBinaryTag currentDimensionData; // 1.16.2+ - private short previousGamemode; // 1.16+ - private int simulationDistance; // 1.18+ - private @Nullable Pair lastDeathPosition; // 1.19+ - private int portalCooldown; // 1.20+ - private int seaLevel; // 1.21.2+ - private boolean enforcesSecureChat; // 1.20.5+ + + private final int entityId; + private final short gamemode; + private final int dimension; + private final long partialHashedSeed; + private final short difficulty; + private final boolean isHardcore; + private final int maxPlayers; + private final @Nullable String levelType; + private final int viewDistance; + private final boolean reducedDebugInfo; + private final boolean showRespawnScreen; + private final boolean doLimitedCrafting; + private final ImmutableSet levelNames; + private final CompoundBinaryTag registry; + private final DimensionInfo dimensionInfo; + private final CompoundBinaryTag currentDimensionData; + private final short previousGamemode; + private final int simulationDistance; + private final @Nullable Pair lastDeathPosition; + private final int portalCooldown; + private final int seaLevel; + private final boolean enforcesSecureChat; + + public JoinGamePacket(int entityId, short gamemode, int dimension, long partialHashedSeed, + short difficulty, boolean isHardcore, int maxPlayers, @Nullable String levelType, + int viewDistance, boolean reducedDebugInfo, boolean showRespawnScreen, + boolean doLimitedCrafting, ImmutableSet levelNames, CompoundBinaryTag registry, + DimensionInfo dimensionInfo, CompoundBinaryTag currentDimensionData, short previousGamemode, + int simulationDistance, @Nullable Pair lastDeathPosition, int portalCooldown, + int seaLevel, boolean enforcesSecureChat) { + this.entityId = entityId; + this.gamemode = gamemode; + this.dimension = dimension; + this.partialHashedSeed = partialHashedSeed; + this.difficulty = difficulty; + this.isHardcore = isHardcore; + this.maxPlayers = maxPlayers; + this.levelType = levelType; + this.viewDistance = viewDistance; + this.reducedDebugInfo = reducedDebugInfo; + this.showRespawnScreen = showRespawnScreen; + this.doLimitedCrafting = doLimitedCrafting; + this.levelNames = levelNames; + this.registry = registry; + this.dimensionInfo = dimensionInfo; + this.currentDimensionData = currentDimensionData; + this.previousGamemode = previousGamemode; + this.simulationDistance = simulationDistance; + this.lastDeathPosition = lastDeathPosition; + this.portalCooldown = portalCooldown; + this.seaLevel = seaLevel; + this.enforcesSecureChat = enforcesSecureChat; + } public int getEntityId() { return entityId; } - public void setEntityId(int entityId) { - this.entityId = entityId; - } - public short getGamemode() { return gamemode; } - public void setGamemode(short gamemode) { - this.gamemode = gamemode; - } - public int getDimension() { return dimension; } - public void setDimension(int dimension) { - this.dimension = dimension; - } - public long getPartialHashedSeed() { return partialHashedSeed; } @@ -86,74 +107,38 @@ public class JoinGamePacket implements MinecraftPacket { return difficulty; } - public void setDifficulty(short difficulty) { - this.difficulty = difficulty; - } - public int getMaxPlayers() { return maxPlayers; } - public void setMaxPlayers(int maxPlayers) { - this.maxPlayers = maxPlayers; - } - public @Nullable String getLevelType() { return levelType; } - public void setLevelType(@Nullable String levelType) { - this.levelType = levelType; - } - public int getViewDistance() { return viewDistance; } - public void setViewDistance(int viewDistance) { - this.viewDistance = viewDistance; - } - public boolean isReducedDebugInfo() { return reducedDebugInfo; } - public void setReducedDebugInfo(boolean reducedDebugInfo) { - this.reducedDebugInfo = reducedDebugInfo; - } - public DimensionInfo getDimensionInfo() { return dimensionInfo; } - public void setDimensionInfo(DimensionInfo dimensionInfo) { - this.dimensionInfo = dimensionInfo; - } - public short getPreviousGamemode() { return previousGamemode; } - public void setPreviousGamemode(short previousGamemode) { - this.previousGamemode = previousGamemode; - } - public boolean getIsHardcore() { return isHardcore; } - public void setIsHardcore(boolean isHardcore) { - this.isHardcore = isHardcore; - } - public boolean getDoLimitedCrafting() { return doLimitedCrafting; } - public void setDoLimitedCrafting(boolean doLimitedCrafting) { - this.doLimitedCrafting = doLimitedCrafting; - } - public CompoundBinaryTag getCurrentDimensionData() { return currentDimensionData; } @@ -162,46 +147,34 @@ public class JoinGamePacket implements MinecraftPacket { return simulationDistance; } - public void setSimulationDistance(int simulationDistance) { - this.simulationDistance = simulationDistance; - } - public Pair getLastDeathPosition() { return lastDeathPosition; } - public void setLastDeathPosition(Pair lastDeathPosition) { - this.lastDeathPosition = lastDeathPosition; - } - public int getPortalCooldown() { return portalCooldown; } - public void setPortalCooldown(int portalCooldown) { - this.portalCooldown = portalCooldown; - } - public int getSeaLevel() { return seaLevel; } - public void setSeaLevel(int seaLevel) { - this.seaLevel = seaLevel; - } - public boolean getEnforcesSecureChat() { return this.enforcesSecureChat; } - public void setEnforcesSecureChat(final boolean enforcesSecureChat) { - this.enforcesSecureChat = enforcesSecureChat; - } - public CompoundBinaryTag getRegistry() { return registry; } + public boolean getShowRespawnScreen() { + return showRespawnScreen; + } + + public ImmutableSet getLevelNames() { + return levelNames; + } + @Override public String toString() { return "JoinGame{" + "entityId=" + entityId + ", gamemode=" + gamemode + ", dimension=" + @@ -217,306 +190,349 @@ public class JoinGamePacket implements MinecraftPacket { '}'; } - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) { - // haha funny, they made 1.20.2 more complicated - this.decode1202Up(buf, version); - } else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) { - // Minecraft 1.16 and above have significantly more complicated logic for reading this packet, - // so separate it out. - this.decode116Up(buf, version); - } else { - this.decodeLegacy(buf, version); - } - } - - private void decodeLegacy(ByteBuf buf, ProtocolVersion version) { - this.entityId = buf.readInt(); - this.gamemode = buf.readByte(); - this.isHardcore = (this.gamemode & 0x08) != 0; - this.gamemode &= ~0x08; - - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_9_1)) { - this.dimension = buf.readInt(); - } else { - this.dimension = buf.readByte(); - } - if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_13_2)) { - this.difficulty = buf.readUnsignedByte(); - } - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_15)) { - this.partialHashedSeed = buf.readLong(); - } - this.maxPlayers = buf.readUnsignedByte(); - this.levelType = ProtocolUtils.readString(buf, 16); - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_14)) { - this.viewDistance = ProtocolUtils.readVarInt(buf); - } - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) { - this.reducedDebugInfo = buf.readBoolean(); - } - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_15)) { - this.showRespawnScreen = buf.readBoolean(); - } - } - - private void decode116Up(ByteBuf buf, ProtocolVersion version) { - this.entityId = buf.readInt(); - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2)) { - this.isHardcore = buf.readBoolean(); - this.gamemode = buf.readByte(); - } else { - this.gamemode = buf.readByte(); - this.isHardcore = (this.gamemode & 0x08) != 0; - this.gamemode &= ~0x08; - } - this.previousGamemode = buf.readByte(); - - this.levelNames = ImmutableSet.copyOf(ProtocolUtils.readStringArray(buf)); - this.registry = ProtocolUtils.readCompoundTag(buf, version, JOINGAME_READER); - String dimensionIdentifier; - String levelName = null; - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2) - && version.lessThan(ProtocolVersion.MINECRAFT_1_19)) { - this.currentDimensionData = ProtocolUtils.readCompoundTag(buf, version, JOINGAME_READER); - dimensionIdentifier = ProtocolUtils.readString(buf); - } else { - dimensionIdentifier = ProtocolUtils.readString(buf); - levelName = ProtocolUtils.readString(buf); - } - - this.partialHashedSeed = buf.readLong(); - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2)) { - this.maxPlayers = ProtocolUtils.readVarInt(buf); - } else { - this.maxPlayers = buf.readUnsignedByte(); - } - - this.viewDistance = ProtocolUtils.readVarInt(buf); - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_18)) { - this.simulationDistance = ProtocolUtils.readVarInt(buf); - } - - this.reducedDebugInfo = buf.readBoolean(); - this.showRespawnScreen = buf.readBoolean(); - - boolean isDebug = buf.readBoolean(); - boolean isFlat = buf.readBoolean(); - this.dimensionInfo = new DimensionInfo(dimensionIdentifier, levelName, isFlat, isDebug, version); - - // optional death location - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19) && buf.readBoolean()) { - this.lastDeathPosition = Pair.of(ProtocolUtils.readString(buf), buf.readLong()); - } - - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20)) { - this.portalCooldown = ProtocolUtils.readVarInt(buf); - } - } - - @SuppressWarnings("checkstyle:VariableDeclarationUsageDistance") - private void decode1202Up(ByteBuf buf, ProtocolVersion version) { - this.entityId = buf.readInt(); - this.isHardcore = buf.readBoolean(); - - this.levelNames = ImmutableSet.copyOf(ProtocolUtils.readStringArray(buf)); - - this.maxPlayers = ProtocolUtils.readVarInt(buf); - - this.viewDistance = ProtocolUtils.readVarInt(buf); - this.simulationDistance = ProtocolUtils.readVarInt(buf); - - this.reducedDebugInfo = buf.readBoolean(); - this.showRespawnScreen = buf.readBoolean(); - this.doLimitedCrafting = buf.readBoolean(); - - String dimensionKey = ""; - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { - dimension = ProtocolUtils.readVarInt(buf); - } else { - dimensionKey = ProtocolUtils.readString(buf); - } - String levelName = ProtocolUtils.readString(buf); - this.partialHashedSeed = buf.readLong(); - - this.gamemode = buf.readByte(); - this.previousGamemode = buf.readByte(); - - boolean isDebug = buf.readBoolean(); - boolean isFlat = buf.readBoolean(); - this.dimensionInfo = new DimensionInfo(dimensionKey, levelName, isFlat, isDebug, version); - - // optional death location - if (buf.readBoolean()) { - this.lastDeathPosition = Pair.of(ProtocolUtils.readString(buf), buf.readLong()); - } - - this.portalCooldown = ProtocolUtils.readVarInt(buf); - - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) { - this.seaLevel = ProtocolUtils.readVarInt(buf); - } - - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { - this.enforcesSecureChat = buf.readBoolean(); - } - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) { - // haha funny, they made 1.20.2 more complicated - this.encode1202Up(buf, version); - } else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) { - // Minecraft 1.16 and above have significantly more complicated logic for reading this packet, - // so separate it out. - this.encode116Up(buf, version); - } else { - this.encodeLegacy(buf, version); - } - } - - private void encodeLegacy(ByteBuf buf, ProtocolVersion version) { - buf.writeInt(entityId); - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2)) { - buf.writeBoolean(isHardcore); - buf.writeByte(gamemode); - } else { - buf.writeByte(isHardcore ? gamemode | 0x8 : gamemode); - } - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_9_1)) { - buf.writeInt(dimension); - } else { - buf.writeByte(dimension); - } - if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_13_2)) { - buf.writeByte(difficulty); - } - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_15)) { - buf.writeLong(partialHashedSeed); - } - buf.writeByte(maxPlayers); - if (levelType == null) { - throw new IllegalStateException("No level type specified."); - } - ProtocolUtils.writeString(buf, levelType); - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_14)) { - ProtocolUtils.writeVarInt(buf, viewDistance); - } - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) { - buf.writeBoolean(reducedDebugInfo); - } - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_15)) { - buf.writeBoolean(showRespawnScreen); - } - } - - private void encode116Up(ByteBuf buf, ProtocolVersion version) { - buf.writeInt(entityId); - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2)) { - buf.writeBoolean(isHardcore); - buf.writeByte(gamemode); - } else { - buf.writeByte(isHardcore ? gamemode | 0x8 : gamemode); - } - buf.writeByte(previousGamemode); - - ProtocolUtils.writeStringArray(buf, levelNames.toArray(String[]::new)); - ProtocolUtils.writeBinaryTag(buf, version, this.registry); - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2) && version.lessThan(ProtocolVersion.MINECRAFT_1_19)) { - ProtocolUtils.writeBinaryTag(buf, version, currentDimensionData); - ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier()); - } else { - ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier()); - ProtocolUtils.writeString(buf, dimensionInfo.getLevelName()); - } - - buf.writeLong(partialHashedSeed); - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2)) { - ProtocolUtils.writeVarInt(buf, maxPlayers); - } else { - buf.writeByte(maxPlayers); - } - - ProtocolUtils.writeVarInt(buf, viewDistance); - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_18)) { - ProtocolUtils.writeVarInt(buf, simulationDistance); - } - - buf.writeBoolean(reducedDebugInfo); - buf.writeBoolean(showRespawnScreen); - - buf.writeBoolean(dimensionInfo.isDebugType()); - buf.writeBoolean(dimensionInfo.isFlat()); - - // optional death location - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) { - if (lastDeathPosition != null) { - buf.writeBoolean(true); - ProtocolUtils.writeString(buf, lastDeathPosition.key()); - buf.writeLong(lastDeathPosition.value()); - } else { - buf.writeBoolean(false); - } - } - - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20)) { - ProtocolUtils.writeVarInt(buf, portalCooldown); - } - } - - private void encode1202Up(ByteBuf buf, ProtocolVersion version) { - buf.writeInt(entityId); - buf.writeBoolean(isHardcore); - - ProtocolUtils.writeStringArray(buf, levelNames.toArray(String[]::new)); - - ProtocolUtils.writeVarInt(buf, maxPlayers); - - ProtocolUtils.writeVarInt(buf, viewDistance); - ProtocolUtils.writeVarInt(buf, simulationDistance); - - buf.writeBoolean(reducedDebugInfo); - buf.writeBoolean(showRespawnScreen); - buf.writeBoolean(doLimitedCrafting); - - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { - ProtocolUtils.writeVarInt(buf, dimension); - } else { - ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier()); - } - ProtocolUtils.writeString(buf, dimensionInfo.getLevelName()); - buf.writeLong(partialHashedSeed); - - buf.writeByte(gamemode); - buf.writeByte(previousGamemode); - - buf.writeBoolean(dimensionInfo.isDebugType()); - buf.writeBoolean(dimensionInfo.isFlat()); - - // optional death location - if (lastDeathPosition != null) { - buf.writeBoolean(true); - ProtocolUtils.writeString(buf, lastDeathPosition.key()); - buf.writeLong(lastDeathPosition.value()); - } else { - buf.writeBoolean(false); - } - - ProtocolUtils.writeVarInt(buf, portalCooldown); - - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) { - ProtocolUtils.writeVarInt(buf, seaLevel); - } - - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { - buf.writeBoolean(this.enforcesSecureChat); - } - } - @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + public static class Codec implements PacketCodec { + @Override + public JoinGamePacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) { + // haha funny, they made 1.20.2 more complicated + return decode1202Up(buf, version); + } else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) { + // Minecraft 1.16 and above have significantly more complicated logic for reading this packet, + // so separate it out. + return decode116Up(buf, version); + } else { + return decodeLegacy(buf, version); + } + } + + @Override + public void encode(JoinGamePacket packet, ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) { + // haha funny, they made 1.20.2 more complicated + encode1202Up(packet, buf, version); + } else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) { + // Minecraft 1.16 and above have significantly more complicated logic for reading this packet, + // so separate it out. + encode116Up(packet, buf, version); + } else { + encodeLegacy(packet, buf, version); + } + } + + private static JoinGamePacket decodeLegacy(ByteBuf buf, ProtocolVersion version) { + int entityId = buf.readInt(); + short gamemode = buf.readByte(); + boolean isHardcore = (gamemode & 0x08) != 0; + gamemode &= ~0x08; + + int dimension; + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_9_1)) { + dimension = buf.readInt(); + } else { + dimension = buf.readByte(); + } + + short difficulty = 0; + if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_13_2)) { + difficulty = buf.readUnsignedByte(); + } + + long partialHashedSeed = 0; + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_15)) { + partialHashedSeed = buf.readLong(); + } + + int maxPlayers = buf.readUnsignedByte(); + String levelType = ProtocolUtils.readString(buf, 16); + + int viewDistance = 0; + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_14)) { + viewDistance = ProtocolUtils.readVarInt(buf); + } + + boolean reducedDebugInfo = false; + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) { + reducedDebugInfo = buf.readBoolean(); + } + + boolean showRespawnScreen = true; + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_15)) { + showRespawnScreen = buf.readBoolean(); + } + + return new JoinGamePacket(entityId, gamemode, dimension, partialHashedSeed, difficulty, + isHardcore, maxPlayers, levelType, viewDistance, reducedDebugInfo, showRespawnScreen, + false, ImmutableSet.of(), null, null, null, (short) 0, 0, null, 0, 0, false); + } + + private static JoinGamePacket decode116Up(ByteBuf buf, ProtocolVersion version) { + int entityId = buf.readInt(); + boolean isHardcore; + short gamemode; + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2)) { + isHardcore = buf.readBoolean(); + gamemode = buf.readByte(); + } else { + gamemode = buf.readByte(); + isHardcore = (gamemode & 0x08) != 0; + gamemode &= ~0x08; + } + short previousGamemode = buf.readByte(); + + ImmutableSet levelNames = ImmutableSet.copyOf(ProtocolUtils.readStringArray(buf)); + CompoundBinaryTag registry = ProtocolUtils.readCompoundTag(buf, version, JOINGAME_READER); + + String dimensionIdentifier; + String levelName = null; + CompoundBinaryTag currentDimensionData = null; + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2) + && version.lessThan(ProtocolVersion.MINECRAFT_1_19)) { + currentDimensionData = ProtocolUtils.readCompoundTag(buf, version, JOINGAME_READER); + dimensionIdentifier = ProtocolUtils.readString(buf); + } else { + dimensionIdentifier = ProtocolUtils.readString(buf); + levelName = ProtocolUtils.readString(buf); + } + + long partialHashedSeed = buf.readLong(); + + int maxPlayers; + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2)) { + maxPlayers = ProtocolUtils.readVarInt(buf); + } else { + maxPlayers = buf.readUnsignedByte(); + } + + int viewDistance = ProtocolUtils.readVarInt(buf); + + int simulationDistance = 0; + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_18)) { + simulationDistance = ProtocolUtils.readVarInt(buf); + } + + boolean reducedDebugInfo = buf.readBoolean(); + boolean showRespawnScreen = buf.readBoolean(); + + boolean isDebug = buf.readBoolean(); + boolean isFlat = buf.readBoolean(); + DimensionInfo dimensionInfo = new DimensionInfo(dimensionIdentifier, levelName, isFlat, isDebug, version); + + Pair lastDeathPosition = null; + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19) && buf.readBoolean()) { + lastDeathPosition = Pair.of(ProtocolUtils.readString(buf), buf.readLong()); + } + + int portalCooldown = 0; + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20)) { + portalCooldown = ProtocolUtils.readVarInt(buf); + } + + return new JoinGamePacket(entityId, gamemode, 0, partialHashedSeed, (short) 0, isHardcore, + maxPlayers, null, viewDistance, reducedDebugInfo, showRespawnScreen, false, levelNames, + registry, dimensionInfo, currentDimensionData, previousGamemode, simulationDistance, + lastDeathPosition, portalCooldown, 0, false); + } + + @SuppressWarnings("checkstyle:VariableDeclarationUsageDistance") + private static JoinGamePacket decode1202Up(ByteBuf buf, ProtocolVersion version) { + int entityId = buf.readInt(); + boolean isHardcore = buf.readBoolean(); + + ImmutableSet levelNames = ImmutableSet.copyOf(ProtocolUtils.readStringArray(buf)); + + int maxPlayers = ProtocolUtils.readVarInt(buf); + + int viewDistance = ProtocolUtils.readVarInt(buf); + int simulationDistance = ProtocolUtils.readVarInt(buf); + + boolean reducedDebugInfo = buf.readBoolean(); + boolean showRespawnScreen = buf.readBoolean(); + boolean doLimitedCrafting = buf.readBoolean(); + + int dimension = 0; + String dimensionKey = ""; + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { + dimension = ProtocolUtils.readVarInt(buf); + } else { + dimensionKey = ProtocolUtils.readString(buf); + } + String levelName = ProtocolUtils.readString(buf); + long partialHashedSeed = buf.readLong(); + + short gamemode = buf.readByte(); + short previousGamemode = buf.readByte(); + + boolean isDebug = buf.readBoolean(); + boolean isFlat = buf.readBoolean(); + DimensionInfo dimensionInfo = new DimensionInfo(dimensionKey, levelName, isFlat, isDebug, version); + + Pair lastDeathPosition = null; + if (buf.readBoolean()) { + lastDeathPosition = Pair.of(ProtocolUtils.readString(buf), buf.readLong()); + } + + int portalCooldown = ProtocolUtils.readVarInt(buf); + + int seaLevel = 0; + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) { + seaLevel = ProtocolUtils.readVarInt(buf); + } + + boolean enforcesSecureChat = false; + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { + enforcesSecureChat = buf.readBoolean(); + } + + return new JoinGamePacket(entityId, gamemode, dimension, partialHashedSeed, (short) 0, + isHardcore, maxPlayers, null, viewDistance, reducedDebugInfo, showRespawnScreen, + doLimitedCrafting, levelNames, null, dimensionInfo, null, previousGamemode, + simulationDistance, lastDeathPosition, portalCooldown, seaLevel, enforcesSecureChat); + } + + private static void encodeLegacy(JoinGamePacket packet, ByteBuf buf, ProtocolVersion version) { + buf.writeInt(packet.entityId); + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2)) { + buf.writeBoolean(packet.isHardcore); + buf.writeByte(packet.gamemode); + } else { + buf.writeByte(packet.isHardcore ? packet.gamemode | 0x8 : packet.gamemode); + } + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_9_1)) { + buf.writeInt(packet.dimension); + } else { + buf.writeByte(packet.dimension); + } + if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_13_2)) { + buf.writeByte(packet.difficulty); + } + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_15)) { + buf.writeLong(packet.partialHashedSeed); + } + buf.writeByte(packet.maxPlayers); + if (packet.levelType == null) { + throw new IllegalStateException("No level type specified."); + } + ProtocolUtils.writeString(buf, packet.levelType); + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_14)) { + ProtocolUtils.writeVarInt(buf, packet.viewDistance); + } + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) { + buf.writeBoolean(packet.reducedDebugInfo); + } + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_15)) { + buf.writeBoolean(packet.showRespawnScreen); + } + } + + private static void encode116Up(JoinGamePacket packet, ByteBuf buf, ProtocolVersion version) { + buf.writeInt(packet.entityId); + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2)) { + buf.writeBoolean(packet.isHardcore); + buf.writeByte(packet.gamemode); + } else { + buf.writeByte(packet.isHardcore ? packet.gamemode | 0x8 : packet.gamemode); + } + buf.writeByte(packet.previousGamemode); + + ProtocolUtils.writeStringArray(buf, packet.levelNames.toArray(String[]::new)); + ProtocolUtils.writeBinaryTag(buf, version, packet.registry); + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2) && version.lessThan(ProtocolVersion.MINECRAFT_1_19)) { + ProtocolUtils.writeBinaryTag(buf, version, packet.currentDimensionData); + ProtocolUtils.writeString(buf, packet.dimensionInfo.getRegistryIdentifier()); + } else { + ProtocolUtils.writeString(buf, packet.dimensionInfo.getRegistryIdentifier()); + ProtocolUtils.writeString(buf, packet.dimensionInfo.getLevelName()); + } + + buf.writeLong(packet.partialHashedSeed); + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2)) { + ProtocolUtils.writeVarInt(buf, packet.maxPlayers); + } else { + buf.writeByte(packet.maxPlayers); + } + + ProtocolUtils.writeVarInt(buf, packet.viewDistance); + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_18)) { + ProtocolUtils.writeVarInt(buf, packet.simulationDistance); + } + + buf.writeBoolean(packet.reducedDebugInfo); + buf.writeBoolean(packet.showRespawnScreen); + + buf.writeBoolean(packet.dimensionInfo.isDebugType()); + buf.writeBoolean(packet.dimensionInfo.isFlat()); + + // optional death location + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) { + if (packet.lastDeathPosition != null) { + buf.writeBoolean(true); + ProtocolUtils.writeString(buf, packet.lastDeathPosition.key()); + buf.writeLong(packet.lastDeathPosition.value()); + } else { + buf.writeBoolean(false); + } + } + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20)) { + ProtocolUtils.writeVarInt(buf, packet.portalCooldown); + } + } + + private static void encode1202Up(JoinGamePacket packet, ByteBuf buf, ProtocolVersion version) { + buf.writeInt(packet.entityId); + buf.writeBoolean(packet.isHardcore); + + ProtocolUtils.writeStringArray(buf, packet.levelNames.toArray(String[]::new)); + + ProtocolUtils.writeVarInt(buf, packet.maxPlayers); + + ProtocolUtils.writeVarInt(buf, packet.viewDistance); + ProtocolUtils.writeVarInt(buf, packet.simulationDistance); + + buf.writeBoolean(packet.reducedDebugInfo); + buf.writeBoolean(packet.showRespawnScreen); + buf.writeBoolean(packet.doLimitedCrafting); + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { + ProtocolUtils.writeVarInt(buf, packet.dimension); + } else { + ProtocolUtils.writeString(buf, packet.dimensionInfo.getRegistryIdentifier()); + } + ProtocolUtils.writeString(buf, packet.dimensionInfo.getLevelName()); + buf.writeLong(packet.partialHashedSeed); + + buf.writeByte(packet.gamemode); + buf.writeByte(packet.previousGamemode); + + buf.writeBoolean(packet.dimensionInfo.isDebugType()); + buf.writeBoolean(packet.dimensionInfo.isFlat()); + + // optional death location + if (packet.lastDeathPosition != null) { + buf.writeBoolean(true); + ProtocolUtils.writeString(buf, packet.lastDeathPosition.key()); + buf.writeLong(packet.lastDeathPosition.value()); + } else { + buf.writeBoolean(false); + } + + ProtocolUtils.writeVarInt(buf, packet.portalCooldown); + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) { + ProtocolUtils.writeVarInt(buf, packet.seaLevel); + } + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { + buf.writeBoolean(packet.enforcesSecureChat); + } + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/KeepAlivePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/KeepAlivePacket.java index a44e50eea..d514b1a01 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/KeepAlivePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/KeepAlivePacket.java @@ -20,21 +20,16 @@ package com.velocitypowered.proxy.protocol.packet; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; -public class KeepAlivePacket implements MinecraftPacket { - - private long randomId; +public record KeepAlivePacket(long randomId) implements MinecraftPacket { public long getRandomId() { return randomId; } - public void setRandomId(long randomId) { - this.randomId = randomId; - } - @Override public String toString() { return "KeepAlive{" @@ -42,30 +37,36 @@ public class KeepAlivePacket implements MinecraftPacket { + '}'; } - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_12_2)) { - randomId = buf.readLong(); - } else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) { - randomId = ProtocolUtils.readVarInt(buf); - } else { - randomId = buf.readInt(); - } - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_12_2)) { - buf.writeLong(randomId); - } else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) { - ProtocolUtils.writeVarInt(buf, (int) randomId); - } else { - buf.writeInt((int) randomId); - } - } - @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + public static class Codec implements PacketCodec { + @Override + public KeepAlivePacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + long randomId; + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_12_2)) { + randomId = buf.readLong(); + } else if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_8)) { + randomId = ProtocolUtils.readVarInt(buf); + } else { + randomId = buf.readInt(); + } + return new KeepAlivePacket(randomId); + } + + @Override + public void encode(KeepAlivePacket packet, ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_12_2)) { + buf.writeLong(packet.randomId); + } else if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_8)) { + ProtocolUtils.writeVarInt(buf, (int) packet.randomId); + } else { + buf.writeInt((int) packet.randomId); + } + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyHandshakePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyHandshakePacket.java index 38483ed86..4dfbfbb81 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyHandshakePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyHandshakePacket.java @@ -20,23 +20,33 @@ package com.velocitypowered.proxy.protocol.packet; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; -public class LegacyHandshakePacket implements MinecraftPacket { +public final class LegacyHandshakePacket implements MinecraftPacket { - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - throw new UnsupportedOperationException(); - } + public static final LegacyHandshakePacket INSTANCE = new LegacyHandshakePacket(); - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - throw new UnsupportedOperationException(); + private LegacyHandshakePacket() { } @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + public static class Codec implements PacketCodec { + @Override + public LegacyHandshakePacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + throw new UnsupportedOperationException(); + } + + @Override + public void encode(LegacyHandshakePacket packet, ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + throw new UnsupportedOperationException(); + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyPingPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyPingPacket.java index 656d3222b..145af5125 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyPingPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyPingPacket.java @@ -20,13 +20,14 @@ package com.velocitypowered.proxy.protocol.packet; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.packet.legacyping.LegacyMinecraftPingVersion; import io.netty.buffer.ByteBuf; import java.net.InetSocketAddress; import org.checkerframework.checker.nullness.qual.Nullable; -public class LegacyPingPacket implements MinecraftPacket { +public final class LegacyPingPacket implements MinecraftPacket { private final LegacyMinecraftPingVersion version; private final @Nullable InetSocketAddress vhost; @@ -41,26 +42,38 @@ public class LegacyPingPacket implements MinecraftPacket { this.vhost = vhost; } - public LegacyMinecraftPingVersion getVersion() { + public LegacyMinecraftPingVersion version() { return version; } - public @Nullable InetSocketAddress getVhost() { + public @Nullable InetSocketAddress vhost() { return vhost; } - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - throw new UnsupportedOperationException(); + public LegacyMinecraftPingVersion getVersion() { + return version(); } - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - throw new UnsupportedOperationException(); + public @Nullable InetSocketAddress getVhost() { + return vhost(); } @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + public static class Codec implements PacketCodec { + @Override + public LegacyPingPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + throw new UnsupportedOperationException(); + } + + @Override + public void encode(LegacyPingPacket packet, ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + throw new UnsupportedOperationException(); + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyPlayerListItemPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyPlayerListItemPacket.java index 268c707d6..f972c0b3c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyPlayerListItemPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LegacyPlayerListItemPacket.java @@ -24,6 +24,7 @@ import com.velocitypowered.api.proxy.player.TabListEntry; import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; import java.util.ArrayList; @@ -33,22 +34,20 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.checkerframework.checker.nullness.qual.Nullable; -public class LegacyPlayerListItemPacket implements MinecraftPacket { +public final class LegacyPlayerListItemPacket implements MinecraftPacket { public static final int ADD_PLAYER = 0; public static final int UPDATE_GAMEMODE = 1; public static final int UPDATE_LATENCY = 2; public static final int UPDATE_DISPLAY_NAME = 3; public static final int REMOVE_PLAYER = 4; - private int action; - private final List items = new ArrayList<>(); + + private final int action; + private final List items; public LegacyPlayerListItemPacket(int action, List items) { this.action = action; - this.items.addAll(items); - } - - public LegacyPlayerListItemPacket() { + this.items = ImmutableList.copyOf(items); } public int getAction() { @@ -59,131 +58,137 @@ public class LegacyPlayerListItemPacket implements MinecraftPacket { return items; } - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) { - action = ProtocolUtils.readVarInt(buf); - int length = ProtocolUtils.readVarInt(buf); - - for (int i = 0; i < length; i++) { - Item item = new Item(ProtocolUtils.readUuid(buf)); - items.add(item); - switch (action) { - case ADD_PLAYER: - item.setName(ProtocolUtils.readString(buf)); - item.setProperties(ProtocolUtils.readProperties(buf)); - item.setGameMode(ProtocolUtils.readVarInt(buf)); - item.setLatency(ProtocolUtils.readVarInt(buf)); - item.setDisplayName(readOptionalComponent(buf, version)); - - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) { - if (buf.readBoolean()) { - item.setPlayerKey(ProtocolUtils.readPlayerKey(version, buf)); - } - } - break; - case UPDATE_GAMEMODE: - item.setGameMode(ProtocolUtils.readVarInt(buf)); - break; - case UPDATE_LATENCY: - item.setLatency(ProtocolUtils.readVarInt(buf)); - break; - case UPDATE_DISPLAY_NAME: - item.setDisplayName(readOptionalComponent(buf, version)); - break; - case REMOVE_PLAYER: - //Do nothing, all that is needed is the uuid - break; - default: - throw new UnsupportedOperationException("Unknown action " + action); - } - } - } else { - Item item = new Item(); - item.setName(ProtocolUtils.readString(buf)); - action = buf.readBoolean() ? ADD_PLAYER : REMOVE_PLAYER; - item.setLatency(buf.readShort()); - items.add(item); - } - } - - private static @Nullable Component readOptionalComponent(ByteBuf buf, ProtocolVersion version) { - if (buf.readBoolean()) { - return ProtocolUtils.getJsonChatSerializer(version) - .deserialize(ProtocolUtils.readString(buf)); - } - return null; - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) { - ProtocolUtils.writeVarInt(buf, action); - ProtocolUtils.writeVarInt(buf, items.size()); - for (Item item : items) { - UUID uuid = item.getUuid(); - assert uuid != null : "UUID-less entry serialization attempt - 1.7 component!"; - - ProtocolUtils.writeUuid(buf, uuid); - switch (action) { - case ADD_PLAYER: - ProtocolUtils.writeString(buf, item.getName()); - ProtocolUtils.writeProperties(buf, item.getProperties()); - ProtocolUtils.writeVarInt(buf, item.getGameMode()); - ProtocolUtils.writeVarInt(buf, item.getLatency()); - writeDisplayName(buf, item.getDisplayName(), version); - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) { - if (item.getPlayerKey() != null) { - buf.writeBoolean(true); - ProtocolUtils.writePlayerKey(buf, item.getPlayerKey()); - } else { - buf.writeBoolean(false); - } - } - break; - case UPDATE_GAMEMODE: - ProtocolUtils.writeVarInt(buf, item.getGameMode()); - break; - case UPDATE_LATENCY: - ProtocolUtils.writeVarInt(buf, item.getLatency()); - break; - case UPDATE_DISPLAY_NAME: - writeDisplayName(buf, item.getDisplayName(), version); - break; - case REMOVE_PLAYER: - // Do nothing, all that is needed is the uuid - break; - default: - throw new UnsupportedOperationException("Unknown action " + action); - } - } - } else { - Item item = items.get(0); - Component displayNameComponent = item.getDisplayName(); - if (displayNameComponent != null) { - String displayName = LegacyComponentSerializer.legacySection() - .serialize(displayNameComponent); - ProtocolUtils.writeString(buf, - displayName.length() > 16 ? displayName.substring(0, 16) : displayName); - } else { - ProtocolUtils.writeString(buf, item.getName()); - } - buf.writeBoolean(action != REMOVE_PLAYER); - buf.writeShort(item.getLatency()); - } - } - @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } - private void writeDisplayName(ByteBuf buf, @Nullable Component displayName, - ProtocolVersion version) { - buf.writeBoolean(displayName != null); - if (displayName != null) { - ProtocolUtils.writeString(buf, ProtocolUtils.getJsonChatSerializer(version) - .serialize(displayName)); + public static class Codec implements PacketCodec { + @Override + public LegacyPlayerListItemPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) { + int action = ProtocolUtils.readVarInt(buf); + int length = ProtocolUtils.readVarInt(buf); + List items = new ArrayList<>(length); + + for (int i = 0; i < length; i++) { + Item item = new Item(ProtocolUtils.readUuid(buf)); + items.add(item); + switch (action) { + case ADD_PLAYER: + item.setName(ProtocolUtils.readString(buf)); + item.setProperties(ProtocolUtils.readProperties(buf)); + item.setGameMode(ProtocolUtils.readVarInt(buf)); + item.setLatency(ProtocolUtils.readVarInt(buf)); + item.setDisplayName(readOptionalComponent(buf, version)); + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) { + if (buf.readBoolean()) { + item.setPlayerKey(ProtocolUtils.readPlayerKey(version, buf)); + } + } + break; + case UPDATE_GAMEMODE: + item.setGameMode(ProtocolUtils.readVarInt(buf)); + break; + case UPDATE_LATENCY: + item.setLatency(ProtocolUtils.readVarInt(buf)); + break; + case UPDATE_DISPLAY_NAME: + item.setDisplayName(readOptionalComponent(buf, version)); + break; + case REMOVE_PLAYER: + //Do nothing, all that is needed is the uuid + break; + default: + throw new UnsupportedOperationException("Unknown action " + action); + } + } + return new LegacyPlayerListItemPacket(action, items); + } else { + Item item = new Item(); + item.setName(ProtocolUtils.readString(buf)); + int action = buf.readBoolean() ? ADD_PLAYER : REMOVE_PLAYER; + item.setLatency(buf.readShort()); + return new LegacyPlayerListItemPacket(action, ImmutableList.of(item)); + } + } + + @Override + public void encode(LegacyPlayerListItemPacket packet, ByteBuf buf, + ProtocolUtils.Direction direction, ProtocolVersion version) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) { + ProtocolUtils.writeVarInt(buf, packet.action); + ProtocolUtils.writeVarInt(buf, packet.items.size()); + for (Item item : packet.items) { + UUID uuid = item.getUuid(); + assert uuid != null : "UUID-less entry serialization attempt - 1.7 component!"; + + ProtocolUtils.writeUuid(buf, uuid); + switch (packet.action) { + case ADD_PLAYER: + ProtocolUtils.writeString(buf, item.getName()); + ProtocolUtils.writeProperties(buf, item.getProperties()); + ProtocolUtils.writeVarInt(buf, item.getGameMode()); + ProtocolUtils.writeVarInt(buf, item.getLatency()); + writeDisplayName(buf, item.getDisplayName(), version); + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) { + if (item.getPlayerKey() != null) { + buf.writeBoolean(true); + ProtocolUtils.writePlayerKey(buf, item.getPlayerKey()); + } else { + buf.writeBoolean(false); + } + } + break; + case UPDATE_GAMEMODE: + ProtocolUtils.writeVarInt(buf, item.getGameMode()); + break; + case UPDATE_LATENCY: + ProtocolUtils.writeVarInt(buf, item.getLatency()); + break; + case UPDATE_DISPLAY_NAME: + writeDisplayName(buf, item.getDisplayName(), version); + break; + case REMOVE_PLAYER: + // Do nothing, all that is needed is the uuid + break; + default: + throw new UnsupportedOperationException("Unknown action " + packet.action); + } + } + } else { + Item item = packet.items.get(0); + Component displayNameComponent = item.getDisplayName(); + if (displayNameComponent != null) { + String displayName = LegacyComponentSerializer.legacySection() + .serialize(displayNameComponent); + ProtocolUtils.writeString(buf, + displayName.length() > 16 ? displayName.substring(0, 16) : displayName); + } else { + ProtocolUtils.writeString(buf, item.getName()); + } + buf.writeBoolean(packet.action != REMOVE_PLAYER); + buf.writeShort(item.getLatency()); + } + } + + private static @Nullable Component readOptionalComponent(ByteBuf buf, ProtocolVersion version) { + if (buf.readBoolean()) { + return ProtocolUtils.getJsonChatSerializer(version) + .deserialize(ProtocolUtils.readString(buf)); + } + return null; + } + + private static void writeDisplayName(ByteBuf buf, @Nullable Component displayName, + ProtocolVersion version) { + buf.writeBoolean(displayName != null); + if (displayName != null) { + ProtocolUtils.writeString(buf, ProtocolUtils.getJsonChatSerializer(version) + .serialize(displayName)); + } } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LoginAcknowledgedPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LoginAcknowledgedPacket.java index 16cf519b0..a2ed268ad 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LoginAcknowledgedPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LoginAcknowledgedPacket.java @@ -20,29 +20,38 @@ package com.velocitypowered.proxy.protocol.packet; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; -public class LoginAcknowledgedPacket implements MinecraftPacket { +public final class LoginAcknowledgedPacket implements MinecraftPacket { - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion protocolVersion) { - } + public static final LoginAcknowledgedPacket INSTANCE = new LoginAcknowledgedPacket(); - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion protocolVersion) { - } - - @Override - public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion version) { - return 0; + private LoginAcknowledgedPacket() { } @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + public static class Codec implements PacketCodec { + @Override + public LoginAcknowledgedPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + return INSTANCE; + } + + @Override + public void encode(LoginAcknowledgedPacket packet, ByteBuf buf, + ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + } + + @Override + public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + return 0; + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LoginPluginMessagePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LoginPluginMessagePacket.java index 2fa82e922..c788311cc 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LoginPluginMessagePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LoginPluginMessagePacket.java @@ -20,23 +20,19 @@ package com.velocitypowered.proxy.protocol.packet; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; -import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder; import io.netty.buffer.ByteBuf; +import io.netty.buffer.DefaultByteBufHolder; import io.netty.buffer.Unpooled; -import org.checkerframework.checker.nullness.qual.Nullable; -public class LoginPluginMessagePacket extends DeferredByteBufHolder implements MinecraftPacket { +public final class LoginPluginMessagePacket extends DefaultByteBufHolder implements MinecraftPacket { - private int id; - private @Nullable String channel; + private final int id; + private final String channel; - public LoginPluginMessagePacket() { - super(null); - } - - public LoginPluginMessagePacket(int id, @Nullable String channel, ByteBuf data) { + public LoginPluginMessagePacket(int id, String channel, ByteBuf data) { super(data); this.id = id; this.channel = channel; @@ -47,9 +43,6 @@ public class LoginPluginMessagePacket extends DeferredByteBufHolder implements M } public String getChannel() { - if (channel == null) { - throw new IllegalStateException("Channel is not specified!"); - } return channel; } @@ -62,34 +55,40 @@ public class LoginPluginMessagePacket extends DeferredByteBufHolder implements M + '}'; } - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - this.id = ProtocolUtils.readVarInt(buf); - this.channel = ProtocolUtils.readString(buf); - if (buf.isReadable()) { - this.replace(buf.readRetainedSlice(buf.readableBytes())); - } else { - this.replace(Unpooled.EMPTY_BUFFER); - } - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - ProtocolUtils.writeVarInt(buf, id); - if (channel == null) { - throw new IllegalStateException("Channel is not specified!"); - } - ProtocolUtils.writeString(buf, channel); - buf.writeBytes(content()); - } - @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } - @Override - public int encodeSizeHint(Direction direction, ProtocolVersion version) { - return content().readableBytes(); + public static class Codec implements PacketCodec { + @Override + public LoginPluginMessagePacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + int id = ProtocolUtils.readVarInt(buf); + String channel = ProtocolUtils.readString(buf); + ByteBuf data; + if (buf.isReadable()) { + data = buf.readRetainedSlice(buf.readableBytes()); + } else { + data = Unpooled.EMPTY_BUFFER; + } + return new LoginPluginMessagePacket(id, channel, data); + } + + @Override + public void encode(LoginPluginMessagePacket packet, ByteBuf buf, + ProtocolUtils.Direction direction, ProtocolVersion version) { + ProtocolUtils.writeVarInt(buf, packet.id); + ProtocolUtils.writeString(buf, packet.channel); + buf.writeBytes(packet.content()); + } + + @Override + public int encodeSizeHint(LoginPluginMessagePacket packet, Direction direction, + ProtocolVersion version) { + return ProtocolUtils.varIntBytes(packet.id) + + ProtocolUtils.stringSizeHint(packet.channel) + + packet.content().readableBytes(); + } } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LoginPluginResponsePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LoginPluginResponsePacket.java index e7d9443dc..bc3b273c0 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LoginPluginResponsePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/LoginPluginResponsePacket.java @@ -20,21 +20,18 @@ package com.velocitypowered.proxy.protocol.packet; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; -import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; -import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder; import io.netty.buffer.ByteBuf; +import io.netty.buffer.DefaultByteBufHolder; import io.netty.buffer.Unpooled; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -public class LoginPluginResponsePacket extends DeferredByteBufHolder implements MinecraftPacket { +public final class LoginPluginResponsePacket extends DefaultByteBufHolder + implements MinecraftPacket { - private int id; - private boolean success; - - public LoginPluginResponsePacket() { - super(Unpooled.EMPTY_BUFFER); - } + private final int id; + private final boolean success; public LoginPluginResponsePacket(int id, boolean success, @MonotonicNonNull ByteBuf buf) { super(buf); @@ -42,20 +39,20 @@ public class LoginPluginResponsePacket extends DeferredByteBufHolder implements this.success = success; } - public int getId() { + public int id() { return id; } - public void setId(int id) { - this.id = id; - } - - public boolean isSuccess() { + public boolean success() { return success; } - public void setSuccess(boolean success) { - this.success = success; + public int getId() { + return id(); + } + + public boolean isSuccess() { + return success(); } @Override @@ -67,31 +64,36 @@ public class LoginPluginResponsePacket extends DeferredByteBufHolder implements + '}'; } - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - this.id = ProtocolUtils.readVarInt(buf); - this.success = buf.readBoolean(); - if (buf.isReadable()) { - this.replace(buf.readRetainedSlice(buf.readableBytes())); - } else { - this.replace(Unpooled.EMPTY_BUFFER); - } - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - ProtocolUtils.writeVarInt(buf, id); - buf.writeBoolean(success); - buf.writeBytes(content()); - } - @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } - @Override - public int encodeSizeHint(Direction direction, ProtocolVersion version) { - return content().readableBytes(); + public static class Codec implements PacketCodec { + @Override + public LoginPluginResponsePacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + int id = ProtocolUtils.readVarInt(buf); + boolean success = buf.readBoolean(); + ByteBuf data; + if (buf.isReadable()) { + data = buf.readRetainedSlice(buf.readableBytes()); + } else { + data = Unpooled.EMPTY_BUFFER; + } + return new LoginPluginResponsePacket(id, success, data); + } + + @Override + public void encode(LoginPluginResponsePacket packet, ByteBuf buf, + ProtocolUtils.Direction direction, ProtocolVersion version) { + ProtocolUtils.writeVarInt(buf, packet.id); + buf.writeBoolean(packet.success); + buf.writeBytes(packet.content()); + } + + public int encodeSizeHint(LoginPluginResponsePacket packet, ProtocolUtils.Direction direction, ProtocolVersion version) { + return ProtocolUtils.varIntBytes(packet.id) + 1 + packet.content().readableBytes(); + } } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/PingIdentifyPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/PingIdentifyPacket.java index 27c1351d5..ca621716f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/PingIdentifyPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/PingIdentifyPacket.java @@ -20,30 +20,28 @@ package com.velocitypowered.proxy.protocol.packet; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; -public class PingIdentifyPacket implements MinecraftPacket { - - private int id; - - @Override - public String toString() { - return "Ping{" + "id=" + id + '}'; - } - - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - id = buf.readInt(); - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - buf.writeInt(id); - } +public record PingIdentifyPacket(int id) implements MinecraftPacket { @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + public static class Codec implements PacketCodec { + @Override + public PingIdentifyPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + return new PingIdentifyPacket(buf.readInt()); + } + + @Override + public void encode(PingIdentifyPacket packet, ByteBuf buf, + ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + buf.writeInt(packet.id); + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/PluginMessagePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/PluginMessagePacket.java index ecf2887fd..50da2b91a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/PluginMessagePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/PluginMessagePacket.java @@ -22,38 +22,24 @@ import static com.velocitypowered.proxy.protocol.util.PluginMessageUtil.transfor import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; -import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; -import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder; import io.netty.buffer.ByteBuf; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import org.checkerframework.checker.nullness.qual.Nullable; +import io.netty.buffer.DefaultByteBufHolder; -public class PluginMessagePacket extends DeferredByteBufHolder implements MinecraftPacket { +public final class PluginMessagePacket extends DefaultByteBufHolder implements MinecraftPacket { - private @Nullable String channel; + private final String channel; - public PluginMessagePacket() { - super(null); - } - - public PluginMessagePacket(String channel, - @MonotonicNonNull ByteBuf backing) { + public PluginMessagePacket(String channel, ByteBuf backing) { super(backing); this.channel = channel; } public String getChannel() { - if (channel == null) { - throw new IllegalStateException("Channel is not specified."); - } return channel; } - public void setChannel(String channel) { - this.channel = channel; - } - @Override public String toString() { return "PluginMessage{" @@ -62,44 +48,6 @@ public class PluginMessagePacket extends DeferredByteBufHolder implements Minecr + '}'; } - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - this.channel = ProtocolUtils.readString(buf); - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_13)) { - this.channel = transformLegacyToModernChannel(this.channel); - } - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) { - this.replace(buf.readRetainedSlice(buf.readableBytes())); - } else { - this.replace(ProtocolUtils.readRetainedByteBufSlice17(buf)); - } - - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - if (channel == null) { - throw new IllegalStateException("Channel is not specified."); - } - - if (refCnt() == 0) { - throw new IllegalStateException("Plugin message contents for " + this.channel - + " freed too many times."); - } - - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_13)) { - ProtocolUtils.writeString(buf, transformLegacyToModernChannel(this.channel)); - } else { - ProtocolUtils.writeString(buf, this.channel); - } - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) { - buf.writeBytes(content()); - } else { - ProtocolUtils.writeByteBuf17(content(), buf, true); // True for Forge support - } - - } - @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); @@ -145,8 +93,41 @@ public class PluginMessagePacket extends DeferredByteBufHolder implements Minecr return (PluginMessagePacket) super.touch(hint); } - @Override - public int encodeSizeHint(Direction direction, ProtocolVersion version) { - return content().readableBytes(); + public static class Codec implements PacketCodec { + @Override + public PluginMessagePacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + String channel = ProtocolUtils.readString(buf); + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_13)) { + channel = transformLegacyToModernChannel(channel); + } + ByteBuf data; + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) { + data = buf.readRetainedSlice(buf.readableBytes()); + } else { + data = ProtocolUtils.readRetainedByteBufSlice17(buf); + } + return new PluginMessagePacket(channel, data); + } + + @Override + public void encode(PluginMessagePacket packet, ByteBuf buf, + ProtocolUtils.Direction direction, ProtocolVersion version) { + if (packet.refCnt() == 0) { + throw new IllegalStateException("Plugin message contents for " + packet.channel + + " freed too many times."); + } + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_13)) { + ProtocolUtils.writeString(buf, transformLegacyToModernChannel(packet.channel)); + } else { + ProtocolUtils.writeString(buf, packet.channel); + } + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) { + buf.writeBytes(packet.content()); + } else { + ProtocolUtils.writeByteBuf17(packet.content(), buf, true); // True for Forge support + } + } } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/RemovePlayerInfoPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/RemovePlayerInfoPacket.java index 90ab38717..4c8c8402a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/RemovePlayerInfoPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/RemovePlayerInfoPacket.java @@ -21,54 +21,42 @@ import com.google.common.collect.Lists; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; -import java.util.ArrayList; import java.util.Collection; import java.util.UUID; -public class RemovePlayerInfoPacket implements MinecraftPacket { - - private Collection profilesToRemove; - - public RemovePlayerInfoPacket() { - this.profilesToRemove = new ArrayList<>(); - } - - public RemovePlayerInfoPacket(Collection profilesToRemove) { - this.profilesToRemove = profilesToRemove; - } +public record RemovePlayerInfoPacket(Collection profilesToRemove) implements MinecraftPacket { public Collection getProfilesToRemove() { return profilesToRemove; } - public void setProfilesToRemove(Collection profilesToRemove) { - this.profilesToRemove = profilesToRemove; - } - - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion protocolVersion) { - int length = ProtocolUtils.readVarInt(buf); - Collection profilesToRemove = Lists.newArrayListWithCapacity(length); - for (int idx = 0; idx < length; idx++) { - profilesToRemove.add(ProtocolUtils.readUuid(buf)); - } - this.profilesToRemove = profilesToRemove; - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion protocolVersion) { - ProtocolUtils.writeVarInt(buf, this.profilesToRemove.size()); - for (UUID uuid : this.profilesToRemove) { - ProtocolUtils.writeUuid(buf, uuid); - } - } - @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + public static class Codec implements PacketCodec { + @Override + public RemovePlayerInfoPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + int length = ProtocolUtils.readVarInt(buf); + Collection profilesToRemove = Lists.newArrayListWithCapacity(length); + for (int idx = 0; idx < length; idx++) { + profilesToRemove.add(ProtocolUtils.readUuid(buf)); + } + return new RemovePlayerInfoPacket(profilesToRemove); + } + + @Override + public void encode(RemovePlayerInfoPacket packet, ByteBuf buf, + ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + ProtocolUtils.writeVarInt(buf, packet.profilesToRemove.size()); + for (UUID uuid : packet.profilesToRemove) { + ProtocolUtils.writeUuid(buf, uuid); + } + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/RemoveResourcePackPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/RemoveResourcePackPacket.java index d003a0a95..dcde828b0 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/RemoveResourcePackPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/RemoveResourcePackPacket.java @@ -20,44 +20,42 @@ package com.velocitypowered.proxy.protocol.packet; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; import io.netty.buffer.ByteBuf; import java.util.UUID; +import org.checkerframework.checker.nullness.qual.Nullable; -public class RemoveResourcePackPacket implements MinecraftPacket { +public record RemoveResourcePackPacket(@Nullable UUID id) implements MinecraftPacket { - private UUID id; - - public RemoveResourcePackPacket() { - } - - public RemoveResourcePackPacket(UUID id) { - this.id = id; - } - - public UUID getId() { + public @Nullable UUID getId() { return id; } - @Override - public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { - if (buf.readBoolean()) { - this.id = ProtocolUtils.readUuid(buf); - } - } - - @Override - public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { - buf.writeBoolean(id != null); - - if (id != null) { - ProtocolUtils.writeUuid(buf, id); - } - } - @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } -} \ No newline at end of file + + public static class Codec implements PacketCodec { + @Override + public RemoveResourcePackPacket decode(ByteBuf buf, Direction direction, + ProtocolVersion protocolVersion) { + UUID id = null; + if (buf.readBoolean()) { + id = ProtocolUtils.readUuid(buf); + } + return new RemoveResourcePackPacket(id); + } + + @Override + public void encode(RemoveResourcePackPacket packet, ByteBuf buf, Direction direction, + ProtocolVersion protocolVersion) { + buf.writeBoolean(packet.id != null); + if (packet.id != null) { + ProtocolUtils.writeUuid(buf, packet.id); + } + } + } +} 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 a0f86aed1..3a782d3d6 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 @@ -23,6 +23,7 @@ import com.velocitypowered.api.proxy.player.ResourcePackInfo; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.player.resourcepack.VelocityResourcePackInfo; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; @@ -30,100 +31,47 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import java.util.UUID; import java.util.regex.Pattern; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.Nullable; -public class ResourcePackRequestPacket implements MinecraftPacket { +public final class ResourcePackRequestPacket implements MinecraftPacket { - private @MonotonicNonNull UUID id; // 1.20.3+ - private @MonotonicNonNull String url; - private @MonotonicNonNull String hash; - private boolean isRequired; // 1.17+ - private @Nullable ComponentHolder prompt; // 1.17+ + private final @Nullable UUID id; // 1.20.3+ + private final String url; + private final String hash; + private final boolean isRequired; // 1.17+ + private final @Nullable ComponentHolder prompt; // 1.17+ private static final Pattern PLAUSIBLE_SHA1_HASH = Pattern.compile("^[a-z0-9]{40}$"); // 1.20.2+ + public ResourcePackRequestPacket(@Nullable UUID id, String url, String hash, + boolean isRequired, @Nullable ComponentHolder prompt) { + this.id = id; + this.url = url; + this.hash = hash; + this.isRequired = isRequired; + this.prompt = prompt; + } + public @Nullable UUID getId() { return id; } - public void setId(UUID id) { - this.id = id; - } - - public @Nullable String getUrl() { + public String getUrl() { return url; } - public void setUrl(String url) { - this.url = url; - } - public boolean isRequired() { return isRequired; } - public @Nullable String getHash() { + public String getHash() { return hash; } - public void setHash(String hash) { - this.hash = hash; - } - - public void setRequired(boolean required) { - isRequired = required; - } - public @Nullable ComponentHolder getPrompt() { return prompt; } - public void setPrompt(@Nullable ComponentHolder prompt) { - this.prompt = prompt; - } - - @Override - public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { - if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) { - this.id = ProtocolUtils.readUuid(buf); - } - this.url = ProtocolUtils.readString(buf); - this.hash = ProtocolUtils.readString(buf); - if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_17)) { - this.isRequired = buf.readBoolean(); - if (buf.readBoolean()) { - this.prompt = ComponentHolder.read(buf, protocolVersion); - } else { - this.prompt = null; - } - } - } - - @Override - public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { - if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) { - if (id == null) { - throw new IllegalStateException("Resource pack id not set yet!"); - } - ProtocolUtils.writeUuid(buf, id); - } - if (url == null || hash == null) { - throw new IllegalStateException("Packet not fully filled in yet!"); - } - ProtocolUtils.writeString(buf, url); - ProtocolUtils.writeString(buf, hash); - if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_17)) { - buf.writeBoolean(isRequired); - if (prompt != null) { - buf.writeBoolean(true); - prompt.write(buf); - } else { - buf.writeBoolean(false); - } - } - } - public VelocityResourcePackInfo toServerPromptedPack() { final ResourcePackInfo.Builder builder = new VelocityResourcePackInfo.BuilderImpl(Preconditions.checkNotNull(url)) @@ -153,4 +101,51 @@ public class ResourcePackRequestPacket implements MinecraftPacket { ", prompt=" + prompt + '}'; } -} \ No newline at end of file + + public static class Codec implements PacketCodec { + @Override + public ResourcePackRequestPacket decode(ByteBuf buf, Direction direction, + ProtocolVersion protocolVersion) { + UUID id = null; + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) { + id = ProtocolUtils.readUuid(buf); + } + String url = ProtocolUtils.readString(buf); + String hash = ProtocolUtils.readString(buf); + boolean isRequired = false; + ComponentHolder prompt = null; + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_17)) { + isRequired = buf.readBoolean(); + if (buf.readBoolean()) { + prompt = ComponentHolder.read(buf, protocolVersion); + } + } + return new ResourcePackRequestPacket(id, url, hash, isRequired, prompt); + } + + @Override + public void encode(ResourcePackRequestPacket packet, ByteBuf buf, Direction direction, + ProtocolVersion protocolVersion) { + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) { + if (packet.id == null) { + throw new IllegalStateException("Resource pack id not set yet!"); + } + ProtocolUtils.writeUuid(buf, packet.id); + } + if (packet.url == null || packet.hash == null) { + throw new IllegalStateException("Packet not fully filled in yet!"); + } + ProtocolUtils.writeString(buf, packet.url); + ProtocolUtils.writeString(buf, packet.hash); + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_17)) { + buf.writeBoolean(packet.isRequired); + if (packet.prompt != null) { + buf.writeBoolean(true); + packet.prompt.write(buf); + } else { + buf.writeBoolean(false); + } + } + } + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ResourcePackResponsePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ResourcePackResponsePacket.java index 020c3530d..3b6ec2552 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ResourcePackResponsePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ResourcePackResponsePacket.java @@ -21,63 +21,26 @@ import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent.Status import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; import io.netty.buffer.ByteBuf; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import java.util.UUID; -public class ResourcePackResponsePacket implements MinecraftPacket { +public record ResourcePackResponsePacket(UUID id, String hash, + Status status) implements MinecraftPacket { - private UUID id; - private String hash = ""; - private @MonotonicNonNull Status status; - - public ResourcePackResponsePacket() { - } - - public ResourcePackResponsePacket(UUID id, String hash, @MonotonicNonNull Status status) { - this.id = id; - this.hash = hash; - this.status = status; - } - - public Status getStatus() { - if (status == null) { - throw new IllegalStateException("Packet not yet deserialized"); - } - return status; + public UUID getId() { + return id; } public String getHash() { return hash; } - public UUID getId() { - return id; - } - - @Override - public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { - if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) { - this.id = ProtocolUtils.readUuid(buf); - } - if (protocolVersion.noGreaterThan(ProtocolVersion.MINECRAFT_1_9_4)) { - this.hash = ProtocolUtils.readString(buf); - } - this.status = Status.values()[ProtocolUtils.readVarInt(buf)]; - } - - @Override - public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { - if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) { - ProtocolUtils.writeUuid(buf, id); - } - if (protocolVersion.noGreaterThan(ProtocolVersion.MINECRAFT_1_9_4)) { - ProtocolUtils.writeString(buf, hash); - } - ProtocolUtils.writeVarInt(buf, status.ordinal()); + public Status getStatus() { + return status; } @Override @@ -85,12 +48,32 @@ public class ResourcePackResponsePacket implements MinecraftPacket { return handler.handle(this); } - @Override - public String toString() { - return "ResourcePackResponsePacket{" + - "id=" + id + - ", hash='" + hash + '\'' + - ", status=" + status + - '}'; + public static class Codec implements PacketCodec { + @Override + public ResourcePackResponsePacket decode(ByteBuf buf, Direction direction, + ProtocolVersion protocolVersion) { + UUID id = null; + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) { + id = ProtocolUtils.readUuid(buf); + } + String hash = ""; + if (protocolVersion.noGreaterThan(ProtocolVersion.MINECRAFT_1_9_4)) { + hash = ProtocolUtils.readString(buf); + } + Status status = Status.values()[ProtocolUtils.readVarInt(buf)]; + return new ResourcePackResponsePacket(id, hash, status); + } + + @Override + public void encode(ResourcePackResponsePacket packet, ByteBuf buf, Direction direction, + ProtocolVersion protocolVersion) { + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) { + ProtocolUtils.writeUuid(buf, packet.id); + } + if (protocolVersion.noGreaterThan(ProtocolVersion.MINECRAFT_1_9_4)) { + ProtocolUtils.writeString(buf, packet.hash); + } + ProtocolUtils.writeVarInt(buf, packet.status.ordinal()); + } } -} \ No newline at end of file +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/RespawnPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/RespawnPacket.java index fd9c8ca77..be47bdcda 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/RespawnPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/RespawnPacket.java @@ -21,6 +21,7 @@ import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.registry.DimensionInfo; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; import it.unimi.dsi.fastutil.Pair; @@ -28,23 +29,20 @@ import net.kyori.adventure.nbt.BinaryTagIO; import net.kyori.adventure.nbt.CompoundBinaryTag; import org.checkerframework.checker.nullness.qual.Nullable; -public class RespawnPacket implements MinecraftPacket { +public final class RespawnPacket implements MinecraftPacket { - private int dimension; - private long partialHashedSeed; - private short difficulty; - private short gamemode; - private String levelType = ""; - private byte dataToKeep; // 1.16+ - private DimensionInfo dimensionInfo; // 1.16-1.16.1 - private short previousGamemode; // 1.16+ - private CompoundBinaryTag currentDimensionData; // 1.16.2+ - private @Nullable Pair lastDeathPosition; // 1.19+ - private int portalCooldown; // 1.20+ - private int seaLevel; // 1.21.2+ - - public RespawnPacket() { - } + private final int dimension; + private final long partialHashedSeed; + private final short difficulty; + private final short gamemode; + private final String levelType; + private final byte dataToKeep; + private final DimensionInfo dimensionInfo; + private final short previousGamemode; + private final CompoundBinaryTag currentDimensionData; + private final @Nullable Pair lastDeathPosition; + private final int portalCooldown; + private final int seaLevel; public RespawnPacket(int dimension, long partialHashedSeed, short difficulty, short gamemode, String levelType, byte dataToKeep, DimensionInfo dimensionInfo, @@ -73,84 +71,60 @@ public class RespawnPacket implements MinecraftPacket { joinGame.getPortalCooldown(), joinGame.getSeaLevel()); } - public int getDimension() { - return dimension; + public static RespawnPacket fromJoinGame(JoinGamePacket joinGame, int newDimension) { + return new RespawnPacket(newDimension, joinGame.getPartialHashedSeed(), + joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(), + (byte) 0, joinGame.getDimensionInfo(), joinGame.getPreviousGamemode(), + joinGame.getCurrentDimensionData(), joinGame.getLastDeathPosition(), + joinGame.getPortalCooldown(), joinGame.getSeaLevel()); } - public void setDimension(int dimension) { - this.dimension = dimension; + public int getDimension() { + return dimension; } public long getPartialHashedSeed() { return partialHashedSeed; } - public void setPartialHashedSeed(long partialHashedSeed) { - this.partialHashedSeed = partialHashedSeed; - } - public short getDifficulty() { return difficulty; } - public void setDifficulty(short difficulty) { - this.difficulty = difficulty; - } - public short getGamemode() { return gamemode; } - public void setGamemode(short gamemode) { - this.gamemode = gamemode; - } - public String getLevelType() { return levelType; } - public void setLevelType(String levelType) { - this.levelType = levelType; - } - public byte getDataToKeep() { return dataToKeep; } - public void setDataToKeep(byte dataToKeep) { - this.dataToKeep = dataToKeep; - } - public short getPreviousGamemode() { return previousGamemode; } - public void setPreviousGamemode(short previousGamemode) { - this.previousGamemode = previousGamemode; - } - public Pair getLastDeathPosition() { return lastDeathPosition; } - public void setLastDeathPosition(Pair lastDeathPosition) { - this.lastDeathPosition = lastDeathPosition; - } - public int getPortalCooldown() { return portalCooldown; } - public void setPortalCooldown(int portalCooldown) { - this.portalCooldown = portalCooldown; - } - public int getSeaLevel() { return seaLevel; } - public void setSeaLevel(int seaLevel) { - this.seaLevel = seaLevel; + public DimensionInfo getDimensionInfo() { + return dimensionInfo; + } + + public CompoundBinaryTag getCurrentDimensionData() { + return currentDimensionData; } @Override @@ -171,124 +145,157 @@ public class RespawnPacket implements MinecraftPacket { + '}'; } - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - String dimensionKey = ""; - String levelName = null; - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) { - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2) - && version.lessThan(ProtocolVersion.MINECRAFT_1_19)) { - this.currentDimensionData = ProtocolUtils.readCompoundTag(buf, version, BinaryTagIO.reader()); - dimensionKey = ProtocolUtils.readString(buf); - } else { - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { - dimension = ProtocolUtils.readVarInt(buf); - } else { - dimensionKey = ProtocolUtils.readString(buf); - } - levelName = ProtocolUtils.readString(buf); - } - } else { - this.dimension = buf.readInt(); - } - if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_13_2)) { - this.difficulty = buf.readUnsignedByte(); - } - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_15)) { - this.partialHashedSeed = buf.readLong(); - } - this.gamemode = buf.readByte(); - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) { - this.previousGamemode = buf.readByte(); - boolean isDebug = buf.readBoolean(); - boolean isFlat = buf.readBoolean(); - this.dimensionInfo = new DimensionInfo(dimensionKey, levelName, isFlat, isDebug, version); - if (version.lessThan(ProtocolVersion.MINECRAFT_1_19_3)) { - this.dataToKeep = (byte) (buf.readBoolean() ? 1 : 0); - } else if (version.lessThan(ProtocolVersion.MINECRAFT_1_20_2)) { - this.dataToKeep = buf.readByte(); - } - } else { - this.levelType = ProtocolUtils.readString(buf, 16); - } - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19) && buf.readBoolean()) { - this.lastDeathPosition = Pair.of(ProtocolUtils.readString(buf), buf.readLong()); - } - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20)) { - this.portalCooldown = ProtocolUtils.readVarInt(buf); - } - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) { - this.seaLevel = ProtocolUtils.readVarInt(buf); - } - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) { - this.dataToKeep = buf.readByte(); - } - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) { - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2) - && version.lessThan(ProtocolVersion.MINECRAFT_1_19)) { - ProtocolUtils.writeBinaryTag(buf, version, currentDimensionData); - ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier()); - } else { - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { - ProtocolUtils.writeVarInt(buf, dimension); - } else { - ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier()); - } - ProtocolUtils.writeString(buf, dimensionInfo.getLevelName()); - } - } else { - buf.writeInt(dimension); - } - if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_13_2)) { - buf.writeByte(difficulty); - } - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_15)) { - buf.writeLong(partialHashedSeed); - } - buf.writeByte(gamemode); - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) { - buf.writeByte(previousGamemode); - buf.writeBoolean(dimensionInfo.isDebugType()); - buf.writeBoolean(dimensionInfo.isFlat()); - if (version.lessThan(ProtocolVersion.MINECRAFT_1_19_3)) { - buf.writeBoolean(dataToKeep != 0); - } else if (version.lessThan(ProtocolVersion.MINECRAFT_1_20_2)) { - buf.writeByte(dataToKeep); - } - } else { - ProtocolUtils.writeString(buf, levelType); - } - - // optional death location - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) { - if (lastDeathPosition != null) { - buf.writeBoolean(true); - ProtocolUtils.writeString(buf, lastDeathPosition.key()); - buf.writeLong(lastDeathPosition.value()); - } else { - buf.writeBoolean(false); - } - } - - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20)) { - ProtocolUtils.writeVarInt(buf, portalCooldown); - } - - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) { - ProtocolUtils.writeVarInt(buf, seaLevel); - } - - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) { - buf.writeByte(dataToKeep); - } - } - @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + public static class Codec implements PacketCodec { + @Override + public RespawnPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + String dimensionKey = ""; + String levelName = null; + int dimension = 0; + CompoundBinaryTag currentDimensionData = null; + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2) + && version.lessThan(ProtocolVersion.MINECRAFT_1_19)) { + currentDimensionData = ProtocolUtils.readCompoundTag(buf, version, BinaryTagIO.reader()); + dimensionKey = ProtocolUtils.readString(buf); + } else { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { + dimension = ProtocolUtils.readVarInt(buf); + } else { + dimensionKey = ProtocolUtils.readString(buf); + } + levelName = ProtocolUtils.readString(buf); + } + } else { + dimension = buf.readInt(); + } + + short difficulty = 0; + if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_13_2)) { + difficulty = buf.readUnsignedByte(); + } + + long partialHashedSeed = 0; + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_15)) { + partialHashedSeed = buf.readLong(); + } + + short gamemode = buf.readByte(); + + DimensionInfo dimensionInfo = null; + String levelType = ""; + short previousGamemode = 0; + byte dataToKeep = 0; + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) { + previousGamemode = buf.readByte(); + boolean isDebug = buf.readBoolean(); + boolean isFlat = buf.readBoolean(); + dimensionInfo = new DimensionInfo(dimensionKey, levelName, isFlat, isDebug, version); + if (version.lessThan(ProtocolVersion.MINECRAFT_1_19_3)) { + dataToKeep = (byte) (buf.readBoolean() ? 1 : 0); + } else if (version.lessThan(ProtocolVersion.MINECRAFT_1_20_2)) { + dataToKeep = buf.readByte(); + } + } else { + levelType = ProtocolUtils.readString(buf, 16); + } + + Pair lastDeathPosition = null; + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19) && buf.readBoolean()) { + lastDeathPosition = Pair.of(ProtocolUtils.readString(buf), buf.readLong()); + } + + int portalCooldown = 0; + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20)) { + portalCooldown = ProtocolUtils.readVarInt(buf); + } + + int seaLevel = 0; + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) { + seaLevel = ProtocolUtils.readVarInt(buf); + } + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) { + dataToKeep = buf.readByte(); + } + + return new RespawnPacket(dimension, partialHashedSeed, difficulty, gamemode, levelType, + dataToKeep, dimensionInfo, previousGamemode, currentDimensionData, lastDeathPosition, + portalCooldown, seaLevel); + } + + @Override + public void encode(RespawnPacket packet, ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2) + && version.lessThan(ProtocolVersion.MINECRAFT_1_19)) { + ProtocolUtils.writeBinaryTag(buf, version, packet.currentDimensionData); + ProtocolUtils.writeString(buf, packet.dimensionInfo.getRegistryIdentifier()); + } else { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { + ProtocolUtils.writeVarInt(buf, packet.dimension); + } else { + ProtocolUtils.writeString(buf, packet.dimensionInfo.getRegistryIdentifier()); + } + ProtocolUtils.writeString(buf, packet.dimensionInfo.getLevelName()); + } + } else { + buf.writeInt(packet.dimension); + } + + if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_13_2)) { + buf.writeByte(packet.difficulty); + } + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_15)) { + buf.writeLong(packet.partialHashedSeed); + } + + buf.writeByte(packet.gamemode); + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) { + buf.writeByte(packet.previousGamemode); + buf.writeBoolean(packet.dimensionInfo.isDebugType()); + buf.writeBoolean(packet.dimensionInfo.isFlat()); + if (version.lessThan(ProtocolVersion.MINECRAFT_1_19_3)) { + buf.writeBoolean(packet.dataToKeep != 0); + } else if (version.lessThan(ProtocolVersion.MINECRAFT_1_20_2)) { + buf.writeByte(packet.dataToKeep); + } + } else { + ProtocolUtils.writeString(buf, packet.levelType); + } + + // optional death location + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) { + if (packet.lastDeathPosition != null) { + buf.writeBoolean(true); + ProtocolUtils.writeString(buf, packet.lastDeathPosition.key()); + buf.writeLong(packet.lastDeathPosition.value()); + } else { + buf.writeBoolean(false); + } + } + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20)) { + ProtocolUtils.writeVarInt(buf, packet.portalCooldown); + } + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) { + ProtocolUtils.writeVarInt(buf, packet.seaLevel); + } + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) { + buf.writeByte(packet.dataToKeep); + } + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerDataPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerDataPacket.java index 325a3c9df..ab54ec44d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerDataPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerDataPacket.java @@ -21,6 +21,7 @@ import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.util.Favicon; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; @@ -29,14 +30,11 @@ import org.jetbrains.annotations.Nullable; import java.nio.charset.StandardCharsets; import java.util.Base64; -public class ServerDataPacket implements MinecraftPacket { +public final class ServerDataPacket implements MinecraftPacket { - private @Nullable ComponentHolder description; - private @Nullable Favicon favicon; - private boolean secureChatEnforced; // Added in 1.19.1 - Removed in 1.20.5 - - public ServerDataPacket() { - } + private final @Nullable ComponentHolder description; + private final @Nullable Favicon favicon; + private final boolean secureChatEnforced; // Added in 1.19.1 - Removed in 1.20.5 public ServerDataPacket(@Nullable ComponentHolder description, @Nullable Favicon favicon, boolean secureChatEnforced) { @@ -45,63 +43,6 @@ public class ServerDataPacket implements MinecraftPacket { this.secureChatEnforced = secureChatEnforced; } - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion protocolVersion) { - if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_4) || buf.readBoolean()) { - this.description = ComponentHolder.read(buf, protocolVersion); - } - if (buf.readBoolean()) { - String iconBase64; - if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_4)) { - byte[] iconBytes = ProtocolUtils.readByteArray(buf); - iconBase64 = "data:image/png;base64," + new String(Base64.getEncoder().encode(iconBytes), StandardCharsets.UTF_8); - } else { - iconBase64 = ProtocolUtils.readString(buf); - } - this.favicon = new Favicon(iconBase64); - } - if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_19_3)) { - buf.readBoolean(); - } - if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_1) - && protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_20_5)) { - this.secureChatEnforced = buf.readBoolean(); - } - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion protocolVersion) { - boolean hasDescription = this.description != null; - if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_19_4)) { - buf.writeBoolean(hasDescription); - } - if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_4) || hasDescription) { - this.description.write(buf); - } - - boolean hasFavicon = this.favicon != null; - buf.writeBoolean(hasFavicon); - if (hasFavicon) { - if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_4)) { - String cutIconBase64 = favicon.getBase64Url().substring("data:image/png;base64,".length()); - byte[] iconBytes = Base64.getDecoder().decode(cutIconBase64.getBytes(StandardCharsets.UTF_8)); - ProtocolUtils.writeByteArray(buf, iconBytes); - } else { - ProtocolUtils.writeString(buf, favicon.getBase64Url()); - } - } - - if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_19_3)) { - buf.writeBoolean(false); - } - if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_1) - && protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_20_5)) { - buf.writeBoolean(this.secureChatEnforced); - } - } - @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); @@ -119,12 +60,68 @@ public class ServerDataPacket implements MinecraftPacket { return secureChatEnforced; } - public void setSecureChatEnforced(boolean secureChatEnforced) { - this.secureChatEnforced = secureChatEnforced; - } + public static class Codec implements PacketCodec { + @Override + public ServerDataPacket decode(ByteBuf buf, Direction direction, + ProtocolVersion protocolVersion) { + ComponentHolder description = null; + Favicon favicon = null; + boolean secureChatEnforced = false; - @Override - public int encodeSizeHint(Direction direction, ProtocolVersion version) { - return 8 * 1024; + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_4) || buf.readBoolean()) { + description = ComponentHolder.read(buf, protocolVersion); + } + if (buf.readBoolean()) { + String iconBase64; + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_4)) { + byte[] iconBytes = ProtocolUtils.readByteArray(buf); + iconBase64 = "data:image/png;base64," + new String(Base64.getEncoder().encode(iconBytes), StandardCharsets.UTF_8); + } else { + iconBase64 = ProtocolUtils.readString(buf); + } + favicon = new Favicon(iconBase64); + } + if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_19_3)) { + buf.readBoolean(); + } + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_1) + && protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_20_5)) { + secureChatEnforced = buf.readBoolean(); + } + + return new ServerDataPacket(description, favicon, secureChatEnforced); + } + + @Override + public void encode(ServerDataPacket packet, ByteBuf buf, Direction direction, + ProtocolVersion protocolVersion) { + boolean hasDescription = packet.description != null; + if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_19_4)) { + buf.writeBoolean(hasDescription); + } + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_4) || hasDescription) { + packet.description.write(buf); + } + + boolean hasFavicon = packet.favicon != null; + buf.writeBoolean(hasFavicon); + if (hasFavicon) { + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_4)) { + String cutIconBase64 = packet.favicon.getBase64Url().substring("data:image/png;base64,".length()); + byte[] iconBytes = Base64.getDecoder().decode(cutIconBase64.getBytes(StandardCharsets.UTF_8)); + ProtocolUtils.writeByteArray(buf, iconBytes); + } else { + ProtocolUtils.writeString(buf, packet.favicon.getBase64Url()); + } + } + + if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_19_3)) { + buf.writeBoolean(false); + } + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_1) + && protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_20_5)) { + buf.writeBoolean(packet.secureChatEnforced); + } + } } -} \ No newline at end of file +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLoginPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLoginPacket.java index 65693cd81..1a9ed47af 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLoginPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLoginPacket.java @@ -17,165 +17,172 @@ package com.velocitypowered.proxy.protocol.packet; -import com.google.common.base.Preconditions; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.proxy.crypto.IdentifiedKey; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; -import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; import com.velocitypowered.proxy.util.except.QuietDecoderException; import io.netty.buffer.ByteBuf; import java.util.UUID; import org.checkerframework.checker.nullness.qual.Nullable; -public class ServerLoginPacket implements MinecraftPacket { +public final class ServerLoginPacket implements MinecraftPacket { private static final QuietDecoderException EMPTY_USERNAME = new QuietDecoderException( "Empty username!"); - private @Nullable String username; - private @Nullable IdentifiedKey playerKey; // Introduced in 1.19.3 - private @Nullable UUID holderUuid; // Used for key revision 2 - - public ServerLoginPacket() { - } + private final String username; + private final @Nullable IdentifiedKey playerKey; // Introduced in 1.19.3 + private final @Nullable UUID holderUuid; // Used for key revision 2 public ServerLoginPacket(String username, @Nullable IdentifiedKey playerKey) { - this.username = Preconditions.checkNotNull(username, "username"); - this.playerKey = playerKey; + this(username, playerKey, null); } public ServerLoginPacket(String username, @Nullable UUID holderUuid) { - this.username = Preconditions.checkNotNull(username, "username"); - this.holderUuid = holderUuid; - this.playerKey = null; + this(username, null, holderUuid); } - public String getUsername() { - if (username == null) { - throw new IllegalStateException("No username found!"); - } + private ServerLoginPacket(String username, @Nullable IdentifiedKey playerKey, + @Nullable UUID holderUuid) { + this.username = username; + this.playerKey = playerKey; + this.holderUuid = holderUuid; + } + + public String username() { return username; } - public @Nullable IdentifiedKey getPlayerKey() { + public String getUsername() { + return username(); + } + + public @Nullable IdentifiedKey playerKey() { return this.playerKey; } - public void setPlayerKey(IdentifiedKey playerKey) { - this.playerKey = playerKey; - } - - public @Nullable UUID getHolderUuid() { + public @Nullable UUID holderUuid() { return holderUuid; } @Override public String toString() { return "ServerLogin{" - + "username='" + username + '\'' - + "playerKey='" + playerKey + '\'' - + "holderUUID='" + holderUuid + '\'' - + '}'; - } - - @Override - public void decode(ByteBuf buf, Direction direction, ProtocolVersion version) { - username = ProtocolUtils.readString(buf, 16); - if (username.isEmpty()) { - throw EMPTY_USERNAME; - } - - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) { - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_3)) { - playerKey = null; - } else { - if (buf.readBoolean()) { - playerKey = ProtocolUtils.readPlayerKey(version, buf); - } else { - playerKey = null; - } - } - - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) { - this.holderUuid = ProtocolUtils.readUuid(buf); - return; - } - - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) { - if (buf.readBoolean()) { - holderUuid = ProtocolUtils.readUuid(buf); - } - } - } else { - playerKey = null; - } - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - if (username == null) { - throw new IllegalStateException("No username found!"); - } - ProtocolUtils.writeString(buf, username); - - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) { - if (version.lessThan(ProtocolVersion.MINECRAFT_1_19_3)) { - if (playerKey != null) { - buf.writeBoolean(true); - ProtocolUtils.writePlayerKey(buf, playerKey); - } else { - buf.writeBoolean(false); - } - } - - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) { - ProtocolUtils.writeUuid(buf, this.holderUuid); - return; - } - - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) { - if (playerKey != null && playerKey.getSignatureHolder() != null) { - buf.writeBoolean(true); - ProtocolUtils.writeUuid(buf, playerKey.getSignatureHolder()); - } else if (this.holderUuid != null) { - buf.writeBoolean(true); - ProtocolUtils.writeUuid(buf, this.holderUuid); - } else { - buf.writeBoolean(false); - } - } - } - } - - @Override - public int decodeExpectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) { - // Accommodate the rare (but likely malicious) use of UTF-8 usernames, since it is technically - // legal on the protocol level. - int base = 1 + (16 * 3); - // Adjustments for Key-authentication - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) { - if (version.lessThan(ProtocolVersion.MINECRAFT_1_19_3)) { - // + 1 for the boolean present/ not present - // + 8 for the long expiry - // + 2 len for varint key size - // + 294 for the key - // + 2 len for varint signature size - // + 512 for signature - base += 1 + 8 + 2 + 294 + 2 + 512; - } - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) { - // +1 boolean uuid optional - // + 2 * 8 for the long msb/lsb - base += 1 + 8 + 8; - } - } - return base; + + "username='" + username + '\'' + + "playerKey='" + playerKey + '\'' + + "holderUUID='" + holderUuid + '\'' + + '}'; } @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + public static class Codec implements PacketCodec { + @Override + public ServerLoginPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + String username = ProtocolUtils.readString(buf, 16); + if (username.isEmpty()) { + throw EMPTY_USERNAME; + } + + IdentifiedKey playerKey = null; + UUID holderUuid = null; + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_3)) { + playerKey = null; + } else { + if (buf.readBoolean()) { + playerKey = ProtocolUtils.readPlayerKey(version, buf); + } else { + playerKey = null; + } + } + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) { + holderUuid = ProtocolUtils.readUuid(buf); + return new ServerLoginPacket(username, playerKey, holderUuid); + } + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) { + if (buf.readBoolean()) { + holderUuid = ProtocolUtils.readUuid(buf); + } + } + } else { + playerKey = null; + } + + return new ServerLoginPacket(username, playerKey, holderUuid); + } + + @Override + public void encode(ServerLoginPacket packet, ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + if (packet.username == null) { + throw new IllegalStateException("No username found!"); + } + ProtocolUtils.writeString(buf, packet.username); + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) { + if (version.lessThan(ProtocolVersion.MINECRAFT_1_19_3)) { + if (packet.playerKey != null) { + buf.writeBoolean(true); + ProtocolUtils.writePlayerKey(buf, packet.playerKey); + } else { + buf.writeBoolean(false); + } + } + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) { + ProtocolUtils.writeUuid(buf, packet.holderUuid); + return; + } + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) { + if (packet.playerKey != null && packet.playerKey.getSignatureHolder() != null) { + buf.writeBoolean(true); + ProtocolUtils.writeUuid(buf, packet.playerKey.getSignatureHolder()); + } else if (packet.holderUuid != null) { + buf.writeBoolean(true); + ProtocolUtils.writeUuid(buf, packet.holderUuid); + } else { + buf.writeBoolean(false); + } + } + } + } + + @Override + public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + // Accommodate the rare (but likely malicious) use of UTF-8 usernames, since it is technically + // legal on the protocol level. + int base = 1 + (16 * 3); + // Adjustments for Key-authentication + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) { + if (version.lessThan(ProtocolVersion.MINECRAFT_1_19_3)) { + // + 1 for the boolean present/ not present + // + 8 for the long expiry + // + 2 len for varint key size + // + 294 for the key + // + 2 len for varint signature size + // + 512 for signature + base += 1 + 8 + 2 + 294 + 2 + 512; + } + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) { + // +1 boolean uuid optional + // + 2 * 8 for the long msb/lsb + base += 1 + 8 + 8; + } + } + return base; + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLoginSuccessPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLoginSuccessPacket.java index 322cd9b19..ae395e317 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLoginSuccessPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLoginSuccessPacket.java @@ -22,52 +22,40 @@ import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.api.util.UuidUtils; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; import com.velocitypowered.proxy.util.VelocityProperties; import io.netty.buffer.ByteBuf; import java.util.List; import java.util.UUID; -import org.checkerframework.checker.nullness.qual.Nullable; -public class ServerLoginSuccessPacket implements MinecraftPacket { +public final class ServerLoginSuccessPacket implements MinecraftPacket { - private @Nullable UUID uuid; - private @Nullable String username; - private @Nullable List properties; + private final UUID uuid; + private final String username; + private final List properties; private static final boolean strictErrorHandling = VelocityProperties .readBoolean("velocity.strictErrorHandling", true); + public ServerLoginSuccessPacket(UUID uuid, String username, List properties) { + this.uuid = uuid; + this.username = username; + this.properties = properties; + } + public UUID getUuid() { - if (uuid == null) { - throw new IllegalStateException("No UUID specified!"); - } return uuid; } - public void setUuid(UUID uuid) { - this.uuid = uuid; - } - public String getUsername() { - if (username == null) { - throw new IllegalStateException("No username specified!"); - } return username; } - public void setUsername(String username) { - this.username = username; - } - public List getProperties() { return properties; } - public void setProperties(List properties) { - this.properties = properties; - } - @Override public String toString() { return "ServerLoginSuccess{" @@ -77,67 +65,70 @@ public class ServerLoginSuccessPacket implements MinecraftPacket { + '}'; } - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) { - uuid = ProtocolUtils.readUuid(buf); - } else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) { - uuid = ProtocolUtils.readUuidIntArray(buf); - } else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_7_6)) { - uuid = UUID.fromString(ProtocolUtils.readString(buf, 36)); - } else { - uuid = UuidUtils.fromUndashed(ProtocolUtils.readString(buf, 32)); - } - username = ProtocolUtils.readString(buf, 16); - - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) { - properties = ProtocolUtils.readProperties(buf); - } - if (version == ProtocolVersion.MINECRAFT_1_20_5 || version == ProtocolVersion.MINECRAFT_1_21) { - buf.readBoolean(); - } - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - if (uuid == null) { - throw new IllegalStateException("No UUID specified!"); - } - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) { - ProtocolUtils.writeUuid(buf, uuid); - } else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) { - ProtocolUtils.writeUuidIntArray(buf, uuid); - } else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_7_6)) { - ProtocolUtils.writeString(buf, uuid.toString()); - } else { - ProtocolUtils.writeString(buf, UuidUtils.toUndashed(uuid)); - } - if (username == null) { - throw new IllegalStateException("No username specified!"); - } - ProtocolUtils.writeString(buf, username); - - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) { - if (properties == null) { - ProtocolUtils.writeVarInt(buf, 0); - } else { - ProtocolUtils.writeProperties(buf, properties); - } - } - if (version == ProtocolVersion.MINECRAFT_1_20_5 || version == ProtocolVersion.MINECRAFT_1_21) { - buf.writeBoolean(strictErrorHandling); - } - } - @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } - @Override - public int encodeSizeHint(Direction direction, ProtocolVersion version) { - // We could compute an exact size, but 4KiB ought to be enough to encode all reasonable - // sizes of this packet. - return 4 * 1024; + public static class Codec implements PacketCodec { + @Override + public ServerLoginSuccessPacket decode(ByteBuf buf, Direction direction, + ProtocolVersion version) { + UUID uuid; + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) { + uuid = ProtocolUtils.readUuid(buf); + } else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) { + uuid = ProtocolUtils.readUuidIntArray(buf); + } else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_7_6)) { + uuid = UUID.fromString(ProtocolUtils.readString(buf, 36)); + } else { + uuid = UuidUtils.fromUndashed(ProtocolUtils.readString(buf, 32)); + } + String username = ProtocolUtils.readString(buf, 16); + + List properties = null; + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) { + properties = ProtocolUtils.readProperties(buf); + } + if (version == ProtocolVersion.MINECRAFT_1_20_5 || version == ProtocolVersion.MINECRAFT_1_21) { + buf.readBoolean(); + } + + return new ServerLoginSuccessPacket(uuid, username, properties); + } + + @Override + public void encode(ServerLoginSuccessPacket packet, ByteBuf buf, Direction direction, + ProtocolVersion version) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) { + ProtocolUtils.writeUuid(buf, packet.uuid); + } else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) { + ProtocolUtils.writeUuidIntArray(buf, packet.uuid); + } else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_7_6)) { + ProtocolUtils.writeString(buf, packet.uuid.toString()); + } else { + ProtocolUtils.writeString(buf, UuidUtils.toUndashed(packet.uuid)); + } + ProtocolUtils.writeString(buf, packet.username); + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) { + if (packet.properties == null) { + ProtocolUtils.writeVarInt(buf, 0); + } else { + ProtocolUtils.writeProperties(buf, packet.properties); + } + } + if (version == ProtocolVersion.MINECRAFT_1_20_5 || version == ProtocolVersion.MINECRAFT_1_21) { + buf.writeBoolean(strictErrorHandling); + } + } + + @Override + public int encodeSizeHint(ServerLoginSuccessPacket packet, Direction direction, ProtocolVersion version) { + // We could compute an exact size, but 4KiB ought to be enough to encode all reasonable + // sizes of this packet. + return 4 * 1024; + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerboundCookieResponsePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerboundCookieResponsePacket.java index bee12b802..32beeccb2 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerboundCookieResponsePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerboundCookieResponsePacket.java @@ -20,16 +20,15 @@ package com.velocitypowered.proxy.protocol.packet; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; import io.netty.buffer.ByteBuf; import net.kyori.adventure.key.Key; import org.checkerframework.checker.nullness.qual.Nullable; -public class ServerboundCookieResponsePacket implements MinecraftPacket { - - private Key key; - private byte @Nullable [] payload; +public record ServerboundCookieResponsePacket(Key key, + byte @Nullable [] payload) implements MinecraftPacket { public Key getKey() { return key; @@ -39,34 +38,32 @@ public class ServerboundCookieResponsePacket implements MinecraftPacket { return payload; } - public ServerboundCookieResponsePacket() { - } - - public ServerboundCookieResponsePacket(final Key key, final byte @Nullable [] payload) { - this.key = key; - this.payload = payload; - } - - @Override - public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { - this.key = ProtocolUtils.readKey(buf); - if (buf.readBoolean()) { - this.payload = ProtocolUtils.readByteArray(buf, 5120); - } - } - - @Override - public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { - ProtocolUtils.writeKey(buf, key); - final boolean hasPayload = payload != null && payload.length > 0; - buf.writeBoolean(hasPayload); - if (hasPayload) { - ProtocolUtils.writeByteArray(buf, payload); - } - } - @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + public static class Codec implements PacketCodec { + @Override + public ServerboundCookieResponsePacket decode(ByteBuf buf, Direction direction, + ProtocolVersion protocolVersion) { + Key key = ProtocolUtils.readKey(buf); + byte[] payload = null; + if (buf.readBoolean()) { + payload = ProtocolUtils.readByteArray(buf, 5120); + } + return new ServerboundCookieResponsePacket(key, payload); + } + + @Override + public void encode(ServerboundCookieResponsePacket packet, ByteBuf buf, Direction direction, + ProtocolVersion protocolVersion) { + ProtocolUtils.writeKey(buf, packet.key); + final boolean hasPayload = packet.payload != null && packet.payload.length > 0; + buf.writeBoolean(hasPayload); + if (hasPayload) { + ProtocolUtils.writeByteArray(buf, packet.payload); + } + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerboundCustomClickActionPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerboundCustomClickActionPacket.java index 6b846c234..9eb434e36 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerboundCustomClickActionPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerboundCustomClickActionPacket.java @@ -20,25 +20,17 @@ package com.velocitypowered.proxy.protocol.packet; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; -import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder; import io.netty.buffer.ByteBuf; +import io.netty.buffer.DefaultByteBufHolder; -public class ServerboundCustomClickActionPacket extends DeferredByteBufHolder implements MinecraftPacket { +public class ServerboundCustomClickActionPacket extends DefaultByteBufHolder + implements MinecraftPacket { - public ServerboundCustomClickActionPacket() { - super(null); - } - - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - replace(buf.readRetainedSlice(buf.readableBytes())); - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - buf.writeBytes(content()); + public ServerboundCustomClickActionPacket(ByteBuf backing) { + super(backing); } @Override @@ -47,7 +39,61 @@ public class ServerboundCustomClickActionPacket extends DeferredByteBufHolder im } @Override - public int encodeSizeHint(Direction direction, ProtocolVersion version) { - return content().readableBytes(); + public ServerboundCustomClickActionPacket copy() { + return (ServerboundCustomClickActionPacket) super.copy(); + } + + @Override + public ServerboundCustomClickActionPacket duplicate() { + return (ServerboundCustomClickActionPacket) super.duplicate(); + } + + @Override + public ServerboundCustomClickActionPacket retainedDuplicate() { + return (ServerboundCustomClickActionPacket) super.retainedDuplicate(); + } + + @Override + public ServerboundCustomClickActionPacket replace(ByteBuf content) { + return (ServerboundCustomClickActionPacket) super.replace(content); + } + + @Override + public ServerboundCustomClickActionPacket retain() { + return (ServerboundCustomClickActionPacket) super.retain(); + } + + @Override + public ServerboundCustomClickActionPacket retain(int increment) { + return (ServerboundCustomClickActionPacket) super.retain(increment); + } + + @Override + public ServerboundCustomClickActionPacket touch() { + return (ServerboundCustomClickActionPacket) super.touch(); + } + + @Override + public ServerboundCustomClickActionPacket touch(Object hint) { + return (ServerboundCustomClickActionPacket) super.touch(hint); + } + + public static class Codec implements PacketCodec { + @Override + public ServerboundCustomClickActionPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + return new ServerboundCustomClickActionPacket(buf.readRetainedSlice(buf.readableBytes())); + } + + @Override + public void encode(ServerboundCustomClickActionPacket packet, ByteBuf buf, + ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + buf.writeBytes(packet.content()); + } + + @Override + public int encodeSizeHint(ServerboundCustomClickActionPacket packet, Direction direction, ProtocolVersion protocolVersion) { + return packet.content().readableBytes(); + } } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/SetCompressionPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/SetCompressionPacket.java index 6710bf85d..53bedf8ea 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/SetCompressionPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/SetCompressionPacket.java @@ -20,28 +20,16 @@ package com.velocitypowered.proxy.protocol.packet; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; -public class SetCompressionPacket implements MinecraftPacket { - - private int threshold; - - public SetCompressionPacket() { - } - - public SetCompressionPacket(int threshold) { - this.threshold = threshold; - } +public record SetCompressionPacket(int threshold) implements MinecraftPacket { public int getThreshold() { return threshold; } - public void setThreshold(int threshold) { - this.threshold = threshold; - } - @Override public String toString() { return "SetCompression{" @@ -49,18 +37,22 @@ public class SetCompressionPacket implements MinecraftPacket { + '}'; } - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - this.threshold = ProtocolUtils.readVarInt(buf); - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - ProtocolUtils.writeVarInt(buf, threshold); - } - @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + public static class Codec implements PacketCodec { + @Override + public SetCompressionPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + return new SetCompressionPacket(ProtocolUtils.readVarInt(buf)); + } + + @Override + public void encode(SetCompressionPacket packet, ByteBuf buf, + ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + ProtocolUtils.writeVarInt(buf, packet.threshold); + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusPingPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusPingPacket.java index 302367044..9b49d914d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusPingPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusPingPacket.java @@ -20,36 +20,41 @@ package com.velocitypowered.proxy.protocol.packet; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; import io.netty.buffer.ByteBuf; -public class StatusPingPacket implements MinecraftPacket { - - private long randomId; - - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - randomId = buf.readLong(); - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - buf.writeLong(randomId); - } +public record StatusPingPacket(long randomId) implements MinecraftPacket { @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } - @Override - public int decodeExpectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) { - return 8; - } + public static class Codec implements PacketCodec { + @Override + public StatusPingPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + return new StatusPingPacket(buf.readLong()); + } - @Override - public int decodeExpectedMinLength(ByteBuf buf, Direction direction, ProtocolVersion version) { - return 8; + @Override + public void encode(StatusPingPacket packet, ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + buf.writeLong(packet.randomId); + } + + @Override + public int decodeExpectedMaxLength(ByteBuf buf, Direction direction, + ProtocolVersion protocolVersion) { + return 8; + } + + @Override + public int decodeExpectedMinLength(ByteBuf buf, Direction direction, + ProtocolVersion protocolVersion) { + return 8; + } } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusRequestPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusRequestPacket.java index 870d99093..ab3fe1f77 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusRequestPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusRequestPacket.java @@ -20,11 +20,11 @@ package com.velocitypowered.proxy.protocol.packet; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; -import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; import io.netty.buffer.ByteBuf; -public class StatusRequestPacket implements MinecraftPacket { +public final class StatusRequestPacket implements MinecraftPacket { public static final StatusRequestPacket INSTANCE = new StatusRequestPacket(); @@ -32,16 +32,6 @@ public class StatusRequestPacket implements MinecraftPacket { } - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - // There is no additional data to decode. - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - // There is no data to decode. - } - @Override public String toString() { return "StatusRequest"; @@ -52,8 +42,22 @@ public class StatusRequestPacket implements MinecraftPacket { return handler.handle(this); } - @Override - public int decodeExpectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) { - return 0; + public static class Codec implements PacketCodec { + @Override + public StatusRequestPacket decode(ByteBuf buf, Direction direction, + ProtocolVersion protocolVersion) { + return INSTANCE; + } + + @Override + public void encode(StatusRequestPacket packet, ByteBuf buf, Direction direction, + ProtocolVersion protocolVersion) { + } + + @Override + public int decodeExpectedMaxLength(ByteBuf buf, Direction direction, + ProtocolVersion protocolVersion) { + return 0; + } } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusResponsePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusResponsePacket.java index 20fada4bc..2e9836442 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusResponsePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/StatusResponsePacket.java @@ -20,29 +20,30 @@ package com.velocitypowered.proxy.protocol.packet; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; -import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; import io.netty.buffer.ByteBuf; import org.checkerframework.checker.nullness.qual.Nullable; -public class StatusResponsePacket implements MinecraftPacket { +public final class StatusResponsePacket implements MinecraftPacket { - private @Nullable CharSequence status; - - public StatusResponsePacket() { - } + private final @Nullable CharSequence status; public StatusResponsePacket(CharSequence status) { this.status = status; } - public String getStatus() { + public String status() { if (status == null) { throw new IllegalStateException("Status is not specified"); } return status.toString(); } + public String getStatus() { + return status(); + } + @Override public String toString() { return "StatusResponse{" @@ -50,26 +51,31 @@ public class StatusResponsePacket implements MinecraftPacket { + '}'; } - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - status = ProtocolUtils.readString(buf, Short.MAX_VALUE); - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - if (status == null) { - throw new IllegalStateException("Status is not specified"); - } - ProtocolUtils.writeString(buf, status); - } - @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } - @Override - public int encodeSizeHint(Direction direction, ProtocolVersion version) { - return ProtocolUtils.stringSizeHint(this.status); + public static class Codec implements PacketCodec { + @Override + public StatusResponsePacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + CharSequence status = ProtocolUtils.readString(buf, Short.MAX_VALUE); + return new StatusResponsePacket(status); + } + + @Override + public void encode(StatusResponsePacket packet, ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + if (packet.status == null) { + throw new IllegalStateException("Status is not specified"); + } + ProtocolUtils.writeString(buf, packet.status); + } + + @Override + public int encodeSizeHint(StatusResponsePacket packet, ProtocolUtils.Direction direction, ProtocolVersion version) { + return ProtocolUtils.stringSizeHint(packet.status); + } } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteRequestPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteRequestPacket.java index dda4695bb..202e7c466 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteRequestPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteRequestPacket.java @@ -21,122 +21,125 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_13; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9; -import com.google.common.base.MoreObjects; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; import org.checkerframework.checker.nullness.qual.Nullable; -public class TabCompleteRequestPacket implements MinecraftPacket { +public final class TabCompleteRequestPacket implements MinecraftPacket { private static final int VANILLA_MAX_TAB_COMPLETE_LEN = 2048; - private @Nullable String command; - private int transactionId; - private boolean assumeCommand; - private boolean hasPosition; - private long position; + private final @Nullable String command; + private final int transactionId; + private final boolean assumeCommand; + private final boolean hasPosition; + private final long position; - public String getCommand() { + public TabCompleteRequestPacket() { + this(null, 0, false, false, 0); + } + + public TabCompleteRequestPacket(@Nullable String command, int transactionId, + boolean assumeCommand, boolean hasPosition, long position) { + this.command = command; + this.transactionId = transactionId; + this.assumeCommand = assumeCommand; + this.hasPosition = hasPosition; + this.position = position; + } + + public String command() { if (command == null) { throw new IllegalStateException("Command is not specified"); } return command; } - public void setCommand(String command) { - this.command = command; + public String getCommand() { + return command(); } - public boolean isAssumeCommand() { + public boolean assumeCommand() { return assumeCommand; } - public void setAssumeCommand(boolean assumeCommand) { - this.assumeCommand = assumeCommand; + public boolean isAssumeCommand() { + return assumeCommand(); } public boolean hasPosition() { return hasPosition; } - public void setHasPosition(boolean hasPosition) { - this.hasPosition = hasPosition; - } - - public long getPosition() { + public long position() { return position; } - public void setPosition(long position) { - this.position = position; - } - - public int getTransactionId() { + public int transactionId() { return transactionId; } - public void setTransactionId(int transactionId) { - this.transactionId = transactionId; - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("command", command) - .add("transactionId", transactionId) - .add("assumeCommand", assumeCommand) - .add("hasPosition", hasPosition) - .add("position", position) - .toString(); - } - - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - if (version.noLessThan(MINECRAFT_1_13)) { - this.transactionId = ProtocolUtils.readVarInt(buf); - this.command = ProtocolUtils.readString(buf, VANILLA_MAX_TAB_COMPLETE_LEN); - } else { - this.command = ProtocolUtils.readString(buf, VANILLA_MAX_TAB_COMPLETE_LEN); - if (version.noLessThan(MINECRAFT_1_9)) { - this.assumeCommand = buf.readBoolean(); - } - if (version.noLessThan(MINECRAFT_1_8)) { - this.hasPosition = buf.readBoolean(); - if (hasPosition) { - this.position = buf.readLong(); - } - } - } - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - if (command == null) { - throw new IllegalStateException("Command is not specified"); - } - - if (version.noLessThan(MINECRAFT_1_13)) { - ProtocolUtils.writeVarInt(buf, transactionId); - ProtocolUtils.writeString(buf, command); - } else { - ProtocolUtils.writeString(buf, command); - if (version.noLessThan(MINECRAFT_1_9)) { - buf.writeBoolean(assumeCommand); - } - if (version.noLessThan(MINECRAFT_1_8)) { - buf.writeBoolean(hasPosition); - if (hasPosition) { - buf.writeLong(position); - } - } - } - } - @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + public static class Codec implements PacketCodec { + @Override + public TabCompleteRequestPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + String command; + int transactionId = 0; + boolean assumeCommand = false; + boolean hasPosition = false; + long position = 0; + + if (version.noLessThan(MINECRAFT_1_13)) { + transactionId = ProtocolUtils.readVarInt(buf); + command = ProtocolUtils.readString(buf, VANILLA_MAX_TAB_COMPLETE_LEN); + } else { + command = ProtocolUtils.readString(buf, VANILLA_MAX_TAB_COMPLETE_LEN); + if (version.noLessThan(MINECRAFT_1_9)) { + assumeCommand = buf.readBoolean(); + } + if (version.noLessThan(MINECRAFT_1_8)) { + hasPosition = buf.readBoolean(); + if (hasPosition) { + position = buf.readLong(); + } + } + } + + return new TabCompleteRequestPacket(command, transactionId, assumeCommand, hasPosition, + position); + } + + @Override + public void encode(TabCompleteRequestPacket packet, ByteBuf buf, + ProtocolUtils.Direction direction, ProtocolVersion version) { + if (packet.command == null) { + throw new IllegalStateException("Command is not specified"); + } + + if (version.noLessThan(MINECRAFT_1_13)) { + ProtocolUtils.writeVarInt(buf, packet.transactionId); + ProtocolUtils.writeString(buf, packet.command); + } else { + ProtocolUtils.writeString(buf, packet.command); + if (version.noLessThan(MINECRAFT_1_9)) { + buf.writeBoolean(packet.assumeCommand); + } + if (version.noLessThan(MINECRAFT_1_8)) { + buf.writeBoolean(packet.hasPosition); + if (packet.hasPosition) { + buf.writeLong(packet.position); + } + } + } + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteResponsePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteResponsePacket.java index a22fff0bd..f263af2b0 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteResponsePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteResponsePacket.java @@ -23,6 +23,7 @@ import com.google.common.base.MoreObjects; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import io.netty.buffer.ByteBuf; @@ -30,39 +31,54 @@ import java.util.ArrayList; import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; -public class TabCompleteResponsePacket implements MinecraftPacket { +public final class TabCompleteResponsePacket implements MinecraftPacket { - private int transactionId; - private int start; - private int length; - private final List offers = new ArrayList<>(); + private final int transactionId; + private final int start; + private final int length; + private final List offers; - public int getTransactionId() { + public TabCompleteResponsePacket(int transactionId, int start, int length, List offers) { + this.transactionId = transactionId; + this.start = start; + this.length = length; + this.offers = offers; + } + + public int transactionId() { return transactionId; } - public void setTransactionId(int transactionId) { - this.transactionId = transactionId; - } - - public int getStart() { + public int start() { return start; } - public void setStart(int start) { - this.start = start; - } - - public int getLength() { + public int length() { return length; } - public void setLength(int length) { - this.length = length; + public List offers() { + return offers; + } + + public int getTransactionId() { + return transactionId(); + } + + public int getStart() { + return start(); + } + + public int getLength() { + return length(); } public List getOffers() { - return offers; + return offers(); + } + + public TabCompleteResponsePacket withOffers(List offers) { + return new TabCompleteResponsePacket(transactionId, start, length, offers); } @Override @@ -75,53 +91,61 @@ public class TabCompleteResponsePacket implements MinecraftPacket { + '}'; } - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - if (version.noLessThan(MINECRAFT_1_13)) { - this.transactionId = ProtocolUtils.readVarInt(buf); - this.start = ProtocolUtils.readVarInt(buf); - this.length = ProtocolUtils.readVarInt(buf); - int offersAvailable = ProtocolUtils.readVarInt(buf); - for (int i = 0; i < offersAvailable; i++) { - String offer = ProtocolUtils.readString(buf); - ComponentHolder tooltip = buf.readBoolean() ? ComponentHolder.read(buf, version) : null; - offers.add(new Offer(offer, tooltip)); - } - } else { - int offersAvailable = ProtocolUtils.readVarInt(buf); - for (int i = 0; i < offersAvailable; i++) { - offers.add(new Offer(ProtocolUtils.readString(buf), null)); - } - } - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - if (version.noLessThan(MINECRAFT_1_13)) { - ProtocolUtils.writeVarInt(buf, this.transactionId); - ProtocolUtils.writeVarInt(buf, this.start); - ProtocolUtils.writeVarInt(buf, this.length); - ProtocolUtils.writeVarInt(buf, offers.size()); - for (Offer offer : offers) { - ProtocolUtils.writeString(buf, offer.text); - buf.writeBoolean(offer.tooltip != null); - if (offer.tooltip != null) { - offer.tooltip.write(buf); - } - } - } else { - ProtocolUtils.writeVarInt(buf, offers.size()); - for (Offer offer : offers) { - ProtocolUtils.writeString(buf, offer.text); - } - } - } - @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + public static class Codec implements PacketCodec { + @Override + public TabCompleteResponsePacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + if (version.noLessThan(MINECRAFT_1_13)) { + int transactionId = ProtocolUtils.readVarInt(buf); + int start = ProtocolUtils.readVarInt(buf); + int length = ProtocolUtils.readVarInt(buf); + int offersAvailable = ProtocolUtils.readVarInt(buf); + List offers = new ArrayList<>(offersAvailable); + for (int i = 0; i < offersAvailable; i++) { + String offer = ProtocolUtils.readString(buf); + ComponentHolder tooltip = buf.readBoolean() ? ComponentHolder.read(buf, version) : null; + offers.add(new Offer(offer, tooltip)); + } + return new TabCompleteResponsePacket(transactionId, start, length, offers); + } else { + int offersAvailable = ProtocolUtils.readVarInt(buf); + List offers = new ArrayList<>(offersAvailable); + for (int i = 0; i < offersAvailable; i++) { + offers.add(new Offer(ProtocolUtils.readString(buf), null)); + } + return new TabCompleteResponsePacket(0, 0, 0, offers); + } + } + + @Override + public void encode(TabCompleteResponsePacket packet, ByteBuf buf, + ProtocolUtils.Direction direction, ProtocolVersion version) { + if (version.noLessThan(MINECRAFT_1_13)) { + ProtocolUtils.writeVarInt(buf, packet.transactionId); + ProtocolUtils.writeVarInt(buf, packet.start); + ProtocolUtils.writeVarInt(buf, packet.length); + ProtocolUtils.writeVarInt(buf, packet.offers.size()); + for (Offer offer : packet.offers) { + ProtocolUtils.writeString(buf, offer.text); + buf.writeBoolean(offer.tooltip != null); + if (offer.tooltip != null) { + offer.tooltip.write(buf); + } + } + } else { + ProtocolUtils.writeVarInt(buf, packet.offers.size()); + for (Offer offer : packet.offers) { + ProtocolUtils.writeString(buf, offer.text); + } + } + } + } + public static class Offer implements Comparable { private final String text; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TransferPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TransferPacket.java index b5a74e49f..c8b468c75 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TransferPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TransferPacket.java @@ -20,22 +20,13 @@ package com.velocitypowered.proxy.protocol.packet; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; import java.net.InetSocketAddress; import org.jetbrains.annotations.Nullable; -public class TransferPacket implements MinecraftPacket { - private String host; - private int port; - - public TransferPacket() { - } - - public TransferPacket(final String host, final int port) { - this.host = host; - this.port = port; - } +public record TransferPacket(String host, int port) implements MinecraftPacket { @Nullable public InetSocketAddress address() { @@ -45,20 +36,31 @@ public class TransferPacket implements MinecraftPacket { return new InetSocketAddress(host, port); } - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { - this.host = ProtocolUtils.readString(buf); - this.port = ProtocolUtils.readVarInt(buf); + public String getHost() { + return host; } - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { - ProtocolUtils.writeString(buf, host); - ProtocolUtils.writeVarInt(buf, port); + public int getPort() { + return port; } @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + public static class Codec implements PacketCodec { + @Override + public TransferPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + return new TransferPacket(ProtocolUtils.readString(buf), ProtocolUtils.readVarInt(buf)); + } + + @Override + public void encode(TransferPacket packet, ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + ProtocolUtils.writeString(buf, packet.host); + ProtocolUtils.writeVarInt(buf, packet.port); + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/UpsertPlayerInfoPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/UpsertPlayerInfoPacket.java index 9ef40ef09..9f829d6bf 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/UpsertPlayerInfoPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/UpsertPlayerInfoPacket.java @@ -21,6 +21,7 @@ import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import com.velocitypowered.proxy.protocol.packet.chat.RemoteChatSession; @@ -28,29 +29,18 @@ import io.netty.buffer.ByteBuf; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; -import java.util.Collection; import java.util.EnumSet; import java.util.List; import java.util.UUID; import org.jetbrains.annotations.Nullable; -public class UpsertPlayerInfoPacket implements MinecraftPacket { +public final class UpsertPlayerInfoPacket implements MinecraftPacket { private static final Action[] ALL_ACTIONS = Action.class.getEnumConstants(); private final EnumSet actions; private final List entries; - public UpsertPlayerInfoPacket() { - this.actions = EnumSet.noneOf(Action.class); - this.entries = new ArrayList<>(); - } - - public UpsertPlayerInfoPacket(Action action) { - this.actions = EnumSet.of(action); - this.entries = new ArrayList<>(); - } - public UpsertPlayerInfoPacket(EnumSet actions, List entries) { this.actions = actions; this.entries = entries; @@ -68,71 +58,60 @@ public class UpsertPlayerInfoPacket implements MinecraftPacket { return this.actions.contains(action); } - public void addAction(Action action) { - this.actions.add(action); - } - - public void addAllActions(Collection actions) { - this.actions.addAll(actions); - } - - public void addEntry(Entry entry) { - this.entries.add(entry); - } - - public void addAllEntries(Collection entries) { - this.entries.addAll(entries); - } - - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion protocolVersion) { - byte[] bytes = new byte[-Math.floorDiv(-ALL_ACTIONS.length, 8)]; - buf.readBytes(bytes); - BitSet actionSet = BitSet.valueOf(bytes); - - for (int idx = 0; idx < ALL_ACTIONS.length; idx++) { - if (actionSet.get(idx)) { - addAction(ALL_ACTIONS[idx]); - } - } - - int length = ProtocolUtils.readVarInt(buf); - for (int idx = 0; idx < length; idx++) { - Entry entry = new Entry(ProtocolUtils.readUuid(buf)); - for (Action action : this.actions) { - action.read.read(protocolVersion, buf, entry); - } - addEntry(entry); - } - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion protocolVersion) { - BitSet set = new BitSet(ALL_ACTIONS.length); - for (int idx = 0; idx < ALL_ACTIONS.length; idx++) { - set.set(idx, this.actions.contains(ALL_ACTIONS[idx])); - } - - byte[] bytes = set.toByteArray(); - buf.writeBytes(Arrays.copyOf(bytes, -Math.floorDiv(-ALL_ACTIONS.length, 8))); - - ProtocolUtils.writeVarInt(buf, this.entries.size()); - for (Entry entry : this.entries) { - ProtocolUtils.writeUuid(buf, entry.profileId); - - for (Action action : this.actions) { - action.write.write(protocolVersion, buf, entry); - } - } - } - @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + public static class Codec implements PacketCodec { + @Override + public UpsertPlayerInfoPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + byte[] bytes = new byte[-Math.floorDiv(-ALL_ACTIONS.length, 8)]; + buf.readBytes(bytes); + BitSet actionSet = BitSet.valueOf(bytes); + + EnumSet actions = EnumSet.noneOf(Action.class); + for (int idx = 0; idx < ALL_ACTIONS.length; idx++) { + if (actionSet.get(idx)) { + actions.add(ALL_ACTIONS[idx]); + } + } + + int length = ProtocolUtils.readVarInt(buf); + List entries = new ArrayList<>(length); + for (int idx = 0; idx < length; idx++) { + Entry entry = new Entry(ProtocolUtils.readUuid(buf)); + for (Action action : actions) { + action.read.read(protocolVersion, buf, entry); + } + entries.add(entry); + } + return new UpsertPlayerInfoPacket(actions, entries); + } + + @Override + public void encode(UpsertPlayerInfoPacket packet, ByteBuf buf, + ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + BitSet set = new BitSet(ALL_ACTIONS.length); + for (int idx = 0; idx < ALL_ACTIONS.length; idx++) { + set.set(idx, packet.actions.contains(ALL_ACTIONS[idx])); + } + + byte[] bytes = set.toByteArray(); + buf.writeBytes(Arrays.copyOf(bytes, -Math.floorDiv(-ALL_ACTIONS.length, 8))); + + ProtocolUtils.writeVarInt(buf, packet.entries.size()); + for (Entry entry : packet.entries) { + ProtocolUtils.writeUuid(buf, entry.profileId); + + for (Action action : packet.actions) { + action.write.write(protocolVersion, buf, entry); + } + } + } + } + public enum Action { ADD_PLAYER((ignored, buf, info) -> { // read info.profile = new GameProfile( @@ -315,4 +294,4 @@ public class UpsertPlayerInfoPacket implements MinecraftPacket { '}'; } } -} \ No newline at end of file +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ChatAcknowledgementPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ChatAcknowledgementPacket.java index b0718090e..0fd6e2525 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ChatAcknowledgementPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ChatAcknowledgementPacket.java @@ -20,42 +20,35 @@ package com.velocitypowered.proxy.protocol.packet.chat; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; -public class ChatAcknowledgementPacket implements MinecraftPacket { - int offset; +public record ChatAcknowledgementPacket(int offset) implements MinecraftPacket { - public ChatAcknowledgementPacket(int offset) { - this.offset = offset; - } + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } - public ChatAcknowledgementPacket() { + @Override + public String toString() { + return "ChatAcknowledgement{" + + "offset=" + offset + + '}'; + } + + public static class Codec implements PacketCodec { + @Override + public ChatAcknowledgementPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + return new ChatAcknowledgementPacket(ProtocolUtils.readVarInt(buf)); } @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { - offset = ProtocolUtils.readVarInt(buf); - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { - ProtocolUtils.writeVarInt(buf, offset); - } - - @Override - public boolean handle(MinecraftSessionHandler handler) { - return handler.handle(this); - } - - @Override - public String toString() { - return "ChatAcknowledgement{" + - "offset=" + offset + - '}'; - } - - public int offset() { - return offset; + public void encode(ChatAcknowledgementPacket packet, ByteBuf buf, + ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + ProtocolUtils.writeVarInt(buf, packet.offset); } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/PlayerChatCompletionPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/PlayerChatCompletionPacket.java index d07b798a5..2a793453f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/PlayerChatCompletionPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/PlayerChatCompletionPacket.java @@ -20,15 +20,17 @@ package com.velocitypowered.proxy.protocol.packet.chat; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; -public class PlayerChatCompletionPacket implements MinecraftPacket { +public final class PlayerChatCompletionPacket implements MinecraftPacket { - private String[] completions; - private Action action; + private final String[] completions; + private final Action action; public PlayerChatCompletionPacket() { + this(new String[0], Action.ADD); } public PlayerChatCompletionPacket(String[] completions, Action action) { @@ -36,36 +38,14 @@ public class PlayerChatCompletionPacket implements MinecraftPacket { this.action = action; } - public String[] getCompletions() { + public String[] completions() { return completions; } - public Action getAction() { + public Action action() { return action; } - public void setCompletions(String[] completions) { - this.completions = completions; - } - - public void setAction(Action action) { - this.action = action; - } - - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion protocolVersion) { - action = Action.values()[ProtocolUtils.readVarInt(buf)]; - completions = ProtocolUtils.readStringArray(buf); - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion protocolVersion) { - ProtocolUtils.writeVarInt(buf, action.ordinal()); - ProtocolUtils.writeStringArray(buf, completions); - } - @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); @@ -76,4 +56,21 @@ public class PlayerChatCompletionPacket implements MinecraftPacket { REMOVE, SET } + + public static class Codec implements PacketCodec { + @Override + public PlayerChatCompletionPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + Action action = Action.values()[ProtocolUtils.readVarInt(buf)]; + String[] completions = ProtocolUtils.readStringArray(buf); + return new PlayerChatCompletionPacket(completions, action); + } + + @Override + public void encode(PlayerChatCompletionPacket packet, ByteBuf buf, + ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + ProtocolUtils.writeVarInt(buf, packet.action.ordinal()); + ProtocolUtils.writeStringArray(buf, packet.completions); + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/SystemChatPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/SystemChatPacket.java index b36014d5b..efe480950 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/SystemChatPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/SystemChatPacket.java @@ -20,12 +20,17 @@ package com.velocitypowered.proxy.protocol.packet.chat; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; -public class SystemChatPacket implements MinecraftPacket { +public final class SystemChatPacket implements MinecraftPacket { + + private final ComponentHolder component; + private final ChatType type; public SystemChatPacket() { + this(null, ChatType.SYSTEM); } public SystemChatPacket(ComponentHolder component, ChatType type) { @@ -33,48 +38,59 @@ public class SystemChatPacket implements MinecraftPacket { this.type = type; } - private ComponentHolder component; - private ChatType type; - - public ChatType getType() { + public ChatType type() { return type; } - public ComponentHolder getComponent() { + public ComponentHolder component() { return component; } - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - component = ComponentHolder.read(buf, version); - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)){ - type = buf.readBoolean() ? ChatType.GAME_INFO : ChatType.SYSTEM; - } else { - type = ChatType.values()[ProtocolUtils.readVarInt(buf)]; - } + public ComponentHolder getComponent() { + return component(); } - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - component.write(buf); - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) { - switch (type) { - case SYSTEM: - buf.writeBoolean(false); - break; - case GAME_INFO: - buf.writeBoolean(true); - break; - default: - throw new IllegalArgumentException("Invalid chat type"); - } - } else { - ProtocolUtils.writeVarInt(buf, type.getId()); - } + public ChatType getType() { + return type(); } @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + public static class Codec implements PacketCodec { + @Override + public SystemChatPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + ComponentHolder component = ComponentHolder.read(buf, version); + ChatType type; + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) { + type = buf.readBoolean() ? ChatType.GAME_INFO : ChatType.SYSTEM; + } else { + type = ChatType.values()[ProtocolUtils.readVarInt(buf)]; + } + return new SystemChatPacket(component, type); + } + + @Override + public void encode(SystemChatPacket packet, ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + packet.component.write(buf); + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) { + switch (packet.type) { + case SYSTEM: + buf.writeBoolean(false); + break; + case GAME_INFO: + buf.writeBoolean(true); + break; + default: + throw new IllegalArgumentException("Invalid chat type"); + } + } else { + ProtocolUtils.writeVarInt(buf, packet.type.getId()); + } + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/keyed/KeyedChatBuilder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/keyed/KeyedChatBuilder.java index d654c619c..c60a18ac4 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/keyed/KeyedChatBuilder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/keyed/KeyedChatBuilder.java @@ -17,7 +17,6 @@ package com.velocitypowered.proxy.protocol.packet.chat.keyed; -import com.google.common.collect.ImmutableList; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.packet.chat.ChatType; @@ -42,12 +41,13 @@ public class KeyedChatBuilder extends ChatBuilderV2 { @Override public MinecraftPacket toServer() { if (message.startsWith("/")) { - return new KeyedPlayerCommandPacket(message.substring(1), ImmutableList.of(), timestamp); + return new KeyedPlayerCommandPacket(false, message.substring(1), timestamp, 0L, + false, new com.velocitypowered.proxy.crypto.SignaturePair[0], null, + java.util.Collections.emptyMap()); } else { // This will produce an error on the server, but needs to be here. - KeyedPlayerChatPacket v1Chat = new KeyedPlayerChatPacket(message); - v1Chat.setExpiry(this.timestamp); - return v1Chat; + return new KeyedPlayerChatPacket(message, false, false, timestamp, null, null, + new com.velocitypowered.proxy.crypto.SignaturePair[0], null); } } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/keyed/KeyedPlayerChatPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/keyed/KeyedPlayerChatPacket.java index 74fa88f5a..84378c0e3 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/keyed/KeyedPlayerChatPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/keyed/KeyedPlayerChatPacket.java @@ -23,41 +23,43 @@ import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.crypto.EncryptionUtils; import com.velocitypowered.proxy.crypto.SignaturePair; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.util.except.QuietDecoderException; import io.netty.buffer.ByteBuf; import java.time.Instant; import org.checkerframework.checker.nullness.qual.Nullable; -public class KeyedPlayerChatPacket implements MinecraftPacket { - - private String message; - private boolean signedPreview; - private boolean unsigned = false; - private @Nullable Instant expiry; - private @Nullable byte[] signature; - private @Nullable byte[] salt; - private SignaturePair[] previousMessages = new SignaturePair[0]; - private @Nullable SignaturePair lastMessage; +public final class KeyedPlayerChatPacket implements MinecraftPacket { public static final int MAXIMUM_PREVIOUS_MESSAGE_COUNT = 5; public static final QuietDecoderException INVALID_PREVIOUS_MESSAGES = new QuietDecoderException("Invalid previous messages"); - public KeyedPlayerChatPacket() { - } + private final String message; + private final boolean signedPreview; + private final boolean unsigned; + private final @Nullable Instant expiry; + private final @Nullable byte[] signature; + private final @Nullable byte[] salt; + private final SignaturePair[] previousMessages; + private final @Nullable SignaturePair lastMessage; - public KeyedPlayerChatPacket(String message) { + public KeyedPlayerChatPacket(String message, boolean signedPreview, boolean unsigned, + @Nullable Instant expiry, @Nullable byte[] signature, @Nullable byte[] salt, + SignaturePair[] previousMessages, @Nullable SignaturePair lastMessage) { this.message = message; - this.unsigned = true; - } - - public void setExpiry(@Nullable Instant expiry) { + this.signedPreview = signedPreview; + this.unsigned = unsigned; this.expiry = expiry; + this.signature = signature; + this.salt = salt; + this.previousMessages = previousMessages; + this.lastMessage = lastMessage; } - public Instant getExpiry() { + public @Nullable Instant getExpiry() { return expiry; } @@ -73,82 +75,96 @@ public class KeyedPlayerChatPacket implements MinecraftPacket { return signedPreview; } - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion protocolVersion) { - message = ProtocolUtils.readString(buf, 256); - - long expiresAt = buf.readLong(); - long saltLong = buf.readLong(); - byte[] signatureBytes = ProtocolUtils.readByteArray(buf); - - if (saltLong != 0L && signatureBytes.length > 0) { - salt = Longs.toByteArray(saltLong); - signature = signatureBytes; - expiry = Instant.ofEpochMilli(expiresAt); - } else if ((protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_1) - || saltLong == 0L) && signatureBytes.length == 0) { - unsigned = true; - } else { - throw EncryptionUtils.INVALID_SIGNATURE; - } - - signedPreview = buf.readBoolean(); - if (signedPreview && unsigned) { - throw EncryptionUtils.PREVIEW_SIGNATURE_MISSING; - } - - if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) { - int size = ProtocolUtils.readVarInt(buf); - if (size < 0 || size > MAXIMUM_PREVIOUS_MESSAGE_COUNT) { - throw INVALID_PREVIOUS_MESSAGES; - } - - SignaturePair[] lastSignatures = new SignaturePair[size]; - for (int i = 0; i < size; i++) { - lastSignatures[i] = new SignaturePair(ProtocolUtils.readUuid(buf), - ProtocolUtils.readByteArray(buf)); - } - previousMessages = lastSignatures; - - if (buf.readBoolean()) { - lastMessage = new SignaturePair(ProtocolUtils.readUuid(buf), - ProtocolUtils.readByteArray(buf)); - } - } - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion protocolVersion) { - ProtocolUtils.writeString(buf, message); - - buf.writeLong(unsigned ? Instant.now().toEpochMilli() : expiry.toEpochMilli()); - buf.writeLong(unsigned ? 0L : Longs.fromByteArray(salt)); - - ProtocolUtils.writeByteArray(buf, unsigned ? EncryptionUtils.EMPTY : signature); - - buf.writeBoolean(signedPreview); - - if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) { - ProtocolUtils.writeVarInt(buf, previousMessages.length); - for (SignaturePair previousMessage : previousMessages) { - ProtocolUtils.writeUuid(buf, previousMessage.getSigner()); - ProtocolUtils.writeByteArray(buf, previousMessage.getSignature()); - } - - if (lastMessage != null) { - buf.writeBoolean(true); - ProtocolUtils.writeUuid(buf, lastMessage.getSigner()); - ProtocolUtils.writeByteArray(buf, lastMessage.getSignature()); - } else { - buf.writeBoolean(false); - } - } - } - @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + public static class Codec implements PacketCodec { + @Override + public KeyedPlayerChatPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + String message = ProtocolUtils.readString(buf, 256); + + long expiresAt = buf.readLong(); + long saltLong = buf.readLong(); + byte[] signatureBytes = ProtocolUtils.readByteArray(buf); + + byte[] salt = null; + byte[] signature = null; + Instant expiry = null; + boolean unsigned; + + if (saltLong != 0L && signatureBytes.length > 0) { + salt = Longs.toByteArray(saltLong); + signature = signatureBytes; + expiry = Instant.ofEpochMilli(expiresAt); + unsigned = false; + } else if ((protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_1) + || saltLong == 0L) && signatureBytes.length == 0) { + unsigned = true; + } else { + throw EncryptionUtils.INVALID_SIGNATURE; + } + + boolean signedPreview = buf.readBoolean(); + if (signedPreview && unsigned) { + throw EncryptionUtils.PREVIEW_SIGNATURE_MISSING; + } + + SignaturePair[] previousMessages = new SignaturePair[0]; + SignaturePair lastMessage = null; + + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) { + int size = ProtocolUtils.readVarInt(buf); + if (size < 0 || size > MAXIMUM_PREVIOUS_MESSAGE_COUNT) { + throw INVALID_PREVIOUS_MESSAGES; + } + + SignaturePair[] lastSignatures = new SignaturePair[size]; + for (int i = 0; i < size; i++) { + lastSignatures[i] = new SignaturePair(ProtocolUtils.readUuid(buf), + ProtocolUtils.readByteArray(buf)); + } + previousMessages = lastSignatures; + + if (buf.readBoolean()) { + lastMessage = new SignaturePair(ProtocolUtils.readUuid(buf), + ProtocolUtils.readByteArray(buf)); + } + } + + return new KeyedPlayerChatPacket(message, signedPreview, unsigned, expiry, signature, salt, + previousMessages, lastMessage); + } + + @Override + public void encode(KeyedPlayerChatPacket packet, ByteBuf buf, + ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + ProtocolUtils.writeString(buf, packet.message); + + buf.writeLong(packet.unsigned ? Instant.now().toEpochMilli() : packet.expiry.toEpochMilli()); + buf.writeLong(packet.unsigned ? 0L : Longs.fromByteArray(packet.salt)); + + ProtocolUtils.writeByteArray(buf, packet.unsigned ? EncryptionUtils.EMPTY : packet.signature); + + buf.writeBoolean(packet.signedPreview); + + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) { + ProtocolUtils.writeVarInt(buf, packet.previousMessages.length); + for (SignaturePair previousMessage : packet.previousMessages) { + ProtocolUtils.writeUuid(buf, previousMessage.getSigner()); + ProtocolUtils.writeByteArray(buf, previousMessage.getSignature()); + } + + if (packet.lastMessage != null) { + buf.writeBoolean(true); + ProtocolUtils.writeUuid(buf, packet.lastMessage.getSigner()); + ProtocolUtils.writeByteArray(buf, packet.lastMessage.getSignature()); + } else { + buf.writeBoolean(false); + } + } + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/keyed/KeyedPlayerCommandPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/keyed/KeyedPlayerCommandPacket.java index 0bb43ec8e..c4e4deb73 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/keyed/KeyedPlayerCommandPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/keyed/KeyedPlayerCommandPacket.java @@ -26,30 +26,43 @@ import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.crypto.EncryptionUtils; import com.velocitypowered.proxy.crypto.SignaturePair; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.util.except.QuietDecoderException; import io.netty.buffer.ByteBuf; import java.time.Instant; import java.util.Arrays; -import java.util.List; import java.util.Map; import org.checkerframework.checker.nullness.qual.Nullable; -public class KeyedPlayerCommandPacket implements MinecraftPacket { +public final class KeyedPlayerCommandPacket implements MinecraftPacket { private static final int MAX_NUM_ARGUMENTS = 8; private static final int MAX_LENGTH_ARGUMENTS = 16; private static final QuietDecoderException LIMITS_VIOLATION = new QuietDecoderException("Command arguments incorrect size"); - private boolean unsigned = false; - private String command; - private Instant timestamp; - private long salt; - private boolean signedPreview; // purely for pass through for 1.19 -> 1.19.2 - this will never be implemented - private SignaturePair[] previousMessages = new SignaturePair[0]; - private @Nullable SignaturePair lastMessage; - private Map arguments = ImmutableMap.of(); + private final boolean unsigned; + private final String command; + private final Instant timestamp; + private final long salt; + private final boolean signedPreview; + private final SignaturePair[] previousMessages; + private final @Nullable SignaturePair lastMessage; + private final Map arguments; + + public KeyedPlayerCommandPacket(boolean unsigned, String command, Instant timestamp, long salt, + boolean signedPreview, SignaturePair[] previousMessages, @Nullable SignaturePair lastMessage, + Map arguments) { + this.unsigned = unsigned; + this.command = command; + this.timestamp = timestamp; + this.salt = salt; + this.signedPreview = signedPreview; + this.previousMessages = previousMessages; + this.lastMessage = lastMessage; + this.arguments = arguments; + } public Instant getTimestamp() { return timestamp; @@ -63,116 +76,6 @@ public class KeyedPlayerCommandPacket implements MinecraftPacket { return command; } - public KeyedPlayerCommandPacket() { - } - - /** - * Creates an {@link KeyedPlayerCommandPacket} packet based on a command and list of arguments. - * - * @param command the command to run - * @param arguments the arguments of the command - * @param timestamp the timestamp of the command execution - */ - public KeyedPlayerCommandPacket(String command, List arguments, Instant timestamp) { - this.unsigned = true; - ImmutableMap.Builder builder = ImmutableMap.builder(); - arguments.forEach(entry -> builder.put(entry, EncryptionUtils.EMPTY)); - this.arguments = builder.build(); - this.timestamp = timestamp; - this.command = command; - this.signedPreview = false; - this.salt = 0L; - } - - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion protocolVersion) { - command = ProtocolUtils.readString(buf, 256); - timestamp = Instant.ofEpochMilli(buf.readLong()); - - salt = buf.readLong(); - - int mapSize = ProtocolUtils.readVarInt(buf); - if (mapSize > MAX_NUM_ARGUMENTS) { - throw LIMITS_VIOLATION; - } - // Mapped as Argument : signature - ImmutableMap.Builder entries = ImmutableMap.builderWithExpectedSize(mapSize); - for (int i = 0; i < mapSize; i++) { - entries.put(ProtocolUtils.readString(buf, MAX_LENGTH_ARGUMENTS), - ProtocolUtils.readByteArray(buf, unsigned ? 0 : ProtocolUtils.DEFAULT_MAX_STRING_SIZE)); - } - arguments = entries.build(); - - this.signedPreview = buf.readBoolean(); - if (unsigned && signedPreview) { - throw EncryptionUtils.PREVIEW_SIGNATURE_MISSING; - } - - if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) { - int size = ProtocolUtils.readVarInt(buf); - if (size < 0 || size > MAXIMUM_PREVIOUS_MESSAGE_COUNT) { - throw INVALID_PREVIOUS_MESSAGES; - } - - SignaturePair[] lastSignatures = new SignaturePair[size]; - for (int i = 0; i < size; i++) { - lastSignatures[i] = new SignaturePair(ProtocolUtils.readUuid(buf), - ProtocolUtils.readByteArray(buf)); - } - previousMessages = lastSignatures; - - if (buf.readBoolean()) { - lastMessage = new SignaturePair(ProtocolUtils.readUuid(buf), - ProtocolUtils.readByteArray(buf)); - } - } - - if (salt == 0L && previousMessages.length == 0) { - unsigned = true; - } - - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion protocolVersion) { - ProtocolUtils.writeString(buf, command); - buf.writeLong(timestamp.toEpochMilli()); - - buf.writeLong(unsigned ? 0L : salt); - - int size = arguments.size(); - if (size > MAX_NUM_ARGUMENTS) { - throw LIMITS_VIOLATION; - } - ProtocolUtils.writeVarInt(buf, size); - for (Map.Entry entry : arguments.entrySet()) { - // What annoys me is that this isn't "sorted" - ProtocolUtils.writeString(buf, entry.getKey()); - ProtocolUtils.writeByteArray(buf, unsigned ? EncryptionUtils.EMPTY : entry.getValue()); - } - - buf.writeBoolean(signedPreview); - - if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) { - ProtocolUtils.writeVarInt(buf, previousMessages.length); - for (SignaturePair previousMessage : previousMessages) { - ProtocolUtils.writeUuid(buf, previousMessage.getSigner()); - ProtocolUtils.writeByteArray(buf, previousMessage.getSignature()); - } - - if (lastMessage != null) { - buf.writeBoolean(true); - ProtocolUtils.writeUuid(buf, lastMessage.getSigner()); - ProtocolUtils.writeByteArray(buf, lastMessage.getSignature()); - } else { - buf.writeBoolean(false); - } - } - - } - @Override public String toString() { return "PlayerCommand{" @@ -190,4 +93,100 @@ public class KeyedPlayerCommandPacket implements MinecraftPacket { public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + public static class Codec implements PacketCodec { + @Override + public KeyedPlayerCommandPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + String command = ProtocolUtils.readString(buf, 256); + Instant timestamp = Instant.ofEpochMilli(buf.readLong()); + + long salt = buf.readLong(); + + int mapSize = ProtocolUtils.readVarInt(buf); + if (mapSize > MAX_NUM_ARGUMENTS) { + throw LIMITS_VIOLATION; + } + + boolean unsigned = false; + ImmutableMap.Builder entries = ImmutableMap.builderWithExpectedSize(mapSize); + for (int i = 0; i < mapSize; i++) { + entries.put(ProtocolUtils.readString(buf, MAX_LENGTH_ARGUMENTS), + ProtocolUtils.readByteArray(buf, unsigned ? 0 : ProtocolUtils.DEFAULT_MAX_STRING_SIZE)); + } + Map arguments = entries.build(); + + boolean signedPreview = buf.readBoolean(); + if (unsigned && signedPreview) { + throw EncryptionUtils.PREVIEW_SIGNATURE_MISSING; + } + + SignaturePair[] previousMessages = new SignaturePair[0]; + SignaturePair lastMessage = null; + + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) { + int size = ProtocolUtils.readVarInt(buf); + if (size < 0 || size > MAXIMUM_PREVIOUS_MESSAGE_COUNT) { + throw INVALID_PREVIOUS_MESSAGES; + } + + SignaturePair[] lastSignatures = new SignaturePair[size]; + for (int i = 0; i < size; i++) { + lastSignatures[i] = new SignaturePair(ProtocolUtils.readUuid(buf), + ProtocolUtils.readByteArray(buf)); + } + previousMessages = lastSignatures; + + if (buf.readBoolean()) { + lastMessage = new SignaturePair(ProtocolUtils.readUuid(buf), + ProtocolUtils.readByteArray(buf)); + } + } + + if (salt == 0L && previousMessages.length == 0) { + unsigned = true; + } + + return new KeyedPlayerCommandPacket(unsigned, command, timestamp, salt, signedPreview, + previousMessages, lastMessage, arguments); + } + + @Override + public void encode(KeyedPlayerCommandPacket packet, ByteBuf buf, + ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + ProtocolUtils.writeString(buf, packet.command); + buf.writeLong(packet.timestamp.toEpochMilli()); + + buf.writeLong(packet.unsigned ? 0L : packet.salt); + + int size = packet.arguments.size(); + if (size > MAX_NUM_ARGUMENTS) { + throw LIMITS_VIOLATION; + } + ProtocolUtils.writeVarInt(buf, size); + for (Map.Entry entry : packet.arguments.entrySet()) { + // What annoys me is that this isn't "sorted" + ProtocolUtils.writeString(buf, entry.getKey()); + ProtocolUtils.writeByteArray(buf, packet.unsigned ? EncryptionUtils.EMPTY : entry.getValue()); + } + + buf.writeBoolean(packet.signedPreview); + + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) { + ProtocolUtils.writeVarInt(buf, packet.previousMessages.length); + for (SignaturePair previousMessage : packet.previousMessages) { + ProtocolUtils.writeUuid(buf, previousMessage.getSigner()); + ProtocolUtils.writeByteArray(buf, previousMessage.getSignature()); + } + + if (packet.lastMessage != null) { + buf.writeBoolean(true); + ProtocolUtils.writeUuid(buf, packet.lastMessage.getSigner()); + ProtocolUtils.writeByteArray(buf, packet.lastMessage.getSignature()); + } else { + buf.writeBoolean(false); + } + } + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/legacy/LegacyChatBuilder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/legacy/LegacyChatBuilder.java index 77df43bb4..f5c8c7d4c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/legacy/LegacyChatBuilder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/legacy/LegacyChatBuilder.java @@ -44,8 +44,6 @@ public class LegacyChatBuilder extends ChatBuilderV2 { @Override public MinecraftPacket toServer() { - LegacyChatPacket chat = new LegacyChatPacket(); - chat.setMessage(message); - return chat; + return new LegacyChatPacket(message, (byte) 0, null); } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/legacy/LegacyChatPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/legacy/LegacyChatPacket.java index 80b239d53..fed490bc4 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/legacy/LegacyChatPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/legacy/LegacyChatPacket.java @@ -20,12 +20,13 @@ package com.velocitypowered.proxy.protocol.packet.chat.legacy; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; import java.util.UUID; import org.checkerframework.checker.nullness.qual.Nullable; -public class LegacyChatPacket implements MinecraftPacket { +public record LegacyChatPacket(String message, byte type, @Nullable UUID sender) implements MinecraftPacket { public static final byte CHAT_TYPE = (byte) 0; public static final byte SYSTEM_TYPE = (byte) 1; @@ -34,91 +35,44 @@ public class LegacyChatPacket implements MinecraftPacket { public static final int MAX_SERVERBOUND_MESSAGE_LENGTH = 256; public static final UUID EMPTY_SENDER = new UUID(0, 0); - private @Nullable String message; - private byte type; - private @Nullable UUID sender; - - public LegacyChatPacket() { - } - - /** - * Creates a Chat packet. - */ - public LegacyChatPacket(String message, byte type, UUID sender) { - this.message = message; - this.type = type; - this.sender = sender; - } - - /** - * Retrieves the Chat message. - */ public String getMessage() { - if (message == null) { - throw new IllegalStateException("Message is not specified"); - } return message; } - public void setMessage(String message) { - this.message = message; - } - - public byte getType() { - return type; - } - - public void setType(byte type) { - this.type = type; - } - - public UUID getSenderUuid() { - return sender; - } - - public void setSenderUuid(UUID sender) { - this.sender = sender; - } - - @Override - public String toString() { - return "Chat{" - + "message='" + message + '\'' - + ", type=" + type - + ", sender=" + sender - + '}'; - } - - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - message = ProtocolUtils.readString(buf, direction == ProtocolUtils.Direction.CLIENTBOUND - ? 262144 : version.noLessThan(ProtocolVersion.MINECRAFT_1_11) ? 256 : 100); - if (direction == ProtocolUtils.Direction.CLIENTBOUND - && version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) { - type = buf.readByte(); - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) { - sender = ProtocolUtils.readUuid(buf); - } - } - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - if (message == null) { - throw new IllegalStateException("Message is not specified"); - } - ProtocolUtils.writeString(buf, message); - if (direction == ProtocolUtils.Direction.CLIENTBOUND - && version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) { - buf.writeByte(type); - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) { - ProtocolUtils.writeUuid(buf, sender == null ? EMPTY_SENDER : sender); - } - } - } - @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + public static class Codec implements PacketCodec { + @Override + public LegacyChatPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + String message = ProtocolUtils.readString(buf, direction == ProtocolUtils.Direction.CLIENTBOUND + ? 262144 : version.noLessThan(ProtocolVersion.MINECRAFT_1_11) ? 256 : 100); + byte type = 0; + UUID sender = null; + if (direction == ProtocolUtils.Direction.CLIENTBOUND + && version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) { + type = buf.readByte(); + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) { + sender = ProtocolUtils.readUuid(buf); + } + } + return new LegacyChatPacket(message, type, sender); + } + + @Override + public void encode(LegacyChatPacket packet, ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + ProtocolUtils.writeString(buf, packet.message); + if (direction == ProtocolUtils.Direction.CLIENTBOUND + && version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) { + buf.writeByte(packet.type); + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) { + ProtocolUtils.writeUuid(buf, packet.sender == null ? EMPTY_SENDER : packet.sender); + } + } + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionChatBuilder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionChatBuilder.java index eb74123fc..46396047c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionChatBuilder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionChatBuilder.java @@ -44,27 +44,14 @@ public class SessionChatBuilder extends ChatBuilderV2 { LastSeenMessages lastSeenMessages = this.lastSeenMessages != null ? this.lastSeenMessages : new LastSeenMessages(); if (message.startsWith("/")) { if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { - UnsignedPlayerCommandPacket command = new UnsignedPlayerCommandPacket(); - command.command = message.substring(1); - return command; + return new UnsignedPlayerCommandPacket(message.substring(1)); } else { - SessionPlayerCommandPacket command = new SessionPlayerCommandPacket(); - command.command = message.substring(1); - command.salt = 0L; - command.timeStamp = timestamp; - command.argumentSignatures = new SessionPlayerCommandPacket.ArgumentSignatures(); - command.lastSeenMessages = lastSeenMessages; - return command; + return new SessionPlayerCommandPacket(message.substring(1), timestamp, 0L, + new SessionPlayerCommandPacket.ArgumentSignatures(), lastSeenMessages); } } else { - SessionPlayerChatPacket chat = new SessionPlayerChatPacket(); - chat.message = message; - chat.signed = false; - chat.signature = new byte[0]; - chat.timestamp = timestamp; - chat.salt = 0L; - chat.lastSeenMessages = lastSeenMessages; - return chat; + return new SessionPlayerChatPacket(message, timestamp, 0L, false, new byte[0], + lastSeenMessages); } } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionPlayerChatPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionPlayerChatPacket.java index 8a00452c6..14556afca 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionPlayerChatPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionPlayerChatPacket.java @@ -20,6 +20,7 @@ package com.velocitypowered.proxy.protocol.packet.chat.session; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.packet.chat.LastSeenMessages; import io.netty.buffer.ByteBuf; @@ -27,14 +28,21 @@ import java.time.Instant; public class SessionPlayerChatPacket implements MinecraftPacket { - protected String message; - protected Instant timestamp; - protected long salt; - protected boolean signed; - protected byte[] signature; - protected LastSeenMessages lastSeenMessages; + protected final String message; + protected final Instant timestamp; + protected final long salt; + protected final boolean signed; + protected final byte[] signature; + protected final LastSeenMessages lastSeenMessages; - public SessionPlayerChatPacket() { + public SessionPlayerChatPacket(String message, Instant timestamp, long salt, boolean signed, + byte[] signature, LastSeenMessages lastSeenMessages) { + this.message = message; + this.timestamp = timestamp; + this.salt = salt; + this.signed = signed; + this.signature = signature; + this.lastSeenMessages = lastSeenMessages; } public String getMessage() { @@ -61,34 +69,6 @@ public class SessionPlayerChatPacket implements MinecraftPacket { return lastSeenMessages; } - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion protocolVersion) { - this.message = ProtocolUtils.readString(buf, 256); - this.timestamp = Instant.ofEpochMilli(buf.readLong()); - this.salt = buf.readLong(); - this.signed = buf.readBoolean(); - if (this.signed) { - this.signature = readMessageSignature(buf); - } else { - this.signature = new byte[0]; - } - this.lastSeenMessages = new LastSeenMessages(buf, protocolVersion); - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion protocolVersion) { - ProtocolUtils.writeString(buf, this.message); - buf.writeLong(this.timestamp.toEpochMilli()); - buf.writeLong(this.salt); - buf.writeBoolean(this.signed); - if (this.signed) { - buf.writeBytes(this.signature); - } - this.lastSeenMessages.encode(buf, protocolVersion); - } - @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); @@ -101,13 +81,38 @@ public class SessionPlayerChatPacket implements MinecraftPacket { } public SessionPlayerChatPacket withLastSeenMessages(LastSeenMessages lastSeenMessages) { - SessionPlayerChatPacket packet = new SessionPlayerChatPacket(); - packet.message = message; - packet.timestamp = timestamp; - packet.salt = salt; - packet.signed = signed; - packet.signature = signature; - packet.lastSeenMessages = lastSeenMessages; - return packet; + return new SessionPlayerChatPacket(message, timestamp, salt, signed, signature, lastSeenMessages); + } + + public static class Codec implements PacketCodec { + @Override + public SessionPlayerChatPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + String message = ProtocolUtils.readString(buf, 256); + Instant timestamp = Instant.ofEpochMilli(buf.readLong()); + long salt = buf.readLong(); + boolean signed = buf.readBoolean(); + byte[] signature; + if (signed) { + signature = readMessageSignature(buf); + } else { + signature = new byte[0]; + } + LastSeenMessages lastSeenMessages = new LastSeenMessages(buf, protocolVersion); + return new SessionPlayerChatPacket(message, timestamp, salt, signed, signature, lastSeenMessages); + } + + @Override + public void encode(SessionPlayerChatPacket packet, ByteBuf buf, + ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + ProtocolUtils.writeString(buf, packet.message); + buf.writeLong(packet.timestamp.toEpochMilli()); + buf.writeLong(packet.salt); + buf.writeBoolean(packet.signed); + if (packet.signed) { + buf.writeBytes(packet.signature); + } + packet.lastSeenMessages.encode(buf, protocolVersion); + } } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionPlayerCommandPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionPlayerCommandPacket.java index f4ea3a1e2..988d68dcb 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionPlayerCommandPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionPlayerCommandPacket.java @@ -22,6 +22,7 @@ import com.velocitypowered.api.event.command.CommandExecuteEvent; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.packet.chat.LastSeenMessages; import com.velocitypowered.proxy.util.except.QuietDecoderException; @@ -33,29 +34,19 @@ import java.util.List; public class SessionPlayerCommandPacket implements MinecraftPacket { - protected String command; - protected Instant timeStamp; - protected long salt; - protected ArgumentSignatures argumentSignatures; - protected LastSeenMessages lastSeenMessages; + protected final String command; + protected final Instant timeStamp; + protected final long salt; + protected final ArgumentSignatures argumentSignatures; + protected final LastSeenMessages lastSeenMessages; - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { - int cap = protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_20_5) ? 256 : ProtocolUtils.DEFAULT_MAX_STRING_SIZE; - this.command = ProtocolUtils.readString(buf, cap); - this.timeStamp = Instant.ofEpochMilli(buf.readLong()); - this.salt = buf.readLong(); - this.argumentSignatures = new ArgumentSignatures(buf); - this.lastSeenMessages = new LastSeenMessages(buf, protocolVersion); - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { - ProtocolUtils.writeString(buf, this.command); - buf.writeLong(this.timeStamp.toEpochMilli()); - buf.writeLong(this.salt); - this.argumentSignatures.encode(buf); - this.lastSeenMessages.encode(buf, protocolVersion); + public SessionPlayerCommandPacket(String command, Instant timeStamp, long salt, + ArgumentSignatures argumentSignatures, LastSeenMessages lastSeenMessages) { + this.command = command; + this.timeStamp = timeStamp; + this.salt = salt; + this.argumentSignatures = argumentSignatures; + this.lastSeenMessages = lastSeenMessages; } public String getCommand() { @@ -92,17 +83,33 @@ public class SessionPlayerCommandPacket implements MinecraftPacket { public SessionPlayerCommandPacket withLastSeenMessages(@Nullable LastSeenMessages lastSeenMessages) { if (lastSeenMessages == null) { - UnsignedPlayerCommandPacket packet = new UnsignedPlayerCommandPacket(); - packet.command = command; - return packet; + return new UnsignedPlayerCommandPacket(command); + } + return new SessionPlayerCommandPacket(command, timeStamp, salt, argumentSignatures, lastSeenMessages); + } + + public static class Codec implements PacketCodec { + @Override + public SessionPlayerCommandPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + int cap = protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_20_5) ? 256 : ProtocolUtils.DEFAULT_MAX_STRING_SIZE; + String command = ProtocolUtils.readString(buf, cap); + Instant timeStamp = Instant.ofEpochMilli(buf.readLong()); + long salt = buf.readLong(); + ArgumentSignatures argumentSignatures = new ArgumentSignatures(buf); + LastSeenMessages lastSeenMessages = new LastSeenMessages(buf, protocolVersion); + return new SessionPlayerCommandPacket(command, timeStamp, salt, argumentSignatures, lastSeenMessages); + } + + @Override + public void encode(SessionPlayerCommandPacket packet, ByteBuf buf, + ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + ProtocolUtils.writeString(buf, packet.command); + buf.writeLong(packet.timeStamp.toEpochMilli()); + buf.writeLong(packet.salt); + packet.argumentSignatures.encode(buf); + packet.lastSeenMessages.encode(buf, protocolVersion); } - SessionPlayerCommandPacket packet = new SessionPlayerCommandPacket(); - packet.command = command; - packet.timeStamp = timeStamp; - packet.salt = salt; - packet.argumentSignatures = argumentSignatures; - packet.lastSeenMessages = lastSeenMessages; - return packet; } public static class ArgumentSignatures { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/UnsignedPlayerCommandPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/UnsignedPlayerCommandPacket.java index 915f0cfbc..ccc0b9b1b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/UnsignedPlayerCommandPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/UnsignedPlayerCommandPacket.java @@ -19,21 +19,18 @@ package com.velocitypowered.proxy.protocol.packet.chat.session; import com.velocitypowered.api.event.command.CommandExecuteEvent; import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.packet.chat.LastSeenMessages; import io.netty.buffer.ByteBuf; import org.checkerframework.checker.nullness.qual.Nullable; -public class UnsignedPlayerCommandPacket extends SessionPlayerCommandPacket { +import java.time.Instant; - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { - this.command = ProtocolUtils.readString(buf, ProtocolUtils.DEFAULT_MAX_STRING_SIZE); - } +public final class UnsignedPlayerCommandPacket extends SessionPlayerCommandPacket { - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { - ProtocolUtils.writeString(buf, this.command); + public UnsignedPlayerCommandPacket(String command) { + super(command, Instant.EPOCH, 0L, new ArgumentSignatures(), null); } @Override @@ -41,6 +38,7 @@ public class UnsignedPlayerCommandPacket extends SessionPlayerCommandPacket { return this; } + @Override public boolean isSigned() { return false; } @@ -56,4 +54,19 @@ public class UnsignedPlayerCommandPacket extends SessionPlayerCommandPacket { "command='" + command + '\'' + '}'; } + + public static class Codec implements PacketCodec { + @Override + public UnsignedPlayerCommandPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + String command = ProtocolUtils.readString(buf, ProtocolUtils.DEFAULT_MAX_STRING_SIZE); + return new UnsignedPlayerCommandPacket(command); + } + + @Override + public void encode(UnsignedPlayerCommandPacket packet, ByteBuf buf, + ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + ProtocolUtils.writeString(buf, packet.command); + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/ActiveFeaturesPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/ActiveFeaturesPacket.java index 79c94b641..f7290ecac 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/ActiveFeaturesPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/ActiveFeaturesPacket.java @@ -20,44 +20,38 @@ package com.velocitypowered.proxy.protocol.packet.config; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; import net.kyori.adventure.key.Key; -public class ActiveFeaturesPacket implements MinecraftPacket { - - private Key[] activeFeatures; - - public ActiveFeaturesPacket(Key[] activeFeatures) { - this.activeFeatures = activeFeatures; - } +public record ActiveFeaturesPacket(Key[] activeFeatures) implements MinecraftPacket { public ActiveFeaturesPacket() { - this.activeFeatures = new Key[0]; - } - - public void setActiveFeatures(Key[] activeFeatures) { - this.activeFeatures = activeFeatures; + this(new Key[0]); } public Key[] getActiveFeatures() { return activeFeatures; } - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion protocolVersion) { - activeFeatures = ProtocolUtils.readKeyArray(buf); - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion protocolVersion) { - ProtocolUtils.writeKeyArray(buf, activeFeatures); - } - @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + public static class Codec implements PacketCodec { + @Override + public ActiveFeaturesPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + Key[] activeFeatures = ProtocolUtils.readKeyArray(buf); + return new ActiveFeaturesPacket(activeFeatures); + } + + @Override + public void encode(ActiveFeaturesPacket packet, ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + ProtocolUtils.writeKeyArray(buf, packet.activeFeatures); + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/ClientboundCustomReportDetailsPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/ClientboundCustomReportDetailsPacket.java index 6a3618cb7..4820da37d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/ClientboundCustomReportDetailsPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/ClientboundCustomReportDetailsPacket.java @@ -20,40 +20,16 @@ package com.velocitypowered.proxy.protocol.packet.config; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; import java.util.HashMap; import java.util.Map; -public class ClientboundCustomReportDetailsPacket implements MinecraftPacket { - - private Map details; +public record ClientboundCustomReportDetailsPacket(Map details) implements MinecraftPacket { public ClientboundCustomReportDetailsPacket() { - } - - public ClientboundCustomReportDetailsPacket(Map details) { - this.details = details; - } - - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { - int detailsCount = ProtocolUtils.readVarInt(buf); - - this.details = new HashMap<>(detailsCount); - for (int i = 0; i < detailsCount; i++) { - details.put(ProtocolUtils.readString(buf), ProtocolUtils.readString(buf)); - } - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { - ProtocolUtils.writeVarInt(buf, details.size()); - - details.forEach((key, detail) -> { - ProtocolUtils.writeString(buf, key); - ProtocolUtils.writeString(buf, detail); - }); + this(Map.of()); } @Override @@ -61,7 +37,28 @@ public class ClientboundCustomReportDetailsPacket implements MinecraftPacket { return handler.handle(this); } - public Map getDetails() { - return details; + public static class Codec implements PacketCodec { + @Override + public ClientboundCustomReportDetailsPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + int detailsCount = ProtocolUtils.readVarInt(buf); + + Map details = new HashMap<>(detailsCount); + for (int i = 0; i < detailsCount; i++) { + details.put(ProtocolUtils.readString(buf), ProtocolUtils.readString(buf)); + } + return new ClientboundCustomReportDetailsPacket(details); + } + + @Override + public void encode(ClientboundCustomReportDetailsPacket packet, ByteBuf buf, + ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + ProtocolUtils.writeVarInt(buf, packet.details.size()); + + packet.details.forEach((key, detail) -> { + ProtocolUtils.writeString(buf, key); + ProtocolUtils.writeString(buf, detail); + }); + } } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/ClientboundServerLinksPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/ClientboundServerLinksPacket.java index d37866d86..1c0619027 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/ClientboundServerLinksPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/ClientboundServerLinksPacket.java @@ -21,40 +21,17 @@ import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.util.ServerLink; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import io.netty.buffer.ByteBuf; import java.util.ArrayList; import java.util.List; -public class ClientboundServerLinksPacket implements MinecraftPacket { - - private List serverLinks; +public record ClientboundServerLinksPacket(List serverLinks) implements MinecraftPacket { public ClientboundServerLinksPacket() { - } - - public ClientboundServerLinksPacket(List serverLinks) { - this.serverLinks = serverLinks; - } - - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - int linksCount = ProtocolUtils.readVarInt(buf); - - this.serverLinks = new ArrayList<>(linksCount); - for (int i = 0; i < linksCount; i++) { - serverLinks.add(ServerLink.read(buf, version)); - } - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { - ProtocolUtils.writeVarInt(buf, serverLinks.size()); - - for (ServerLink serverLink : serverLinks) { - serverLink.write(buf); - } + this(List.of()); } @Override @@ -62,10 +39,6 @@ public class ClientboundServerLinksPacket implements MinecraftPacket { return handler.handle(this); } - public List getServerLinks() { - return serverLinks; - } - public record ServerLink(int id, ComponentHolder displayName, String url) { private static ServerLink read(ByteBuf buf, ProtocolVersion version) { @@ -87,4 +60,28 @@ public class ClientboundServerLinksPacket implements MinecraftPacket { ProtocolUtils.writeString(buf, url); } } + + public static class Codec implements PacketCodec { + @Override + public ClientboundServerLinksPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion version) { + int linksCount = ProtocolUtils.readVarInt(buf); + + List serverLinks = new ArrayList<>(linksCount); + for (int i = 0; i < linksCount; i++) { + serverLinks.add(ServerLink.read(buf, version)); + } + return new ClientboundServerLinksPacket(serverLinks); + } + + @Override + public void encode(ClientboundServerLinksPacket packet, ByteBuf buf, + ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + ProtocolUtils.writeVarInt(buf, packet.serverLinks.size()); + + for (ServerLink serverLink : packet.serverLinks) { + serverLink.write(buf); + } + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/CodeOfConductAcceptPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/CodeOfConductAcceptPacket.java index e9811f9fc..9b290c163 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/CodeOfConductAcceptPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/CodeOfConductAcceptPacket.java @@ -20,26 +20,32 @@ package com.velocitypowered.proxy.protocol.packet.config; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; import io.netty.buffer.ByteBuf; -public class CodeOfConductAcceptPacket implements MinecraftPacket { +public final class CodeOfConductAcceptPacket implements MinecraftPacket { public static final CodeOfConductAcceptPacket INSTANCE = new CodeOfConductAcceptPacket(); private CodeOfConductAcceptPacket() { } - @Override - public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { - } - - @Override - public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { - } - @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + public static class Codec implements PacketCodec { + @Override + public CodeOfConductAcceptPacket decode(ByteBuf buf, Direction direction, + ProtocolVersion protocolVersion) { + return INSTANCE; + } + + @Override + public void encode(CodeOfConductAcceptPacket packet, ByteBuf buf, Direction direction, + ProtocolVersion protocolVersion) { + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/CodeOfConductPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/CodeOfConductPacket.java index 41433307e..8cd632dbd 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/CodeOfConductPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/CodeOfConductPacket.java @@ -20,24 +20,15 @@ package com.velocitypowered.proxy.protocol.packet.config; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; -import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder; import io.netty.buffer.ByteBuf; +import io.netty.buffer.DefaultByteBufHolder; -public class CodeOfConductPacket extends DeferredByteBufHolder implements MinecraftPacket { +public class CodeOfConductPacket extends DefaultByteBufHolder implements MinecraftPacket { - public CodeOfConductPacket() { - super(null); - } - - @Override - public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { - this.replace(buf.readRetainedSlice(buf.readableBytes())); - } - - @Override - public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { - buf.writeBytes(this.content()); + public CodeOfConductPacket(ByteBuf buf) { + super(buf); } @Override @@ -46,7 +37,61 @@ public class CodeOfConductPacket extends DeferredByteBufHolder implements Minecr } @Override - public int encodeSizeHint(Direction direction, ProtocolVersion version) { - return content().readableBytes(); + public CodeOfConductPacket copy() { + return (CodeOfConductPacket) super.copy(); + } + + @Override + public CodeOfConductPacket duplicate() { + return (CodeOfConductPacket) super.duplicate(); + } + + @Override + public CodeOfConductPacket retainedDuplicate() { + return (CodeOfConductPacket) super.retainedDuplicate(); + } + + @Override + public CodeOfConductPacket replace(ByteBuf content) { + return (CodeOfConductPacket) super.replace(content); + } + + @Override + public CodeOfConductPacket retain() { + return (CodeOfConductPacket) super.retain(); + } + + @Override + public CodeOfConductPacket retain(int increment) { + return (CodeOfConductPacket) super.retain(increment); + } + + @Override + public CodeOfConductPacket touch() { + return (CodeOfConductPacket) super.touch(); + } + + @Override + public CodeOfConductPacket touch(Object hint) { + return (CodeOfConductPacket) super.touch(hint); + } + + public static class Codec implements PacketCodec { + @Override + public CodeOfConductPacket decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { + return new CodeOfConductPacket(buf.readRetainedSlice(buf.readableBytes())); + } + + @Override + public void encode(CodeOfConductPacket packet, ByteBuf buf, Direction direction, + ProtocolVersion protocolVersion) { + buf.writeBytes(packet.content()); + } + + @Override + public int encodeSizeHint(CodeOfConductPacket packet, Direction direction, + ProtocolVersion version) { + return packet.content().readableBytes(); + } } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/FinishedUpdatePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/FinishedUpdatePacket.java index 20d40fd4b..e356d17e0 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/FinishedUpdatePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/FinishedUpdatePacket.java @@ -20,33 +20,37 @@ package com.velocitypowered.proxy.protocol.packet.config; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; -public class FinishedUpdatePacket implements MinecraftPacket { +public final class FinishedUpdatePacket implements MinecraftPacket { public static final FinishedUpdatePacket INSTANCE = new FinishedUpdatePacket(); private FinishedUpdatePacket() { } - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion protocolVersion) { - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion protocolVersion) { - } - - @Override - public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion version) { - return 0; - } - @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + public static class Codec implements PacketCodec { + @Override + public FinishedUpdatePacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + return INSTANCE; + } + + @Override + public void encode(FinishedUpdatePacket packet, ByteBuf buf, + ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + } + + @Override + public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + return 0; + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/KnownPacksPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/KnownPacksPacket.java index b3fb0de4f..780b43b04 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/KnownPacksPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/KnownPacksPacket.java @@ -20,43 +20,15 @@ package com.velocitypowered.proxy.protocol.packet.config; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.util.except.QuietDecoderException; import io.netty.buffer.ByteBuf; -public class KnownPacksPacket implements MinecraftPacket { +public record KnownPacksPacket(KnownPack[] packs) implements MinecraftPacket { - private static final int MAX_LENGTH_PACKS = Integer.getInteger("velocity.max-known-packs", 64); - private static final QuietDecoderException TOO_MANY_PACKS = - new QuietDecoderException("too many known packs"); - - private KnownPack[] packs; - - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion protocolVersion) { - final int packCount = ProtocolUtils.readVarInt(buf); - if (direction == ProtocolUtils.Direction.SERVERBOUND && packCount > MAX_LENGTH_PACKS) { - throw TOO_MANY_PACKS; - } - - final KnownPack[] packs = new KnownPack[packCount]; - - for (int i = 0; i < packCount; i++) { - packs[i] = KnownPack.read(buf); - } - - this.packs = packs; - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion protocolVersion) { - ProtocolUtils.writeVarInt(buf, packs.length); - - for (KnownPack pack : packs) { - pack.write(buf); - } + public KnownPacksPacket() { + this(new KnownPack[0]); } @Override @@ -75,4 +47,37 @@ public class KnownPacksPacket implements MinecraftPacket { ProtocolUtils.writeString(buf, version); } } + + public static class Codec implements PacketCodec { + private static final int MAX_LENGTH_PACKS = Integer.getInteger("velocity.max-known-packs", 64); + private static final QuietDecoderException TOO_MANY_PACKS = + new QuietDecoderException("too many known packs"); + + @Override + public KnownPacksPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + final int packCount = ProtocolUtils.readVarInt(buf); + if (direction == ProtocolUtils.Direction.SERVERBOUND && packCount > MAX_LENGTH_PACKS) { + throw TOO_MANY_PACKS; + } + + final KnownPack[] packs = new KnownPack[packCount]; + + for (int i = 0; i < packCount; i++) { + packs[i] = KnownPack.read(buf); + } + + return new KnownPacksPacket(packs); + } + + @Override + public void encode(KnownPacksPacket packet, ByteBuf buf, + ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + ProtocolUtils.writeVarInt(buf, packet.packs.length); + + for (KnownPack pack : packet.packs) { + pack.write(buf); + } + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/RegistrySyncPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/RegistrySyncPacket.java index 2d9ed230e..6dc6c52ed 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/RegistrySyncPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/RegistrySyncPacket.java @@ -20,28 +20,16 @@ package com.velocitypowered.proxy.protocol.packet.config; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; -import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder; import io.netty.buffer.ByteBuf; +import io.netty.buffer.DefaultByteBufHolder; -public class RegistrySyncPacket extends DeferredByteBufHolder implements MinecraftPacket { +public final class RegistrySyncPacket extends DefaultByteBufHolder implements MinecraftPacket { - public RegistrySyncPacket() { - super(null); - } - - // NBT change in 1.20.2 makes it difficult to parse this packet. - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion protocolVersion) { - this.replace(buf.readRetainedSlice(buf.readableBytes())); - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion protocolVersion) { - buf.writeBytes(content()); + public RegistrySyncPacket(ByteBuf backing) { + super(backing); } @Override @@ -49,8 +37,22 @@ public class RegistrySyncPacket extends DeferredByteBufHolder implements Minecra return handler.handle(this); } - @Override public int encodeSizeHint(Direction direction, ProtocolVersion version) { return content().readableBytes(); } + + public static class Codec implements PacketCodec { + @Override + public RegistrySyncPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + // NBT change in 1.20.2 makes it difficult to parse this packet. + return new RegistrySyncPacket(buf.readRetainedSlice(buf.readableBytes())); + } + + @Override + public void encode(RegistrySyncPacket packet, ByteBuf buf, + ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + buf.writeBytes(packet.content()); + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/StartUpdatePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/StartUpdatePacket.java index d41265ce5..33067ea06 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/StartUpdatePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/StartUpdatePacket.java @@ -20,33 +20,37 @@ package com.velocitypowered.proxy.protocol.packet.config; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; -public class StartUpdatePacket implements MinecraftPacket { +public final class StartUpdatePacket implements MinecraftPacket { public static final StartUpdatePacket INSTANCE = new StartUpdatePacket(); private StartUpdatePacket() { } - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion protocolVersion) { - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion protocolVersion) { - } - - @Override - public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion version) { - return 0; - } - @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + public static class Codec implements PacketCodec { + @Override + public StartUpdatePacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + return INSTANCE; + } + + @Override + public void encode(StartUpdatePacket packet, ByteBuf buf, + ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + } + + @Override + public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + return 0; + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/TagsUpdatePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/TagsUpdatePacket.java index c0cf414e0..677533cd9 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/TagsUpdatePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/TagsUpdatePacket.java @@ -21,59 +21,23 @@ import com.google.common.collect.ImmutableMap; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; import io.netty.buffer.ByteBuf; import java.util.Map; -public class TagsUpdatePacket implements MinecraftPacket { +public final class TagsUpdatePacket implements MinecraftPacket { - private Map> tags; + private final Map> tags; public TagsUpdatePacket(Map> tags) { - this.tags = tags; + this.tags = ImmutableMap.copyOf(tags); } - public TagsUpdatePacket() { - this.tags = Map.of(); - } - - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion protocolVersion) { - ImmutableMap.Builder> builder = ImmutableMap.builder(); - int size = ProtocolUtils.readVarInt(buf); - for (int i = 0; i < size; i++) { - String key = ProtocolUtils.readString(buf); - - int innerSize = ProtocolUtils.readVarInt(buf); - ImmutableMap.Builder innerBuilder = ImmutableMap.builder(); - for (int j = 0; j < innerSize; j++) { - String innerKey = ProtocolUtils.readString(buf); - int[] innerValue = ProtocolUtils.readVarIntArray(buf); - innerBuilder.put(innerKey, innerValue); - } - - builder.put(key, innerBuilder.build()); - } - tags = builder.build(); - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion protocolVersion) { - ProtocolUtils.writeVarInt(buf, tags.size()); - for (Map.Entry> entry : tags.entrySet()) { - ProtocolUtils.writeString(buf, entry.getKey()); - // Oh, joy - ProtocolUtils.writeVarInt(buf, entry.getValue().size()); - for (Map.Entry innerEntry : entry.getValue().entrySet()) { - // Yea, object oriented programming be damned - ProtocolUtils.writeString(buf, innerEntry.getKey()); - ProtocolUtils.writeVarIntArray(buf, innerEntry.getValue()); - } - } + public Map> getTags() { + return tags; } @Override @@ -81,21 +45,61 @@ public class TagsUpdatePacket implements MinecraftPacket { return handler.handle(this); } - @Override - public int encodeSizeHint(Direction direction, ProtocolVersion version) { - int size = ProtocolUtils.varIntBytes(tags.size()); - for (Map.Entry> entry : tags.entrySet()) { - size += ProtocolUtils.stringSizeHint(entry.getKey()); - size += ProtocolUtils.varIntBytes(entry.getValue().size()); - for (Map.Entry innerEntry : entry.getValue().entrySet()) { - size += ProtocolUtils.stringSizeHint(innerEntry.getKey()); - size += ProtocolUtils.varIntBytes(innerEntry.getValue().length); - for (int innerEntryValue : innerEntry.getValue()) { - size += ProtocolUtils.varIntBytes(innerEntryValue); + public static class Codec implements PacketCodec { + @Override + public TagsUpdatePacket decode(ByteBuf buf, Direction direction, + ProtocolVersion protocolVersion) { + ImmutableMap.Builder> builder = ImmutableMap.builder(); + int size = ProtocolUtils.readVarInt(buf); + for (int i = 0; i < size; i++) { + String key = ProtocolUtils.readString(buf); + + int innerSize = ProtocolUtils.readVarInt(buf); + ImmutableMap.Builder innerBuilder = ImmutableMap.builder(); + for (int j = 0; j < innerSize; j++) { + String innerKey = ProtocolUtils.readString(buf); + int[] innerValue = ProtocolUtils.readVarIntArray(buf); + innerBuilder.put(innerKey, innerValue); + } + + builder.put(key, innerBuilder.build()); + } + return new TagsUpdatePacket(builder.build()); + } + + @Override + public void encode(TagsUpdatePacket packet, ByteBuf buf, Direction direction, + ProtocolVersion protocolVersion) { + ProtocolUtils.writeVarInt(buf, packet.tags.size()); + for (Map.Entry> entry : packet.tags.entrySet()) { + ProtocolUtils.writeString(buf, entry.getKey()); + // Oh, joy + ProtocolUtils.writeVarInt(buf, entry.getValue().size()); + for (Map.Entry innerEntry : entry.getValue().entrySet()) { + // Yea, object oriented programming be damned + ProtocolUtils.writeString(buf, innerEntry.getKey()); + ProtocolUtils.writeVarIntArray(buf, innerEntry.getValue()); } } } - return size; + @Override + public int encodeSizeHint(TagsUpdatePacket packet, Direction direction, ProtocolVersion version) { + var tags = packet.tags; + int size = ProtocolUtils.varIntBytes(tags.size()); + for (Map.Entry> entry : tags.entrySet()) { + size += ProtocolUtils.stringSizeHint(entry.getKey()); + size += ProtocolUtils.varIntBytes(entry.getValue().size()); + for (Map.Entry innerEntry : entry.getValue().entrySet()) { + size += ProtocolUtils.stringSizeHint(innerEntry.getKey()); + size += ProtocolUtils.varIntBytes(innerEntry.getValue().length); + for (int innerEntryValue : innerEntry.getValue()) { + size += ProtocolUtils.varIntBytes(innerEntryValue); + } + } + } + + return size; + } } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/GenericTitlePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/GenericTitlePacket.java index d6f5e0e54..7f0174ac8 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/GenericTitlePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/GenericTitlePacket.java @@ -19,9 +19,7 @@ package com.velocitypowered.proxy.protocol.packet.title; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.protocol.MinecraftPacket; -import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; -import io.netty.buffer.ByteBuf; public abstract class GenericTitlePacket implements MinecraftPacket { @@ -45,10 +43,9 @@ public abstract class GenericTitlePacket implements MinecraftPacket { } } + private final ActionType action; - private ActionType action; - - protected void setAction(ActionType action) { + protected GenericTitlePacket(ActionType action) { this.action = action; } @@ -60,76 +57,77 @@ public abstract class GenericTitlePacket implements MinecraftPacket { throw new UnsupportedOperationException("Invalid function for this TitlePacket ActionType"); } - public void setComponent(ComponentHolder component) { - throw new UnsupportedOperationException("Invalid function for this TitlePacket ActionType"); - } - public int getFadeIn() { throw new UnsupportedOperationException("Invalid function for this TitlePacket ActionType"); } - public void setFadeIn(int fadeIn) { - throw new UnsupportedOperationException("Invalid function for this TitlePacket ActionType"); - } - public int getStay() { throw new UnsupportedOperationException("Invalid function for this TitlePacket ActionType"); } - public void setStay(int stay) { - throw new UnsupportedOperationException("Invalid function for this TitlePacket ActionType"); - } - public int getFadeOut() { throw new UnsupportedOperationException("Invalid function for this TitlePacket ActionType"); } - public void setFadeOut(int fadeOut) { - throw new UnsupportedOperationException("Invalid function for this TitlePacket ActionType"); - } - - - @Override - public final void decode(ByteBuf buf, ProtocolUtils.Direction direction, - ProtocolVersion version) { - throw new UnsupportedOperationException(); // encode only - } - /** - * Creates a version and type dependent TitlePacket. + * Creates a version and type dependent TitlePacket for HIDE/RESET actions. * - * @param type Action the packet should invoke + * @param type Action the packet should invoke (HIDE or RESET) * @param version Protocol version of the target player * @return GenericTitlePacket instance that follows the invoker type/version */ - public static GenericTitlePacket constructTitlePacket(ActionType type, ProtocolVersion version) { - GenericTitlePacket packet = null; + public static GenericTitlePacket createClearTitlePacket(ActionType type, ProtocolVersion version) { + if (type != ActionType.HIDE && type != ActionType.RESET) { + throw new IllegalArgumentException("createClearTitlePacket only accepts HIDE and RESET actions"); + } + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_17)) { + return new TitleClearPacket(type == ActionType.RESET); + } else { + return new LegacyTitlePacket(type, null, 0, 0, 0); + } + } + + /** + * Creates a version and type dependent TitlePacket for component-based actions. + * + * @param type Action the packet should invoke + * @param component Component to display + * @param version Protocol version of the target player + * @return GenericTitlePacket instance that follows the invoker type/version + */ + public static GenericTitlePacket createComponentTitlePacket(ActionType type, + ComponentHolder component, ProtocolVersion version) { if (version.noLessThan(ProtocolVersion.MINECRAFT_1_17)) { switch (type) { case SET_ACTION_BAR: - packet = new TitleActionbarPacket(); - break; + return new TitleActionbarPacket(component); case SET_SUBTITLE: - packet = new TitleSubtitlePacket(); - break; - case SET_TIMES: - packet = new TitleTimesPacket(); - break; + return new TitleSubtitlePacket(component); case SET_TITLE: - packet = new TitleTextPacket(); - break; - case HIDE: - case RESET: - packet = new TitleClearPacket(); - break; + return new TitleTextPacket(component); default: - throw new IllegalArgumentException("Invalid ActionType"); + throw new IllegalArgumentException("Invalid ActionType for component title: " + type); } } else { - packet = new LegacyTitlePacket(); + return new LegacyTitlePacket(type, component, 0, 0, 0); } - packet.setAction(type); - return packet; } + /** + * Creates a version dependent TitlePacket for times. + * + * @param fadeIn Fade in time + * @param stay Stay time + * @param fadeOut Fade out time + * @param version Protocol version of the target player + * @return GenericTitlePacket instance that follows the invoker type/version + */ + public static GenericTitlePacket createTimesTitlePacket(int fadeIn, int stay, int fadeOut, + ProtocolVersion version) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_17)) { + return new TitleTimesPacket(fadeIn, stay, fadeOut); + } else { + return new LegacyTitlePacket(ActionType.SET_TIMES, null, fadeIn, stay, fadeOut); + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/LegacyTitlePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/LegacyTitlePacket.java index 0425f2d3c..889933145 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/LegacyTitlePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/LegacyTitlePacket.java @@ -19,52 +19,26 @@ package com.velocitypowered.proxy.protocol.packet.title; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import io.netty.buffer.ByteBuf; import org.checkerframework.checker.nullness.qual.Nullable; -public class LegacyTitlePacket extends GenericTitlePacket { +public final class LegacyTitlePacket extends GenericTitlePacket { - private @Nullable ComponentHolder component; - private int fadeIn; - private int stay; - private int fadeOut; + private final @Nullable ComponentHolder component; + private final int fadeIn; + private final int stay; + private final int fadeOut; - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - if (version.lessThan(ProtocolVersion.MINECRAFT_1_11) - && getAction() == ActionType.SET_ACTION_BAR) { - throw new IllegalStateException("Action bars are only supported on 1.11 and newer"); - } - ProtocolUtils.writeVarInt(buf, getAction().getAction(version)); - - switch (getAction()) { - case SET_TITLE: - case SET_SUBTITLE: - case SET_ACTION_BAR: - if (component == null) { - throw new IllegalStateException("No component found for " + getAction()); - } - component.write(buf); - break; - case SET_TIMES: - buf.writeInt(fadeIn); - buf.writeInt(stay); - buf.writeInt(fadeOut); - break; - case HIDE: - case RESET: - break; - default: - throw new UnsupportedOperationException("Unknown action " + getAction()); - } - - } - - @Override - public void setAction(ActionType action) { - super.setAction(action); + public LegacyTitlePacket(ActionType action, @Nullable ComponentHolder component, + int fadeIn, int stay, int fadeOut) { + super(action); + this.component = component; + this.fadeIn = fadeIn; + this.stay = stay; + this.fadeOut = fadeOut; } @Override @@ -72,44 +46,24 @@ public class LegacyTitlePacket extends GenericTitlePacket { return component; } - @Override - public void setComponent(@Nullable ComponentHolder component) { - this.component = component; - } - @Override public int getFadeIn() { return fadeIn; } - @Override - public void setFadeIn(int fadeIn) { - this.fadeIn = fadeIn; - } - @Override public int getStay() { return stay; } - @Override - public void setStay(int stay) { - this.stay = stay; - } - @Override public int getFadeOut() { return fadeOut; } - @Override - public void setFadeOut(int fadeOut) { - this.fadeOut = fadeOut; - } - @Override public String toString() { - return "GenericTitlePacket{" + return "LegacyTitlePacket{" + "action=" + getAction() + ", component='" + component + '\'' + ", fadeIn=" + fadeIn @@ -122,4 +76,43 @@ public class LegacyTitlePacket extends GenericTitlePacket { public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + public static class Codec implements PacketCodec { + @Override + public LegacyTitlePacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + throw new UnsupportedOperationException(); // encode only + } + + @Override + public void encode(LegacyTitlePacket packet, ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_11) + && packet.getAction() == ActionType.SET_ACTION_BAR) { + throw new IllegalStateException("Action bars are only supported on 1.11 and newer"); + } + ProtocolUtils.writeVarInt(buf, packet.getAction().getAction(protocolVersion)); + + switch (packet.getAction()) { + case SET_TITLE: + case SET_SUBTITLE: + case SET_ACTION_BAR: + if (packet.component == null) { + throw new IllegalStateException("No component found for " + packet.getAction()); + } + packet.component.write(buf); + break; + case SET_TIMES: + buf.writeInt(packet.fadeIn); + buf.writeInt(packet.stay); + buf.writeInt(packet.fadeOut); + break; + case HIDE: + case RESET: + break; + default: + throw new UnsupportedOperationException("Unknown action " + packet.getAction()); + } + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleActionbarPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleActionbarPacket.java index f34983eaf..464e9afc3 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleActionbarPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleActionbarPacket.java @@ -19,21 +19,18 @@ package com.velocitypowered.proxy.protocol.packet.title; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import io.netty.buffer.ByteBuf; -public class TitleActionbarPacket extends GenericTitlePacket { +public final class TitleActionbarPacket extends GenericTitlePacket { - private ComponentHolder component; + private final ComponentHolder component; - public TitleActionbarPacket() { - setAction(ActionType.SET_TITLE); - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - component.write(buf); + public TitleActionbarPacket(ComponentHolder component) { + super(ActionType.SET_ACTION_BAR); + this.component = component; } @Override @@ -41,15 +38,10 @@ public class TitleActionbarPacket extends GenericTitlePacket { return component; } - @Override - public void setComponent(ComponentHolder component) { - this.component = component; - } - @Override public String toString() { return "TitleActionbarPacket{" - + ", component='" + component + '\'' + + "component='" + component + '\'' + '}'; } @@ -57,4 +49,18 @@ public class TitleActionbarPacket extends GenericTitlePacket { public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + public static class Codec implements PacketCodec { + @Override + public TitleActionbarPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + throw new UnsupportedOperationException(); // encode only + } + + @Override + public void encode(TitleActionbarPacket packet, ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + packet.component.write(buf); + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleClearPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleClearPacket.java index 1b3489691..f29422b6b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleClearPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleClearPacket.java @@ -19,32 +19,27 @@ package com.velocitypowered.proxy.protocol.packet.title; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; -public class TitleClearPacket extends GenericTitlePacket { +public final class TitleClearPacket extends GenericTitlePacket { - public TitleClearPacket() { - setAction(ActionType.HIDE); + private final boolean reset; + + public TitleClearPacket(boolean reset) { + super(reset ? ActionType.RESET : ActionType.HIDE); + this.reset = reset; } - @Override - public void setAction(ActionType action) { - if (action != ActionType.HIDE && action != ActionType.RESET) { - throw new IllegalArgumentException("TitleClearPacket only accepts CLEAR and RESET actions"); - } - super.setAction(action); - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - buf.writeBoolean(getAction() == ActionType.RESET); + public boolean isReset() { + return reset; } @Override public String toString() { return "TitleClearPacket{" - + ", resetTimes=" + (getAction() == ActionType.RESET) + + "resetTimes=" + reset + '}'; } @@ -52,4 +47,18 @@ public class TitleClearPacket extends GenericTitlePacket { public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + public static class Codec implements PacketCodec { + @Override + public TitleClearPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + throw new UnsupportedOperationException(); // encode only + } + + @Override + public void encode(TitleClearPacket packet, ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + buf.writeBoolean(packet.reset); + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleSubtitlePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleSubtitlePacket.java index 0f375ae20..95d950224 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleSubtitlePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleSubtitlePacket.java @@ -19,21 +19,18 @@ package com.velocitypowered.proxy.protocol.packet.title; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import io.netty.buffer.ByteBuf; -public class TitleSubtitlePacket extends GenericTitlePacket { +public final class TitleSubtitlePacket extends GenericTitlePacket { - private ComponentHolder component; + private final ComponentHolder component; - public TitleSubtitlePacket() { - setAction(ActionType.SET_SUBTITLE); - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - component.write(buf); + public TitleSubtitlePacket(ComponentHolder component) { + super(ActionType.SET_SUBTITLE); + this.component = component; } @Override @@ -41,15 +38,10 @@ public class TitleSubtitlePacket extends GenericTitlePacket { return component; } - @Override - public void setComponent(ComponentHolder component) { - this.component = component; - } - @Override public String toString() { return "TitleSubtitlePacket{" - + ", component='" + component + '\'' + + "component='" + component + '\'' + '}'; } @@ -57,4 +49,18 @@ public class TitleSubtitlePacket extends GenericTitlePacket { public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + public static class Codec implements PacketCodec { + @Override + public TitleSubtitlePacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + throw new UnsupportedOperationException(); // encode only + } + + @Override + public void encode(TitleSubtitlePacket packet, ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + packet.component.write(buf); + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleTextPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleTextPacket.java index ae75f5d61..3e6a960f4 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleTextPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleTextPacket.java @@ -19,21 +19,18 @@ package com.velocitypowered.proxy.protocol.packet.title; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import io.netty.buffer.ByteBuf; -public class TitleTextPacket extends GenericTitlePacket { +public final class TitleTextPacket extends GenericTitlePacket { - private ComponentHolder component; + private final ComponentHolder component; - public TitleTextPacket() { - setAction(ActionType.SET_TITLE); - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - component.write(buf); + public TitleTextPacket(ComponentHolder component) { + super(ActionType.SET_TITLE); + this.component = component; } @Override @@ -41,15 +38,10 @@ public class TitleTextPacket extends GenericTitlePacket { return component; } - @Override - public void setComponent(ComponentHolder component) { - this.component = component; - } - @Override public String toString() { return "TitleTextPacket{" - + ", component='" + component + '\'' + + "component='" + component + '\'' + '}'; } @@ -57,4 +49,18 @@ public class TitleTextPacket extends GenericTitlePacket { public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + public static class Codec implements PacketCodec { + @Override + public TitleTextPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + throw new UnsupportedOperationException(); // encode only + } + + @Override + public void encode(TitleTextPacket packet, ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + packet.component.write(buf); + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleTimesPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleTimesPacket.java index 8764a12fa..69764e767 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleTimesPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleTimesPacket.java @@ -19,24 +19,21 @@ package com.velocitypowered.proxy.protocol.packet.title; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.PacketCodec; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; -public class TitleTimesPacket extends GenericTitlePacket { +public final class TitleTimesPacket extends GenericTitlePacket { - private int fadeIn; - private int stay; - private int fadeOut; + private final int fadeIn; + private final int stay; + private final int fadeOut; - public TitleTimesPacket() { - setAction(ActionType.SET_TIMES); - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - buf.writeInt(fadeIn); - buf.writeInt(stay); - buf.writeInt(fadeOut); + public TitleTimesPacket(int fadeIn, int stay, int fadeOut) { + super(ActionType.SET_TIMES); + this.fadeIn = fadeIn; + this.stay = stay; + this.fadeOut = fadeOut; } @Override @@ -44,35 +41,20 @@ public class TitleTimesPacket extends GenericTitlePacket { return fadeIn; } - @Override - public void setFadeIn(int fadeIn) { - this.fadeIn = fadeIn; - } - @Override public int getStay() { return stay; } - @Override - public void setStay(int stay) { - this.stay = stay; - } - @Override public int getFadeOut() { return fadeOut; } - @Override - public void setFadeOut(int fadeOut) { - this.fadeOut = fadeOut; - } - @Override public String toString() { return "TitleTimesPacket{" - + ", fadeIn=" + fadeIn + + "fadeIn=" + fadeIn + ", stay=" + stay + ", fadeOut=" + fadeOut + '}'; @@ -82,4 +64,20 @@ public class TitleTimesPacket extends GenericTitlePacket { public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); } + + public static class Codec implements PacketCodec { + @Override + public TitleTimesPacket decode(ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + throw new UnsupportedOperationException(); // encode only + } + + @Override + public void encode(TitleTimesPacket packet, ByteBuf buf, ProtocolUtils.Direction direction, + ProtocolVersion protocolVersion) { + buf.writeInt(packet.fadeIn); + buf.writeInt(packet.stay); + buf.writeInt(packet.fadeOut); + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/DeferredByteBufHolder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/DeferredByteBufHolder.java deleted file mode 100644 index 368ac503d..000000000 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/DeferredByteBufHolder.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (C) 2019-2021 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.util; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufHolder; -import io.netty.util.IllegalReferenceCountException; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; - -/** - * A special-purpose implementation of {@code ByteBufHolder} that can defer accepting its buffer. - * This is required because Velocity packets are, for better or worse, mutable. - */ -public class DeferredByteBufHolder implements ByteBufHolder { - - @MonotonicNonNull - private ByteBuf backing; - - public DeferredByteBufHolder( - @MonotonicNonNull ByteBuf backing) { - this.backing = backing; - } - - @Override - public ByteBuf content() { - if (backing == null) { - throw new IllegalStateException("Trying to obtain contents of holder with a null buffer"); - } - if (backing.refCnt() <= 0) { - throw new IllegalReferenceCountException(backing.refCnt()); - } - return backing; - } - - @Override - public ByteBufHolder copy() { - if (backing == null) { - throw new IllegalStateException("Trying to obtain contents of holder with a null buffer"); - } - return new DeferredByteBufHolder(backing.copy()); - } - - @Override - public ByteBufHolder duplicate() { - if (backing == null) { - throw new IllegalStateException("Trying to obtain contents of holder with a null buffer"); - } - return new DeferredByteBufHolder(backing.duplicate()); - } - - @Override - public ByteBufHolder retainedDuplicate() { - if (backing == null) { - throw new IllegalStateException("Trying to obtain contents of holder with a null buffer"); - } - return new DeferredByteBufHolder(backing.retainedDuplicate()); - } - - @Override - public ByteBufHolder replace(ByteBuf content) { - if (content == null) { - throw new NullPointerException("content"); - } - this.backing = content; - return this; - } - - @Override - public int refCnt() { - if (backing == null) { - throw new IllegalStateException("Trying to obtain contents of holder with a null buffer"); - } - return backing.refCnt(); - } - - @Override - public ByteBufHolder retain() { - if (backing == null) { - throw new IllegalStateException("Trying to obtain contents of holder with a null buffer"); - } - backing.retain(); - return this; - } - - @Override - public ByteBufHolder retain(int increment) { - if (backing == null) { - throw new IllegalStateException("Trying to obtain contents of holder with a null buffer"); - } - backing.retain(increment); - return this; - } - - @Override - public ByteBufHolder touch() { - if (backing == null) { - throw new IllegalStateException("Trying to obtain contents of holder with a null buffer"); - } - backing.touch(); - return this; - } - - @Override - public ByteBufHolder touch(Object hint) { - if (backing == null) { - throw new IllegalStateException("Trying to obtain contents of holder with a null buffer"); - } - backing.touch(hint); - return this; - } - - @Override - public boolean release() { - if (backing == null) { - throw new IllegalStateException("Trying to obtain contents of holder with a null buffer"); - } - return backing.release(); - } - - @Override - public boolean release(int decrement) { - if (backing == null) { - throw new IllegalStateException("Trying to obtain contents of holder with a null buffer"); - } - return backing.release(decrement); - } - - @Override - public String toString() { - String str = "DeferredByteBufHolder["; - if (backing == null) { - str += "null"; - } else { - str += backing.toString(); - } - return str + "]"; - } -} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/server/PingSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/server/PingSessionHandler.java index e58c72b44..0491e6480 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/server/PingSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/server/PingSessionHandler.java @@ -56,12 +56,10 @@ public class PingSessionHandler implements MinecraftSessionHandler { @Override public void activated() { - HandshakePacket handshake = new HandshakePacket(); - handshake.setIntent(HandshakeIntent.STATUS); - handshake.setServerAddress(this.virtualHostString == null || this.virtualHostString.isEmpty() - ? server.getServerInfo().getAddress().getHostString() : this.virtualHostString); - handshake.setPort(server.getServerInfo().getAddress().getPort()); - handshake.setProtocolVersion(version); + String serverAddress = this.virtualHostString == null || this.virtualHostString.isEmpty() + ? server.getServerInfo().getAddress().getHostString() : this.virtualHostString; + HandshakePacket handshake = new HandshakePacket(version, serverAddress, + server.getServerInfo().getAddress().getPort(), HandshakeIntent.STATUS); connection.delayedWrite(handshake); connection.setActiveSessionHandler(StateRegistry.STATUS); diff --git a/proxy/src/test/java/com/velocitypowered/proxy/protocol/PacketRegistryTest.java b/proxy/src/test/java/com/velocitypowered/proxy/protocol/PacketRegistryTest.java index f65674ea1..cedcc1e8e 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/protocol/PacketRegistryTest.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/protocol/PacketRegistryTest.java @@ -45,7 +45,7 @@ class PacketRegistryTest { private StateRegistry.PacketRegistry setupRegistry() { StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry( ProtocolUtils.Direction.CLIENTBOUND, StateRegistry.PLAY); - registry.register(HandshakePacket.class, HandshakePacket::new, + registry.register(HandshakePacket.class, new HandshakePacket.Codec(), new StateRegistry.PacketMapping(0x01, MINECRAFT_1_8, null, false), new StateRegistry.PacketMapping(0x00, MINECRAFT_1_12, null, false), new StateRegistry.PacketMapping(0x00, MINECRAFT_1_15, MINECRAFT_1_16, false)); @@ -55,29 +55,30 @@ class PacketRegistryTest { @Test void packetRegistryWorks() { StateRegistry.PacketRegistry registry = setupRegistry(); - MinecraftPacket packet = registry.getProtocolRegistry(MINECRAFT_1_12).createPacket(0); + PacketCodec packet = registry.getProtocolRegistry(MINECRAFT_1_12).getCodec(0); assertNotNull(packet, "Packet was not found in registry"); - assertEquals(HandshakePacket.class, packet.getClass(), "Registry returned wrong class"); + assertEquals(HandshakePacket.Codec.class, packet.getClass(), "Registry returned wrong class"); - assertEquals(0, registry.getProtocolRegistry(MINECRAFT_1_12).getPacketId(packet), + assertEquals(0, registry.getProtocolRegistry(MINECRAFT_1_12).getPacketId(new HandshakePacket()), "Registry did not return the correct packet ID"); } @Test void packetRegistryLinkingWorks() { StateRegistry.PacketRegistry registry = setupRegistry(); - MinecraftPacket packet = registry.getProtocolRegistry(MINECRAFT_1_12_1).createPacket(0); + PacketCodec packet = registry.getProtocolRegistry(MINECRAFT_1_12_1).getCodec(0); assertNotNull(packet, "Packet was not found in registry"); - assertEquals(HandshakePacket.class, packet.getClass(), "Registry returned wrong class"); - assertEquals(0, registry.getProtocolRegistry(MINECRAFT_1_12_1).getPacketId(packet), + assertEquals(HandshakePacket.Codec.class, packet.getClass(), "Registry returned wrong class"); + HandshakePacket handshakePacket = new HandshakePacket(); + assertEquals(0, registry.getProtocolRegistry(MINECRAFT_1_12_1).getPacketId(handshakePacket), "Registry did not return the correct packet ID"); - assertEquals(0, registry.getProtocolRegistry(MINECRAFT_1_14_2).getPacketId(packet), + assertEquals(0, registry.getProtocolRegistry(MINECRAFT_1_14_2).getPacketId(handshakePacket), "Registry did not return the correct packet ID"); - assertEquals(1, registry.getProtocolRegistry(MINECRAFT_1_11).getPacketId(packet), + assertEquals(1, registry.getProtocolRegistry(MINECRAFT_1_11).getPacketId(handshakePacket), "Registry did not return the correct packet ID"); - assertNull(registry.getProtocolRegistry(MINECRAFT_1_14_2).createPacket(0x01), + assertNull(registry.getProtocolRegistry(MINECRAFT_1_14_2).getCodec(0x01), "Registry should return a null"); - assertNull(registry.getProtocolRegistry(MINECRAFT_1_16_2).createPacket(0), + assertNull(registry.getProtocolRegistry(MINECRAFT_1_16_2).getCodec(0), "Registry should return null"); } @@ -86,7 +87,7 @@ class PacketRegistryTest { StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry( ProtocolUtils.Direction.CLIENTBOUND, StateRegistry.PLAY); assertThrows(IllegalArgumentException.class, - () -> registry.register(HandshakePacket.class, HandshakePacket::new)); + () -> registry.register(HandshakePacket.class, new HandshakePacket.Codec())); assertThrows(IllegalArgumentException.class, () -> registry.getProtocolRegistry(ProtocolVersion.UNKNOWN) .getPacketId(new HandshakePacket())); @@ -97,18 +98,18 @@ class PacketRegistryTest { StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry( ProtocolUtils.Direction.CLIENTBOUND, StateRegistry.PLAY); assertThrows(IllegalArgumentException.class, - () -> registry.register(HandshakePacket.class, HandshakePacket::new, + () -> registry.register(HandshakePacket.class, new HandshakePacket.Codec(), new StateRegistry.PacketMapping(0x01, MINECRAFT_1_13, null, false), new StateRegistry.PacketMapping(0x00, MINECRAFT_1_8, null, false))); assertThrows(IllegalArgumentException.class, - () -> registry.register(HandshakePacket.class, HandshakePacket::new, + () -> registry.register(HandshakePacket.class, new HandshakePacket.Codec(), new StateRegistry.PacketMapping(0x01, MINECRAFT_1_13, null, false), new StateRegistry.PacketMapping(0x01, MINECRAFT_1_13, null, false))); assertThrows(IllegalArgumentException.class, - () -> registry.register(HandshakePacket.class, HandshakePacket::new, + () -> registry.register(HandshakePacket.class, new HandshakePacket.Codec(), new StateRegistry.PacketMapping(0x01, MINECRAFT_1_13, MINECRAFT_1_8, false))); assertThrows(IllegalArgumentException.class, - () -> registry.register(HandshakePacket.class, HandshakePacket::new, + () -> registry.register(HandshakePacket.class, new HandshakePacket.Codec(), new StateRegistry.PacketMapping(0x01, MINECRAFT_1_8, MINECRAFT_1_14, false), new StateRegistry.PacketMapping(0x00, MINECRAFT_1_16, null, false))); } @@ -117,13 +118,13 @@ class PacketRegistryTest { void failOnDuplicate() { StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry( ProtocolUtils.Direction.CLIENTBOUND, StateRegistry.PLAY); - registry.register(HandshakePacket.class, HandshakePacket::new, + registry.register(HandshakePacket.class, new HandshakePacket.Codec(), new StateRegistry.PacketMapping(0x00, MINECRAFT_1_8, null, false)); assertThrows(IllegalArgumentException.class, - () -> registry.register(HandshakePacket.class, HandshakePacket::new, + () -> registry.register(HandshakePacket.class, new HandshakePacket.Codec(), new StateRegistry.PacketMapping(0x01, MINECRAFT_1_12, null, false))); assertThrows(IllegalArgumentException.class, - () -> registry.register(StatusPingPacket.class, StatusPingPacket::new, + () -> registry.register(StatusPingPacket.class, new StatusPingPacket.Codec(), new StateRegistry.PacketMapping(0x00, MINECRAFT_1_13, null, false))); } @@ -131,7 +132,7 @@ class PacketRegistryTest { void shouldNotFailWhenRegisterLatestProtocolVersion() { StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry( ProtocolUtils.Direction.CLIENTBOUND, StateRegistry.PLAY); - assertDoesNotThrow(() -> registry.register(HandshakePacket.class, HandshakePacket::new, + assertDoesNotThrow(() -> registry.register(HandshakePacket.class, new HandshakePacket.Codec(), new StateRegistry.PacketMapping(0x00, MINECRAFT_1_8, null, false), new StateRegistry.PacketMapping(0x01, getLast(ProtocolVersion.SUPPORTED_VERSIONS), null, false))); @@ -141,19 +142,19 @@ class PacketRegistryTest { void registrySuppliesCorrectPacketsByProtocol() { StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry( ProtocolUtils.Direction.CLIENTBOUND, StateRegistry.PLAY); - registry.register(HandshakePacket.class, HandshakePacket::new, + registry.register(HandshakePacket.class, new HandshakePacket.Codec(), new StateRegistry.PacketMapping(0x00, MINECRAFT_1_12, null, false), new StateRegistry.PacketMapping(0x01, MINECRAFT_1_12_1, null, false), new StateRegistry.PacketMapping(0x02, MINECRAFT_1_13, null, false)); - assertEquals(HandshakePacket.class, - registry.getProtocolRegistry(MINECRAFT_1_12).createPacket(0x00).getClass()); - assertEquals(HandshakePacket.class, - registry.getProtocolRegistry(MINECRAFT_1_12_1).createPacket(0x01).getClass()); - assertEquals(HandshakePacket.class, - registry.getProtocolRegistry(MINECRAFT_1_12_2).createPacket(0x01).getClass()); - assertEquals(HandshakePacket.class, - registry.getProtocolRegistry(MINECRAFT_1_13).createPacket(0x02).getClass()); - assertEquals(HandshakePacket.class, - registry.getProtocolRegistry(MINECRAFT_1_14_2).createPacket(0x02).getClass()); + assertEquals(HandshakePacket.Codec.class, + registry.getProtocolRegistry(MINECRAFT_1_12).getCodec(0x00).getClass()); + assertEquals(HandshakePacket.Codec.class, + registry.getProtocolRegistry(MINECRAFT_1_12_1).getCodec(0x01).getClass()); + assertEquals(HandshakePacket.Codec.class, + registry.getProtocolRegistry(MINECRAFT_1_12_2).getCodec(0x01).getClass()); + assertEquals(HandshakePacket.Codec.class, + registry.getProtocolRegistry(MINECRAFT_1_13).getCodec(0x02).getClass()); + assertEquals(HandshakePacket.Codec.class, + registry.getProtocolRegistry(MINECRAFT_1_14_2).getCodec(0x02).getClass()); } } \ No newline at end of file