From 8f4adb1cec79dbfcc60c4b893a9b59dbb6c6612a Mon Sep 17 00:00:00 2001 From: Adrian <68704415+4drian3d@users.noreply.github.com> Date: Mon, 22 Apr 2024 21:12:25 -0500 Subject: [PATCH] 1.20.5 Support (#1198) Added support to Minecraft 1.20.5 (Release Candidate 3) * Initial 1.20.5 update * Snapshot 24w03a * Handle Transfer Handshake intent * Snapshot 24w03b * Implement PreTransferEvent * 24w04a * Updated EncryptionRequestPacket, JoinGamePacket and ServerDataPacket to 24w04a * Snapshot 24w05a * Snapshot 24w05b * Snapshot 24w06a * Added migration to add new configuration option * Snapshot 24w07a * Snapshot 24w09a * Snapshot 24w10a * Snapshot 24w12a * Snapshot 24w14a * 1.20.5-rc1 * fix unsigned commands * fix NPE * fix respawn packet id * 1.20.5 Release Candidate 2 * Restored old ConnectionHandshakeEvent constructor * Added `-Dvelocity.strictErrorHandling` system property * 1.20.5 Release Candidate 3 --------- Co-authored-by: Gero --- .../connection/ConnectionHandshakeEvent.java | 21 +++ .../event/connection/PreTransferEvent.java | 100 +++++++++++++ .../api/network/HandshakeIntent.java | 42 ++++++ .../api/network/ProtocolState.java | 5 +- .../api/network/ProtocolVersion.java | 3 +- .../com/velocitypowered/api/proxy/Player.java | 10 ++ .../com/velocitypowered/proxy/Velocity.java | 5 +- .../proxy/config/VelocityConfiguration.java | 16 ++- .../migration/ConfigurationMigration.java | 5 +- .../TransferIntegrationMigration.java | 40 ++++++ .../connection/MinecraftSessionHandler.java | 5 + .../backend/BackendPlaySessionHandler.java | 27 ++++ .../backend/ConfigSessionHandler.java | 27 ++++ .../backend/VelocityServerConnection.java | 3 +- .../connection/client/ConnectedPlayer.java | 21 +++ .../client/HandshakeSessionHandler.java | 11 +- .../client/InitialLoginSessionHandler.java | 6 +- .../connection/registry/DimensionInfo.java | 11 +- .../proxy/protocol/StateRegistry.java | 136 ++++++++++++------ .../packet/EncryptionRequestPacket.java | 7 + .../protocol/packet/HandshakePacket.java | 14 +- .../proxy/protocol/packet/JoinGamePacket.java | 37 ++++- .../proxy/protocol/packet/RespawnPacket.java | 18 ++- .../protocol/packet/ServerDataPacket.java | 12 +- .../protocol/packet/ServerLoginPacket.java | 11 +- .../packet/ServerLoginSuccessPacket.java | 10 +- .../proxy/protocol/packet/TransferPacket.java | 64 +++++++++ .../brigadier/ArgumentPropertyRegistry.java | 78 +++++----- .../RegistryIdArgumentSerializer.java | 37 +++++ .../chat/session/SessionChatBuilder.java | 20 ++- .../chat/session/SessionCommandHandler.java | 2 +- .../session/SessionPlayerCommandPacket.java | 6 +- .../session/UnsignedPlayerCommandPacket.java | 46 ++++++ .../proxy/server/PingSessionHandler.java | 3 +- .../proxy/util/VelocityProperties.java | 59 ++++++++ .../src/main/resources/default-velocity.toml | 6 +- 36 files changed, 782 insertions(+), 142 deletions(-) create mode 100644 api/src/main/java/com/velocitypowered/api/event/connection/PreTransferEvent.java create mode 100644 api/src/main/java/com/velocitypowered/api/network/HandshakeIntent.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/config/migration/TransferIntegrationMigration.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TransferPacket.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/RegistryIdArgumentSerializer.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/UnsignedPlayerCommandPacket.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/util/VelocityProperties.java diff --git a/api/src/main/java/com/velocitypowered/api/event/connection/ConnectionHandshakeEvent.java b/api/src/main/java/com/velocitypowered/api/event/connection/ConnectionHandshakeEvent.java index 14baac413..e344b6c50 100644 --- a/api/src/main/java/com/velocitypowered/api/event/connection/ConnectionHandshakeEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/connection/ConnectionHandshakeEvent.java @@ -8,6 +8,7 @@ package com.velocitypowered.api.event.connection; import com.google.common.base.Preconditions; +import com.velocitypowered.api.network.HandshakeIntent; import com.velocitypowered.api.proxy.InboundConnection; /** @@ -18,19 +19,39 @@ import com.velocitypowered.api.proxy.InboundConnection; public final class ConnectionHandshakeEvent { private final InboundConnection connection; + private final HandshakeIntent intent; + public ConnectionHandshakeEvent(InboundConnection connection, HandshakeIntent intent) { + this.connection = Preconditions.checkNotNull(connection, "connection"); + this.intent = Preconditions.checkNotNull(intent, "intent"); + } + + /** + * This method is only retained to avoid breaking plugins + * that have not yet updated their integration tests. + * + * @param connection the inbound connection + * @deprecated use {@link #ConnectionHandshakeEvent(InboundConnection, HandshakeIntent)} + */ + @Deprecated(forRemoval = true) public ConnectionHandshakeEvent(InboundConnection connection) { this.connection = Preconditions.checkNotNull(connection, "connection"); + this.intent = HandshakeIntent.LOGIN; } public InboundConnection getConnection() { return connection; } + public HandshakeIntent getIntent() { + return this.intent; + } + @Override public String toString() { return "ConnectionHandshakeEvent{" + "connection=" + connection + + ", intent=" + intent + '}'; } } diff --git a/api/src/main/java/com/velocitypowered/api/event/connection/PreTransferEvent.java b/api/src/main/java/com/velocitypowered/api/event/connection/PreTransferEvent.java new file mode 100644 index 000000000..584ab0052 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/connection/PreTransferEvent.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2024 Velocity Contributors + * + * The Velocity API is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package com.velocitypowered.api.event.connection; + +import static java.util.Objects.requireNonNull; + +import com.velocitypowered.api.event.ResultedEvent; +import com.velocitypowered.api.event.annotation.AwaitingEvent; +import com.velocitypowered.api.proxy.Player; +import java.net.InetSocketAddress; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +/** + * This event is executed before sending a player to another host, + * either by the backend server or by a plugin using + * the {@link Player#transferToHost(InetSocketAddress)} method. + */ +@AwaitingEvent +@ApiStatus.Experimental +public final class PreTransferEvent implements ResultedEvent { + private final InetSocketAddress originalAddress; + private final Player player; + private TransferResult result = TransferResult.ALLOWED; + + public PreTransferEvent(final Player player, final InetSocketAddress address) { + this.player = requireNonNull(player); + this.originalAddress = requireNonNull(address); + } + + public Player player() { + return this.player; + } + + public InetSocketAddress originalAddress() { + return this.originalAddress; + } + + @Override + public TransferResult getResult() { + return this.result; + } + + @Override + public void setResult(final TransferResult result) { + requireNonNull(result); + this.result = result; + } + + /** + * Transfer Result of a player to another host. + */ + public static class TransferResult implements ResultedEvent.Result { + private static final TransferResult ALLOWED = new TransferResult(true, null); + private static final TransferResult DENIED = new TransferResult(false, null); + + private final InetSocketAddress address; + private final boolean allowed; + + private TransferResult(final boolean allowed, final InetSocketAddress address) { + this.address = address; + this.allowed = allowed; + } + + public static TransferResult allowed() { + return ALLOWED; + } + + public static TransferResult denied() { + return DENIED; + } + + /** + * Sets the result of transfer to a specific host. + * + * @param address the address specified + * @return a new TransferResult + */ + public static TransferResult transferTo(final InetSocketAddress address) { + requireNonNull(address); + + return new TransferResult(true, address); + } + + @Override + public boolean isAllowed() { + return this.allowed; + } + + @Nullable + public InetSocketAddress address() { + return this.address; + } + } +} diff --git a/api/src/main/java/com/velocitypowered/api/network/HandshakeIntent.java b/api/src/main/java/com/velocitypowered/api/network/HandshakeIntent.java new file mode 100644 index 000000000..44aa4e689 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/network/HandshakeIntent.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2024 Velocity Contributors + * + * The Velocity API is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package com.velocitypowered.api.network; + +/** + * Represents the ClientIntent of a client in the Handshake state. + */ +public enum HandshakeIntent { + STATUS(1), + LOGIN(2), + TRANSFER(3); + + private final int id; + + HandshakeIntent(int id) { + this.id = id; + } + + public int id() { + return this.id; + } + + /** + * Obtain the HandshakeIntent by ID. + * + * @param id the intent id + * @return the HandshakeIntent desired + */ + public static HandshakeIntent getById(int id) { + return switch (id) { + case 1 -> STATUS; + case 2 -> LOGIN; + case 3 -> TRANSFER; + default -> null; + }; + } +} diff --git a/api/src/main/java/com/velocitypowered/api/network/ProtocolState.java b/api/src/main/java/com/velocitypowered/api/network/ProtocolState.java index f5df6fa59..0ecc8287a 100644 --- a/api/src/main/java/com/velocitypowered/api/network/ProtocolState.java +++ b/api/src/main/java/com/velocitypowered/api/network/ProtocolState.java @@ -16,14 +16,15 @@ package com.velocitypowered.api.network; public enum ProtocolState { /** * Initial connection State. - *

This status can be caused by a STATUS, LOGIN or TRANSFER intent.

+ *

This status can be caused by a {@link HandshakeIntent#STATUS}, + * {@link HandshakeIntent#LOGIN} or {@link HandshakeIntent#TRANSFER} intent.

* If the intent is LOGIN or TRANSFER, the next state will be {@link #LOGIN}, * otherwise, it will go to the {@link #STATUS} state. */ HANDSHAKE, /** * Ping State of a connection. - *

Connections with the STATUS HandshakeIntent will pass through this state + *

Connections with the {@link HandshakeIntent#STATUS} intent will pass through this state * and be disconnected after it requests the ping from the server * and the server responds with the respective ping.

*/ diff --git a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java index e124245d5..ba69c6887 100644 --- a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java +++ b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java @@ -85,7 +85,8 @@ public enum ProtocolVersion implements Ordered { MINECRAFT_1_19_4(762, "1.19.4"), MINECRAFT_1_20(763, "1.20", "1.20.1"), MINECRAFT_1_20_2(764, "1.20.2"), - MINECRAFT_1_20_3(765, "1.20.3", "1.20.4"); + MINECRAFT_1_20_3(765, "1.20.3", "1.20.4"), + MINECRAFT_1_20_5(-1, 191, "1.20.5"); // Future Minecraft 1.20.5 | Protocol 766 | Release Candidate 3 private static final int SNAPSHOT_BIT = 30; diff --git a/api/src/main/java/com/velocitypowered/api/proxy/Player.java b/api/src/main/java/com/velocitypowered/api/proxy/Player.java index ae9a8006c..57b307575 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/Player.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/Player.java @@ -20,6 +20,7 @@ import com.velocitypowered.api.proxy.player.TabList; import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.api.util.ModInfo; +import java.net.InetSocketAddress; import java.util.Collection; import java.util.List; import java.util.Locale; @@ -426,4 +427,13 @@ public interface Player extends @Override default void openBook(@NotNull Book book) { } + + /** + * Transfers a Player to a host. + * + * @param address the host address + * @throws IllegalArgumentException if the player is from a version lower than 1.20.5 + * @since 3.3.0 + */ + void transferToHost(@NotNull InetSocketAddress address); } \ No newline at end of file diff --git a/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java b/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java index cd4720cf5..0ec8622dd 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java @@ -17,6 +17,7 @@ package com.velocitypowered.proxy; +import com.velocitypowered.proxy.util.VelocityProperties; import io.netty.util.ResourceLeakDetector; import io.netty.util.ResourceLeakDetector.Level; import java.text.DecimalFormat; @@ -42,13 +43,13 @@ public class Velocity { // If Velocity's natives are being extracted to a different temporary directory, make sure the // Netty natives are extracted there as well - if (System.getProperty("velocity.natives-tmpdir") != null) { + if (VelocityProperties.hasProperty("velocity.natives-tmpdir")) { System.setProperty("io.netty.native.workdir", System.getProperty("velocity.natives-tmpdir")); } // Disable the resource leak detector by default as it reduces performance. Allow the user to // override this if desired. - if (System.getProperty("io.netty.leakDetection.level") == null) { + if (!VelocityProperties.hasProperty("io.netty.leakDetection.level")) { ResourceLeakDetector.setLevel(Level.DISABLED); } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java index 0d5cc8567..11c65d527 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java @@ -30,6 +30,7 @@ import com.velocitypowered.proxy.config.migration.ConfigurationMigration; import com.velocitypowered.proxy.config.migration.ForwardingMigration; import com.velocitypowered.proxy.config.migration.KeyAuthenticationMigration; import com.velocitypowered.proxy.config.migration.MotdMigration; +import com.velocitypowered.proxy.config.migration.TransferIntegrationMigration; import com.velocitypowered.proxy.util.AddressUtil; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.IOException; @@ -398,6 +399,10 @@ public class VelocityConfiguration implements ProxyConfig { return advanced.isLogPlayerConnections(); } + public boolean isAcceptTransfers() { + return this.advanced.isAcceptTransfers(); + } + public boolean isForceKeyAuthentication() { return forceKeyAuthentication; } @@ -456,7 +461,8 @@ public class VelocityConfiguration implements ProxyConfig { final ConfigurationMigration[] migrations = { new ForwardingMigration(), new KeyAuthenticationMigration(), - new MotdMigration() + new MotdMigration(), + new TransferIntegrationMigration() }; for (final ConfigurationMigration migration : migrations) { @@ -708,6 +714,8 @@ public class VelocityConfiguration implements ProxyConfig { private boolean logCommandExecutions = false; @Expose private boolean logPlayerConnections = true; + @Expose + private boolean acceptTransfers = false; private Advanced() { } @@ -732,6 +740,7 @@ public class VelocityConfiguration implements ProxyConfig { this.announceProxyCommands = config.getOrElse("announce-proxy-commands", true); this.logCommandExecutions = config.getOrElse("log-command-executions", false); this.logPlayerConnections = config.getOrElse("log-player-connections", true); + this.acceptTransfers = config.getOrElse("accepts-transfers", false); } } @@ -791,6 +800,10 @@ public class VelocityConfiguration implements ProxyConfig { return logPlayerConnections; } + public boolean isAcceptTransfers() { + return this.acceptTransfers; + } + @Override public String toString() { return "Advanced{" @@ -807,6 +820,7 @@ public class VelocityConfiguration implements ProxyConfig { + ", announceProxyCommands=" + announceProxyCommands + ", logCommandExecutions=" + logCommandExecutions + ", logPlayerConnections=" + logPlayerConnections + + ", acceptTransfers=" + acceptTransfers + '}'; } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/migration/ConfigurationMigration.java b/proxy/src/main/java/com/velocitypowered/proxy/config/migration/ConfigurationMigration.java index 2dc37cb44..a7fbb89fe 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/migration/ConfigurationMigration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/migration/ConfigurationMigration.java @@ -25,7 +25,10 @@ import org.apache.logging.log4j.Logger; * Configuration Migration interface. */ public sealed interface ConfigurationMigration - permits ForwardingMigration, KeyAuthenticationMigration, MotdMigration { + permits ForwardingMigration, + KeyAuthenticationMigration, + MotdMigration, + TransferIntegrationMigration { boolean shouldMigrate(CommentedFileConfig config); void migrate(CommentedFileConfig config, Logger logger) throws IOException; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/migration/TransferIntegrationMigration.java b/proxy/src/main/java/com/velocitypowered/proxy/config/migration/TransferIntegrationMigration.java new file mode 100644 index 000000000..26d89855a --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/migration/TransferIntegrationMigration.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2024 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.config.migration; + +import com.electronwill.nightconfig.core.file.CommentedFileConfig; +import org.apache.logging.log4j.Logger; + +/** + * Creation of the configuration option "accepts-transfers". + */ +public final class TransferIntegrationMigration implements ConfigurationMigration { + @Override + public boolean shouldMigrate(final CommentedFileConfig config) { + return configVersion(config) < 2.7; + } + + @Override + public void migrate(final CommentedFileConfig config, final Logger logger) { + config.set("advanced.accepts-transfers", false); + config.setComment("advanced.accepts-transfers", """ + Allows players transferred from other hosts via the + Transfer packet (Minecraft 1.20.5) to be received."""); + config.set("config-version", "2.7"); + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java index ad6b6db17..fd9ddfc1b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java @@ -51,6 +51,7 @@ import com.velocitypowered.proxy.protocol.packet.StatusRequestPacket; import com.velocitypowered.proxy.protocol.packet.StatusResponsePacket; import com.velocitypowered.proxy.protocol.packet.TabCompleteRequestPacket; import com.velocitypowered.proxy.protocol.packet.TabCompleteResponsePacket; +import com.velocitypowered.proxy.protocol.packet.TransferPacket; import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfoPacket; import com.velocitypowered.proxy.protocol.packet.chat.ChatAcknowledgementPacket; import com.velocitypowered.proxy.protocol.packet.chat.PlayerChatCompletionPacket; @@ -329,4 +330,8 @@ public interface MinecraftSessionHandler { default boolean handle(BundleDelimiterPacket bundleDelimiterPacket) { return false; } + + default boolean handle(TransferPacket transfer) { + return false; + } } 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 18f242fe3..c5d1aa108 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 @@ -25,6 +25,7 @@ import com.mojang.brigadier.tree.RootCommandNode; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.event.command.PlayerAvailableCommandsEvent; import com.velocitypowered.api.event.connection.PluginMessageEvent; +import com.velocitypowered.api.event.connection.PreTransferEvent; import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent; import com.velocitypowered.api.event.player.ServerResourcePackSendEvent; import com.velocitypowered.api.event.proxy.ProxyPingEvent; @@ -57,6 +58,7 @@ import com.velocitypowered.proxy.protocol.packet.ResourcePackRequestPacket; import com.velocitypowered.proxy.protocol.packet.ResourcePackResponsePacket; import com.velocitypowered.proxy.protocol.packet.ServerDataPacket; import com.velocitypowered.proxy.protocol.packet.TabCompleteResponsePacket; +import com.velocitypowered.proxy.protocol.packet.TransferPacket; import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfoPacket; import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import com.velocitypowered.proxy.protocol.packet.config.StartUpdatePacket; @@ -66,6 +68,7 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.handler.timeout.ReadTimeoutException; +import java.net.InetSocketAddress; import java.util.regex.Pattern; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -363,6 +366,30 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { return true; } + @Override + public boolean handle(TransferPacket packet) { + final InetSocketAddress originalAddress = packet.address(); + if (originalAddress == null) { + logger.error(""" + Unexpected nullable address received in TransferPacket \ + from Backend Server in Play State"""); + return true; + } + this.server.getEventManager() + .fire(new PreTransferEvent(this.serverConn.getPlayer(), originalAddress)) + .thenAcceptAsync(event -> { + if (event.getResult().isAllowed()) { + InetSocketAddress resultedAddress = event.getResult().address(); + if (resultedAddress == null) { + resultedAddress = originalAddress; + } + this.playerConnection.write(new TransferPacket( + resultedAddress.getHostName(), resultedAddress.getPort())); + } + }, playerConnection.eventLoop()); + return true; + } + @Override public void handleGeneric(MinecraftPacket packet) { if (packet instanceof PluginMessagePacket) { 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 32c07c042..31d1dedb0 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 @@ -17,6 +17,7 @@ package com.velocitypowered.proxy.connection.backend; +import com.velocitypowered.api.event.connection.PreTransferEvent; import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent; import com.velocitypowered.api.event.player.ServerResourcePackSendEvent; import com.velocitypowered.api.network.ProtocolVersion; @@ -38,12 +39,14 @@ import com.velocitypowered.proxy.protocol.packet.KeepAlivePacket; import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket; import com.velocitypowered.proxy.protocol.packet.ResourcePackRequestPacket; import com.velocitypowered.proxy.protocol.packet.ResourcePackResponsePacket; +import com.velocitypowered.proxy.protocol.packet.TransferPacket; import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket; import com.velocitypowered.proxy.protocol.packet.config.RegistrySyncPacket; import com.velocitypowered.proxy.protocol.packet.config.StartUpdatePacket; import com.velocitypowered.proxy.protocol.packet.config.TagsUpdatePacket; import com.velocitypowered.proxy.protocol.util.PluginMessageUtil; import java.io.IOException; +import java.net.InetSocketAddress; import java.util.concurrent.CompletableFuture; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -224,6 +227,30 @@ public class ConfigSessionHandler implements MinecraftSessionHandler { return true; } + @Override + public boolean handle(TransferPacket packet) { + final InetSocketAddress originalAddress = packet.address(); + if (originalAddress == null) { + logger.error(""" + Unexpected nullable address received in TransferPacket \ + from Backend Server in Configuration State"""); + return true; + } + this.server.getEventManager() + .fire(new PreTransferEvent(this.serverConn.getPlayer(), originalAddress)) + .thenAcceptAsync(event -> { + if (event.getResult().isAllowed()) { + InetSocketAddress resultedAddress = event.getResult().address(); + if (resultedAddress == null) { + resultedAddress = originalAddress; + } + serverConn.getPlayer().getConnection().write(new TransferPacket( + resultedAddress.getHostName(), resultedAddress.getPort())); + } + }, serverConn.ensureConnected().eventLoop()); + return true; + } + @Override public void disconnected() { resultFuture.completeExceptionally( 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 8c027546f..edf3a9148 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 @@ -22,6 +22,7 @@ import static com.velocitypowered.proxy.network.Connections.HANDLER; import static java.util.Objects.requireNonNull; import com.google.common.base.Preconditions; +import com.velocitypowered.api.network.HandshakeIntent; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.proxy.ServerConnection; import com.velocitypowered.api.proxy.messages.ChannelIdentifier; @@ -171,7 +172,7 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, .getHostString(); HandshakePacket handshake = new HandshakePacket(); - handshake.setNextStatus(StateRegistry.LOGIN_ID); + handshake.setIntent(HandshakeIntent.LOGIN); handshake.setProtocolVersion(protocolVersion); if (forwardingMode == PlayerInfoForwarding.LEGACY) { handshake.setServerAddress(createLegacyForwardingAddress()); 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 16df7bb84..edefbc2d8 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 @@ -26,6 +26,7 @@ import com.google.common.base.Preconditions; import com.google.gson.JsonObject; import com.velocitypowered.api.event.connection.DisconnectEvent; import com.velocitypowered.api.event.connection.DisconnectEvent.LoginStatus; +import com.velocitypowered.api.event.connection.PreTransferEvent; import com.velocitypowered.api.event.player.KickedFromServerEvent; import com.velocitypowered.api.event.player.KickedFromServerEvent.DisconnectPlayer; import com.velocitypowered.api.event.player.KickedFromServerEvent.Notify; @@ -69,6 +70,7 @@ import com.velocitypowered.proxy.protocol.packet.HeaderAndFooterPacket; import com.velocitypowered.proxy.protocol.packet.KeepAlivePacket; import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket; import com.velocitypowered.proxy.protocol.packet.RemoveResourcePackPacket; +import com.velocitypowered.proxy.protocol.packet.TransferPacket; import com.velocitypowered.proxy.protocol.packet.chat.ChatQueue; import com.velocitypowered.proxy.protocol.packet.chat.ChatType; import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; @@ -987,6 +989,25 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, this.clientBrand = clientBrand; } + @Override + public void transferToHost(final InetSocketAddress address) { + Preconditions.checkNotNull(address); + Preconditions.checkArgument( + this.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_5) >= 0, + "Player version must be 1.20.5 to be able to transfer to another host"); + + server.getEventManager().fire(new PreTransferEvent(this, address)).thenAccept((event) -> { + if (event.getResult().isAllowed()) { + InetSocketAddress resultedAddress = event.getResult().address(); + if (resultedAddress == null) { + resultedAddress = address; + } + connection.write(new TransferPacket( + resultedAddress.getHostName(), resultedAddress.getPort())); + } + }); + } + @Override public void addCustomChatCompletions(@NotNull Collection completions) { Preconditions.checkNotNull(completions, "completions"); 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 5c5e928fe..fae6533db 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 @@ -20,6 +20,7 @@ package com.velocitypowered.proxy.connection.client; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.velocitypowered.api.event.connection.ConnectionHandshakeEvent; +import com.velocitypowered.api.network.HandshakeIntent; import com.velocitypowered.api.network.ProtocolState; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.VelocityServer; @@ -93,6 +94,11 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler { } else { final InitialInboundConnection ic = new InitialInboundConnection(connection, cleanVhost(handshake.getServerAddress()), handshake); + if (handshake.getIntent() == HandshakeIntent.TRANSFER + && !server.getConfiguration().isAcceptTransfers()) { + ic.disconnect(Component.translatable("multiplayer.disconnect.transfers_disabled")); + return true; + } connection.setProtocolVersion(handshake.getProtocolVersion()); connection.setAssociation(ic); @@ -112,7 +118,7 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler { private static @Nullable StateRegistry getStateForProtocol(int status) { return switch (status) { case StateRegistry.STATUS_ID -> StateRegistry.STATUS; - case StateRegistry.LOGIN_ID -> StateRegistry.LOGIN; + case StateRegistry.LOGIN_ID, StateRegistry.TRANSFER_ID -> StateRegistry.LOGIN; default -> null; }; } @@ -150,7 +156,8 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler { } final LoginInboundConnection lic = new LoginInboundConnection(ic); - server.getEventManager().fireAndForget(new ConnectionHandshakeEvent(lic)); + server.getEventManager().fireAndForget( + new ConnectionHandshakeEvent(lic, handshake.getIntent())); connection.setActiveSessionHandler(StateRegistry.LOGIN, new InitialLoginSessionHandler(server, connection, lic)); } 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 5cc5371c0..00187d347 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 @@ -40,6 +40,7 @@ import com.velocitypowered.proxy.protocol.packet.EncryptionRequestPacket; import com.velocitypowered.proxy.protocol.packet.EncryptionResponsePacket; import com.velocitypowered.proxy.protocol.packet.LoginPluginResponsePacket; import com.velocitypowered.proxy.protocol.packet.ServerLoginPacket; +import com.velocitypowered.proxy.util.VelocityProperties; import io.netty.buffer.ByteBuf; import java.net.InetSocketAddress; import java.net.URI; @@ -82,9 +83,8 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler { this.server = Preconditions.checkNotNull(server, "server"); this.mcConnection = Preconditions.checkNotNull(mcConnection, "mcConnection"); this.inbound = Preconditions.checkNotNull(inbound, "inbound"); - this.forceKeyAuthentication = System.getProperties().containsKey("auth.forceSecureProfiles") - ? Boolean.getBoolean("auth.forceSecureProfiles") - : server.getConfiguration().isForceKeyAuthentication(); + this.forceKeyAuthentication = VelocityProperties.readBoolean( + "auth.forceSecureProfiles", server.getConfiguration().isForceKeyAuthentication()); } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionInfo.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionInfo.java index 6dec08eeb..150c1f457 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionInfo.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionInfo.java @@ -18,6 +18,7 @@ package com.velocitypowered.proxy.connection.registry; import com.google.common.base.Preconditions; +import com.velocitypowered.api.network.ProtocolVersion; import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -40,11 +41,11 @@ public final class DimensionInfo { * @param isDebugType if true constrains the world to the very limited debug-type world */ public DimensionInfo(String registryIdentifier, @Nullable String levelName, - boolean isFlat, boolean isDebugType) { - this.registryIdentifier = Preconditions.checkNotNull( - registryIdentifier, "registryIdentifier cannot be null"); - Preconditions.checkArgument(registryIdentifier.length() > 0, - "registryIdentifier cannot be empty"); + boolean isFlat, boolean isDebugType, ProtocolVersion protocolVersion) { + this.registryIdentifier = Preconditions.checkNotNull(registryIdentifier, "registryIdentifier cannot be null"); + if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_20_5)) { + Preconditions.checkArgument(!registryIdentifier.isEmpty(), "registryIdentifier cannot be empty"); + } this.levelName = levelName; this.isFlat = isFlat; this.isDebugType = isDebugType; 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 f53604a5a..e3cb2bc08 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -35,6 +35,7 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_3; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_4; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_2; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_3; +import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_5; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_7_2; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9; @@ -78,6 +79,7 @@ import com.velocitypowered.proxy.protocol.packet.StatusRequestPacket; import com.velocitypowered.proxy.protocol.packet.StatusResponsePacket; import com.velocitypowered.proxy.protocol.packet.TabCompleteRequestPacket; import com.velocitypowered.proxy.protocol.packet.TabCompleteResponsePacket; +import com.velocitypowered.proxy.protocol.packet.TransferPacket; import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfoPacket; import com.velocitypowered.proxy.protocol.packet.chat.ChatAcknowledgementPacket; import com.velocitypowered.proxy.protocol.packet.chat.PlayerChatCompletionPacket; @@ -87,6 +89,7 @@ import com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedPlayerCommandPa import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyChatPacket; import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerChatPacket; import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerCommandPacket; +import com.velocitypowered.proxy.protocol.packet.chat.session.UnsignedPlayerCommandPacket; import com.velocitypowered.proxy.protocol.packet.config.ActiveFeaturesPacket; import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket; import com.velocitypowered.proxy.protocol.packet.config.RegistrySyncPacket; @@ -144,49 +147,66 @@ public enum StateRegistry { map(0x00, MINECRAFT_1_20_2, false)); serverbound.register( PluginMessagePacket.class, PluginMessagePacket::new, - map(0x01, MINECRAFT_1_20_2, false)); + map(0x01, MINECRAFT_1_20_2, false), + map(0x02, MINECRAFT_1_20_5, false)); serverbound.register( FinishedUpdatePacket.class, () -> FinishedUpdatePacket.INSTANCE, - map(0x02, MINECRAFT_1_20_2, false)); + map(0x02, MINECRAFT_1_20_2, false), + map(0x03, MINECRAFT_1_20_5, false)); serverbound.register(KeepAlivePacket.class, KeepAlivePacket::new, - map(0x03, MINECRAFT_1_20_2, false)); + map(0x03, MINECRAFT_1_20_2, false), + map(0x04, MINECRAFT_1_20_5, false)); serverbound.register( PingIdentifyPacket.class, PingIdentifyPacket::new, - map(0x04, MINECRAFT_1_20_2, false)); + map(0x04, MINECRAFT_1_20_2, false), + map(0x05, MINECRAFT_1_20_5, false)); serverbound.register( ResourcePackResponsePacket.class, ResourcePackResponsePacket::new, - map(0x05, MINECRAFT_1_20_2, false)); + map(0x05, MINECRAFT_1_20_2, false), + map(0x06, MINECRAFT_1_20_5, false)); clientbound.register( PluginMessagePacket.class, PluginMessagePacket::new, - map(0x00, MINECRAFT_1_20_2, false)); + map(0x00, MINECRAFT_1_20_2, false), + map(0x01, MINECRAFT_1_20_5, false)); clientbound.register( DisconnectPacket.class, () -> new DisconnectPacket(this), - map(0x01, MINECRAFT_1_20_2, false)); + map(0x01, MINECRAFT_1_20_2, false), + map(0x02, MINECRAFT_1_20_5, false)); clientbound.register( FinishedUpdatePacket.class, () -> FinishedUpdatePacket.INSTANCE, - map(0x02, MINECRAFT_1_20_2, false)); + map(0x02, MINECRAFT_1_20_2, false), + map(0x03, MINECRAFT_1_20_5, false)); clientbound.register(KeepAlivePacket.class, KeepAlivePacket::new, - map(0x03, MINECRAFT_1_20_2, false)); + map(0x03, MINECRAFT_1_20_2, false), + map(0x04, MINECRAFT_1_20_5, false)); clientbound.register( PingIdentifyPacket.class, PingIdentifyPacket::new, - map(0x04, MINECRAFT_1_20_2, false)); + map(0x04, MINECRAFT_1_20_2, false), + map(0x05, MINECRAFT_1_20_5, false)); clientbound.register( RegistrySyncPacket.class, RegistrySyncPacket::new, - map(0x05, MINECRAFT_1_20_2, false)); + map(0x05, MINECRAFT_1_20_2, false), + map(0x07, MINECRAFT_1_20_5, false)); clientbound.register( RemoveResourcePackPacket.class, RemoveResourcePackPacket::new, - map(0x06, MINECRAFT_1_20_3, false)); + map(0x06, MINECRAFT_1_20_3, false), + map(0x08, MINECRAFT_1_20_5, false)); clientbound.register(ResourcePackRequestPacket.class, ResourcePackRequestPacket::new, map(0x06, MINECRAFT_1_20_2, false), - map(0x07, MINECRAFT_1_20_3, false)); + map(0x07, MINECRAFT_1_20_3, false), + map(0x09, MINECRAFT_1_20_5, false)); + clientbound.register(TransferPacket.class, TransferPacket::new, + map(0x0B, MINECRAFT_1_20_5, false)); clientbound.register(ActiveFeaturesPacket.class, ActiveFeaturesPacket::new, map(0x07, MINECRAFT_1_20_2, false), - map(0x08, MINECRAFT_1_20_3, false)); + map(0x08, MINECRAFT_1_20_3, false), + map(0x0C, MINECRAFT_1_20_5, false)); clientbound.register(TagsUpdatePacket.class, TagsUpdatePacket::new, map(0x08, MINECRAFT_1_20_2, false), - map(0x09, MINECRAFT_1_20_3, false)); + map(0x09, MINECRAFT_1_20_3, false), + map(0x0D, MINECRAFT_1_20_5, false)); } }, PLAY { @@ -205,7 +225,8 @@ public enum StateRegistry { map(0x09, MINECRAFT_1_19_1, false), map(0x08, MINECRAFT_1_19_3, false), map(0x09, MINECRAFT_1_19_4, false), - map(0x0A, MINECRAFT_1_20_2, false)); + map(0x0A, MINECRAFT_1_20_2, false), + map(0x0B, MINECRAFT_1_20_5, false)); serverbound.register( LegacyChatPacket.class, LegacyChatPacket::new, @@ -225,11 +246,15 @@ public enum StateRegistry { map(0x04, MINECRAFT_1_19, false), map(0x05, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false)); serverbound.register(SessionPlayerCommandPacket.class, SessionPlayerCommandPacket::new, - map(0x04, MINECRAFT_1_19_3, false)); + map(0x04, MINECRAFT_1_19_3, false), + map(0x05, MINECRAFT_1_20_5, false)); + serverbound.register(UnsignedPlayerCommandPacket.class, UnsignedPlayerCommandPacket::new, + map(0x04, MINECRAFT_1_20_5, false)); serverbound.register( SessionPlayerChatPacket.class, SessionPlayerChatPacket::new, - map(0x05, MINECRAFT_1_19_3, false)); + map(0x05, MINECRAFT_1_19_3, false), + map(0x06, MINECRAFT_1_20_5, false)); serverbound.register( ClientSettingsPacket.class, ClientSettingsPacket::new, @@ -242,7 +267,8 @@ public enum StateRegistry { map(0x08, MINECRAFT_1_19_1, false), map(0x07, MINECRAFT_1_19_3, false), map(0x08, MINECRAFT_1_19_4, false), - map(0x09, MINECRAFT_1_20_2, false)); + map(0x09, MINECRAFT_1_20_2, false), + map(0x0A, MINECRAFT_1_20_5, false)); serverbound.register( PluginMessagePacket.class, PluginMessagePacket::new, @@ -258,7 +284,8 @@ public enum StateRegistry { map(0x0C, MINECRAFT_1_19_3, false), map(0x0D, MINECRAFT_1_19_4, false), map(0x0F, MINECRAFT_1_20_2, false), - map(0x10, MINECRAFT_1_20_3, false)); + map(0x10, MINECRAFT_1_20_3, false), + map(0x12, MINECRAFT_1_20_5, false)); serverbound.register( KeepAlivePacket.class, KeepAlivePacket::new, @@ -275,7 +302,8 @@ public enum StateRegistry { map(0x11, MINECRAFT_1_19_3, false), map(0x12, MINECRAFT_1_19_4, false), map(0x14, MINECRAFT_1_20_2, false), - map(0x15, MINECRAFT_1_20_3, false)); + map(0x15, MINECRAFT_1_20_3, false), + map(0x18, MINECRAFT_1_20_5, false)); serverbound.register( ResourcePackResponsePacket.class, ResourcePackResponsePacket::new, @@ -289,10 +317,12 @@ public enum StateRegistry { map(0x23, MINECRAFT_1_19, false), map(0x24, MINECRAFT_1_19_1, false), map(0x27, MINECRAFT_1_20_2, false), - map(0x28, MINECRAFT_1_20_3, false)); + map(0x28, MINECRAFT_1_20_3, false), + map(0x2B, MINECRAFT_1_20_5, false)); serverbound.register( FinishedUpdatePacket.class, () -> FinishedUpdatePacket.INSTANCE, - map(0x0B, MINECRAFT_1_20_2, false)); + map(0x0B, MINECRAFT_1_20_2, false), + map(0x0C, MINECRAFT_1_20_5, false)); clientbound.register( BossBarPacket.class, @@ -352,7 +382,8 @@ public enum StateRegistry { map(0x16, MINECRAFT_1_19_1, false), map(0x15, MINECRAFT_1_19_3, false), map(0x17, MINECRAFT_1_19_4, false), - map(0x18, MINECRAFT_1_20_2, false)); + map(0x18, MINECRAFT_1_20_2, false), + map(0x19, MINECRAFT_1_20_5, false)); clientbound.register( DisconnectPacket.class, () -> new DisconnectPacket(this), @@ -368,7 +399,8 @@ public enum StateRegistry { map(0x19, MINECRAFT_1_19_1, false), map(0x17, MINECRAFT_1_19_3, false), map(0x1A, MINECRAFT_1_19_4, false), - map(0x1B, MINECRAFT_1_20_2, false)); + map(0x1B, MINECRAFT_1_20_2, false), + map(0x1D, MINECRAFT_1_20_5, false)); clientbound.register( KeepAlivePacket.class, KeepAlivePacket::new, @@ -384,7 +416,8 @@ public enum StateRegistry { map(0x20, MINECRAFT_1_19_1, false), map(0x1F, MINECRAFT_1_19_3, false), map(0x23, MINECRAFT_1_19_4, false), - map(0x24, MINECRAFT_1_20_2, false)); + map(0x24, MINECRAFT_1_20_2, false), + map(0x26, MINECRAFT_1_20_5, false)); clientbound.register( JoinGamePacket.class, JoinGamePacket::new, @@ -400,7 +433,8 @@ public enum StateRegistry { map(0x25, MINECRAFT_1_19_1, false), map(0x24, MINECRAFT_1_19_3, false), map(0x28, MINECRAFT_1_19_4, false), - map(0x29, MINECRAFT_1_20_2, false)); + map(0x29, MINECRAFT_1_20_2, false), + map(0x2B, MINECRAFT_1_20_5, false)); clientbound.register( RespawnPacket.class, RespawnPacket::new, @@ -419,11 +453,13 @@ public enum StateRegistry { map(0x3D, MINECRAFT_1_19_3, true), map(0x41, MINECRAFT_1_19_4, true), map(0x43, MINECRAFT_1_20_2, true), - map(0x45, MINECRAFT_1_20_3, true)); + map(0x45, MINECRAFT_1_20_3, true), + map(0x47, MINECRAFT_1_20_5, true)); clientbound.register( RemoveResourcePackPacket.class, RemoveResourcePackPacket::new, - map(0x43, MINECRAFT_1_20_3, false)); + map(0x43, MINECRAFT_1_20_3, false), + map(0x45, MINECRAFT_1_20_5, false)); clientbound.register( ResourcePackRequestPacket.class, ResourcePackRequestPacket::new, @@ -442,7 +478,8 @@ public enum StateRegistry { map(0x3C, MINECRAFT_1_19_3, false), map(0x40, MINECRAFT_1_19_4, false), map(0x42, MINECRAFT_1_20_2, false), - map(0x44, MINECRAFT_1_20_3, false)); + map(0x44, MINECRAFT_1_20_3, false), + map(0x46, MINECRAFT_1_20_5, false)); clientbound.register( HeaderAndFooterPacket.class, HeaderAndFooterPacket::new, @@ -462,7 +499,8 @@ public enum StateRegistry { map(0x61, MINECRAFT_1_19_3, true), map(0x65, MINECRAFT_1_19_4, true), map(0x68, MINECRAFT_1_20_2, true), - map(0x6A, MINECRAFT_1_20_3, true)); + map(0x6A, MINECRAFT_1_20_3, true), + map(0x6D, MINECRAFT_1_20_5, true)); clientbound.register( LegacyTitlePacket.class, LegacyTitlePacket::new, @@ -481,7 +519,8 @@ public enum StateRegistry { map(0x59, MINECRAFT_1_19_3, true), map(0x5D, MINECRAFT_1_19_4, true), map(0x5F, MINECRAFT_1_20_2, true), - map(0x61, MINECRAFT_1_20_3, true)); + map(0x61, MINECRAFT_1_20_3, true), + map(0x63, MINECRAFT_1_20_5, true)); clientbound.register( TitleTextPacket.class, TitleTextPacket::new, @@ -491,7 +530,8 @@ public enum StateRegistry { map(0x5B, MINECRAFT_1_19_3, true), map(0x5F, MINECRAFT_1_19_4, true), map(0x61, MINECRAFT_1_20_2, true), - map(0x63, MINECRAFT_1_20_3, true)); + map(0x63, MINECRAFT_1_20_3, true), + map(0x65, MINECRAFT_1_20_5, true)); clientbound.register( TitleActionbarPacket.class, TitleActionbarPacket::new, @@ -501,7 +541,8 @@ public enum StateRegistry { map(0x42, MINECRAFT_1_19_3, true), map(0x46, MINECRAFT_1_19_4, true), map(0x48, MINECRAFT_1_20_2, true), - map(0x4A, MINECRAFT_1_20_3, true)); + map(0x4A, MINECRAFT_1_20_3, true), + map(0x4C, MINECRAFT_1_20_5, true)); clientbound.register( TitleTimesPacket.class, TitleTimesPacket::new, @@ -511,7 +552,8 @@ public enum StateRegistry { map(0x5C, MINECRAFT_1_19_3, true), map(0x60, MINECRAFT_1_19_4, true), map(0x62, MINECRAFT_1_20_2, true), - map(0x64, MINECRAFT_1_20_3, true)); + map(0x64, MINECRAFT_1_20_3, true), + map(0x66, MINECRAFT_1_20_5, true)); clientbound.register( TitleClearPacket.class, TitleClearPacket::new, @@ -537,13 +579,15 @@ public enum StateRegistry { clientbound.register(RemovePlayerInfoPacket.class, RemovePlayerInfoPacket::new, map(0x35, MINECRAFT_1_19_3, false), map(0x39, MINECRAFT_1_19_4, false), - map(0x3B, MINECRAFT_1_20_2, false)); + map(0x3B, MINECRAFT_1_20_2, false), + map(0x3D, MINECRAFT_1_20_5, false)); clientbound.register( UpsertPlayerInfoPacket.class, UpsertPlayerInfoPacket::new, map(0x36, MINECRAFT_1_19_3, false), map(0x3A, MINECRAFT_1_19_4, false), - map(0x3C, MINECRAFT_1_20_2, false)); + map(0x3C, MINECRAFT_1_20_2, false), + map(0x3E, MINECRAFT_1_20_5, false)); clientbound.register( SystemChatPacket.class, SystemChatPacket::new, @@ -552,14 +596,16 @@ public enum StateRegistry { map(0x60, MINECRAFT_1_19_3, true), map(0x64, MINECRAFT_1_19_4, true), map(0x67, MINECRAFT_1_20_2, true), - map(0x69, MINECRAFT_1_20_3, true)); + map(0x69, MINECRAFT_1_20_3, true), + map(0x6C, MINECRAFT_1_20_5, true)); clientbound.register( PlayerChatCompletionPacket.class, PlayerChatCompletionPacket::new, map(0x15, MINECRAFT_1_19_1, true), map(0x14, MINECRAFT_1_19_3, true), map(0x16, MINECRAFT_1_19_4, true), - map(0x17, MINECRAFT_1_20_2, true)); + map(0x17, MINECRAFT_1_20_2, true), + map(0x18, MINECRAFT_1_20_5, true)); clientbound.register( ServerDataPacket.class, ServerDataPacket::new, @@ -568,16 +614,23 @@ public enum StateRegistry { map(0x41, MINECRAFT_1_19_3, false), map(0x45, MINECRAFT_1_19_4, false), map(0x47, MINECRAFT_1_20_2, false), - map(0x49, MINECRAFT_1_20_3, false)); + map(0x49, MINECRAFT_1_20_3, false), + map(0x4B, MINECRAFT_1_20_5, false)); clientbound.register( StartUpdatePacket.class, () -> StartUpdatePacket.INSTANCE, map(0x65, MINECRAFT_1_20_2, false), - map(0x67, MINECRAFT_1_20_3, false)); + map(0x67, MINECRAFT_1_20_3, false), + map(0x69, MINECRAFT_1_20_5, false)); clientbound.register( BundleDelimiterPacket.class, () -> BundleDelimiterPacket.INSTANCE, map(0x00, MINECRAFT_1_19_4, false)); + clientbound.register( + TransferPacket.class, + TransferPacket::new, + map(0x73, MINECRAFT_1_20_5, false) + ); } }, LOGIN { @@ -616,6 +669,7 @@ public enum StateRegistry { public static final int STATUS_ID = 1; public static final int LOGIN_ID = 2; + public static final int TRANSFER_ID = 3; protected final PacketRegistry clientbound = new PacketRegistry(CLIENTBOUND, this); protected final PacketRegistry serverbound = new PacketRegistry(SERVERBOUND, this); 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 98b644d0a..422d5e117 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 @@ -31,6 +31,7 @@ public class EncryptionRequestPacket implements MinecraftPacket { private String serverId = ""; private byte[] publicKey = EMPTY_BYTE_ARRAY; private byte[] verifyToken = EMPTY_BYTE_ARRAY; + private boolean shouldAuthenticate = true; public byte[] getPublicKey() { return publicKey.clone(); @@ -63,6 +64,9 @@ public class EncryptionRequestPacket implements MinecraftPacket { 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); @@ -76,6 +80,9 @@ public class EncryptionRequestPacket implements MinecraftPacket { 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); 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 107ea6f04..2edaed9ef 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 @@ -19,6 +19,7 @@ package com.velocitypowered.proxy.protocol.packet; import static com.velocitypowered.proxy.connection.forge.legacy.LegacyForgeConstants.HANDSHAKE_HOSTNAME_TOKEN; +import com.velocitypowered.api.network.HandshakeIntent; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; @@ -33,6 +34,7 @@ public class HandshakePacket implements MinecraftPacket { private ProtocolVersion protocolVersion; private String serverAddress = ""; private int port; + private HandshakeIntent intent; private int nextStatus; public ProtocolVersion getProtocolVersion() { @@ -60,11 +62,16 @@ public class HandshakePacket implements MinecraftPacket { } public int getNextStatus() { - return nextStatus; + return this.nextStatus; } - public void setNextStatus(int nextStatus) { - this.nextStatus = nextStatus; + public void setIntent(HandshakeIntent intent) { + this.intent = intent; + this.nextStatus = intent.id(); + } + + public HandshakeIntent getIntent() { + return this.intent; } @Override @@ -84,6 +91,7 @@ public class HandshakePacket implements MinecraftPacket { this.serverAddress = ProtocolUtils.readString(buf, MAXIMUM_HOSTNAME_LENGTH); this.port = buf.readUnsignedShort(); this.nextStatus = ProtocolUtils.readVarInt(buf); + this.intent = HandshakeIntent.getById(nextStatus); } @Override 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 aa5ccaf7a..f1d2b6400 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 @@ -51,6 +51,7 @@ public class JoinGamePacket implements MinecraftPacket { private int simulationDistance; // 1.18+ private @Nullable Pair lastDeathPosition; // 1.19+ private int portalCooldown; // 1.20+ + private boolean enforcesSecureChat; // 1.20.5+ public int getEntityId() { return entityId; @@ -180,6 +181,14 @@ public class JoinGamePacket implements MinecraftPacket { this.portalCooldown = portalCooldown; } + public boolean getEnforcesSecureChat() { + return this.enforcesSecureChat; + } + + public void setEnforcesSecureChat(final boolean enforcesSecureChat) { + this.enforcesSecureChat = enforcesSecureChat; + } + public CompoundBinaryTag getRegistry() { return registry; } @@ -284,7 +293,7 @@ public class JoinGamePacket implements MinecraftPacket { boolean isDebug = buf.readBoolean(); boolean isFlat = buf.readBoolean(); - this.dimensionInfo = new DimensionInfo(dimensionIdentifier, levelName, isFlat, isDebug); + this.dimensionInfo = new DimensionInfo(dimensionIdentifier, levelName, isFlat, isDebug, version); // optional death location if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19) && buf.readBoolean()) { @@ -296,6 +305,7 @@ public class JoinGamePacket implements MinecraftPacket { } } + @SuppressWarnings("checkstyle:VariableDeclarationUsageDistance") private void decode1202Up(ByteBuf buf, ProtocolVersion version) { this.entityId = buf.readInt(); this.isHardcore = buf.readBoolean(); @@ -311,7 +321,12 @@ public class JoinGamePacket implements MinecraftPacket { this.showRespawnScreen = buf.readBoolean(); this.doLimitedCrafting = buf.readBoolean(); - String dimensionIdentifier = ProtocolUtils.readString(buf); + 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(); @@ -320,7 +335,7 @@ public class JoinGamePacket implements MinecraftPacket { boolean isDebug = buf.readBoolean(); boolean isFlat = buf.readBoolean(); - this.dimensionInfo = new DimensionInfo(dimensionIdentifier, levelName, isFlat, isDebug); + this.dimensionInfo = new DimensionInfo(dimensionKey, levelName, isFlat, isDebug, version); // optional death location if (buf.readBoolean()) { @@ -328,6 +343,9 @@ public class JoinGamePacket implements MinecraftPacket { } this.portalCooldown = ProtocolUtils.readVarInt(buf); + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { + this.enforcesSecureChat = buf.readBoolean(); + } } @Override @@ -391,8 +409,7 @@ public class JoinGamePacket implements MinecraftPacket { 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)) { + 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 { @@ -449,7 +466,11 @@ public class JoinGamePacket implements MinecraftPacket { buf.writeBoolean(showRespawnScreen); buf.writeBoolean(doLimitedCrafting); - ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier()); + 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); @@ -469,6 +490,10 @@ public class JoinGamePacket implements MinecraftPacket { } ProtocolUtils.writeVarInt(buf, portalCooldown); + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { + buf.writeBoolean(this.enforcesSecureChat); + } } @Override 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 7e9562c44..ceaee9fd2 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 @@ -160,15 +160,19 @@ public class RespawnPacket implements MinecraftPacket { @Override public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - String dimensionIdentifier = null; + 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()); - dimensionIdentifier = ProtocolUtils.readString(buf); + dimensionKey = ProtocolUtils.readString(buf); } else { - dimensionIdentifier = ProtocolUtils.readString(buf); + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { + dimension = ProtocolUtils.readVarInt(buf); + } else { + dimensionKey = ProtocolUtils.readString(buf); + } levelName = ProtocolUtils.readString(buf); } } else { @@ -185,7 +189,7 @@ public class RespawnPacket implements MinecraftPacket { this.previousGamemode = buf.readByte(); boolean isDebug = buf.readBoolean(); boolean isFlat = buf.readBoolean(); - this.dimensionInfo = new DimensionInfo(dimensionIdentifier, levelName, isFlat, isDebug); + 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)) { @@ -213,7 +217,11 @@ public class RespawnPacket implements MinecraftPacket { ProtocolUtils.writeBinaryTag(buf, version, currentDimensionData); ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier()); } else { - ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier()); + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { + ProtocolUtils.writeVarInt(buf, dimension); + } else { + ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier()); + } ProtocolUtils.writeString(buf, dimensionInfo.getLevelName()); } } else { 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 d92bf0c46..610462a94 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 @@ -32,7 +32,7 @@ public class ServerDataPacket implements MinecraftPacket { private @Nullable ComponentHolder description; private @Nullable Favicon favicon; - private boolean secureChatEnforced; // Added in 1.19.1 + private boolean secureChatEnforced; // Added in 1.19.1 - Removed in 1.20.5 public ServerDataPacket() { } @@ -63,7 +63,8 @@ public class ServerDataPacket implements MinecraftPacket { if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_19_3)) { buf.readBoolean(); } - if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) { + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_1) + && protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_20_5)) { this.secureChatEnforced = buf.readBoolean(); } } @@ -94,7 +95,8 @@ public class ServerDataPacket implements MinecraftPacket { if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_19_3)) { buf.writeBoolean(false); } - if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) { + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_1) + && protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_20_5)) { buf.writeBoolean(this.secureChatEnforced); } } @@ -115,4 +117,8 @@ public class ServerDataPacket implements MinecraftPacket { public boolean isSecureChatEnforced() { return secureChatEnforced; } + + public void setSecureChatEnforced(boolean secureChatEnforced) { + this.secureChatEnforced = 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 210447cc7..74eabb5c3 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 @@ -26,9 +26,8 @@ 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.UUID; +import org.checkerframework.checker.nullness.qual.Nullable; public class ServerLoginPacket implements MinecraftPacket { @@ -75,10 +74,10 @@ public class ServerLoginPacket implements MinecraftPacket { @Override public String toString() { return "ServerLogin{" - + "username='" + username + '\'' - + "uuid='" + holderUuid + '\'' - + "playerKey='" + playerKey + '\'' - + '}'; + + "username='" + username + '\'' + + "playerKey='" + playerKey + '\'' + + "holderUUID='" + holderUuid + '\'' + + '}'; } @Override 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 fb7c087b4..f4fa6bc30 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 @@ -23,8 +23,8 @@ import com.velocitypowered.api.util.UuidUtils; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolUtils; +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; @@ -34,6 +34,8 @@ public class ServerLoginSuccessPacket implements MinecraftPacket { private @Nullable UUID uuid; private @Nullable String username; private @Nullable List properties; + private static final boolean strictErrorHandling = VelocityProperties + .readBoolean("velocity.strictErrorHandling", true); public UUID getUuid() { if (uuid == null) { @@ -90,6 +92,9 @@ public class ServerLoginSuccessPacket implements MinecraftPacket { if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) { properties = ProtocolUtils.readProperties(buf); } + if (version == ProtocolVersion.MINECRAFT_1_20_5) { + buf.readBoolean(); + } } @Override @@ -118,6 +123,9 @@ public class ServerLoginSuccessPacket implements MinecraftPacket { ProtocolUtils.writeProperties(buf, properties); } } + if (version == ProtocolVersion.MINECRAFT_1_20_5) { + buf.writeBoolean(strictErrorHandling); + } } @Override 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 new file mode 100644 index 000000000..b5a74e49f --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TransferPacket.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2024 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.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 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; + } + + @Nullable + public InetSocketAddress address() { + if (host == null) { + return null; + } + 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); + } + + @Override + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + ProtocolUtils.writeString(buf, host); + ProtocolUtils.writeVarInt(buf, port); + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java index 9eca02fbf..17bdd976f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java @@ -21,6 +21,7 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_3; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_4; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_3; +import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_5; import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.id; import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.mapSet; import static com.velocitypowered.proxy.protocol.packet.brigadier.DoubleArgumentPropertySerializer.DOUBLE; @@ -190,8 +191,7 @@ public class ArgumentPropertyRegistry { }); register(id("brigadier:float", mapSet(MINECRAFT_1_19, 1)), FloatArgumentType.class, FLOAT); register(id("brigadier:double", mapSet(MINECRAFT_1_19, 2)), DoubleArgumentType.class, DOUBLE); - register(id("brigadier:integer", mapSet(MINECRAFT_1_19, 3)), IntegerArgumentType.class, - INTEGER); + register(id("brigadier:integer", mapSet(MINECRAFT_1_19, 3)), IntegerArgumentType.class, INTEGER); register(id("brigadier:long", mapSet(MINECRAFT_1_19, 4)), LongArgumentType.class, LONG); register(id("brigadier:string", mapSet(MINECRAFT_1_19, 5)), StringArgumentType.class, STRING); @@ -209,77 +209,65 @@ public class ArgumentPropertyRegistry { empty(id("minecraft:component", mapSet(MINECRAFT_1_19, 17))); empty(id("minecraft:style", mapSet(MINECRAFT_1_20_3, 18))); // added 1.20.3 empty(id("minecraft:message", mapSet(MINECRAFT_1_20_3, 19), mapSet(MINECRAFT_1_19, 18))); - empty(id("minecraft:nbt_compound_tag", mapSet(MINECRAFT_1_20_3, 20), - mapSet(MINECRAFT_1_19, 19))); // added in 1.14 - empty(id("minecraft:nbt_tag", mapSet(MINECRAFT_1_20_3, 21), - mapSet(MINECRAFT_1_19, 20))); // added in 1.14 + empty(id("minecraft:nbt_compound_tag", mapSet(MINECRAFT_1_20_3, 20), mapSet(MINECRAFT_1_19, 19))); // added in 1.14 + empty(id("minecraft:nbt_tag", mapSet(MINECRAFT_1_20_3, 21), mapSet(MINECRAFT_1_19, 20))); // added in 1.14 empty(id("minecraft:nbt_path", mapSet(MINECRAFT_1_20_3, 22), mapSet(MINECRAFT_1_19, 21))); empty(id("minecraft:objective", mapSet(MINECRAFT_1_20_3, 23), mapSet(MINECRAFT_1_19, 22))); - empty(id("minecraft:objective_criteria", mapSet(MINECRAFT_1_20_3, 24), - mapSet(MINECRAFT_1_19, 23))); + empty(id("minecraft:objective_criteria", mapSet(MINECRAFT_1_20_3, 24), mapSet(MINECRAFT_1_19, 23))); empty(id("minecraft:operation", mapSet(MINECRAFT_1_20_3, 25), mapSet(MINECRAFT_1_19, 24))); empty(id("minecraft:particle", mapSet(MINECRAFT_1_20_3, 26), mapSet(MINECRAFT_1_19, 25))); - empty(id("minecraft:angle", mapSet(MINECRAFT_1_20_3, 27), - mapSet(MINECRAFT_1_19, 26))); // added in 1.16.2 + empty(id("minecraft:angle", mapSet(MINECRAFT_1_20_3, 27), mapSet(MINECRAFT_1_19, 26))); // added in 1.16.2 empty(id("minecraft:rotation", mapSet(MINECRAFT_1_20_3, 28), mapSet(MINECRAFT_1_19, 27))); - empty( - id("minecraft:scoreboard_slot", mapSet(MINECRAFT_1_20_3, 29), mapSet(MINECRAFT_1_19, 28))); - empty(id("minecraft:score_holder", mapSet(MINECRAFT_1_20_3, 30), mapSet(MINECRAFT_1_19, 29)), - ByteArgumentPropertySerializer.BYTE); + empty(id("minecraft:scoreboard_slot", mapSet(MINECRAFT_1_20_3, 29), mapSet(MINECRAFT_1_19, 28))); + empty(id("minecraft:score_holder", mapSet(MINECRAFT_1_20_3, 30), mapSet(MINECRAFT_1_19, 29)), ByteArgumentPropertySerializer.BYTE); empty(id("minecraft:swizzle", mapSet(MINECRAFT_1_20_3, 31), mapSet(MINECRAFT_1_19, 30))); empty(id("minecraft:team", mapSet(MINECRAFT_1_20_3, 32), mapSet(MINECRAFT_1_19, 31))); empty(id("minecraft:item_slot", mapSet(MINECRAFT_1_20_3, 33), mapSet(MINECRAFT_1_19, 32))); - empty(id("minecraft:resource_location", mapSet(MINECRAFT_1_20_3, 34), - mapSet(MINECRAFT_1_19, 33))); + empty(id("minecraft:item_slots", mapSet(MINECRAFT_1_20_5, 34))); // added 1.20.5 + empty(id("minecraft:resource_location", mapSet(MINECRAFT_1_20_5, 35), mapSet(MINECRAFT_1_20_3, 34), mapSet(MINECRAFT_1_19, 33))); empty(id("minecraft:mob_effect", mapSet(MINECRAFT_1_19_3, -1), mapSet(MINECRAFT_1_19, 34))); - empty(id("minecraft:function", mapSet(MINECRAFT_1_20_3, 35), mapSet(MINECRAFT_1_19_3, 34), + empty(id("minecraft:function", mapSet(MINECRAFT_1_20_5, 36), mapSet(MINECRAFT_1_20_3, 35), mapSet(MINECRAFT_1_19_3, 34), mapSet(MINECRAFT_1_19, 35))); - empty(id("minecraft:entity_anchor", mapSet(MINECRAFT_1_20_3, 36), mapSet(MINECRAFT_1_19_3, 35), + empty(id("minecraft:entity_anchor", mapSet(MINECRAFT_1_20_5, 37), mapSet(MINECRAFT_1_20_3, 36), mapSet(MINECRAFT_1_19_3, 35), mapSet(MINECRAFT_1_19, 36))); - empty(id("minecraft:int_range", mapSet(MINECRAFT_1_20_3, 37), mapSet(MINECRAFT_1_19_3, 36), + empty(id("minecraft:int_range", mapSet(MINECRAFT_1_20_5, 38), mapSet(MINECRAFT_1_20_3, 37), mapSet(MINECRAFT_1_19_3, 36), mapSet(MINECRAFT_1_19, 37))); - empty(id("minecraft:float_range", mapSet(MINECRAFT_1_20_3, 38), mapSet(MINECRAFT_1_19_3, 37), + empty(id("minecraft:float_range", mapSet(MINECRAFT_1_20_5, 39), mapSet(MINECRAFT_1_20_3, 38), mapSet(MINECRAFT_1_19_3, 37), mapSet(MINECRAFT_1_19, 38))); - empty( - id("minecraft:item_enchantment", mapSet(MINECRAFT_1_19_3, -1), mapSet(MINECRAFT_1_19, 39))); + empty(id("minecraft:item_enchantment", mapSet(MINECRAFT_1_19_3, -1), mapSet(MINECRAFT_1_19, 39))); empty(id("minecraft:entity_summon", mapSet(MINECRAFT_1_19_3, -1), mapSet(MINECRAFT_1_19, 40))); - empty(id("minecraft:dimension", mapSet(MINECRAFT_1_20_3, 39), mapSet(MINECRAFT_1_19_3, 38), + empty(id("minecraft:dimension", mapSet(MINECRAFT_1_20_5, 40), mapSet(MINECRAFT_1_20_3, 39), mapSet(MINECRAFT_1_19_3, 38), mapSet(MINECRAFT_1_19, 41))); - empty(id("minecraft:gamemode", mapSet(MINECRAFT_1_20_3, 40), - mapSet(MINECRAFT_1_19_3, 39))); // 1.19.3 + empty(id("minecraft:gamemode", mapSet(MINECRAFT_1_20_5, 41), mapSet(MINECRAFT_1_20_3, 40), mapSet(MINECRAFT_1_19_3, 39))); // 1.19.3 - empty(id("minecraft:time", mapSet(MINECRAFT_1_20_3, 41), mapSet(MINECRAFT_1_19_3, 40), + empty(id("minecraft:time", mapSet(MINECRAFT_1_20_5, 42), mapSet(MINECRAFT_1_20_3, 41), mapSet(MINECRAFT_1_19_3, 40), mapSet(MINECRAFT_1_19, 42)), TimeArgumentSerializer.TIME); // added in 1.14 - register( - id("minecraft:resource_or_tag", mapSet(MINECRAFT_1_20_3, 42), mapSet(MINECRAFT_1_19_3, 41), - mapSet(MINECRAFT_1_19, 43)), - RegistryKeyArgument.class, RegistryKeyArgumentSerializer.REGISTRY); - register(id("minecraft:resource_or_tag_key", mapSet(MINECRAFT_1_20_3, 43), - mapSet(MINECRAFT_1_19_3, 42)), + register(id("minecraft:resource_or_tag", mapSet(MINECRAFT_1_20_5, 43), mapSet(MINECRAFT_1_20_3, 42), mapSet(MINECRAFT_1_19_3, 41), + mapSet(MINECRAFT_1_19, 43)), RegistryKeyArgument.class, RegistryKeyArgumentSerializer.REGISTRY); + register(id("minecraft:resource_or_tag_key", mapSet(MINECRAFT_1_20_5, 44), mapSet(MINECRAFT_1_20_3, 43), mapSet(MINECRAFT_1_19_3, 42)), RegistryKeyArgumentList.ResourceOrTagKey.class, RegistryKeyArgumentList.ResourceOrTagKey.Serializer.REGISTRY); - register(id("minecraft:resource", mapSet(MINECRAFT_1_20_3, 44), mapSet(MINECRAFT_1_19_3, 43), - mapSet(MINECRAFT_1_19, 44)), + register(id("minecraft:resource", mapSet(MINECRAFT_1_20_5, 45), mapSet(MINECRAFT_1_20_3, 44), mapSet(MINECRAFT_1_19_3, 43), + mapSet(MINECRAFT_1_19, 44)), RegistryKeyArgument.class, RegistryKeyArgumentSerializer.REGISTRY); - register( - id("minecraft:resource_key", mapSet(MINECRAFT_1_20_3, 45), mapSet(MINECRAFT_1_19_3, 44)), + register(id("minecraft:resource_key", mapSet(MINECRAFT_1_20_5, 46), mapSet(MINECRAFT_1_20_3, 45), mapSet(MINECRAFT_1_19_3, 44)), RegistryKeyArgumentList.ResourceKey.class, RegistryKeyArgumentList.ResourceKey.Serializer.REGISTRY); - empty(id("minecraft:template_mirror", mapSet(MINECRAFT_1_20_3, 46), - mapSet(MINECRAFT_1_19, 45))); // 1.19 - empty(id("minecraft:template_rotation", mapSet(MINECRAFT_1_20_3, 47), - mapSet(MINECRAFT_1_19, 46))); // 1.19 - empty(id("minecraft:heightmap", mapSet(MINECRAFT_1_20_3, 49), - mapSet(MINECRAFT_1_19_4, 47))); // 1.19.4 + empty(id("minecraft:template_mirror", mapSet(MINECRAFT_1_20_5, 47), mapSet(MINECRAFT_1_20_3, 46), mapSet(MINECRAFT_1_19, 45))); // 1.19 + empty(id("minecraft:template_rotation", mapSet(MINECRAFT_1_20_5, 48), mapSet(MINECRAFT_1_20_3, 47), mapSet(MINECRAFT_1_19, 46))); // 1.19 + empty(id("minecraft:heightmap", mapSet(MINECRAFT_1_20_3, 49), mapSet(MINECRAFT_1_19_4, 47))); // 1.19.4 - empty(id("minecraft:uuid", mapSet(MINECRAFT_1_20_3, 48), mapSet(MINECRAFT_1_19_4, 48), + empty(id("minecraft:uuid", mapSet(MINECRAFT_1_20_5, 53), mapSet(MINECRAFT_1_20_3, 48), mapSet(MINECRAFT_1_19_4, 48), mapSet(MINECRAFT_1_19, 47))); // added in 1.16 + empty(id("minecraft:loot_table", mapSet(MINECRAFT_1_20_5, 50)), RegistryIdArgumentSerializer.REGISTRY_ID); + empty(id("minecraft:loot_predicate", mapSet(MINECRAFT_1_20_5, 51)), RegistryIdArgumentSerializer.REGISTRY_ID); + empty(id("minecraft:loot_modifier", mapSet(MINECRAFT_1_20_5, 52)), RegistryIdArgumentSerializer.REGISTRY_ID); + // Crossstitch support - register(id("crossstitch:mod_argument", mapSet(MINECRAFT_1_19, -256)), - ModArgumentProperty.class, MOD); + register(id("crossstitch:mod_argument", mapSet(MINECRAFT_1_19, -256)), ModArgumentProperty.class, MOD); empty(id("minecraft:nbt")); // No longer in 1.19+ } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/RegistryIdArgumentSerializer.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/RegistryIdArgumentSerializer.java new file mode 100644 index 000000000..5e8e1daf1 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/RegistryIdArgumentSerializer.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2018-2023 Velocity Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.velocitypowered.proxy.protocol.packet.brigadier; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; + +public class RegistryIdArgumentSerializer implements ArgumentPropertySerializer { + + static final RegistryIdArgumentSerializer REGISTRY_ID = new RegistryIdArgumentSerializer(); + + @Override + public Integer deserialize(ByteBuf buf, ProtocolVersion protocolVersion) { + return ProtocolUtils.readVarInt(buf); + } + + @Override + public void serialize(Integer object, ByteBuf buf, ProtocolVersion protocolVersion) { + ProtocolUtils.writeVarInt(buf, object); + } +} 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 7f4df1be2..9a4fdc725 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 @@ -42,13 +42,19 @@ public class SessionChatBuilder extends ChatBuilderV2 { @Override public MinecraftPacket toServer() { if (message.startsWith("/")) { - SessionPlayerCommandPacket command = new SessionPlayerCommandPacket(); - command.command = message.substring(1); - command.salt = 0L; - command.timeStamp = timestamp; - command.argumentSignatures = new SessionPlayerCommandPacket.ArgumentSignatures(); - command.lastSeenMessages = new LastSeenMessages(); - return command; + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { + UnsignedPlayerCommandPacket command = new UnsignedPlayerCommandPacket(); + command.command = message.substring(1); + return command; + } else { + SessionPlayerCommandPacket command = new SessionPlayerCommandPacket(); + command.command = message.substring(1); + command.salt = 0L; + command.timeStamp = timestamp; + command.argumentSignatures = new SessionPlayerCommandPacket.ArgumentSignatures(); + command.lastSeenMessages = new LastSeenMessages(); + return command; + } } else { SessionPlayerChatPacket chat = new SessionPlayerChatPacket(); chat.message = message; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionCommandHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionCommandHandler.java index b9f2b238b..5048294ca 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionCommandHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionCommandHandler.java @@ -108,7 +108,7 @@ public class SessionCommandHandler implements CommandHandler. + */ + +package com.velocitypowered.proxy.protocol.packet.chat.session; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; + +public class UnsignedPlayerCommandPacket extends SessionPlayerCommandPacket { + + @Override + public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + this.command = ProtocolUtils.readString(buf, 256); + } + + @Override + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + ProtocolUtils.writeString(buf, this.command); + } + + public boolean isSigned() { + return false; + } + + @Override + public String toString() { + return "UnsignedPlayerCommandPacket{" + + "command='" + command + '\'' + + '}'; + } +} 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 c47d5d436..89760344a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/server/PingSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/server/PingSessionHandler.java @@ -17,6 +17,7 @@ package com.velocitypowered.proxy.server; +import com.velocitypowered.api.network.HandshakeIntent; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.ServerPing; @@ -54,7 +55,7 @@ public class PingSessionHandler implements MinecraftSessionHandler { @Override public void activated() { HandshakePacket handshake = new HandshakePacket(); - handshake.setNextStatus(StateRegistry.STATUS_ID); + handshake.setIntent(HandshakeIntent.STATUS); handshake.setServerAddress(server.getServerInfo().getAddress().getHostString()); handshake.setPort(server.getServerInfo().getAddress().getPort()); handshake.setProtocolVersion(version); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/VelocityProperties.java b/proxy/src/main/java/com/velocitypowered/proxy/util/VelocityProperties.java new file mode 100644 index 000000000..d2b304a86 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/VelocityProperties.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2024 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.util; + +import static java.util.Objects.requireNonNull; + +/** + * Utils for easy handling of properties. + * + * @since 3.3.0 + */ +public final class VelocityProperties { + /** + * Attempts to read a system property as boolean. + * + * @param property the system property to read + * @param defaultValue the default value + * @return the default value if the property is not set, + * if the string is present and is "true" (case-insensitive), + * it will return {@code true}, otherwise, it will return false. + * @since 3.3.0 + */ + public static boolean readBoolean(final String property, final boolean defaultValue) { + requireNonNull(property); + final String value = System.getProperty(property); + if (value == null) { + return defaultValue; + } + return Boolean.parseBoolean(value); + } + + /** + * Check if there is a value assigned to this system property. + * + * @param property the system property to check + * @return if a value is assigned to this system property + * @since 3.3.0 + */ + public static boolean hasProperty(final String property) { + requireNonNull(property); + + return System.getProperty(property) != null; + } +} diff --git a/proxy/src/main/resources/default-velocity.toml b/proxy/src/main/resources/default-velocity.toml index 0f18208b5..3f4f6318c 100644 --- a/proxy/src/main/resources/default-velocity.toml +++ b/proxy/src/main/resources/default-velocity.toml @@ -1,5 +1,5 @@ # Config version. Do not change this -config-version = "2.6" +config-version = "2.7" # What port should the proxy be bound to? By default, we'll bind to all addresses on port 25577. bind = "0.0.0.0:25577" @@ -141,6 +141,10 @@ log-command-executions = false # and disconnecting from the proxy. log-player-connections = true +# Allows players transferred from other hosts via the +# Transfer packet (Minecraft 1.20.5) to be received. +accepts-transfers = false + [query] # Whether to enable responding to GameSpy 4 query responses or not. enabled = false