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 d1764a5c6..93b1a3720 100644 --- a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java +++ b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java @@ -13,6 +13,8 @@ import java.util.Set; public enum ProtocolVersion { UNKNOWN(-1, "Unknown"), LEGACY(-2, "Legacy"), + MINECRAFT_1_7_2(4, "1.7.2"), + MINECRAFT_1_7_6(5, "1.7.6"), MINECRAFT_1_8(47, "1.8"), MINECRAFT_1_9(107, "1.9"), MINECRAFT_1_9_1(108, "1.9.1"), @@ -39,7 +41,7 @@ public enum ProtocolVersion { /** * Represents the lowest supported version. */ - public static final ProtocolVersion MINIMUM_VERSION = MINECRAFT_1_8; + public static final ProtocolVersion MINIMUM_VERSION = MINECRAFT_1_7_2; /** * Represents the highest supported version. */ 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 54fcc8d99..b2ef61409 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/Player.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/Player.java @@ -65,6 +65,13 @@ public interface Player extends CommandSource, InboundConnection, ChannelMessage */ long getPing(); + /** + * Returns the player's connection status. + * + * @return true if the player is authenticated with Mojang servers + */ + boolean isOnlineMode(); + /** * Sends a chat message to the player's client. * diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/ConnectionTypes.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/ConnectionTypes.java index baf97c90f..150ab816e 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/ConnectionTypes.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/ConnectionTypes.java @@ -3,6 +3,7 @@ package com.velocitypowered.proxy.connection; import com.velocitypowered.proxy.connection.backend.BackendConnectionPhases; import com.velocitypowered.proxy.connection.client.ClientConnectionPhases; import com.velocitypowered.proxy.connection.forge.legacy.LegacyForgeConnectionType; +import com.velocitypowered.proxy.connection.forge.legacy.LegacyForgeHandshakeClientPhase; import com.velocitypowered.proxy.connection.util.ConnectionTypeImpl; /** @@ -24,6 +25,9 @@ public final class ConnectionTypes { public static final ConnectionType VANILLA = new ConnectionTypeImpl(ClientConnectionPhases.VANILLA, BackendConnectionPhases.VANILLA); + public static final ConnectionType UNDETERMINED_17 = new ConnectionTypeImpl( + LegacyForgeHandshakeClientPhase.NOT_STARTED, BackendConnectionPhases.UNKNOWN); + /** * Indicates that the connection is a 1.8-1.12 Forge * connection. 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 b43fc0a2a..ba6678b78 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 @@ -102,8 +102,8 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { } if (PluginMessageUtil.isMcBrand(packet)) { - PluginMessage rewritten = PluginMessageUtil.rewriteMinecraftBrand(packet, - server.getVersion()); + PluginMessage rewritten = PluginMessageUtil.rewriteMinecraftBrand(packet, server.getVersion(), + playerConnection.getProtocolVersion()); playerConnection.write(rewritten); return true; } 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 2ae0d08a1..e0bf9dc38 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 @@ -1,6 +1,7 @@ package com.velocitypowered.proxy.connection.client; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_13; +import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8; import static com.velocitypowered.proxy.protocol.util.PluginMessageUtil.constructChannelsPacket; import com.velocitypowered.api.event.connection.PluginMessageEvent; @@ -177,7 +178,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { player.getKnownChannels().removeAll(PluginMessageUtil.getChannels(packet)); backendConn.write(packet.retain()); } else if (PluginMessageUtil.isMcBrand(packet)) { - backendConn.write(PluginMessageUtil.rewriteMinecraftBrand(packet, server.getVersion())); + backendConn.write(PluginMessageUtil + .rewriteMinecraftBrand(packet, server.getVersion(), player.getProtocolVersion())); } else { if (serverConn.getPhase() == BackendConnectionPhases.IN_TRANSITION) { // We must bypass the currently-connected server when forwarding Forge packets. @@ -347,8 +349,10 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { } // Clear any title from the previous server. - player.getConnection() - .delayedWrite(TitlePacket.resetForProtocolVersion(player.getProtocolVersion())); + if (player.getProtocolVersion().compareTo(MINECRAFT_1_8) >= 0) { + player.getConnection() + .delayedWrite(TitlePacket.resetForProtocolVersion(player.getProtocolVersion())); + } // Flush everything player.getConnection().flush(); 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 83c6c86a6..c012e8fcb 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 @@ -46,6 +46,7 @@ import com.velocitypowered.proxy.protocol.packet.TitlePacket; import com.velocitypowered.proxy.protocol.util.PluginMessageUtil; import com.velocitypowered.proxy.server.VelocityRegisteredServer; import com.velocitypowered.proxy.tablist.VelocityTabList; +import com.velocitypowered.proxy.tablist.VelocityTabListLegacy; import com.velocitypowered.proxy.util.VelocityMessages; import com.velocitypowered.proxy.util.collect.CappedSet; import io.netty.buffer.ByteBufUtil; @@ -89,6 +90,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { private PermissionFunction permissionFunction; private int tryIndex = 0; private long ping = -1; + private final boolean onlineMode; private @Nullable VelocityServerConnection connectedServer; private @Nullable VelocityServerConnection connectionInFlight; private @Nullable PlayerSettings settings; @@ -101,16 +103,21 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { private @MonotonicNonNull List serversToTry = null; - ConnectedPlayer(VelocityServer server, GameProfile profile, - MinecraftConnection connection, @Nullable InetSocketAddress virtualHost) { + ConnectedPlayer(VelocityServer server, GameProfile profile, MinecraftConnection connection, + @Nullable InetSocketAddress virtualHost, boolean onlineMode) { this.server = server; - this.tabList = new VelocityTabList(connection); + if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) { + this.tabList = new VelocityTabList(connection); + } else { + this.tabList = new VelocityTabListLegacy(connection); + } this.profile = profile; this.connection = connection; this.virtualHost = virtualHost; this.permissionFunction = PermissionFunction.ALWAYS_UNDEFINED; this.connectionPhase = connection.getType().getInitialClientPhase(); this.knownChannels = CappedSet.create(MAX_PLUGIN_CHANNELS); + this.onlineMode = onlineMode; } @Override @@ -146,6 +153,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { this.ping = ping; } + @Override + public boolean isOnlineMode() { + return onlineMode; + } + @Override public PlayerSettings getPlayerSettings() { return settings == null ? ClientSettingsWrapper.DEFAULT : this.settings; 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 fdc16fb87..1eb25a2ba 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 @@ -114,6 +114,10 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler { if (handshake.getServerAddress().endsWith(LegacyForgeConstants.HANDSHAKE_HOSTNAME_TOKEN) && handshake.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_13) < 0) { return ConnectionTypes.LEGACY_FORGE; + } else if (handshake.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_7_6) <= 0) { + // 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; } else { // For later: See if we can determine Forge 1.13+ here, else this will need to be UNDETERMINED // until later in the cycle (most likely determinable during the LOGIN phase) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java index 6e38f0518..05b032ce6 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java @@ -2,6 +2,7 @@ package com.velocitypowered.proxy.connection.client; import static com.google.common.net.UrlEscapers.urlFormParameterEscaper; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_13; +import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8; import static com.velocitypowered.proxy.VelocityServer.GSON; import static com.velocitypowered.proxy.connection.VelocityConstants.EMPTY_BYTE_ARRAY; import static com.velocitypowered.proxy.connection.VelocityConstants.VELOCITY_IP_FORWARDING_CHANNEL; @@ -231,7 +232,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler { // Initiate a regular connection and move over to it. ConnectedPlayer player = new ConnectedPlayer(server, profileEvent.getGameProfile(), mcConnection, - inbound.getVirtualHost().orElse(null)); + inbound.getVirtualHost().orElse(null), onlineMode); this.connectedPlayer = player; if (!server.canRegisterConnection(player)) { player.disconnect(VelocityMessages.ALREADY_CONNECTED); @@ -260,7 +261,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler { } int threshold = server.getConfiguration().getCompressionThreshold(); - if (threshold >= 0) { + if (threshold >= 0 && mcConnection.getProtocolVersion().compareTo(MINECRAFT_1_8) >= 0) { mcConnection.write(new SetCompression(threshold)); mcConnection.setCompressionThreshold(threshold); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeHandshakeBackendPhase.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeHandshakeBackendPhase.java index bf44814aa..c0da5ba99 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeHandshakeBackendPhase.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeHandshakeBackendPhase.java @@ -90,7 +90,7 @@ public enum LegacyForgeHandshakeBackendPhase implements BackendConnectionPhase { @Nullable private final Integer packetToAdvanceOn; /** - * Creates an instance of the {@link LegacyForgeHandshakeClientPhase}. + * Creates an instance of the {@link LegacyForgeHandshakeBackendPhase}. * * @param packetToAdvanceOn The ID of the packet discriminator that indicates * that the server has moved onto a new phase, and diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java index 907a8640f..555885575 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java @@ -3,11 +3,11 @@ package com.velocitypowered.proxy.protocol; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; +import com.google.common.base.Preconditions; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.util.GameProfile; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; - import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -194,12 +194,161 @@ public enum ProtocolUtils { return properties; } + private static final int FORGE_MAX_ARRAY_LENGTH = Integer.MAX_VALUE & 0x1FFF9A; + + /** + * Reads an byte array for legacy version 1.7 from the specified {@code buf} + * + * @param buf the buffer to read from + * @return the read byte array + */ + public static byte[] readByteArray17(ByteBuf buf) { + // Read in a 2 or 3 byte number that represents the length of the packet. (3 byte "shorts" for + // Forge only) + // No vanilla packet should give a 3 byte packet + int len = readExtendedForgeShort(buf); + + Preconditions.checkArgument(len <= (FORGE_MAX_ARRAY_LENGTH), + "Cannot receive array longer than %s (got %s bytes)", FORGE_MAX_ARRAY_LENGTH, len); + + byte[] ret = new byte[len]; + buf.readBytes(ret); + return ret; + } + + /** + * Reads a retained {@link ByteBuf} slice of the specified {@code buf} with the 1.7 style length. + * + * @param buf the buffer to read from + * @return the retained slice + */ + public static ByteBuf readRetainedByteBufSlice17(ByteBuf buf) { + // Read in a 2 or 3 byte number that represents the length of the packet. (3 byte "shorts" for + // Forge only) + // No vanilla packet should give a 3 byte packet + int len = readExtendedForgeShort(buf); + + Preconditions.checkArgument(len <= (FORGE_MAX_ARRAY_LENGTH), + "Cannot receive array longer than %s (got %s bytes)", FORGE_MAX_ARRAY_LENGTH, len); + + return buf.readRetainedSlice(len); + } + + /** + * Writes an byte array for legacy version 1.7 to the specified {@code buf} + * + * @param b array + * @param buf buf + * @param allowExtended forge + */ + public static void writeByteArray17(byte[] b, ByteBuf buf, boolean allowExtended) { + if (allowExtended) { + Preconditions + .checkArgument(b.length <= (FORGE_MAX_ARRAY_LENGTH), + "Cannot send array longer than %s (got %s bytes)", FORGE_MAX_ARRAY_LENGTH, + b.length); + } else { + Preconditions.checkArgument(b.length <= Short.MAX_VALUE, + "Cannot send array longer than Short.MAX_VALUE (got %s bytes)", b.length); + } + // Write a 2 or 3 byte number that represents the length of the packet. (3 byte "shorts" for + // Forge only) + // No vanilla packet should give a 3 byte packet, this method will still retain vanilla + // behaviour. + writeExtendedForgeShort(buf, b.length); + buf.writeBytes(b); + } + + /** + * Writes an {@link ByteBuf} for legacy version 1.7 to the specified {@code buf} + * + * @param b array + * @param buf buf + * @param allowExtended forge + */ + public static void writeByteBuf17(ByteBuf b, ByteBuf buf, boolean allowExtended) { + if (allowExtended) { + Preconditions + .checkArgument(b.readableBytes() <= (FORGE_MAX_ARRAY_LENGTH), + "Cannot send array longer than %s (got %s bytes)", FORGE_MAX_ARRAY_LENGTH, + b.readableBytes()); + } else { + Preconditions.checkArgument(b.readableBytes() <= Short.MAX_VALUE, + "Cannot send array longer than Short.MAX_VALUE (got %s bytes)", b.readableBytes()); + } + // Write a 2 or 3 byte number that represents the length of the packet. (3 byte "shorts" for + // Forge only) + // No vanilla packet should give a 3 byte packet, this method will still retain vanilla + // behaviour. + writeExtendedForgeShort(buf, b.readableBytes()); + buf.writeBytes(b); + } + + /** + * Reads a Minecraft-style extended short from the specified {@code buf}. + * + * @param buf buf to write + * @return read extended short + */ + public static int readExtendedForgeShort(ByteBuf buf) { + int low = buf.readUnsignedShort(); + int high = 0; + if ((low & 0x8000) != 0) { + low = low & 0x7FFF; + high = buf.readUnsignedByte(); + } + return ((high & 0xFF) << 15) | low; + } + + /** + * Writes a Minecraft-style extended short to the specified {@code buf}. + * + * @param buf buf to write + * @param toWrite the extended short to write + */ + public static void writeExtendedForgeShort(ByteBuf buf, int toWrite) { + int low = toWrite & 0x7FFF; + int high = (toWrite & 0x7F8000) >> 15; + if (high != 0) { + low = low | 0x8000; + } + buf.writeShort(low); + if (high != 0) { + buf.writeByte(high); + } + } + + /** + * Reads a non length-prefixed string from the {@code buf}. We need this for the legacy 1.7 + * version, being inconsistent when sending the brand. + * + * @param buf the buffer to read from + * @return the decoded string + */ + public static String readStringWithoutLength(ByteBuf buf) { + int length = buf.readableBytes(); + int cap = DEFAULT_MAX_STRING_SIZE; + checkArgument(length >= 0, "Got a negative-length string (%s)", length); + // `cap` is interpreted as a UTF-8 character length. To cover the full Unicode plane, we must + // consider the length of a UTF-8 character, which can be up to a 4 bytes. We do an initial + // sanity check and then check again to make sure our optimistic guess was good. + checkArgument(length <= cap * 4, "Bad string size (got %s, maximum is %s)", length, cap); + checkState(buf.isReadable(length), + "Trying to read a string that is too long (wanted %s, only have %s)", length, + buf.readableBytes()); + String str = buf.toString(buf.readerIndex(), length, StandardCharsets.UTF_8); + buf.skipBytes(length); + checkState(str.length() <= cap, "Got a too-long string (got %s, max %s)", + str.length(), cap); + return str; + } + public enum Direction { SERVERBOUND, CLIENTBOUND; public StateRegistry.PacketRegistry.ProtocolRegistry getProtocolRegistry(StateRegistry state, - ProtocolVersion version) { + ProtocolVersion version) { return (this == SERVERBOUND ? state.serverbound : state.clientbound) .getProtocolRegistry(version); } 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 fd5c268b2..4444722cb 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -5,6 +5,7 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_12; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_12_1; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_13; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_14; +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; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9_4; @@ -44,14 +45,12 @@ import io.netty.util.collection.IntObjectHashMap; import io.netty.util.collection.IntObjectMap; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; - import java.util.Collections; import java.util.EnumMap; import java.util.EnumSet; import java.util.Map; import java.util.Objects; import java.util.function.Supplier; - import org.checkerframework.checker.nullness.qual.Nullable; public enum StateRegistry { @@ -59,20 +58,20 @@ public enum StateRegistry { HANDSHAKE { { serverbound.register(Handshake.class, Handshake::new, - map(0x00, MINECRAFT_1_8, false)); + map(0x00, MINECRAFT_1_7_2, false)); } }, STATUS { { serverbound.register(StatusRequest.class, () -> StatusRequest.INSTANCE, - map(0x00, MINECRAFT_1_8, false)); + map(0x00, MINECRAFT_1_7_2, false)); serverbound.register(StatusPing.class, StatusPing::new, - map(0x01, MINECRAFT_1_8, false)); + map(0x01, MINECRAFT_1_7_2, false)); clientbound.register(StatusResponse.class, StatusResponse::new, - map(0x00, MINECRAFT_1_8, false)); + map(0x00, MINECRAFT_1_7_2, false)); clientbound.register(StatusPing.class, StatusPing::new, - map(0x01, MINECRAFT_1_8, false)); + map(0x01, MINECRAFT_1_7_2, false)); } }, PLAY { @@ -81,33 +80,33 @@ public enum StateRegistry { clientbound.fallback = false; serverbound.register(TabCompleteRequest.class, TabCompleteRequest::new, - map(0x14, MINECRAFT_1_8, false), + map(0x14, MINECRAFT_1_7_2, false), map(0x01, MINECRAFT_1_9, false), map(0x02, MINECRAFT_1_12, false), map(0x01, MINECRAFT_1_12_1, false), map(0x05, MINECRAFT_1_13, false), map(0x06, MINECRAFT_1_14, false)); serverbound.register(Chat.class, Chat::new, - map(0x01, MINECRAFT_1_8, false), + map(0x01, MINECRAFT_1_7_2, false), map(0x02, MINECRAFT_1_9, false), map(0x03, MINECRAFT_1_12, false), map(0x02, MINECRAFT_1_12_1, false), map(0x03, MINECRAFT_1_14, false)); serverbound.register(ClientSettings.class, ClientSettings::new, - map(0x15, MINECRAFT_1_8, false), + map(0x15, MINECRAFT_1_7_2, false), map(0x04, MINECRAFT_1_9, false), map(0x05, MINECRAFT_1_12, false), map(0x04, MINECRAFT_1_12_1, false), map(0x05, MINECRAFT_1_14, false)); serverbound.register(PluginMessage.class, PluginMessage::new, - map(0x17, MINECRAFT_1_8, false), + map(0x17, MINECRAFT_1_7_2, false), map(0x09, MINECRAFT_1_9, false), map(0x0A, MINECRAFT_1_12, false), map(0x09, MINECRAFT_1_12_1, false), map(0x0A, MINECRAFT_1_13, false), map(0x0B, MINECRAFT_1_14, false)); serverbound.register(KeepAlive.class, KeepAlive::new, - map(0x00, MINECRAFT_1_8, false), + map(0x00, MINECRAFT_1_7_2, false), map(0x0B, MINECRAFT_1_9, false), map(0x0C, MINECRAFT_1_12, false), map(0x0B, MINECRAFT_1_12_1, false), @@ -123,37 +122,37 @@ public enum StateRegistry { clientbound.register(BossBar.class, BossBar::new, map(0x0C, MINECRAFT_1_9, false)); clientbound.register(Chat.class, Chat::new, - map(0x02, MINECRAFT_1_8, true), + map(0x02, MINECRAFT_1_7_2, true), map(0x0F, MINECRAFT_1_9, true), map(0x0E, MINECRAFT_1_13, true)); clientbound.register(TabCompleteResponse.class, TabCompleteResponse::new, - map(0x3A, MINECRAFT_1_8, false), + map(0x3A, MINECRAFT_1_7_2, false), map(0x0E, MINECRAFT_1_9, false), map(0x10, MINECRAFT_1_13, false)); clientbound.register(AvailableCommands.class, AvailableCommands::new, map(0x11, MINECRAFT_1_13, false)); clientbound.register(PluginMessage.class, PluginMessage::new, - map(0x3F, MINECRAFT_1_8, false), + map(0x3F, MINECRAFT_1_7_2, false), map(0x18, MINECRAFT_1_9, false), map(0x19, MINECRAFT_1_13, false), map(0x18, MINECRAFT_1_14, false)); clientbound.register(Disconnect.class, Disconnect::new, - map(0x40, MINECRAFT_1_8, false), + map(0x40, MINECRAFT_1_7_2, false), map(0x1A, MINECRAFT_1_9, false), map(0x1B, MINECRAFT_1_13, false), map(0x1A, MINECRAFT_1_14, false)); clientbound.register(KeepAlive.class, KeepAlive::new, - map(0x00, MINECRAFT_1_8, false), + map(0x00, MINECRAFT_1_7_2, false), map(0x1F, MINECRAFT_1_9, false), map(0x21, MINECRAFT_1_13, false), map(0x20, MINECRAFT_1_14, false)); clientbound.register(JoinGame.class, JoinGame::new, - map(0x01, MINECRAFT_1_8, false), + map(0x01, MINECRAFT_1_7_2, false), map(0x23, MINECRAFT_1_9, false), map(0x25, MINECRAFT_1_13, false), map(0x25, MINECRAFT_1_14, false)); clientbound.register(Respawn.class, Respawn::new, - map(0x07, MINECRAFT_1_8, true), + map(0x07, MINECRAFT_1_7_2, true), map(0x33, MINECRAFT_1_9, true), map(0x34, MINECRAFT_1_12, true), map(0x35, MINECRAFT_1_12_1, true), @@ -182,7 +181,7 @@ public enum StateRegistry { map(0x4B, MINECRAFT_1_13, true), map(0x4F, MINECRAFT_1_14, true)); clientbound.register(PlayerListItem.class, PlayerListItem::new, - map(0x38, MINECRAFT_1_8, false), + map(0x38, MINECRAFT_1_7_2, false), map(0x2D, MINECRAFT_1_9, false), map(0x2E, MINECRAFT_1_12_1, false), map(0x30, MINECRAFT_1_13, false), @@ -192,17 +191,17 @@ public enum StateRegistry { LOGIN { { serverbound.register(ServerLogin.class, ServerLogin::new, - map(0x00, MINECRAFT_1_8, false)); + map(0x00, MINECRAFT_1_7_2, false)); serverbound.register(EncryptionResponse.class, EncryptionResponse::new, - map(0x01, MINECRAFT_1_8, false)); + map(0x01, MINECRAFT_1_7_2, false)); serverbound.register(LoginPluginResponse.class, LoginPluginResponse::new, map(0x02, MINECRAFT_1_13, false)); clientbound.register(Disconnect.class, Disconnect::new, - map(0x00, MINECRAFT_1_8, false)); + map(0x00, MINECRAFT_1_7_2, false)); clientbound.register(EncryptionRequest.class, EncryptionRequest::new, - map(0x01, MINECRAFT_1_8, false)); + map(0x01, MINECRAFT_1_7_2, false)); clientbound.register(ServerLoginSuccess.class, ServerLoginSuccess::new, - map(0x02, MINECRAFT_1_8, false)); + map(0x02, MINECRAFT_1_7_2, false)); clientbound.register(SetCompression.class, SetCompression::new, map(0x03, MINECRAFT_1_8, false)); clientbound.register(LoginPluginMessage.class, LoginPluginMessage::new, @@ -246,7 +245,7 @@ public enum StateRegistry { }

void register(Class

clazz, Supplier

packetSupplier, - PacketMapping... mappings) { + PacketMapping... mappings) { if (mappings.length == 0) { throw new IllegalArgumentException("At least one mapping must be provided."); } @@ -382,8 +381,8 @@ public enum StateRegistry { /** * Creates a PacketMapping using the provided arguments. * - * @param id Packet Id - * @param version Protocol version + * @param id Packet Id + * @param version Protocol version * @param encodeOnly When true packet decoding will be disabled * @return PacketMapping with the provided arguments */ diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Chat.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Chat.java index c4761a667..2321b518f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Chat.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Chat.java @@ -56,7 +56,7 @@ public class Chat implements MinecraftPacket { @Override public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { message = ProtocolUtils.readString(buf); - if (direction == ProtocolUtils.Direction.CLIENTBOUND) { + if (direction == ProtocolUtils.Direction.CLIENTBOUND && version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) { type = buf.readByte(); } } @@ -67,7 +67,7 @@ public class Chat implements MinecraftPacket { throw new IllegalStateException("Message is not specified"); } ProtocolUtils.writeString(buf, message); - if (direction == ProtocolUtils.Direction.CLIENTBOUND) { + if (direction == ProtocolUtils.Direction.CLIENTBOUND && version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) { buf.writeByte(type); } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientSettings.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientSettings.java index 80db8e997..361b1178e 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientSettings.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientSettings.java @@ -13,6 +13,7 @@ public class ClientSettings implements MinecraftPacket { private byte viewDistance; private int chatVisibility; private boolean chatColors; + private byte difficulty; // 1.7 Protocol private short skinParts; private int mainHand; @@ -98,6 +99,11 @@ public class ClientSettings implements MinecraftPacket { this.viewDistance = buf.readByte(); this.chatVisibility = ProtocolUtils.readVarInt(buf); this.chatColors = buf.readBoolean(); + + if (version.compareTo(ProtocolVersion.MINECRAFT_1_7_6) <= 0) { + this.difficulty = buf.readByte(); + } + this.skinParts = buf.readUnsignedByte(); if (version.compareTo(ProtocolVersion.MINECRAFT_1_9) >= 0) { @@ -114,6 +120,11 @@ public class ClientSettings implements MinecraftPacket { buf.writeByte(viewDistance); ProtocolUtils.writeVarInt(buf, chatVisibility); buf.writeBoolean(chatColors); + + if (version.compareTo(ProtocolVersion.MINECRAFT_1_7_6) <= 0) { + buf.writeByte(difficulty); + } + buf.writeByte(skinParts); if (version.compareTo(ProtocolVersion.MINECRAFT_1_9) >= 0) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/EncryptionRequest.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/EncryptionRequest.java index a238226a2..a60165b6e 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/EncryptionRequest.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/EncryptionRequest.java @@ -42,15 +42,27 @@ public class EncryptionRequest implements MinecraftPacket { @Override public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { this.serverId = ProtocolUtils.readString(buf, 20); - publicKey = ProtocolUtils.readByteArray(buf, 256); - verifyToken = ProtocolUtils.readByteArray(buf, 16); + + if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) { + publicKey = ProtocolUtils.readByteArray(buf, 256); + verifyToken = ProtocolUtils.readByteArray(buf, 16); + } 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); - ProtocolUtils.writeByteArray(buf, publicKey); - ProtocolUtils.writeByteArray(buf, verifyToken); + + if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) { + ProtocolUtils.writeByteArray(buf, publicKey); + ProtocolUtils.writeByteArray(buf, verifyToken); + } else { + ProtocolUtils.writeByteArray17(publicKey, buf, false); + ProtocolUtils.writeByteArray17(verifyToken, buf, false); + } } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/EncryptionResponse.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/EncryptionResponse.java index 7d64ca4ca..04316a8f6 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/EncryptionResponse.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/EncryptionResponse.java @@ -40,14 +40,24 @@ public class EncryptionResponse implements MinecraftPacket { @Override public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - this.sharedSecret = ProtocolUtils.readByteArray(buf, 256); - this.verifyToken = ProtocolUtils.readByteArray(buf, 128); + if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) { + this.sharedSecret = ProtocolUtils.readByteArray(buf, 256); + this.verifyToken = ProtocolUtils.readByteArray(buf, 128); + } else { + this.sharedSecret = ProtocolUtils.readByteArray17(buf); + this.verifyToken = ProtocolUtils.readByteArray17(buf); + } } @Override public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - ProtocolUtils.writeByteArray(buf, sharedSecret); - ProtocolUtils.writeByteArray(buf, verifyToken); + if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) { + ProtocolUtils.writeByteArray(buf, sharedSecret); + ProtocolUtils.writeByteArray(buf, verifyToken); + } else { + ProtocolUtils.writeByteArray17(sharedSecret, buf, false); + ProtocolUtils.writeByteArray17(verifyToken, buf, false); + } } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGame.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGame.java index af5eb6774..0b542803f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGame.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGame.java @@ -116,7 +116,9 @@ public class JoinGame implements MinecraftPacket { if (version.compareTo(ProtocolVersion.MINECRAFT_1_14) >= 0) { this.viewDistance = ProtocolUtils.readVarInt(buf); } - this.reducedDebugInfo = buf.readBoolean(); + if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) { + this.reducedDebugInfo = buf.readBoolean(); + } } @Override @@ -137,9 +139,11 @@ public class JoinGame implements MinecraftPacket { } ProtocolUtils.writeString(buf, levelType); if (version.compareTo(ProtocolVersion.MINECRAFT_1_14) >= 0) { - ProtocolUtils.writeVarInt(buf,viewDistance); + ProtocolUtils.writeVarInt(buf, viewDistance); + } + if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) { + buf.writeBoolean(reducedDebugInfo); } - buf.writeBoolean(reducedDebugInfo); } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/KeepAlive.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/KeepAlive.java index ac6810a65..32febe2b6 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/KeepAlive.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/KeepAlive.java @@ -29,8 +29,10 @@ public class KeepAlive implements MinecraftPacket { public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_12_2) >= 0) { randomId = buf.readLong(); - } else { + } else if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) { randomId = ProtocolUtils.readVarInt(buf); + } else { + randomId = buf.readInt(); } } @@ -38,8 +40,10 @@ public class KeepAlive implements MinecraftPacket { public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_12_2) >= 0) { buf.writeLong(randomId); - } else { + } else if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) { ProtocolUtils.writeVarInt(buf, (int) randomId); + } else { + buf.writeInt((int) randomId); } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/PlayerListItem.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/PlayerListItem.java index 4c31d92a4..1037ac765 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/PlayerListItem.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/PlayerListItem.java @@ -13,6 +13,7 @@ import java.util.List; import java.util.UUID; import net.kyori.text.Component; import net.kyori.text.serializer.gson.GsonComponentSerializer; +import net.kyori.text.serializer.legacy.LegacyComponentSerializer; import org.checkerframework.checker.nullness.qual.Nullable; public class PlayerListItem implements MinecraftPacket { @@ -43,35 +44,43 @@ public class PlayerListItem implements MinecraftPacket { @Override public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - action = ProtocolUtils.readVarInt(buf); - int length = ProtocolUtils.readVarInt(buf); + if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) { + 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)); - 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)); - break; - case REMOVE_PLAYER: - //Do nothing, all that is needed is the uuid - break; - default: - throw new UnsupportedOperationException("Unknown action " + action); + 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)); + 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)); + 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); } } @@ -84,34 +93,47 @@ public class PlayerListItem implements MinecraftPacket { @Override public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - ProtocolUtils.writeVarInt(buf, action); - ProtocolUtils.writeVarInt(buf, items.size()); - for (Item item : items) { - ProtocolUtils.writeUuid(buf, item.getUuid()); - 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()); + if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) { + ProtocolUtils.writeVarInt(buf, action); + ProtocolUtils.writeVarInt(buf, items.size()); + for (Item item : items) { + ProtocolUtils.writeUuid(buf, item.getUuid()); + 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()); - 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()); - break; - case REMOVE_PLAYER: - //Do nothing, all that is needed is the uuid - break; - default: - throw new UnsupportedOperationException("Unknown action " + action); + writeDisplayName(buf, item.getDisplayName()); + 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()); + 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); + if (item.getDisplayName() != null) { + String displayName = LegacyComponentSerializer.legacy().serialize(item.getDisplayName()); + 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()); } } @@ -136,6 +158,10 @@ public class PlayerListItem implements MinecraftPacket { private int latency; private @Nullable Component displayName; + public Item() { + uuid = null; + } + public Item(UUID uuid) { this.uuid = uuid; } @@ -149,7 +175,7 @@ public class PlayerListItem implements MinecraftPacket { .setDisplayName(entry.getDisplayName().orElse(null)); } - public UUID getUuid() { + public @Nullable UUID getUuid() { return uuid; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/PluginMessage.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/PluginMessage.java index e0269f77a..5b9b3f136 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/PluginMessage.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/PluginMessage.java @@ -50,7 +50,12 @@ public class PluginMessage extends DeferredByteBufHolder implements MinecraftPac if (version.compareTo(ProtocolVersion.MINECRAFT_1_13) >= 0) { this.channel = transformLegacyToModernChannel(this.channel); } - this.replace(buf.readRetainedSlice(buf.readableBytes())); + if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) { + this.replace(buf.readRetainedSlice(buf.readableBytes())); + } else { + this.replace(ProtocolUtils.readRetainedByteBufSlice17(buf)); + } + } @Override @@ -63,7 +68,12 @@ public class PluginMessage extends DeferredByteBufHolder implements MinecraftPac } else { ProtocolUtils.writeString(buf, this.channel); } - buf.writeBytes(content()); + if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) { + buf.writeBytes(content()); + } else { + ProtocolUtils.writeByteBuf17(content(), buf, true); // True for Forge support + } + } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLoginSuccess.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLoginSuccess.java index 0d068bd7a..76b599466 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLoginSuccess.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLoginSuccess.java @@ -1,6 +1,7 @@ package com.velocitypowered.proxy.protocol.packet; import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.api.util.UuidUtils; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolUtils; @@ -45,7 +46,11 @@ public class ServerLoginSuccess implements MinecraftPacket { @Override public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - uuid = UUID.fromString(ProtocolUtils.readString(buf, 36)); + if (version.compareTo(ProtocolVersion.MINECRAFT_1_7_6) >= 0) { + uuid = UUID.fromString(ProtocolUtils.readString(buf, 36)); + } else { + uuid = UuidUtils.fromUndashed(ProtocolUtils.readString(buf, 32)); + } username = ProtocolUtils.readString(buf, 16); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteRequest.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteRequest.java index 27afacaf9..e382861fe 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteRequest.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteRequest.java @@ -1,6 +1,7 @@ package com.velocitypowered.proxy.protocol.packet; 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; @@ -83,9 +84,11 @@ public class TabCompleteRequest implements MinecraftPacket { if (version.compareTo(MINECRAFT_1_9) >= 0) { this.assumeCommand = buf.readBoolean(); } - this.hasPosition = buf.readBoolean(); - if (hasPosition) { - this.position = buf.readLong(); + if (version.compareTo(MINECRAFT_1_8) >= 0) { + this.hasPosition = buf.readBoolean(); + if (hasPosition) { + this.position = buf.readLong(); + } } } } @@ -104,9 +107,11 @@ public class TabCompleteRequest implements MinecraftPacket { if (version.compareTo(MINECRAFT_1_9) >= 0) { buf.writeBoolean(assumeCommand); } - buf.writeBoolean(hasPosition); - if (hasPosition) { - buf.writeLong(position); + if (version.compareTo(MINECRAFT_1_8) >= 0) { + buf.writeBoolean(hasPosition); + if (hasPosition) { + buf.writeLong(position); + } } } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/PluginMessageUtil.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/PluginMessageUtil.java index c659826b7..8dea2e01d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/PluginMessageUtil.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/PluginMessageUtil.java @@ -11,9 +11,7 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.packet.PluginMessage; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; -import io.netty.util.ByteProcessor; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Locale; @@ -127,7 +125,8 @@ public class PluginMessageUtil { * @param version the proxy version * @return the rewritten plugin message */ - public static PluginMessage rewriteMinecraftBrand(PluginMessage message, ProxyVersion version) { + public static PluginMessage rewriteMinecraftBrand(PluginMessage message, ProxyVersion version, + ProtocolVersion protocolVersion) { checkNotNull(message, "message"); checkNotNull(version, "version"); checkArgument(isMcBrand(message), "message is not a brand plugin message"); @@ -135,8 +134,14 @@ public class PluginMessageUtil { String toAppend = " (" + version.getName() + ")"; ByteBuf rewrittenBuf = Unpooled.buffer(); - String currentBrand = ProtocolUtils.readString(message.content().slice()); - ProtocolUtils.writeString(rewrittenBuf, currentBrand + toAppend); + + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) { + String currentBrand = ProtocolUtils.readString(message.content().slice()); + ProtocolUtils.writeString(rewrittenBuf, currentBrand + toAppend); + } else { + String currentBrand = ProtocolUtils.readStringWithoutLength(message.content().slice()); + rewrittenBuf.writeBytes((currentBrand + toAppend).getBytes()); + } return new PluginMessage(message.getChannel(), rewrittenBuf); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabList.java b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabList.java index aacf26012..09d8c01d5 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabList.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabList.java @@ -7,6 +7,7 @@ import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter; import com.velocitypowered.proxy.protocol.packet.PlayerListItem; +import com.velocitypowered.proxy.protocol.packet.PlayerListItem.Item; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -20,8 +21,8 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class VelocityTabList implements TabList { - private final MinecraftConnection connection; - private final Map entries = new ConcurrentHashMap<>(); + protected final MinecraftConnection connection; + protected final Map entries = new ConcurrentHashMap<>(); public VelocityTabList(MinecraftConnection connection) { this.connection = connection; @@ -76,9 +77,9 @@ public class VelocityTabList implements TabList { } /** - * Clears all entries from the tab list. Note that the entries are written with - * {@link MinecraftConnection#delayedWrite(Object)}, so make sure to do an explicit - * {@link MinecraftConnection#flush()}. + * Clears all entries from the tab list. Note that the entries are written with {@link + * MinecraftConnection#delayedWrite(Object)}, so make sure to do an explicit {@link + * MinecraftConnection#flush()}. */ public void clearAll() { List items = new ArrayList<>(); @@ -86,7 +87,9 @@ public class VelocityTabList implements TabList { items.add(PlayerListItem.Item.from(value)); } entries.clear(); - connection.delayedWrite(new PlayerListItem(PlayerListItem.REMOVE_PLAYER, items)); + if (!items.isEmpty()) { + connection.delayedWrite(new PlayerListItem(PlayerListItem.REMOVE_PLAYER, items)); + } } @Override @@ -102,12 +105,14 @@ public class VelocityTabList implements TabList { /** * Processes a tab list entry packet from the backend. + * * @param packet the packet to process */ public void processBackendPacket(PlayerListItem packet) { // Packets are already forwarded on, so no need to do that here for (PlayerListItem.Item item : packet.getItems()) { UUID uuid = item.getUuid(); + if (packet.getAction() != PlayerListItem.ADD_PLAYER && !entries.containsKey(uuid)) { // Sometimes UPDATE_GAMEMODE is sent before ADD_PLAYER so don't want to warn here continue; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListEntryLegacy.java b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListEntryLegacy.java new file mode 100644 index 000000000..7ef7b5dd7 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListEntryLegacy.java @@ -0,0 +1,20 @@ +package com.velocitypowered.proxy.tablist; + +import com.velocitypowered.api.proxy.player.TabListEntry; +import com.velocitypowered.api.util.GameProfile; +import net.kyori.text.Component; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class VelocityTabListEntryLegacy extends VelocityTabListEntry { + + VelocityTabListEntryLegacy(VelocityTabListLegacy tabList, GameProfile profile, + @Nullable Component displayName, int latency, int gameMode) { + super(tabList, profile, displayName, latency, gameMode); + } + + @Override + public TabListEntry setDisplayName(@Nullable Component displayName) { + getTabList().removeEntry(getProfile().getId()); // We have to remove first if updating + return super.setDisplayName(displayName); + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListLegacy.java b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListLegacy.java new file mode 100644 index 000000000..b1e5c2610 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListLegacy.java @@ -0,0 +1,119 @@ +package com.velocitypowered.proxy.tablist; + +import com.google.common.collect.ImmutableList; +import com.velocitypowered.api.proxy.player.TabListEntry; +import com.velocitypowered.api.util.GameProfile; +import com.velocitypowered.proxy.connection.MinecraftConnection; +import com.velocitypowered.proxy.protocol.packet.PlayerListItem; +import com.velocitypowered.proxy.protocol.packet.PlayerListItem.Item; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import net.kyori.text.Component; +import net.kyori.text.serializer.legacy.LegacyComponentSerializer; +import net.kyori.text.serializer.plain.PlainComponentSerializer; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class VelocityTabListLegacy extends VelocityTabList { + + private final Map nameMapping = new ConcurrentHashMap<>(); + + public VelocityTabListLegacy(MinecraftConnection connection) { + super(connection); + } + + @Override + public void setHeaderAndFooter(Component header, Component footer) { + } + + @Override + public void clearHeaderAndFooter() { + } + + @Override + public void addEntry(TabListEntry entry) { + super.addEntry(entry); + nameMapping.put(entry.getProfile().getName(), entry.getProfile().getId()); + } + + @Override + public Optional removeEntry(UUID uuid) { + Optional entry = super.removeEntry(uuid); + entry.map(TabListEntry::getProfile).map(GameProfile::getName).ifPresent(nameMapping::remove); + return entry; + } + + @Override + public void clearAll() { + for (TabListEntry value : entries.values()) { + connection.delayedWrite(new PlayerListItem(PlayerListItem.REMOVE_PLAYER, + Collections.singletonList(PlayerListItem.Item.from(value)))); + } + entries.clear(); + nameMapping.clear(); + } + + @Override + public void processBackendPacket(PlayerListItem packet) { + + Item item = packet.getItems().get(0); // Only one item per packet in 1.7 + + Component displayName = LegacyComponentSerializer.legacy().deserialize(item.getName()); + String strippedName = PlainComponentSerializer.INSTANCE.serialize(displayName); + + switch (packet.getAction()) { + case PlayerListItem.ADD_PLAYER: + if (nameMapping.containsKey(strippedName)) { // ADD_PLAYER also used for updating ping + VelocityTabListEntry entry = entries.get(nameMapping.get(strippedName)); + if (entry != null) { + entry.setLatency(item.getLatency()); + } + } else { + UUID uuid = UUID.randomUUID(); // Use a fake uuid to preserve function of custom entries + nameMapping.put(strippedName, uuid); + entries.put(uuid, (VelocityTabListEntry) TabListEntry.builder() + .tabList(this) + .profile(new GameProfile(uuid, strippedName, ImmutableList.of())) + .displayName(displayName) + .latency(item.getLatency()) + .build()); + } + break; + case PlayerListItem.REMOVE_PLAYER: + UUID removedUuid = nameMapping.remove(strippedName); + if (removedUuid != null) { + entries.remove(removedUuid); + } + break; + default: + // For 1.7 there is only add and remove + break; + } + + } + + @Override + void updateEntry(int action, TabListEntry entry) { + if (entries.containsKey(entry.getProfile().getId())) { + switch (action) { + case PlayerListItem.UPDATE_LATENCY: + case PlayerListItem.UPDATE_DISPLAY_NAME: // Add here because we removed beforehand + connection + .write(new PlayerListItem(PlayerListItem.ADD_PLAYER, // ADD_PLAYER also updates ping + Collections.singletonList(PlayerListItem.Item.from(entry)))); + break; + default: + // Can't do anything else + break; + } + } + } + + @Override + public TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency, + int gameMode) { + return new VelocityTabListEntryLegacy(this, profile, displayName, latency, gameMode); + } +}