diff --git a/README.md b/README.md index c83e79669..4b35a9a02 100644 --- a/README.md +++ b/README.md @@ -34,15 +34,4 @@ Once you've built Velocity, you can copy and run the `-all` JAR from and you can configure it from there. Alternatively, you can get the proxy JAR from the [downloads](https://www.velocitypowered.com/downloads) -page. - -## Status - -Velocity is currently in beta. Production networks are successfully running -Velocity with many hundreds of concurrent players online, but your mileage -may vary. - -Velocity supports Minecraft 1.8-1.14.4. Velocity is best supported with Paper -and SpongeVanilla. Minecraft Forge is fully supported but mod compatibility -may vary. Generally, Velocity will support many mods better than BungeeCord -or Waterfall do but compatibility can not always be ensured. +page. \ No newline at end of file diff --git a/api/build.gradle b/api/build.gradle index 2a43ac049..a9e466865 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -24,7 +24,7 @@ dependencies { compile "net.kyori:text-serializer-plain:${textVersion}" compile 'com.moandjiezana.toml:toml4j:0.7.2' compile "org.slf4j:slf4j-api:${slf4jVersion}" - compile 'com.google.inject:guice:4.2.0' + compile 'com.google.inject:guice:4.2.2' compile "org.checkerframework:checker-qual:${checkerFrameworkVersion}" compile "org.spongepowered:configurate-hocon:${configurateVersion}" 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/build.gradle b/build.gradle index 5add3e122..8d9b45507 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,7 @@ allprojects { ext { // dependency versions - textVersion = '3.0.1' + textVersion = '3.0.2' junitVersion = '5.3.0-M1' slf4jVersion = '1.7.25' log4jVersion = '2.11.2' diff --git a/native/src/main/java/com/velocitypowered/natives/encryption/JavaVelocityCipher.java b/native/src/main/java/com/velocitypowered/natives/encryption/JavaVelocityCipher.java index c8c15686a..5382da898 100644 --- a/native/src/main/java/com/velocitypowered/natives/encryption/JavaVelocityCipher.java +++ b/native/src/main/java/com/velocitypowered/natives/encryption/JavaVelocityCipher.java @@ -2,9 +2,8 @@ package com.velocitypowered.natives.encryption; import com.google.common.base.Preconditions; import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; +import io.netty.buffer.ByteBufUtil; import io.netty.channel.ChannelHandlerContext; -import io.netty.util.concurrent.FastThreadLocal; import java.security.GeneralSecurityException; import javax.crypto.Cipher; import javax.crypto.SecretKey; @@ -24,13 +23,6 @@ public class JavaVelocityCipher implements VelocityCipher { return new JavaVelocityCipher(false, key); } }; - private static final int INITIAL_BUFFER_SIZE = 1024 * 8; - private static final FastThreadLocal inBufLocal = new FastThreadLocal() { - @Override - protected byte[] initialValue() { - return new byte[INITIAL_BUFFER_SIZE]; - } - }; private final Cipher cipher; private boolean disposed = false; @@ -46,20 +38,12 @@ public class JavaVelocityCipher implements VelocityCipher { ensureNotDisposed(); int inBytes = source.readableBytes(); - ByteBuf asHeapBuf = asHeapBuf(source); + byte[] asBytes = ByteBufUtil.getBytes(source); int outputSize = cipher.getOutputSize(inBytes); - if (!destination.hasArray()) { - byte[] outBuf = new byte[outputSize]; - cipher.update(asHeapBuf.array(), asHeapBuf.arrayOffset(), inBytes, outBuf); - destination.writeBytes(outBuf); - } else { - // If the destination we write to is an array, we can use the backing array directly. - destination.ensureWritable(outputSize); - int produced = cipher.update(asHeapBuf.array(), asHeapBuf.arrayOffset(), inBytes, - destination.array(), destination.arrayOffset()); - destination.writerIndex(destination.writerIndex() + produced); - } + byte[] outBuf = new byte[outputSize]; + cipher.update(asBytes, 0, inBytes, outBuf); + destination.writeBytes(outBuf); } @Override @@ -67,28 +51,31 @@ public class JavaVelocityCipher implements VelocityCipher { ensureNotDisposed(); int inBytes = source.readableBytes(); - ByteBuf asHeapBuf = asHeapBuf(source); - + ByteBuf asHeapBuf = toHeap(source); ByteBuf out = ctx.alloc().heapBuffer(cipher.getOutputSize(inBytes)); - out.writerIndex(cipher.update(asHeapBuf.array(), asHeapBuf.arrayOffset(), inBytes, out.array(), - out.arrayOffset())); - return out; + try { + out.writerIndex( + cipher.update(asHeapBuf.array(), asHeapBuf.arrayOffset() + asHeapBuf.readerIndex(), + inBytes, out.array(), out.arrayOffset() + out.writerIndex())); + return out; + } catch (ShortBufferException e) { + out.release(); + throw e; + } finally { + asHeapBuf.release(); + } } - private static ByteBuf asHeapBuf(ByteBuf source) { - if (source.hasArray()) { - // If this byte buffer is backed by an array, we can just use this buffer directly. - return source; + private static ByteBuf toHeap(ByteBuf src) { + if (src.hasArray()) { + return src.retain(); } - int inBytes = source.readableBytes(); - byte[] inBuf = inBufLocal.get(); - if (inBuf.length <= inBytes) { - inBuf = new byte[inBytes]; - inBufLocal.set(inBuf); - } - source.readBytes(inBuf, 0, inBytes); - return Unpooled.wrappedBuffer(inBuf, 0, inBytes); + // Copy into a temporary heap buffer. We could use a local buffer, but Netty pools all buffers, + // so we'd lose more than we gain. + ByteBuf asHeapBuf = src.alloc().heapBuffer(src.readableBytes()); + asHeapBuf.writeBytes(src); + return asHeapBuf; } @Override diff --git a/native/src/main/java/com/velocitypowered/natives/util/NativeConstants.java b/native/src/main/java/com/velocitypowered/natives/util/NativeConstants.java deleted file mode 100644 index f96656125..000000000 --- a/native/src/main/java/com/velocitypowered/natives/util/NativeConstants.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.velocitypowered.natives.util; - -public class NativeConstants { - /** - * The default preferred output buffer size for zlib. - */ - public static final int ZLIB_BUFFER_SIZE = 8192; - - private NativeConstants() { - throw new AssertionError(); - } -} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index 5b032e180..2f260cce6 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -505,7 +505,7 @@ public class VelocityServer implements ProxyServer { Preconditions.checkNotNull(component, "component"); Chat chat = Chat.createClientbound(component); for (ConnectedPlayer player : connectionsByUuid.values()) { - player.getMinecraftConnection().write(chat); + player.getConnection().write(chat); } } 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 571a7323c..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 @@ -5,20 +5,17 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.RequiredArgumentBuilder; import com.mojang.brigadier.tree.LiteralCommandNode; import com.velocitypowered.api.event.connection.PluginMessageEvent; -import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.proxy.messages.ChannelIdentifier; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler; -import com.velocitypowered.proxy.connection.forge.legacy.LegacyForgeConstants; import com.velocitypowered.proxy.connection.util.ConnectionMessages; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.packet.AvailableCommands; import com.velocitypowered.proxy.protocol.packet.AvailableCommands.ProtocolSuggestionProvider; import com.velocitypowered.proxy.protocol.packet.BossBar; import com.velocitypowered.proxy.protocol.packet.Disconnect; -import com.velocitypowered.proxy.protocol.packet.JoinGame; import com.velocitypowered.proxy.protocol.packet.KeepAlive; import com.velocitypowered.proxy.protocol.packet.PlayerListItem; import com.velocitypowered.proxy.protocol.packet.PluginMessage; @@ -39,7 +36,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { BackendPlaySessionHandler(VelocityServer server, VelocityServerConnection serverConn) { this.server = server; this.serverConn = serverConn; - this.playerConnection = serverConn.getPlayer().getMinecraftConnection(); + this.playerConnection = serverConn.getPlayer().getConnection(); MinecraftSessionHandler psh = playerConnection.getSessionHandler(); if (!(psh instanceof ClientPlaySessionHandler)) { @@ -105,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/backend/TransitionSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/TransitionSessionHandler.java index b8d7ed75f..1c6a0b05b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/TransitionSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/TransitionSessionHandler.java @@ -84,13 +84,13 @@ public class TransitionSessionHandler implements MinecraftSessionHandler { .whenCompleteAsync((x, error) -> { // Strap on the ClientPlaySessionHandler if required. ClientPlaySessionHandler playHandler; - if (serverConn.getPlayer().getMinecraftConnection().getSessionHandler() + if (serverConn.getPlayer().getConnection().getSessionHandler() instanceof ClientPlaySessionHandler) { - playHandler = (ClientPlaySessionHandler) serverConn.getPlayer().getMinecraftConnection() + playHandler = (ClientPlaySessionHandler) serverConn.getPlayer().getConnection() .getSessionHandler(); } else { playHandler = new ClientPlaySessionHandler(server, serverConn.getPlayer()); - serverConn.getPlayer().getMinecraftConnection().setSessionHandler(playHandler); + serverConn.getPlayer().getConnection().setSessionHandler(playHandler); } playHandler.handleBackendJoinGame(packet, serverConn); @@ -167,7 +167,7 @@ public class TransitionSessionHandler implements MinecraftSessionHandler { return true; } - serverConn.getPlayer().getMinecraftConnection().write(packet.retain()); + serverConn.getPlayer().getConnection().write(packet.retain()); return true; } 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 2a1910c4c..6dc1868cc 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 @@ -79,7 +79,7 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, CompletableFuture result = new CompletableFuture<>(); // Note: we use the event loop for the connection the player is on. This reduces context // switches. - server.createBootstrap(proxyPlayer.getMinecraftConnection().eventLoop()) + server.createBootstrap(proxyPlayer.getConnection().eventLoop()) .handler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { @@ -143,13 +143,13 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, PlayerInfoForwarding forwardingMode = server.getConfiguration().getPlayerInfoForwardingMode(); // Initiate the handshake. - ProtocolVersion protocolVersion = proxyPlayer.getMinecraftConnection().getNextProtocolVersion(); + ProtocolVersion protocolVersion = proxyPlayer.getConnection().getNextProtocolVersion(); Handshake handshake = new Handshake(); handshake.setNextStatus(StateRegistry.LOGIN_ID); handshake.setProtocolVersion(protocolVersion); if (forwardingMode == PlayerInfoForwarding.LEGACY) { handshake.setServerAddress(createLegacyForwardingAddress()); - } else if (proxyPlayer.getMinecraftConnection().getType() == ConnectionTypes.LEGACY_FORGE) { + } else if (proxyPlayer.getConnection().getType() == ConnectionTypes.LEGACY_FORGE) { handshake.setServerAddress(handshake.getServerAddress() + HANDSHAKE_HOSTNAME_TOKEN); } else { handshake.setServerAddress(registeredServer.getServerInfo().getAddress().getHostString()); 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 32a67b755..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; @@ -40,7 +41,6 @@ import java.util.List; import java.util.Optional; import java.util.Queue; import java.util.UUID; -import net.kyori.text.Component; import net.kyori.text.TextComponent; import net.kyori.text.format.TextColor; import org.apache.logging.log4j.LogManager; @@ -78,7 +78,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { .getProtocolVersion()); if (!channels.isEmpty()) { PluginMessage register = constructChannelsPacket(player.getProtocolVersion(), channels); - player.getMinecraftConnection().write(register); + player.getConnection().write(register); player.getKnownChannels().addAll(channels); } } @@ -178,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. @@ -275,7 +276,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { public void writabilityChanged() { VelocityServerConnection serverConn = player.getConnectedServer(); if (serverConn != null) { - boolean writable = player.getMinecraftConnection().getChannel().isWritable(); + boolean writable = player.getConnection().getChannel().isWritable(); MinecraftConnection smc = serverConn.getConnection(); if (smc != null) { smc.setAutoReading(writable); @@ -295,7 +296,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { if (!spawned) { // Nothing special to do with regards to spawning the player spawned = true; - player.getMinecraftConnection().delayedWrite(joinGame); + player.getConnection().delayedWrite(joinGame); // Required for Legacy Forge player.getPhase().onFirstJoin(player); @@ -315,12 +316,12 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { // Most notably, by having the client accept the join game packet, we can work around the need // to perform entity ID rewrites, eliminating potential issues from rewriting packets and // improving compatibility with mods. - player.getMinecraftConnection().delayedWrite(joinGame); + player.getConnection().delayedWrite(joinGame); int tempDim = joinGame.getDimension() == 0 ? -1 : 0; - player.getMinecraftConnection().delayedWrite( + player.getConnection().delayedWrite( new Respawn(tempDim, joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType())); - player.getMinecraftConnection().delayedWrite( + player.getConnection().delayedWrite( new Respawn(joinGame.getDimension(), joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType())); } @@ -331,7 +332,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { BossBar deletePacket = new BossBar(); deletePacket.setUuid(serverBossBar); deletePacket.setAction(BossBar.REMOVE); - player.getMinecraftConnection().delayedWrite(deletePacket); + player.getConnection().delayedWrite(deletePacket); } serverBossBars.clear(); @@ -348,11 +349,13 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { } // Clear any title from the previous server. - player.getMinecraftConnection() - .delayedWrite(TitlePacket.resetForProtocolVersion(player.getProtocolVersion())); + if (player.getProtocolVersion().compareTo(MINECRAFT_1_8) >= 0) { + player.getConnection() + .delayedWrite(TitlePacket.resetForProtocolVersion(player.getProtocolVersion())); + } // Flush everything - player.getMinecraftConnection().flush(); + player.getConnection().flush(); serverMc.flush(); destination.completeJoin(); } @@ -410,7 +413,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { resp.setLength(length); resp.getOffers().addAll(offers); - player.getMinecraftConnection().write(resp); + player.getConnection().write(resp); return true; } @@ -450,7 +453,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { response.getOffers().add(new Offer(offer, null)); } response.getOffers().sort(null); - player.getMinecraftConnection().write(response); + player.getConnection().write(response); } catch (Exception e) { logger.error("Unable to provide tab list completions for {} for command '{}'", player.getUsername(), @@ -469,8 +472,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { for (String s : e.getSuggestions()) { response.getOffers().add(new Offer(s)); } - player.getMinecraftConnection().write(response); - }, player.getMinecraftConnection().eventLoop()); + player.getConnection().write(response); + }, player.getConnection().eventLoop()); } /** 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 19d19d9c7..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; @@ -58,7 +59,6 @@ import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; -import java.util.concurrent.ExecutionException; import java.util.concurrent.ThreadLocalRandom; import net.kyori.text.Component; import net.kyori.text.TextComponent; @@ -84,12 +84,13 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { /** * The actual Minecraft connection. This is actually a wrapper object around the Netty channel. */ - private final MinecraftConnection minecraftConnection; + private final MinecraftConnection connection; private final @Nullable InetSocketAddress virtualHost; private GameProfile profile; 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; @@ -102,16 +103,21 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { private @MonotonicNonNull List serversToTry = null; - ConnectedPlayer(VelocityServer server, GameProfile profile, - MinecraftConnection minecraftConnection, @Nullable InetSocketAddress virtualHost) { + ConnectedPlayer(VelocityServer server, GameProfile profile, MinecraftConnection connection, + @Nullable InetSocketAddress virtualHost, boolean onlineMode) { this.server = server; - this.tabList = new VelocityTabList(minecraftConnection); + if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) { + this.tabList = new VelocityTabList(connection); + } else { + this.tabList = new VelocityTabListLegacy(connection); + } this.profile = profile; - this.minecraftConnection = minecraftConnection; + this.connection = connection; this.virtualHost = virtualHost; this.permissionFunction = PermissionFunction.ALWAYS_UNDEFINED; - this.connectionPhase = minecraftConnection.getType().getInitialClientPhase(); + this.connectionPhase = connection.getType().getInitialClientPhase(); this.knownChannels = CappedSet.create(MAX_PLUGIN_CHANNELS); + this.onlineMode = onlineMode; } @Override @@ -134,8 +140,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { return profile; } - public MinecraftConnection getMinecraftConnection() { - return minecraftConnection; + public MinecraftConnection getConnection() { + return connection; } @Override @@ -147,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; @@ -170,7 +181,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { @Override public InetSocketAddress getRemoteAddress() { - return (InetSocketAddress) minecraftConnection.getRemoteAddress(); + return (InetSocketAddress) connection.getRemoteAddress(); } @Override @@ -184,12 +195,12 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { @Override public boolean isActive() { - return minecraftConnection.getChannel().isActive(); + return connection.getChannel().isActive(); } @Override public ProtocolVersion getProtocolVersion() { - return minecraftConnection.getProtocolVersion(); + return connection.getProtocolVersion(); } @Override @@ -205,7 +216,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { TitlePacket pkt = new TitlePacket(); pkt.setAction(TitlePacket.SET_ACTION_BAR); pkt.setComponent(GsonComponentSerializer.INSTANCE.serialize(component)); - minecraftConnection.write(pkt); + connection.write(pkt); return; } else { // Due to issues with action bar packets, we'll need to convert the text message into a @@ -221,7 +232,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { Chat chat = new Chat(); chat.setType(pos); chat.setMessage(json); - minecraftConnection.write(chat); + connection.write(chat); } @Override @@ -258,23 +269,23 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { public void disconnect(Component reason) { logger.info("{} has disconnected: {}", this, LegacyComponentSerializer.legacy().serialize(reason)); - minecraftConnection.closeWith(Disconnect.create(reason)); + connection.closeWith(Disconnect.create(reason)); } @Override public void sendTitle(Title title) { Preconditions.checkNotNull(title, "title"); - ProtocolVersion protocolVersion = minecraftConnection.getProtocolVersion(); + ProtocolVersion protocolVersion = connection.getProtocolVersion(); if (title.equals(Titles.reset())) { - minecraftConnection.write(TitlePacket.resetForProtocolVersion(protocolVersion)); + connection.write(TitlePacket.resetForProtocolVersion(protocolVersion)); } else if (title.equals(Titles.hide())) { - minecraftConnection.write(TitlePacket.hideForProtocolVersion(protocolVersion)); + connection.write(TitlePacket.hideForProtocolVersion(protocolVersion)); } else if (title instanceof TextTitle) { TextTitle tt = (TextTitle) title; if (tt.isResetBeforeSend()) { - minecraftConnection.delayedWrite(TitlePacket.resetForProtocolVersion(protocolVersion)); + connection.delayedWrite(TitlePacket.resetForProtocolVersion(protocolVersion)); } Optional titleText = tt.getTitle(); @@ -282,7 +293,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { TitlePacket titlePkt = new TitlePacket(); titlePkt.setAction(TitlePacket.SET_TITLE); titlePkt.setComponent(GsonComponentSerializer.INSTANCE.serialize(titleText.get())); - minecraftConnection.delayedWrite(titlePkt); + connection.delayedWrite(titlePkt); } Optional subtitleText = tt.getSubtitle(); @@ -290,7 +301,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { TitlePacket titlePkt = new TitlePacket(); titlePkt.setAction(TitlePacket.SET_SUBTITLE); titlePkt.setComponent(GsonComponentSerializer.INSTANCE.serialize(subtitleText.get())); - minecraftConnection.delayedWrite(titlePkt); + connection.delayedWrite(titlePkt); } if (tt.areTimesSet()) { @@ -298,9 +309,9 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { timesPkt.setFadeIn(tt.getFadeIn()); timesPkt.setStay(tt.getStay()); timesPkt.setFadeOut(tt.getFadeOut()); - minecraftConnection.delayedWrite(timesPkt); + connection.delayedWrite(timesPkt); } - minecraftConnection.flush(); + connection.flush(); } else { throw new IllegalArgumentException("Unknown title class " + title.getClass().getName()); } @@ -454,7 +465,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { } else { sendMessage(VelocityMessages.MOVED_TO_NEW_SERVER.append(friendlyReason)); } - }, minecraftConnection.eventLoop()); + }, connection.eventLoop()); } else if (event.getResult() instanceof Notify) { Notify res = (Notify) event.getResult(); if (event.kickedDuringServerConnect()) { @@ -466,7 +477,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { // In case someone gets creative, assume we want to disconnect the player. disconnect(friendlyReason); } - }, minecraftConnection.eventLoop()); + }, connection.eventLoop()); } /** @@ -579,7 +590,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { Preconditions.checkNotNull(identifier, "identifier"); Preconditions.checkNotNull(data, "data"); PluginMessage message = new PluginMessage(identifier.getId(), Unpooled.wrappedBuffer(data)); - minecraftConnection.write(message); + connection.write(message); return true; } @@ -598,7 +609,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { ResourcePackRequest request = new ResourcePackRequest(); request.setUrl(url); request.setHash(""); - minecraftConnection.write(request); + connection.write(request); } @Override @@ -610,7 +621,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { ResourcePackRequest request = new ResourcePackRequest(); request.setUrl(url); request.setHash(ByteBufUtil.hexDump(hash)); - minecraftConnection.write(request); + connection.write(request); } /** @@ -619,10 +630,10 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { * ID last sent by the server. */ public void sendKeepAlive() { - if (minecraftConnection.getState() == StateRegistry.PLAY) { + if (connection.getState() == StateRegistry.PLAY) { KeepAlive keepAlive = new KeepAlive(); keepAlive.setRandomId(ThreadLocalRandom.current().nextLong()); - minecraftConnection.write(keepAlive); + connection.write(keepAlive); } } @@ -749,7 +760,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { } else if ((status != null && !status.isSuccessful())) { resetInFlightConnection(); } - }, minecraftConnection.eventLoop()) + }, connection.eventLoop()) .thenApply(x -> x); } @@ -782,7 +793,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { // The only remaining value is successful (no need to do anything!) break; } - }, minecraftConnection.eventLoop()) + }, connection.eventLoop()) .thenApply(Result::isSuccessful); } 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 1c8594a22..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 @@ -114,7 +114,7 @@ public enum LegacyForgeHandshakeBackendPhase implements BackendConnectionPhase { serverConnection.setConnectionPhase(newPhase); // Write the packet to the player, we don't need it now. - player.getMinecraftConnection().write(message.retain()); + player.getConnection().write(message.retain()); return true; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeHandshakeClientPhase.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeHandshakeClientPhase.java index f75dc04b7..b394cb137 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeHandshakeClientPhase.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeHandshakeClientPhase.java @@ -135,7 +135,7 @@ public enum LegacyForgeHandshakeClientPhase implements ClientConnectionPhase { COMPLETE(null) { @Override public void resetConnectionPhase(ConnectedPlayer player) { - player.getMinecraftConnection().write(LegacyForgeUtil.resetPacket()); + player.getConnection().write(LegacyForgeUtil.resetPacket()); player.setPhase(LegacyForgeHandshakeClientPhase.NOT_STARTED); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java b/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java index 0323d24a3..765af2261 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java @@ -34,7 +34,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; public final class ConnectionManager { - private static final WriteBufferWaterMark SERVER_WRITE_MARK = new WriteBufferWaterMark(1 << 21, + private static final WriteBufferWaterMark SERVER_WRITE_MARK = new WriteBufferWaterMark(1 << 20, 1 << 21); private static final Logger LOGGER = LogManager.getLogger(ConnectionManager.class); private final Map endpoints = new HashMap<>(); 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/netty/MinecraftCompressDecoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressDecoder.java index a57485f03..9a040dec4 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressDecoder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressDecoder.java @@ -33,7 +33,7 @@ public class MinecraftCompressDecoder extends MessageToMessageDecoder { return; } - checkFrame(expectedSize >= threshold, "Uncompressed size %s is greater than threshold %s", + checkFrame(expectedSize >= threshold, "Uncompressed size %s is less than threshold %s", expectedSize, threshold); int initialCapacity = Math.min(expectedSize, MAXIMUM_UNCOMPRESSED_SIZE); ByteBuf compatibleIn = ensureCompatible(ctx.alloc(), compressor, in); 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); + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/bossbar/VelocityBossBar.java b/proxy/src/main/java/com/velocitypowered/proxy/util/bossbar/VelocityBossBar.java index 98dc81d50..6b4d9b01a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/bossbar/VelocityBossBar.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/bossbar/VelocityBossBar.java @@ -271,6 +271,6 @@ public class VelocityBossBar implements com.velocitypowered.api.util.bossbar.Bos private void sendPacket(Player player, MinecraftPacket packet) { ConnectedPlayer connected = (ConnectedPlayer) player; - connected.getMinecraftConnection().write(packet); + connected.getConnection().write(packet); } }