From e31b2b87dce393b90d68250343ed59ae3ba52187 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 2 Jul 2019 02:32:25 -0400 Subject: [PATCH 01/76] Update Netty to 4.1.37.Final --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 5b96ec9d3..cfa7ca7cb 100644 --- a/build.gradle +++ b/build.gradle @@ -24,7 +24,7 @@ allprojects { junitVersion = '5.3.0-M1' slf4jVersion = '1.7.25' log4jVersion = '2.11.2' - nettyVersion = '4.1.35.Final' + nettyVersion = '4.1.37.Final' guavaVersion = '25.1-jre' checkerFrameworkVersion = '2.7.0' From 5b518eaf20ebc6e72bf4b67d0651b31fd49da9b8 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 2 Jul 2019 02:36:46 -0400 Subject: [PATCH 02/76] Remove kqueue bug workaround since the issue is now fixed upstream See https://github.com/netty/netty/pull/9149 --- .../connection/client/ConnectedPlayer.java | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) 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 7589300fa..19fedd546 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 @@ -404,18 +404,14 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { } if (connectedServer == null) { - // The player isn't yet connected to a server. Note that we need to do this in a future run - // of the event loop due to an issue with the Netty kqueue transport. - minecraftConnection.eventLoop().execute(() -> { - Optional nextServer = getNextServerToTry(rs); - if (nextServer.isPresent()) { - // There can't be any connection in flight now. - resetInFlightConnection(); - createConnectionRequest(nextServer.get()).fireAndForget(); - } else { - disconnect(friendlyReason); - } - }); + Optional nextServer = getNextServerToTry(rs); + if (nextServer.isPresent()) { + // There can't be any connection in flight now. + resetInFlightConnection(); + createConnectionRequest(nextServer.get()).fireAndForget(); + } else { + disconnect(friendlyReason); + } } else { boolean kickedFromCurrent = connectedServer.getServer().equals(rs); ServerKickResult result; From 145dfa8ac6e7999b682e2d8969a33075e2c95b73 Mon Sep 17 00:00:00 2001 From: Seppe Volkaerts Date: Fri, 5 Jul 2019 05:49:40 +0200 Subject: [PATCH 03/76] Wait for player disconnect events on shutdown. (#229) --- .../velocitypowered/proxy/VelocityServer.java | 34 ++++++++++++++++--- .../connection/client/ConnectedPlayer.java | 9 ++++- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index a8ee585b5..f4041ea76 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -59,9 +59,14 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.IntFunction; import java.util.stream.Collectors; import net.kyori.text.Component; import net.kyori.text.TextComponent; @@ -379,14 +384,35 @@ public class VelocityServer implements ProxyServer { Runnable shutdownProcess = () -> { logger.info("Shutting down the proxy..."); - for (ConnectedPlayer player : ImmutableList.copyOf(connectionsByUuid.values())) { + // Shutdown the connection manager, this should be + // done first to refuse new connections + cm.shutdown(); + + ImmutableList players = ImmutableList.copyOf(connectionsByUuid.values()); + for (ConnectedPlayer player : players) { player.disconnect(TextComponent.of("Proxy shutting down.")); } - this.cm.shutdown(); - try { - if (!eventManager.shutdown() || !scheduler.shutdown()) { + boolean timedOut = false; + + try { + // Wait for the connections finish tearing down, this + // makes sure that all the disconnect events are being fired + + CompletableFuture playersTeardownFuture = CompletableFuture.allOf(players.stream() + .map(ConnectedPlayer::getTeardownFuture) + .toArray((IntFunction[]>) CompletableFuture[]::new)); + + playersTeardownFuture.get(10, TimeUnit.SECONDS); + } catch (TimeoutException | ExecutionException e) { + timedOut = true; + } + + timedOut = !eventManager.shutdown() || timedOut; + timedOut = !scheduler.shutdown() || timedOut; + + if (timedOut) { logger.error("Your plugins took over 10 seconds to shut down."); } } catch (InterruptedException e) { 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 19fedd546..65b6d800b 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 @@ -57,6 +57,7 @@ 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; @@ -96,6 +97,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { private final VelocityServer server; private ClientConnectionPhase connectionPhase; private final Collection knownChannels; + private final CompletableFuture teardownFuture = new CompletableFuture<>(); private @MonotonicNonNull List serversToTry = null; @@ -553,7 +555,12 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { connectedServer.disconnect(); } server.unregisterConnection(this); - server.getEventManager().fireAndForget(new DisconnectEvent(this)); + server.getEventManager().fire(new DisconnectEvent(this)) + .thenRun(() -> this.teardownFuture.complete(null)); + } + + public CompletableFuture getTeardownFuture() { + return teardownFuture; } @Override From 58b52cce0c00791fc33d91629dc8acca74d30400 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Fri, 12 Jul 2019 14:32:37 -0400 Subject: [PATCH 04/76] Add flag to disable native transports --- .../java/com/velocitypowered/proxy/network/TransportType.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/TransportType.java b/proxy/src/main/java/com/velocitypowered/proxy/network/TransportType.java index beb42d92f..d53fc84b7 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/network/TransportType.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/TransportType.java @@ -67,6 +67,10 @@ enum TransportType { } public static TransportType bestType() { + if (Boolean.getBoolean("velocity.disable-native-transport")) { + return NIO; + } + if (Epoll.isAvailable()) { return EPOLL; } else if (KQueue.isAvailable()) { From 950104850e3215da2a21f64d88bd115a69a497a1 Mon Sep 17 00:00:00 2001 From: Jamie Campbell Date: Fri, 19 Jul 2019 17:47:05 +0100 Subject: [PATCH 05/76] 1.14.4 support --- README.md | 2 +- .../java/com/velocitypowered/api/network/ProtocolVersion.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c518f6537..c83e79669 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ 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.3. Velocity is best supported with Paper +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. 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 7b096f194..d1764a5c6 100644 --- a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java +++ b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java @@ -30,7 +30,8 @@ public enum ProtocolVersion { MINECRAFT_1_14(477, "1.14"), MINECRAFT_1_14_1(480, "1.14.1"), MINECRAFT_1_14_2(485, "1.14.2"), - MINECRAFT_1_14_3(490, "1.14.3"); + MINECRAFT_1_14_3(490, "1.14.3"), + MINECRAFT_1_14_4(498, "1.14.4"); private final int protocol; private final String name; From a60d134e1eb226a355750ab58cbff8a3d7f0bf4f Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Fri, 19 Jul 2019 13:28:38 -0400 Subject: [PATCH 06/76] Velocity 1.0.2 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index cfa7ca7cb..85df79d62 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ plugins { allprojects { group 'com.velocitypowered' - version '1.0.2-SNAPSHOT' + version '1.0.2' sourceCompatibility = 1.8 targetCompatibility = 1.8 From 57433a657c94e368a49586e0c8f3aaf091a192f7 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Fri, 19 Jul 2019 13:28:59 -0400 Subject: [PATCH 07/76] Velocity 1.0.3-SNAPSHOT --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 85df79d62..48abae421 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ plugins { allprojects { group 'com.velocitypowered' - version '1.0.2' + version '1.0.3-SNAPSHOT' sourceCompatibility = 1.8 targetCompatibility = 1.8 From bfea41e7e31e9b0a2eb1b5edfbb3f5a78ce97414 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Fri, 26 Jul 2019 01:36:28 -0400 Subject: [PATCH 08/76] Fix Travis build matrix --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 62f109821..ff0d0f19d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,4 +7,5 @@ cache: - $HOME/.gradle/caches/ - $HOME/.gradle/wrapper/ jdk: - - oraclejdk8 \ No newline at end of file + - openjdk8 + - openjdk11 \ No newline at end of file From 4940f6b4471838e0ceab1942cd120c5b0046e489 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 30 Jul 2019 17:10:10 -0400 Subject: [PATCH 09/76] Allow certain mangled large packets to pass through --- .../proxy/protocol/netty/MinecraftCompressDecoder.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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 56af37e65..a57485f03 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 @@ -35,10 +35,9 @@ public class MinecraftCompressDecoder extends MessageToMessageDecoder { checkFrame(expectedSize >= threshold, "Uncompressed size %s is greater than threshold %s", expectedSize, threshold); - checkFrame(expectedSize <= MAXIMUM_UNCOMPRESSED_SIZE, "Expected uncompressed size" - + "%s is larger than protocol maximum of %s", expectedSize, MAXIMUM_UNCOMPRESSED_SIZE); + int initialCapacity = Math.min(expectedSize, MAXIMUM_UNCOMPRESSED_SIZE); ByteBuf compatibleIn = ensureCompatible(ctx.alloc(), compressor, in); - ByteBuf uncompressed = preferredBuffer(ctx.alloc(), compressor, expectedSize); + ByteBuf uncompressed = preferredBuffer(ctx.alloc(), compressor, initialCapacity); try { compressor.inflate(compatibleIn, uncompressed, expectedSize); out.add(uncompressed); From 7c2cbdbf1fb59ea88b3d6fe95ce89084c16aae4f Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Wed, 7 Aug 2019 16:21:03 -0400 Subject: [PATCH 10/76] Explicitly parse IP addresses before using an unresolved address This allows plugins to more correctly use InetSocketAddress#getAddress(), however "gotchas" remain. --- .../java/com/velocitypowered/proxy/util/AddressUtil.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/AddressUtil.java b/proxy/src/main/java/com/velocitypowered/proxy/util/AddressUtil.java index 8874509aa..033babcb9 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/AddressUtil.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/AddressUtil.java @@ -1,6 +1,8 @@ package com.velocitypowered.proxy.util; import com.google.common.base.Preconditions; +import com.google.common.net.InetAddresses; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.URI; @@ -19,7 +21,12 @@ public class AddressUtil { public static InetSocketAddress parseAddress(String ip) { Preconditions.checkNotNull(ip, "ip"); URI uri = URI.create("tcp://" + ip); - return InetSocketAddress.createUnresolved(uri.getHost(), uri.getPort()); + try { + InetAddress ia = InetAddresses.forUriString(uri.getHost()); + return new InetSocketAddress(ia, uri.getPort()); + } catch (IllegalArgumentException e) { + return InetSocketAddress.createUnresolved(uri.getHost(), uri.getPort()); + } } /** From c64d16326cf2d5c25f208c96661b336521305c32 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Thu, 8 Aug 2019 17:22:38 -0400 Subject: [PATCH 11/76] Fix bug where connect() wouldn't reset in-flight connections --- .../proxy/connection/client/ConnectedPlayer.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 65b6d800b..3c9a5cae4 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 @@ -747,8 +747,10 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { if (status != null && !status.isSafe()) { // If it's not safe to continue the connection we need to shut it down. handleConnectionException(status.getAttemptedConnection(), throwable, true); + } else if ((status != null && !status.isSuccessful())) { + resetInFlightConnection(); } - }) + }, minecraftConnection.eventLoop()) .thenApply(x -> x); } From e3e7b726e0001631fa441e4dcc0ca352b4b1f3a9 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Fri, 9 Aug 2019 15:38:36 -0400 Subject: [PATCH 12/76] Bump to Guice 4.2.2 for Java 11 plugin support --- api/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/build.gradle b/api/build.gradle index 7322e4718..c46a2360c 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}" testCompile "org.junit.jupiter:junit-jupiter-api:${junitVersion}" From 10ce976084e5d685db3a17818c4b7f596b7a9379 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sun, 11 Aug 2019 20:36:00 -0400 Subject: [PATCH 13/76] Update text 3.0.2 cc @lucko --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 48abae421..bb658d42d 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' From 057b84c9336815288226d7f430208c6a984d513f Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sat, 17 Aug 2019 20:12:09 -0400 Subject: [PATCH 14/76] Remove inaccurate "Status" section I'm just one person, I can't keep this stuff updated everywhere. --- README.md | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) 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 From e42c1d876defb33663bae7774b22abcd50e9a683 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sat, 24 Aug 2019 00:27:20 -0400 Subject: [PATCH 15/76] Fix Travis (again) --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index ff0d0f19d..3d0c36340 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,5 +7,4 @@ cache: - $HOME/.gradle/caches/ - $HOME/.gradle/wrapper/ jdk: - - openjdk8 - - openjdk11 \ No newline at end of file + - openjdk8 \ No newline at end of file From 36082126a08f2f7ab3120ec366cdff3cd54839e7 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sat, 24 Aug 2019 00:27:43 -0400 Subject: [PATCH 16/76] Velocity 1.0.3 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index bb658d42d..471177d3c 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ plugins { allprojects { group 'com.velocitypowered' - version '1.0.3-SNAPSHOT' + version '1.0.3' sourceCompatibility = 1.8 targetCompatibility = 1.8 From c0010d6f8b952027392a5433a6c65f19c662dc6c Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sat, 24 Aug 2019 00:28:10 -0400 Subject: [PATCH 17/76] Velocity 1.0.4-SNAPSHOT --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 471177d3c..945fee619 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ plugins { allprojects { group 'com.velocitypowered' - version '1.0.3' + version '1.0.4-SNAPSHOT' sourceCompatibility = 1.8 targetCompatibility = 1.8 From aff06164cdd5a0b0f50249efaf321a5c34c74b39 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sun, 15 Sep 2019 19:54:33 -0400 Subject: [PATCH 18/76] Fix "all" not appearing in the tab-complete for /server --- .../velocitypowered/proxy/command/ServerCommand.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/ServerCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/command/ServerCommand.java index 9a64bc95b..471975339 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/ServerCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/ServerCommand.java @@ -12,6 +12,7 @@ import com.velocitypowered.api.proxy.server.ServerInfo; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; +import java.util.stream.Stream; import net.kyori.text.TextComponent; import net.kyori.text.event.ClickEvent; import net.kyori.text.event.HoverEvent; @@ -83,13 +84,12 @@ public class ServerCommand implements Command { @Override public List suggest(CommandSource source, String @NonNull [] currentArgs) { + Stream possibilities = Stream.concat(Stream.of("all"), server.getAllServers() + .stream().map(rs -> rs.getServerInfo().getName())); if (currentArgs.length == 0) { - return server.getAllServers().stream() - .map(rs -> rs.getServerInfo().getName()) - .collect(Collectors.toList()); + return possibilities.collect(Collectors.toList()); } else if (currentArgs.length == 1) { - return server.getAllServers().stream() - .map(rs -> rs.getServerInfo().getName()) + return possibilities .filter(name -> name.regionMatches(true, 0, currentArgs[0], 0, currentArgs[0].length())) .collect(Collectors.toList()); } else { From 1824c7ad7e1caba1fa4ce0c8104b0adcc48f0ea8 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Fri, 27 Sep 2019 22:23:40 -0400 Subject: [PATCH 19/76] Suppress invalid protocol spam --- .../connection/client/HandshakeSessionHandler.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) 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..8b8dca861 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 @@ -26,9 +26,13 @@ import java.util.Optional; import net.kyori.text.TextComponent; import net.kyori.text.TranslatableComponent; import net.kyori.text.format.TextColor; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class HandshakeSessionHandler implements MinecraftSessionHandler { + private static final Logger LOGGER = LogManager.getLogger(HandshakeSessionHandler.class); + private final MinecraftConnection connection; private final VelocityServer server; @@ -58,11 +62,11 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler { public boolean handle(Handshake handshake) { InitialInboundConnection ic = new InitialInboundConnection(connection, cleanVhost(handshake.getServerAddress()), handshake); - connection.setAssociation(ic); switch (handshake.getNextStatus()) { case StateRegistry.STATUS_ID: connection.setState(StateRegistry.STATUS); connection.setProtocolVersion(handshake.getProtocolVersion()); + connection.setAssociation(ic); connection.setSessionHandler(new StatusSessionHandler(server, connection, ic)); return true; case StateRegistry.LOGIN_ID: @@ -101,11 +105,14 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler { return true; } + connection.setAssociation(ic); server.getEventManager().fireAndForget(new ConnectionHandshakeEvent(ic)); connection.setSessionHandler(new LoginSessionHandler(server, connection, ic)); return true; default: - throw new IllegalArgumentException("Invalid state " + handshake.getNextStatus()); + LOGGER.error("{} provided invalid protocol {}", ic, handshake.getNextStatus()); + connection.close(); + return true; } } From 17e6944daea8130e03903ccdfbf63f111c573849 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Fri, 27 Sep 2019 22:37:42 -0400 Subject: [PATCH 20/76] Clean up HandshakeSessionHandler --- .../proxy/connection/ConnectionType.java | 10 -- .../client/HandshakeSessionHandler.java | 114 ++++++++++-------- .../forge/legacy/LegacyForgeConstants.java | 2 +- 3 files changed, 64 insertions(+), 62 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/ConnectionType.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/ConnectionType.java index a69768946..b6d5fc75a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/ConnectionType.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/ConnectionType.java @@ -34,14 +34,4 @@ public interface ConnectionType { */ GameProfile addGameProfileTokensIfRequired(GameProfile original, PlayerInfoForwarding forwardingType); - - /** - * Tests whether the hostname is the handshake packet is valid. - * - * @param address The address to check - * @return true if valid. - */ - default boolean checkServerAddressIsValid(String address) { - return true; - } } 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 8b8dca861..3d0fb39c1 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 @@ -28,6 +28,7 @@ import net.kyori.text.TranslatableComponent; import net.kyori.text.format.TextColor; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.checkerframework.checker.nullness.qual.Nullable; public class HandshakeSessionHandler implements MinecraftSessionHandler { @@ -62,68 +63,79 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler { public boolean handle(Handshake handshake) { InitialInboundConnection ic = new InitialInboundConnection(connection, cleanVhost(handshake.getServerAddress()), handshake); - switch (handshake.getNextStatus()) { + StateRegistry nextState = getStateForProtocol(handshake.getNextStatus()); + if (nextState == null) { + LOGGER.error("{} provided invalid protocol {}", ic, handshake.getNextStatus()); + connection.close(); + } else { + connection.setState(nextState); + connection.setProtocolVersion(handshake.getProtocolVersion()); + connection.setAssociation(ic); + + switch (nextState) { + case STATUS: + connection.setSessionHandler(new StatusSessionHandler(server, connection, ic)); + break; + case LOGIN: + this.handleLogin(handshake, ic); + break; + default: + // If you get this, it's a bug in Velocity. + throw new AssertionError("getStateForProtocol provided invalid state!"); + } + } + + return true; + } + + private static @Nullable StateRegistry getStateForProtocol(int status) { + switch (status) { case StateRegistry.STATUS_ID: - connection.setState(StateRegistry.STATUS); - connection.setProtocolVersion(handshake.getProtocolVersion()); - connection.setAssociation(ic); - connection.setSessionHandler(new StatusSessionHandler(server, connection, ic)); - return true; + return StateRegistry.STATUS; case StateRegistry.LOGIN_ID: - connection.setState(StateRegistry.LOGIN); - connection.setProtocolVersion(handshake.getProtocolVersion()); - - if (!ProtocolVersion.isSupported(handshake.getProtocolVersion())) { - connection.closeWith(Disconnect - .create(TranslatableComponent.of("multiplayer.disconnect.outdated_client"))); - return true; - } - - InetAddress address = ((InetSocketAddress) connection.getRemoteAddress()).getAddress(); - if (!server.getIpAttemptLimiter().attempt(address)) { - connection.closeWith( - Disconnect.create(TextComponent.of("You are logging in too fast, try again later."))); - return true; - } - - ConnectionType type = checkForForge(handshake); - connection.setType(type); - - // Make sure legacy forwarding is not in use on this connection. - if (!type.checkServerAddressIsValid(handshake.getServerAddress())) { - connection.closeWith(Disconnect - .create(TextComponent.of("Running Velocity behind Velocity is unsupported."))); - return true; - } - - // If the proxy is configured for modern forwarding, we must deny connections from 1.12.2 - // and lower, otherwise IP information will never get forwarded. - if (server.getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN - && handshake.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_13) < 0) { - connection.closeWith(Disconnect - .create(TextComponent.of("This server is only compatible with 1.13 and above."))); - return true; - } - - connection.setAssociation(ic); - server.getEventManager().fireAndForget(new ConnectionHandshakeEvent(ic)); - connection.setSessionHandler(new LoginSessionHandler(server, connection, ic)); - return true; + return StateRegistry.LOGIN; default: - LOGGER.error("{} provided invalid protocol {}", ic, handshake.getNextStatus()); - connection.close(); - return true; + return null; } } - private ConnectionType checkForForge(Handshake handshake) { + private void handleLogin(Handshake handshake, InitialInboundConnection ic) { + if (!ProtocolVersion.isSupported(handshake.getProtocolVersion())) { + connection.closeWith(Disconnect + .create(TranslatableComponent.of("multiplayer.disconnect.outdated_client"))); + return; + } + + InetAddress address = ((InetSocketAddress) connection.getRemoteAddress()).getAddress(); + if (!server.getIpAttemptLimiter().attempt(address)) { + connection.closeWith( + Disconnect.create(TextComponent.of("You are logging in too fast, try again later."))); + return; + } + + connection.setType(getHandshakeConnectionType(handshake)); + + // If the proxy is configured for modern forwarding, we must deny connections from 1.12.2 + // and lower, otherwise IP information will never get forwarded. + if (server.getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN + && handshake.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_13) < 0) { + connection.closeWith(Disconnect + .create(TextComponent.of("This server is only compatible with 1.13 and above."))); + return; + } + + server.getEventManager().fireAndForget(new ConnectionHandshakeEvent(ic)); + connection.setSessionHandler(new LoginSessionHandler(server, connection, ic)); + } + + private ConnectionType getHandshakeConnectionType(Handshake handshake) { // Determine if we're using Forge (1.8 to 1.12, may not be the case in 1.13). if (handshake.getServerAddress().endsWith(LegacyForgeConstants.HANDSHAKE_HOSTNAME_TOKEN) && handshake.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_13) < 0) { return ConnectionTypes.LEGACY_FORGE; } 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) + // Note for future implementation: Forge 1.13+ identifies itself using a slightly different + // hostname token. return ConnectionTypes.VANILLA; } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeConstants.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeConstants.java index 43e25e65c..1a932c4f0 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeConstants.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/forge/legacy/LegacyForgeConstants.java @@ -6,7 +6,7 @@ package com.velocitypowered.proxy.connection.forge.legacy; public class LegacyForgeConstants { /** - * Clients attempting to connect to 1.8+ Forge servers will have + * Clients attempting to connect to 1.8-1.12.2 Forge servers will have * this token appended to the hostname in the initial handshake * packet. */ From da63406ee7943effce7ec4204a4a857eceb63676 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sat, 23 Nov 2019 01:06:00 -0500 Subject: [PATCH 21/76] Fix potential UDP speculative reflection attack --- .../proxy/protocol/netty/GS4QueryHandler.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/GS4QueryHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/GS4QueryHandler.java index 8bcda72de..b3407c980 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/GS4QueryHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/GS4QueryHandler.java @@ -18,6 +18,7 @@ import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.socket.DatagramPacket; import java.net.InetAddress; import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; import java.util.Collection; import java.util.Collections; import java.util.Iterator; @@ -59,6 +60,7 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler private final Cache sessions = CacheBuilder.newBuilder() .expireAfterWrite(30, TimeUnit.SECONDS) .build(); + private final SecureRandom random; private volatile @MonotonicNonNull List pluginInformationList = null; @@ -67,6 +69,7 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler public GS4QueryHandler(VelocityServer server) { this.server = server; + this.random = new SecureRandom(); } private QueryResponse createInitialResponse() { @@ -111,7 +114,7 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler switch (type) { case QUERY_TYPE_HANDSHAKE: { // Generate new challenge token and put it into the sessions cache - int challengeToken = ThreadLocalRandom.current().nextInt(); + int challengeToken = random.nextInt(); sessions.put(senderAddress, challengeToken); // Respond with challenge token From 893391202b645de6a0fbdfab82b7bb569af51c70 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 26 Nov 2019 13:47:58 -0500 Subject: [PATCH 22/76] Manually backport e29e20b from Velocity 1.1.0 --- .../client/ClientPlaySessionHandler.java | 26 +++++-------------- 1 file changed, 7 insertions(+), 19 deletions(-) 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 9593108a8..8defbdda8 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 @@ -175,31 +175,19 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { } List offers = new ArrayList<>(); - int longestLength = 0; for (String suggestion : suggestions) { offers.add(new Offer(suggestion)); - if (suggestion.length() > longestLength) { - longestLength = suggestion.length(); - } } - TabCompleteResponse resp = new TabCompleteResponse(); - resp.setTransactionId(packet.getTransactionId()); - int startPos = packet.getCommand().lastIndexOf(' ') + 1; - int length; - if (startPos == 0) { - startPos = packet.getCommand().length() + 1; - length = longestLength; - } else { - length = packet.getCommand().length() - startPos; + if (startPos > 0) { + TabCompleteResponse resp = new TabCompleteResponse(); + resp.setTransactionId(packet.getTransactionId()); + resp.setStart(startPos); + resp.setLength(packet.getCommand().length() - startPos); + resp.getOffers().addAll(offers); + player.getMinecraftConnection().write(resp); } - - resp.setStart(startPos); - resp.setLength(length); - resp.getOffers().addAll(offers); - - player.getMinecraftConnection().write(resp); return true; } From 2baa162d91cf2a617898e1d46245618bd4f63257 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 26 Nov 2019 14:07:55 -0500 Subject: [PATCH 23/76] Make sure we only tab-complete commands for which we have access to --- .../com/velocitypowered/proxy/command/VelocityCommand.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommand.java index a67582a0a..1c3b777c3 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommand.java @@ -72,7 +72,10 @@ public class VelocityCommand implements Command { @Override public List suggest(CommandSource source, String @NonNull [] currentArgs) { if (currentArgs.length == 0) { - return ImmutableList.copyOf(subcommands.keySet()); + return subcommands.entrySet().stream() + .filter(e -> e.getValue().hasPermission(source, new String[0])) + .map(Map.Entry::getKey) + .collect(ImmutableList.toImmutableList()); } if (currentArgs.length == 1) { @@ -81,7 +84,7 @@ public class VelocityCommand implements Command { currentArgs[0].length())) .filter(e -> e.getValue().hasPermission(source, new String[0])) .map(Map.Entry::getKey) - .collect(Collectors.toList()); + .collect(ImmutableList.toImmutableList()); } Command command = subcommands.get(currentArgs[0].toLowerCase(Locale.US)); From d8dba8a96c65c2522351eca431709c9f7536dbea Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Fri, 29 Nov 2019 12:03:38 -0500 Subject: [PATCH 24/76] Graceful fallback when /tmp is amounted noexec on Linux Fixes #260 --- .../java/com/velocitypowered/natives/util/Natives.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/native/src/main/java/com/velocitypowered/natives/util/Natives.java b/native/src/main/java/com/velocitypowered/natives/util/Natives.java index 44329434a..a3a229037 100644 --- a/native/src/main/java/com/velocitypowered/natives/util/Natives.java +++ b/native/src/main/java/com/velocitypowered/natives/util/Natives.java @@ -37,7 +37,12 @@ public class Natives { // Well, it doesn't matter... } })); - System.load(tempFile.toAbsolutePath().toString()); + + try { + System.load(tempFile.toAbsolutePath().toString()); + } catch (UnsatisfiedLinkError e) { + throw new NativeSetupException("Unable to load native " + tempFile.toAbsolutePath(), e); + } } catch (IOException e) { throw new NativeSetupException("Unable to copy natives", e); } From e06e2e4cf96aaba6510255f8fe5bc5bcd702482f Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Fri, 29 Nov 2019 14:26:59 -0500 Subject: [PATCH 25/76] Introduce velocity.natives-tmpdir property for properly handling noexec /tmp --- .../com/velocitypowered/natives/util/Natives.java | 12 +++++++++++- .../java/com/velocitypowered/proxy/Velocity.java | 6 ++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/native/src/main/java/com/velocitypowered/natives/util/Natives.java b/native/src/main/java/com/velocitypowered/natives/util/Natives.java index a3a229037..9545bf9d6 100644 --- a/native/src/main/java/com/velocitypowered/natives/util/Natives.java +++ b/native/src/main/java/com/velocitypowered/natives/util/Natives.java @@ -12,6 +12,7 @@ import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.nio.file.StandardCopyOption; public class Natives { @@ -23,12 +24,12 @@ public class Natives { private static Runnable copyAndLoadNative(String path) { return () -> { try { - Path tempFile = Files.createTempFile("native-", path.substring(path.lastIndexOf('.'))); InputStream nativeLib = Natives.class.getResourceAsStream(path); if (nativeLib == null) { throw new IllegalStateException("Native library " + path + " not found."); } + Path tempFile = createTemporaryNativeFilename(path.substring(path.lastIndexOf('.'))); Files.copy(nativeLib, tempFile, StandardCopyOption.REPLACE_EXISTING); Runtime.getRuntime().addShutdownHook(new Thread(() -> { try { @@ -49,6 +50,15 @@ public class Natives { }; } + private static Path createTemporaryNativeFilename(String ext) throws IOException { + String temporaryFolderPath = System.getProperty("velocity.natives-tmpdir"); + if (temporaryFolderPath != null) { + return Files.createTempFile(Paths.get(temporaryFolderPath), "native-", ext); + } else { + return Files.createTempFile("native-", ext); + } + } + public static final NativeCodeLoader compress = new NativeCodeLoader<>( ImmutableList.of( new NativeCodeLoader.Variant<>(NativeConstraints.MACOS, diff --git a/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java b/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java index d00274735..1a7e33c65 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java @@ -23,6 +23,12 @@ public class Velocity { if (System.getProperty("io.netty.allocator.maxOrder") == null) { System.setProperty("io.netty.allocator.maxOrder", "9"); } + + // If Velocity's natives are being extracted to a different temporary directory, make sure the + // Netty natives are extracted there as well + if (System.getProperty("velocity.natives-tmpdir") != null) { + System.setProperty("io.netty.native.workdir", System.getProperty("velocity.natives-tmpdir")); + } } /** From b56302b17ef4b132500bd4523d38d378c58a5c0c Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 10 Dec 2019 12:20:13 -0500 Subject: [PATCH 26/76] Minecraft 1.15 (backport from 1.1.0) --- .../api/network/ProtocolVersion.java | 3 +- .../client/ClientPlaySessionHandler.java | 8 ++-- .../proxy/protocol/StateRegistry.java | 40 +++++++++++++------ .../proxy/protocol/packet/JoinGame.java | 19 +++++++++ .../proxy/protocol/packet/Respawn.java | 20 +++++++++- 5 files changed, 71 insertions(+), 19 deletions(-) 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..8b5b45f15 100644 --- a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java +++ b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java @@ -31,7 +31,8 @@ public enum ProtocolVersion { MINECRAFT_1_14_1(480, "1.14.1"), MINECRAFT_1_14_2(485, "1.14.2"), MINECRAFT_1_14_3(490, "1.14.3"), - MINECRAFT_1_14_4(498, "1.14.4"); + MINECRAFT_1_14_4(498, "1.14.4"), + MINECRAFT_1_15(573, "1.15"); private final int protocol; private final String name; 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 8defbdda8..e9d099d68 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 @@ -336,11 +336,11 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { player.getMinecraftConnection().delayedWrite(joinGame); int tempDim = joinGame.getDimension() == 0 ? -1 : 0; player.getMinecraftConnection().delayedWrite( - new Respawn(tempDim, joinGame.getDifficulty(), joinGame.getGamemode(), - joinGame.getLevelType())); + new Respawn(tempDim, joinGame.getPartialHashedSeed(), joinGame.getDifficulty(), + joinGame.getGamemode(), joinGame.getLevelType())); player.getMinecraftConnection().delayedWrite( - new Respawn(joinGame.getDimension(), joinGame.getDifficulty(), joinGame.getGamemode(), - joinGame.getLevelType())); + new Respawn(joinGame.getDimension(), joinGame.getPartialHashedSeed(), + joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType())); } // Remove previous boss bars. These don't get cleared when sending JoinGame, thus the need to 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..bae659122 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_15; 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; @@ -121,51 +122,61 @@ public enum StateRegistry { map(0x1F, MINECRAFT_1_14, false)); clientbound.register(BossBar.class, BossBar::new, - map(0x0C, MINECRAFT_1_9, false)); + map(0x0C, MINECRAFT_1_9, false), + map(0x0D, MINECRAFT_1_15, false)); clientbound.register(Chat.class, Chat::new, map(0x02, MINECRAFT_1_8, true), map(0x0F, MINECRAFT_1_9, true), - map(0x0E, MINECRAFT_1_13, true)); + map(0x0E, MINECRAFT_1_13, true), + map(0x0F, MINECRAFT_1_15, true)); clientbound.register(TabCompleteResponse.class, TabCompleteResponse::new, map(0x3A, MINECRAFT_1_8, false), map(0x0E, MINECRAFT_1_9, false), - map(0x10, MINECRAFT_1_13, false)); + map(0x10, MINECRAFT_1_13, false), + map(0x11, MINECRAFT_1_15, false)); clientbound.register(AvailableCommands.class, AvailableCommands::new, - map(0x11, MINECRAFT_1_13, false)); + map(0x11, MINECRAFT_1_13, false), + map(0x12, MINECRAFT_1_15, false)); clientbound.register(PluginMessage.class, PluginMessage::new, map(0x3F, MINECRAFT_1_8, false), map(0x18, MINECRAFT_1_9, false), map(0x19, MINECRAFT_1_13, false), - map(0x18, MINECRAFT_1_14, false)); + map(0x18, MINECRAFT_1_14, false), + map(0x19, MINECRAFT_1_15, false)); clientbound.register(Disconnect.class, Disconnect::new, map(0x40, MINECRAFT_1_8, false), map(0x1A, MINECRAFT_1_9, false), map(0x1B, MINECRAFT_1_13, false), - map(0x1A, MINECRAFT_1_14, false)); + map(0x1A, MINECRAFT_1_14, false), + map(0x1B, MINECRAFT_1_15, false)); clientbound.register(KeepAlive.class, KeepAlive::new, map(0x00, MINECRAFT_1_8, false), map(0x1F, MINECRAFT_1_9, false), map(0x21, MINECRAFT_1_13, false), - map(0x20, MINECRAFT_1_14, false)); + map(0x20, MINECRAFT_1_14, false), + map(0x21, MINECRAFT_1_15, false)); clientbound.register(JoinGame.class, JoinGame::new, map(0x01, MINECRAFT_1_8, false), map(0x23, MINECRAFT_1_9, false), map(0x25, MINECRAFT_1_13, false), - map(0x25, MINECRAFT_1_14, false)); + map(0x25, MINECRAFT_1_14, false), + map(0x26, MINECRAFT_1_15, false)); clientbound.register(Respawn.class, Respawn::new, map(0x07, MINECRAFT_1_8, true), map(0x33, MINECRAFT_1_9, true), map(0x34, MINECRAFT_1_12, true), map(0x35, MINECRAFT_1_12_1, true), map(0x38, MINECRAFT_1_13, true), - map(0x3A, MINECRAFT_1_14, true)); + map(0x3A, MINECRAFT_1_14, true), + map(0x3B, MINECRAFT_1_15, true)); clientbound.register(ResourcePackRequest.class, ResourcePackRequest::new, map(0x48, MINECRAFT_1_8, true), map(0x32, MINECRAFT_1_9, true), map(0x33, MINECRAFT_1_12, true), map(0x34, MINECRAFT_1_12_1, true), map(0x37, MINECRAFT_1_13, true), - map(0x39, MINECRAFT_1_14, true)); + map(0x39, MINECRAFT_1_14, true), + map(0x3A, MINECRAFT_1_15, true)); clientbound.register(HeaderAndFooter.class, HeaderAndFooter::new, map(0x47, MINECRAFT_1_8, true), map(0x48, MINECRAFT_1_9, true), @@ -173,20 +184,23 @@ public enum StateRegistry { map(0x49, MINECRAFT_1_12, true), map(0x4A, MINECRAFT_1_12_1, true), map(0x4E, MINECRAFT_1_13, true), - map(0x53, MINECRAFT_1_14, true)); + map(0x53, MINECRAFT_1_14, true), + map(0x54, MINECRAFT_1_15, true)); clientbound.register(TitlePacket.class, TitlePacket::new, map(0x45, MINECRAFT_1_8, true), map(0x45, MINECRAFT_1_9, true), map(0x47, MINECRAFT_1_12, true), map(0x48, MINECRAFT_1_12_1, true), map(0x4B, MINECRAFT_1_13, true), - map(0x4F, MINECRAFT_1_14, true)); + map(0x4F, MINECRAFT_1_14, true), + map(0x50, MINECRAFT_1_15, true)); clientbound.register(PlayerListItem.class, PlayerListItem::new, map(0x38, MINECRAFT_1_8, false), map(0x2D, MINECRAFT_1_9, false), map(0x2E, MINECRAFT_1_12_1, false), map(0x30, MINECRAFT_1_13, false), - map(0x33, MINECRAFT_1_14, false)); + map(0x33, MINECRAFT_1_14, false), + map(0x34, MINECRAFT_1_15, false)); } }, LOGIN { 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..b249bcf9c 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 @@ -12,11 +12,13 @@ public class JoinGame implements MinecraftPacket { private int entityId; private short gamemode; private int dimension; + private long partialHashedSeed; // 1.15+ private short difficulty; private short maxPlayers; private @Nullable String levelType; private int viewDistance; //1.14+ private boolean reducedDebugInfo; + private boolean mystery; public int getEntityId() { return entityId; @@ -42,6 +44,10 @@ public class JoinGame implements MinecraftPacket { this.dimension = dimension; } + public long getPartialHashedSeed() { + return partialHashedSeed; + } + public short getDifficulty() { return difficulty; } @@ -91,6 +97,7 @@ public class JoinGame implements MinecraftPacket { + "entityId=" + entityId + ", gamemode=" + gamemode + ", dimension=" + dimension + + ", partialHashedSeed=" + partialHashedSeed + ", difficulty=" + difficulty + ", maxPlayers=" + maxPlayers + ", levelType='" + levelType + '\'' @@ -111,12 +118,18 @@ public class JoinGame implements MinecraftPacket { if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) { this.difficulty = buf.readUnsignedByte(); } + if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) { + this.partialHashedSeed = buf.readLong(); + } this.maxPlayers = buf.readUnsignedByte(); this.levelType = ProtocolUtils.readString(buf, 16); if (version.compareTo(ProtocolVersion.MINECRAFT_1_14) >= 0) { this.viewDistance = ProtocolUtils.readVarInt(buf); } this.reducedDebugInfo = buf.readBoolean(); + if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) { + this.mystery = buf.readBoolean(); + } } @Override @@ -131,6 +144,9 @@ public class JoinGame implements MinecraftPacket { if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) { buf.writeByte(difficulty); } + if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) { + buf.writeLong(partialHashedSeed); + } buf.writeByte(maxPlayers); if (levelType == null) { throw new IllegalStateException("No level type specified."); @@ -140,6 +156,9 @@ public class JoinGame implements MinecraftPacket { ProtocolUtils.writeVarInt(buf,viewDistance); } buf.writeBoolean(reducedDebugInfo); + if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) { + buf.writeBoolean(mystery); + } } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java index 4979954fe..847a722c0 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java @@ -9,6 +9,7 @@ import io.netty.buffer.ByteBuf; public class Respawn implements MinecraftPacket { private int dimension; + private long partialHashedSeed; private short difficulty; private short gamemode; private String levelType = ""; @@ -16,8 +17,10 @@ public class Respawn implements MinecraftPacket { public Respawn() { } - public Respawn(int dimension, short difficulty, short gamemode, String levelType) { + public Respawn(int dimension, long partialHashedSeed, short difficulty, short gamemode, + String levelType) { this.dimension = dimension; + this.partialHashedSeed = partialHashedSeed; this.difficulty = difficulty; this.gamemode = gamemode; this.levelType = levelType; @@ -31,6 +34,14 @@ public class Respawn implements MinecraftPacket { this.dimension = dimension; } + public long getPartialHashedSeed() { + return partialHashedSeed; + } + + public void setPartialHashedSeed(long partialHashedSeed) { + this.partialHashedSeed = partialHashedSeed; + } + public short getDifficulty() { return difficulty; } @@ -59,6 +70,7 @@ public class Respawn implements MinecraftPacket { public String toString() { return "Respawn{" + "dimension=" + dimension + + ", partialHashedSeed=" + partialHashedSeed + ", difficulty=" + difficulty + ", gamemode=" + gamemode + ", levelType='" + levelType + '\'' @@ -71,6 +83,9 @@ public class Respawn implements MinecraftPacket { if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) { this.difficulty = buf.readUnsignedByte(); } + if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) { + this.partialHashedSeed = buf.readLong(); + } this.gamemode = buf.readUnsignedByte(); this.levelType = ProtocolUtils.readString(buf, 16); } @@ -81,6 +96,9 @@ public class Respawn implements MinecraftPacket { if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) { buf.writeByte(difficulty); } + if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) { + buf.writeLong(partialHashedSeed); + } buf.writeByte(gamemode); ProtocolUtils.writeString(buf, levelType); } From c3068ea26bc608d30f5966744425686ce2937708 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sat, 14 Dec 2019 17:43:42 -0500 Subject: [PATCH 27/76] Rename mystery field in JoinGame --- .../com/velocitypowered/proxy/protocol/packet/JoinGame.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 b249bcf9c..b422d9876 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 @@ -18,7 +18,7 @@ public class JoinGame implements MinecraftPacket { private @Nullable String levelType; private int viewDistance; //1.14+ private boolean reducedDebugInfo; - private boolean mystery; + private boolean showRespawnScreen; public int getEntityId() { return entityId; @@ -128,7 +128,7 @@ public class JoinGame implements MinecraftPacket { } this.reducedDebugInfo = buf.readBoolean(); if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) { - this.mystery = buf.readBoolean(); + this.showRespawnScreen = buf.readBoolean(); } } @@ -157,7 +157,7 @@ public class JoinGame implements MinecraftPacket { } buf.writeBoolean(reducedDebugInfo); if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) { - buf.writeBoolean(mystery); + buf.writeBoolean(showRespawnScreen); } } From 1e50615993345de576507483759157db8fd0a635 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 17 Dec 2019 13:57:23 -0500 Subject: [PATCH 28/76] Minecraft 1.15.1 --- .../java/com/velocitypowered/api/network/ProtocolVersion.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 8b5b45f15..beb2e0b32 100644 --- a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java +++ b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java @@ -32,7 +32,8 @@ public enum ProtocolVersion { MINECRAFT_1_14_2(485, "1.14.2"), MINECRAFT_1_14_3(490, "1.14.3"), MINECRAFT_1_14_4(498, "1.14.4"), - MINECRAFT_1_15(573, "1.15"); + MINECRAFT_1_15(573, "1.15"), + MINECRAFT_1_15_1(575, "1.15.1"); private final int protocol; private final String name; From 78af6f7cdfa9c82c9dcfba1e6d7162128ce0f05b Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Mon, 23 Dec 2019 01:59:15 -0500 Subject: [PATCH 29/76] Velocity 1.0.4 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 945fee619..f310a6195 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ plugins { allprojects { group 'com.velocitypowered' - version '1.0.4-SNAPSHOT' + version '1.0.4' sourceCompatibility = 1.8 targetCompatibility = 1.8 From ad8c6677ed4687fb459ddf270a737a3fb005e65a Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Mon, 23 Dec 2019 02:02:39 -0500 Subject: [PATCH 30/76] Begin 1.0.5-SNAPSHOT --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index f310a6195..636f94fb6 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ plugins { allprojects { group 'com.velocitypowered' - version '1.0.4' + version '1.0.5-SNAPSHOT' sourceCompatibility = 1.8 targetCompatibility = 1.8 From a1577dd8a5ba8fc637c3dae151dab87f85e2e5ac Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 21 Jan 2020 13:28:01 -0500 Subject: [PATCH 31/76] Minecraft 1.15.2 --- .../java/com/velocitypowered/api/network/ProtocolVersion.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 beb2e0b32..4d6dcd85d 100644 --- a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java +++ b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java @@ -33,7 +33,8 @@ public enum ProtocolVersion { MINECRAFT_1_14_3(490, "1.14.3"), MINECRAFT_1_14_4(498, "1.14.4"), MINECRAFT_1_15(573, "1.15"), - MINECRAFT_1_15_1(575, "1.15.1"); + MINECRAFT_1_15_1(575, "1.15.1"), + MINECRAFT_1_15_2(578, "1.15.2"); private final int protocol; private final String name; From 0c7a9957678f9546f97842d28158053ef7435910 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Mon, 20 Jan 2020 17:37:21 -0500 Subject: [PATCH 32/76] Don't attempt to retain the buffer if it goes to a closed connection --- .../proxy/connection/client/ClientPlaySessionHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 e9d099d68..aa5ff5329 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 @@ -273,7 +273,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { } MinecraftConnection smc = serverConnection.getConnection(); - if (smc != null && serverConnection.getPhase().consideredComplete()) { + if (smc != null && !smc.isClosed() && serverConnection.getPhase().consideredComplete()) { smc.write(buf.retain()); } } From 8fba9d7438c89fcbb99f6c35f41765c57d5c3267 Mon Sep 17 00:00:00 2001 From: Crypnotic Date: Sun, 9 Feb 2020 13:40:15 -0600 Subject: [PATCH 33/76] Add cleanServerName to VelocityConfiguration to remove quotes and other unforeseen characters from server names before registration --- .../proxy/config/VelocityConfiguration.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java index 6fd13239e..3bca8dcb5 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java @@ -467,7 +467,7 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi Map servers = new HashMap<>(); for (Map.Entry entry : toml.entrySet()) { if (entry.getValue() instanceof String) { - servers.put(entry.getKey(), (String) entry.getValue()); + servers.put(cleanServerName(entry.getKey()), (String) entry.getValue()); } else { if (!entry.getKey().equalsIgnoreCase("try")) { throw new IllegalArgumentException( @@ -501,6 +501,19 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi this.attemptConnectionOrder = attemptConnectionOrder; } + /** + * TOML requires keys to match a regex of {@code [A-Za-z0-9_-]} unless it is wrapped in + * quotes; however, the TOML parser returns the key with the quotes so we need to clean the + * server name before we pass it onto server registration to keep proper server name behavior. + * + * @param name the server name to clean + * + * @return the cleaned server name + */ + private String cleanServerName(String name) { + return name.replace("\"", ""); + } + @Override public String toString() { return "Servers{" From 29168ae549454ca2d0092d5dcb1d4e2d78e3a3a9 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Thu, 13 Feb 2020 12:57:48 -0500 Subject: [PATCH 34/76] Update to new bStats revision --- .../java/com/velocitypowered/proxy/Metrics.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/Metrics.java b/proxy/src/main/java/com/velocitypowered/proxy/Metrics.java index 6f2dfa535..4edfb5171 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/Metrics.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/Metrics.java @@ -35,7 +35,7 @@ import org.apache.logging.log4j.Logger; public class Metrics { // The version of this bStats class - private static final int B_STATS_VERSION = 1; + private static final int B_STATS_METRICS_REVISION = 2; // The url to which the data is sent private static final String URL = "https://bstats.org/submitData/server-implementation"; @@ -49,6 +49,9 @@ public class Metrics { // The name of the server software private final String name; + // The plugin ID for the server software as assigned by bStats. + private final int pluginId; + // The uuid of the server private final String serverUuid; @@ -60,13 +63,15 @@ public class Metrics { /** * Class constructor. * @param name The name of the server software. + * @param pluginId The plugin ID for the server software as assigned by bStats. * @param serverUuid The uuid of the server. * @param logFailedRequests Whether failed requests should be logged or not. * @param server The Velocity server instance. */ - private Metrics(String name, String serverUuid, boolean logFailedRequests, + private Metrics(String name, int pluginId, String serverUuid, boolean logFailedRequests, VelocityServer server) { this.name = name; + this.pluginId = pluginId; this.serverUuid = serverUuid; Metrics.logFailedRequests = logFailedRequests; this.server = server; @@ -114,6 +119,8 @@ public class Metrics { JsonObject data = new JsonObject(); data.addProperty("pluginName", name); // Append the name of the server software + data.addProperty("id", pluginId); + data.addProperty("metricsRevision", B_STATS_METRICS_REVISION); JsonArray customCharts = new JsonArray(); for (CustomChart customChart : charts) { // Add the data of the custom charts @@ -569,7 +576,7 @@ public class Metrics { boolean logFailedRequests = metricsConfig.isLogFailure(); // Only start Metrics, if it's enabled in the config if (metricsConfig.isEnabled()) { - Metrics metrics = new Metrics("Velocity", serverUuid, logFailedRequests, server); + Metrics metrics = new Metrics("Velocity", 4752, serverUuid, logFailedRequests, server); metrics.addCustomChart( new Metrics.SingleLineChart("players", server::getPlayerCount) From 11dfbd3b9ec04ddde85689d5115f4c9ed49c48aa Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Thu, 13 Feb 2020 13:04:17 -0500 Subject: [PATCH 35/76] This is Velocity 1.0.5 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 636f94fb6..5190f6403 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ plugins { allprojects { group 'com.velocitypowered' - version '1.0.5-SNAPSHOT' + version '1.0.5' sourceCompatibility = 1.8 targetCompatibility = 1.8 From 5b75927586bcb3b5fb340d982e03cb6663867632 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Thu, 13 Feb 2020 13:04:50 -0500 Subject: [PATCH 36/76] Bump to 1.0.6-SNAPSHOT for further development --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 5190f6403..10e8110ec 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ plugins { allprojects { group 'com.velocitypowered' - version '1.0.5' + version '1.0.6-SNAPSHOT' sourceCompatibility = 1.8 targetCompatibility = 1.8 From 7d1770d37e4c0ab048b2cf426bf12f149cfdae1c Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Thu, 13 Feb 2020 13:32:22 -0500 Subject: [PATCH 37/76] Deal with potentially nullable player sample entries --- .../java/com/velocitypowered/api/proxy/server/ServerPing.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java b/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java index f571c4517..2522f4b7b 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java @@ -114,7 +114,7 @@ public final class ServerPing { if (players != null) { builder.onlinePlayers = players.online; builder.maximumPlayers = players.max; - builder.samplePlayers.addAll(players.sample); + builder.samplePlayers.addAll(players.getSample()); } else { builder.nullOutPlayers = true; } @@ -354,7 +354,7 @@ public final class ServerPing { } public List getSample() { - return sample; + return sample == null ? ImmutableList.of() : sample; } @Override From 5c6163d8cd1f2e5c6143fd34d87be4798b1659ac Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Thu, 13 Feb 2020 19:20:53 -0500 Subject: [PATCH 38/76] Use more obvious/broken-down test cases for topological sort tests --- .../util/PluginDependencyUtilsTest.java | 95 +++++++++++-------- 1 file changed, 53 insertions(+), 42 deletions(-) diff --git a/proxy/src/test/java/com/velocitypowered/proxy/plugin/util/PluginDependencyUtilsTest.java b/proxy/src/test/java/com/velocitypowered/proxy/plugin/util/PluginDependencyUtilsTest.java index 99033c689..5709b2864 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/plugin/util/PluginDependencyUtilsTest.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/plugin/util/PluginDependencyUtilsTest.java @@ -14,49 +14,64 @@ import org.junit.jupiter.api.Test; class PluginDependencyUtilsTest { - private static final PluginDescription NO_DEPENDENCY_1_EXAMPLE = testDescription("example"); - private static final PluginDescription NEVER_DEPENDED = testDescription("and-again"); - private static final PluginDescription SOFT_DEPENDENCY_EXISTS = testDescription("soft", - ImmutableList.of(new PluginDependency("example", "", true))); - private static final PluginDescription SOFT_DEPENDENCY_DOES_NOT_EXIST = testDescription("fluffy", - ImmutableList.of(new PluginDependency("i-dont-exist", "", false))); - private static final PluginDescription MULTI_DEPENDENCY = testDescription("multi-depend", - ImmutableList.of( - new PluginDependency("example", "", false) - ) - ); - private static final PluginDescription TEST_WITH_DUPLICATE_DEPEND = testDescription("dup-depend", - ImmutableList.of( - new PluginDependency("multi-depend", "", false) - ) - ); + private static final PluginDescription NO_DEPENDENCY = testDescription("trivial"); + private static final PluginDescription NO_DEPENDENCY_2 = testDescription("trivial2"); + private static final PluginDescription HAS_DEPENDENCY_1 = testDescription("dependent1", + new PluginDependency("trivial", null, false)); + private static final PluginDescription HAS_DEPENDENCY_2 = testDescription("dependent2", + new PluginDependency("dependent1", null, false)); + private static final PluginDescription HAS_DEPENDENCY_3 = testDescription("dependent3", + new PluginDependency("trivial", null, false)); private static final PluginDescription CIRCULAR_DEPENDENCY_1 = testDescription("circle", - ImmutableList.of(new PluginDependency("oval", "", false))); + new PluginDependency("oval", "", false)); private static final PluginDescription CIRCULAR_DEPENDENCY_2 = testDescription("oval", - ImmutableList.of(new PluginDependency("circle", "", false))); - - private static final ImmutableList EXPECTED = ImmutableList.of( - NEVER_DEPENDED, - NO_DEPENDENCY_1_EXAMPLE, - MULTI_DEPENDENCY, - TEST_WITH_DUPLICATE_DEPEND, - SOFT_DEPENDENCY_DOES_NOT_EXIST, - SOFT_DEPENDENCY_EXISTS - ); + new PluginDependency("circle", "", false)); @Test - void sortCandidates() throws Exception { + void sortCandidatesTrivial() throws Exception { List descriptionList = new ArrayList<>(); - descriptionList.add(NO_DEPENDENCY_1_EXAMPLE); - descriptionList.add(NEVER_DEPENDED); - descriptionList.add(SOFT_DEPENDENCY_DOES_NOT_EXIST); - descriptionList.add(SOFT_DEPENDENCY_EXISTS); - descriptionList.add(MULTI_DEPENDENCY); - descriptionList.add(TEST_WITH_DUPLICATE_DEPEND); - descriptionList.sort(Comparator.comparing(PluginDescription::getId)); + assertEquals(descriptionList, PluginDependencyUtils.sortCandidates(descriptionList)); + } - assertEquals(EXPECTED, PluginDependencyUtils.sortCandidates(descriptionList)); + @Test + void sortCandidatesSingleton() throws Exception { + List plugins = ImmutableList.of(NO_DEPENDENCY); + assertEquals(plugins, PluginDependencyUtils.sortCandidates(plugins)); + } + + @Test + void sortCandidatesBasicDependency() throws Exception { + List plugins = ImmutableList.of(HAS_DEPENDENCY_1, NO_DEPENDENCY); + List expected = ImmutableList.of(NO_DEPENDENCY, HAS_DEPENDENCY_1); + assertEquals(expected, PluginDependencyUtils.sortCandidates(plugins)); + } + + @Test + void sortCandidatesNestedDependency() throws Exception { + List plugins = ImmutableList.of(HAS_DEPENDENCY_1, HAS_DEPENDENCY_2, + NO_DEPENDENCY); + List expected = ImmutableList.of(NO_DEPENDENCY, HAS_DEPENDENCY_1, + HAS_DEPENDENCY_2); + assertEquals(expected, PluginDependencyUtils.sortCandidates(plugins)); + } + + @Test + void sortCandidatesTypical() throws Exception { + List plugins = ImmutableList.of(HAS_DEPENDENCY_2, NO_DEPENDENCY_2, + HAS_DEPENDENCY_1, NO_DEPENDENCY); + List expected = ImmutableList.of(NO_DEPENDENCY, HAS_DEPENDENCY_1, + HAS_DEPENDENCY_2, NO_DEPENDENCY_2); + assertEquals(expected, PluginDependencyUtils.sortCandidates(plugins)); + } + + @Test + void sortCandidatesMultiplePluginsDependentOnOne() throws Exception { + List plugins = ImmutableList.of(HAS_DEPENDENCY_3, HAS_DEPENDENCY_1, + NO_DEPENDENCY); + List expected = ImmutableList.of(NO_DEPENDENCY, HAS_DEPENDENCY_3, + HAS_DEPENDENCY_1); + assertEquals(expected, PluginDependencyUtils.sortCandidates(plugins)); } @Test @@ -65,14 +80,10 @@ class PluginDependencyUtilsTest { assertThrows(IllegalStateException.class, () -> PluginDependencyUtils.sortCandidates(descs)); } - private static PluginDescription testDescription(String id) { - return testDescription(id, ImmutableList.of()); - } - - private static PluginDescription testDescription(String id, List dependencies) { + private static PluginDescription testDescription(String id, PluginDependency... dependencies) { return new VelocityPluginDescription( id, "tuxed", "0.1", null, null, ImmutableList.of(), - dependencies, null + ImmutableList.copyOf(dependencies), null ); } } From 6ec6beedbdd290d094fd6e011e1d20429947121b Mon Sep 17 00:00:00 2001 From: Hugo Manrique Date: Fri, 13 Mar 2020 18:17:06 +0100 Subject: [PATCH 39/76] Remove tasks from lookup map upon completion --- .../com/velocitypowered/proxy/scheduler/VelocityScheduler.java | 1 + 1 file changed, 1 insertion(+) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java b/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java index 0bd4bcd3d..f0605f0b6 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java @@ -187,6 +187,7 @@ public class VelocityScheduler implements Scheduler { } catch (Exception e) { Log.logger.error("Exception in task {} by plugin {}", runnable, plugin, e); } finally { + onFinish(); currentTaskThread = null; } }); From d7224229473819617b555d22a8da1783ed335fe7 Mon Sep 17 00:00:00 2001 From: Hugo Manrique Date: Fri, 13 Mar 2020 18:52:15 +0100 Subject: [PATCH 40/76] Only finish non-repeating tasks --- .../velocitypowered/proxy/scheduler/VelocityScheduler.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java b/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java index f0605f0b6..1bec0d119 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java @@ -3,7 +3,6 @@ package com.velocitypowered.proxy.scheduler; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; -import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; @@ -187,7 +186,9 @@ public class VelocityScheduler implements Scheduler { } catch (Exception e) { Log.logger.error("Exception in task {} by plugin {}", runnable, plugin, e); } finally { - onFinish(); + if (repeat == 0) { + onFinish(); + } currentTaskThread = null; } }); From 8a29ff0e873aa1b208e4c0d98ff2a16f5381eb39 Mon Sep 17 00:00:00 2001 From: kashike Date: Sun, 29 Mar 2020 16:13:43 -0700 Subject: [PATCH 41/76] update text to 3.0.3 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 10e8110ec..617b1e1e2 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,7 @@ allprojects { ext { // dependency versions - textVersion = '3.0.2' + textVersion = '3.0.3' junitVersion = '5.3.0-M1' slf4jVersion = '1.7.25' log4jVersion = '2.11.2' From 8df4467392e1136c14edcfeae0031c120aecb789 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Mon, 17 Feb 2020 19:34:22 -0500 Subject: [PATCH 42/76] Upon connection exception, discard all incoming packets instead --- .../protocol/netty/MinecraftDecoder.java | 40 +++++++++++++++---- .../proxy/util/except/QuietException.java | 17 ++++++++ 2 files changed, 49 insertions(+), 8 deletions(-) create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/util/except/QuietException.java diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftDecoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftDecoder.java index a6f616941..0204aefb3 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftDecoder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftDecoder.java @@ -5,6 +5,7 @@ import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.StateRegistry; +import com.velocitypowered.proxy.util.except.QuietException; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.CorruptedFrameException; @@ -13,6 +14,11 @@ import java.util.List; public class MinecraftDecoder extends MessageToMessageDecoder { + private static final boolean DEBUG = Boolean.getBoolean("velocity.packet-decode-logging"); + private static final QuietException DECODE_FAILED = + new QuietException("A packet did not decode successfully (invalid data). If you are a " + + "developer, launch Velocity with -Dvelocity.packet-decode-logging=true to see more."); + private final ProtocolUtils.Direction direction; private StateRegistry state; private StateRegistry.PacketRegistry.ProtocolRegistry registry; @@ -46,21 +52,39 @@ public class MinecraftDecoder extends MessageToMessageDecoder { try { packet.decode(msg, direction, registry.version); } catch (Exception e) { - throw new CorruptedFrameException( - "Error decoding " + packet.getClass() + " Direction " + direction - + " Protocol " + registry.version + " State " + state + " ID " + Integer - .toHexString(packetId), e); + throw handleDecodeFailure(e, packet, packetId); } + if (msg.isReadable()) { - throw new CorruptedFrameException( - "Did not read full packet for " + packet.getClass() + " Direction " + direction - + " Protocol " + registry.version + " State " + state + " ID " + Integer - .toHexString(packetId)); + throw handleNotReadEnough(packet, packetId); } out.add(packet); } } + private Exception handleNotReadEnough(MinecraftPacket packet, int packetId) { + if (DEBUG) { + return new CorruptedFrameException("Did not read full packet for " + packet.getClass() + " " + + getExtraConnectionDetail(packetId)); + } else { + return DECODE_FAILED; + } + } + + private Exception handleDecodeFailure(Exception cause, MinecraftPacket packet, int packetId) { + if (DEBUG) { + return new CorruptedFrameException( + "Error decoding " + packet.getClass() + " " + getExtraConnectionDetail(packetId), cause); + } else { + return DECODE_FAILED; + } + } + + private String getExtraConnectionDetail(int packetId) { + return "Direction " + direction + " Protocol " + registry.version + " State " + state + + " ID " + Integer.toHexString(packetId); + } + public void setProtocolVersion(ProtocolVersion protocolVersion) { this.registry = direction.getProtocolRegistry(state, protocolVersion); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/except/QuietException.java b/proxy/src/main/java/com/velocitypowered/proxy/util/except/QuietException.java new file mode 100644 index 000000000..d16a00724 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/except/QuietException.java @@ -0,0 +1,17 @@ +package com.velocitypowered.proxy.util.except; + +/** + * A special-purpose exception thrown when we want to indicate an error condition but do not want + * to see a large stack trace in logs. + */ +public class QuietException extends RuntimeException { + + public QuietException(String message) { + super(message); + } + + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } +} From 386e7e94c826a8c7828f456ddd1c8cb21e3515a2 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sat, 11 Apr 2020 21:30:09 -0400 Subject: [PATCH 43/76] Velocity 1.0.6 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 617b1e1e2..4472dabc9 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ plugins { allprojects { group 'com.velocitypowered' - version '1.0.6-SNAPSHOT' + version '1.0.6' sourceCompatibility = 1.8 targetCompatibility = 1.8 From 6ad8381645979ce7f05b0ff8635b75d5dfa42b8b Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sat, 11 Apr 2020 21:30:25 -0400 Subject: [PATCH 44/76] Velocity 1.0.7-SNAPSHOT --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 4472dabc9..04404f345 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ plugins { allprojects { group 'com.velocitypowered' - version '1.0.6' + version '1.0.7-SNAPSHOT' sourceCompatibility = 1.8 targetCompatibility = 1.8 From 2e38e0e1cb62b42bfe456355f379b6577f12c07f Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sat, 11 Apr 2020 21:52:01 -0400 Subject: [PATCH 45/76] Properly fix the previous patch --- .../proxy/connection/MinecraftConnection.java | 23 ++++++++++++++++--- .../proxy/network/netty/DiscardHandler.java | 17 ++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/network/netty/DiscardHandler.java diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java index beefa6b05..d36846cc7 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java @@ -16,6 +16,7 @@ import com.velocitypowered.natives.encryption.VelocityCipher; import com.velocitypowered.natives.encryption.VelocityCipherFactory; import com.velocitypowered.natives.util.Natives; import com.velocitypowered.proxy.VelocityServer; +import com.velocitypowered.proxy.network.netty.DiscardHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.netty.MinecraftCipherDecoder; @@ -131,8 +132,8 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { try { sessionHandler.exception(cause); } catch (Exception ex) { - logger.error("{}: exception handling exception", (association != null ? association : - channel.remoteAddress()), cause); + logger.error("{}: exception handling exception in {}", + (association != null ? association : channel.remoteAddress()), sessionHandler, cause); } } @@ -140,10 +141,11 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { if (cause instanceof ReadTimeoutException) { logger.error("{}: read timed out", association); } else { - logger.error("{}: exception encountered", association, cause); + logger.error("{}: exception encountered in {}", association, sessionHandler, cause); } } + ctx.pipeline().addBefore(MINECRAFT_DECODER, "discard", DiscardHandler.HANDLER); ctx.close(); } } @@ -155,6 +157,10 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { } } + private void ensureInEventLoop() { + Preconditions.checkState(this.channel.eventLoop().inEventLoop(), "Not in event loop"); + } + public EventLoop eventLoop() { return channel.eventLoop(); } @@ -233,6 +239,8 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { * @param autoReading whether or not we should read data automatically */ public void setAutoReading(boolean autoReading) { + ensureInEventLoop(); + channel.config().setAutoRead(autoReading); if (autoReading) { // For some reason, the channel may not completely read its queued contents once autoread @@ -249,6 +257,8 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { * @param state the new state */ public void setState(StateRegistry state) { + ensureInEventLoop(); + this.state = state; this.channel.pipeline().get(MinecraftEncoder.class).setState(state); this.channel.pipeline().get(MinecraftDecoder.class).setState(state); @@ -263,6 +273,8 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { * @param protocolVersion the protocol version to use */ public void setProtocolVersion(ProtocolVersion protocolVersion) { + ensureInEventLoop(); + this.protocolVersion = protocolVersion; this.nextProtocolVersion = protocolVersion; if (protocolVersion != ProtocolVersion.LEGACY) { @@ -284,6 +296,8 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { * @param sessionHandler the handler to use */ public void setSessionHandler(MinecraftSessionHandler sessionHandler) { + ensureInEventLoop(); + if (this.sessionHandler != null) { this.sessionHandler.deactivated(); } @@ -302,6 +316,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { */ public void setCompressionThreshold(int threshold) { ensureOpen(); + ensureInEventLoop(); if (threshold == -1) { channel.pipeline().remove(COMPRESSION_DECODER); @@ -325,6 +340,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { */ public void enableEncryption(byte[] secret) throws GeneralSecurityException { ensureOpen(); + ensureInEventLoop(); SecretKey key = new SecretKeySpec(secret, "AES"); @@ -342,6 +358,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { } public void setAssociation(MinecraftConnectionAssociation association) { + ensureInEventLoop(); this.association = association; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/netty/DiscardHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/network/netty/DiscardHandler.java new file mode 100644 index 000000000..b4421fd96 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/netty/DiscardHandler.java @@ -0,0 +1,17 @@ +package com.velocitypowered.proxy.network.netty; + +import io.netty.channel.ChannelHandler.Sharable; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.util.ReferenceCountUtil; + +@Sharable +public class DiscardHandler extends ChannelInboundHandlerAdapter { + + public static final DiscardHandler HANDLER = new DiscardHandler(); + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + ReferenceCountUtil.release(msg); + } +} \ No newline at end of file From 3e24d7ea8d59da24157247e1cf970c200315babf Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sat, 11 Apr 2020 21:56:07 -0400 Subject: [PATCH 46/76] Velocity 1.0.7 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 04404f345..6287c47f9 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ plugins { allprojects { group 'com.velocitypowered' - version '1.0.7-SNAPSHOT' + version '1.0.7' sourceCompatibility = 1.8 targetCompatibility = 1.8 From 5d3479aae5b0e59adfb6699b713b4554fceeabc5 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sat, 11 Apr 2020 21:56:28 -0400 Subject: [PATCH 47/76] Velocity 1.0.8-SNAPSHOT --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 6287c47f9..e379c5a11 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ plugins { allprojects { group 'com.velocitypowered' - version '1.0.7' + version '1.0.8-SNAPSHOT' sourceCompatibility = 1.8 targetCompatibility = 1.8 From 558c1585924c805b49d2d83688301e4c9de6c8d2 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sun, 12 Apr 2020 17:05:36 -0400 Subject: [PATCH 48/76] Close one last "proxy crasher" loophole --- .../proxy/connection/MinecraftConnection.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java index d36846cc7..53c83310a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java @@ -145,7 +145,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { } } - ctx.pipeline().addBefore(MINECRAFT_DECODER, "discard", DiscardHandler.HANDLER); + installDiscardHandler(ctx); ctx.close(); } } @@ -161,6 +161,14 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { Preconditions.checkState(this.channel.eventLoop().inEventLoop(), "Not in event loop"); } + private void installDiscardHandler(ChannelHandlerContext ctx) { + ctx.pipeline().addBefore(MINECRAFT_DECODER, "discard", DiscardHandler.HANDLER); + } + + private void installDiscardHandler() { + channel.pipeline().addBefore(MINECRAFT_DECODER, "discard", DiscardHandler.HANDLER); + } + public EventLoop eventLoop() { return channel.eventLoop(); } @@ -201,6 +209,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { public void closeWith(Object msg) { if (channel.isActive()) { knownDisconnect = true; + installDiscardHandler(); channel.writeAndFlush(msg).addListener(ChannelFutureListener.CLOSE); } } @@ -210,6 +219,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { */ public void close() { if (channel.isActive()) { + installDiscardHandler(); channel.close(); } } From 4494033fbe3b5c07ca0dbaab753c43c9c6d530b4 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sun, 12 Apr 2020 17:20:38 -0400 Subject: [PATCH 49/76] No need to register multiple times --- .../proxy/connection/MinecraftConnection.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java index 53c83310a..d3214787c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java @@ -162,11 +162,15 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { } private void installDiscardHandler(ChannelHandlerContext ctx) { - ctx.pipeline().addBefore(MINECRAFT_DECODER, "discard", DiscardHandler.HANDLER); + if (ctx.pipeline().get("discard") == null) { + ctx.pipeline().addBefore(MINECRAFT_DECODER, "discard", DiscardHandler.HANDLER); + } } private void installDiscardHandler() { - channel.pipeline().addBefore(MINECRAFT_DECODER, "discard", DiscardHandler.HANDLER); + if (channel.pipeline().get("discard") == null) { + channel.pipeline().addBefore(MINECRAFT_DECODER, "discard", DiscardHandler.HANDLER); + } } public EventLoop eventLoop() { From 187a625aa478a1fbef96ea0e68ec7d94b3bc87e4 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Wed, 22 Apr 2020 10:27:08 -0400 Subject: [PATCH 50/76] Bump Netty to 4.1.49.Final This is intended to fix netty/netty#10165 directly, and supersede our current workaround. --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index e379c5a11..45dfb364d 100644 --- a/build.gradle +++ b/build.gradle @@ -24,7 +24,7 @@ allprojects { junitVersion = '5.3.0-M1' slf4jVersion = '1.7.25' log4jVersion = '2.11.2' - nettyVersion = '4.1.37.Final' + nettyVersion = '4.1.49.Final' guavaVersion = '25.1-jre' checkerFrameworkVersion = '2.7.0' From bb129a3d0ba1b32fd77fad01727f4f2b305d8233 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sun, 19 Apr 2020 03:39:15 -0400 Subject: [PATCH 51/76] Fix rare race condition with transitioning If the player unexpectedly disconnects after ServerConnectEvent is fired, but before the connection transitions to the new player, Velocity would throw an exception thinking the connection was not present. This is the correct behavior, but the behavior is very surprising. Instead we will double-check to ensure the connection has not been lost before we continue with transitioning to the new server. --- .../proxy/connection/backend/TransitionSessionHandler.java | 7 +++++++ 1 file changed, 7 insertions(+) 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 6f32e7ad1..9bd6a35ff 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 @@ -82,6 +82,13 @@ public class TransitionSessionHandler implements MinecraftSessionHandler { server.getEventManager() .fire(new ServerConnectedEvent(serverConn.getPlayer(), serverConn.getServer())) .whenCompleteAsync((x, error) -> { + // Make sure we can still transition (player might have disconnected here). + if (!serverConn.isActive()) { + // Connection is obsolete. + serverConn.disconnect(); + return; + } + // Strap on the ClientPlaySessionHandler if required. ClientPlaySessionHandler playHandler; if (serverConn.getPlayer().getMinecraftConnection().getSessionHandler() From 88641662d8f84d9a898f7835d745b73763718db1 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Thu, 23 Apr 2020 16:19:49 -0400 Subject: [PATCH 52/76] Implement brigadier:long argument type, fixes #295 --- api/build.gradle | 1 + proxy/build.gradle | 2 +- .../brigadier/ArgumentPropertyRegistry.java | 3 ++ .../packet/brigadier/DummyProperty.java | 10 ++--- .../LongArgumentPropertySerializer.java | 40 +++++++++++++++++++ 5 files changed, 50 insertions(+), 6 deletions(-) create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/LongArgumentPropertySerializer.java diff --git a/api/build.gradle b/api/build.gradle index c46a2360c..758a9eb30 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -59,6 +59,7 @@ artifacts { javadoc { options.encoding = 'UTF-8' options.charSet = 'UTF-8' + options.source = '8' options.links( 'http://www.slf4j.org/apidocs/', 'https://google.github.io/guava/releases/25.1-jre/api/docs/', diff --git a/proxy/build.gradle b/proxy/build.gradle index 54ddc7b86..b4a59f90d 100644 --- a/proxy/build.gradle +++ b/proxy/build.gradle @@ -64,7 +64,7 @@ dependencies { compile 'it.unimi.dsi:fastutil:8.2.2' compile 'net.kyori:event-method-asm:3.0.0' - compile 'com.mojang:brigadier:1.0.15' + compile 'com.mojang:brigadier:1.0.17' testCompile "org.junit.jupiter:junit-jupiter-api:${junitVersion}" testCompile "org.junit.jupiter:junit-jupiter-engine:${junitVersion}" diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java index 9560a8b29..e7847ec8e 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java @@ -4,6 +4,7 @@ import static com.velocitypowered.proxy.protocol.packet.brigadier.DoubleArgument import static com.velocitypowered.proxy.protocol.packet.brigadier.DummyVoidArgumentPropertySerializer.DUMMY; import static com.velocitypowered.proxy.protocol.packet.brigadier.FloatArgumentPropertySerializer.FLOAT; import static com.velocitypowered.proxy.protocol.packet.brigadier.IntegerArgumentPropertySerializer.INTEGER; +import static com.velocitypowered.proxy.protocol.packet.brigadier.LongArgumentPropertySerializer.LONG; import static com.velocitypowered.proxy.protocol.packet.brigadier.StringArgumentPropertySerializer.STRING; import com.mojang.brigadier.arguments.ArgumentType; @@ -11,6 +12,7 @@ import com.mojang.brigadier.arguments.BoolArgumentType; import com.mojang.brigadier.arguments.DoubleArgumentType; import com.mojang.brigadier.arguments.FloatArgumentType; import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.arguments.LongArgumentType; import com.mojang.brigadier.arguments.StringArgumentType; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; @@ -90,6 +92,7 @@ public class ArgumentPropertyRegistry { register("brigadier:double", DoubleArgumentType.class, DOUBLE); register("brigadier:bool", BoolArgumentType.class, VoidArgumentPropertySerializer.create(BoolArgumentType::bool)); + register("brigadier:long", LongArgumentType.class, LONG); // Minecraft argument types with extra properties dummy("minecraft:entity", ByteArgumentPropertySerializer.BYTE); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/DummyProperty.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/DummyProperty.java index 762b52686..9b514f903 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/DummyProperty.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/DummyProperty.java @@ -17,11 +17,6 @@ class DummyProperty implements ArgumentType { this.result = result; } - @Override - public T parse(StringReader reader) throws CommandSyntaxException { - throw new UnsupportedOperationException(); - } - public String getIdentifier() { return identifier; } @@ -33,4 +28,9 @@ class DummyProperty implements ArgumentType { public @Nullable T getResult() { return result; } + + @Override + public T parse(StringReader reader) { + throw new UnsupportedOperationException(); + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/LongArgumentPropertySerializer.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/LongArgumentPropertySerializer.java new file mode 100644 index 000000000..9d3b40b6d --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/LongArgumentPropertySerializer.java @@ -0,0 +1,40 @@ +package com.velocitypowered.proxy.protocol.packet.brigadier; + +import static com.velocitypowered.proxy.protocol.packet.brigadier.IntegerArgumentPropertySerializer.HAS_MAXIMUM; +import static com.velocitypowered.proxy.protocol.packet.brigadier.IntegerArgumentPropertySerializer.HAS_MINIMUM; +import static com.velocitypowered.proxy.protocol.packet.brigadier.IntegerArgumentPropertySerializer.getFlags; + +import com.mojang.brigadier.arguments.LongArgumentType; +import io.netty.buffer.ByteBuf; + +class LongArgumentPropertySerializer implements ArgumentPropertySerializer { + + static final LongArgumentPropertySerializer LONG = new LongArgumentPropertySerializer(); + + private LongArgumentPropertySerializer() { + + } + + @Override + public LongArgumentType deserialize(ByteBuf buf) { + byte flags = buf.readByte(); + long minimum = (flags & HAS_MINIMUM) != 0 ? buf.readLong() : Long.MIN_VALUE; + long maximum = (flags & HAS_MAXIMUM) != 0 ? buf.readLong() : Long.MAX_VALUE; + return LongArgumentType.longArg(minimum, maximum); + } + + @Override + public void serialize(LongArgumentType object, ByteBuf buf) { + boolean hasMinimum = object.getMinimum() != Long.MIN_VALUE; + boolean hasMaximum = object.getMaximum() != Long.MAX_VALUE; + byte flag = getFlags(hasMinimum, hasMaximum); + + buf.writeByte(flag); + if (hasMinimum) { + buf.writeLong(object.getMinimum()); + } + if (hasMaximum) { + buf.writeLong(object.getMaximum()); + } + } +} From f7e70cff20d1472bfd43c001dbb69000f61732f5 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Fri, 8 May 2020 14:03:30 -0400 Subject: [PATCH 53/76] We're well into 2020, just saying. --- .../java/com/velocitypowered/proxy/command/VelocityCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommand.java index 1c3b777c3..ec245152e 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommand.java @@ -166,7 +166,7 @@ public class VelocityCommand implements Command { .append(TextComponent.of(version.getVersion()).decoration(TextDecoration.BOLD, false)) .build(); TextComponent copyright = TextComponent - .of("Copyright 2018-2019 " + version.getVendor() + ". " + version.getName() + .of("Copyright 2018-2020 " + version.getVendor() + ". " + version.getName() + " is freely licensed under the terms of the MIT License."); source.sendMessage(velocity); source.sendMessage(copyright); From 10680f16d376371401f4a203d94c888b22f24f14 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Fri, 8 May 2020 18:16:14 -0400 Subject: [PATCH 54/76] Reject invalid tab complete command requests. --- .../proxy/protocol/packet/TabCompleteRequest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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..91a83915d 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 @@ -77,9 +77,9 @@ public class TabCompleteRequest implements MinecraftPacket { public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { if (version.compareTo(MINECRAFT_1_13) >= 0) { this.transactionId = ProtocolUtils.readVarInt(buf); - this.command = ProtocolUtils.readString(buf); + this.command = ProtocolUtils.readString(buf, Chat.MAX_SERVERBOUND_MESSAGE_LENGTH); } else { - this.command = ProtocolUtils.readString(buf); + this.command = ProtocolUtils.readString(buf, Chat.MAX_SERVERBOUND_MESSAGE_LENGTH); if (version.compareTo(MINECRAFT_1_9) >= 0) { this.assumeCommand = buf.readBoolean(); } From d37b6a361cb05f69db2608c29c4608dc6881edc7 Mon Sep 17 00:00:00 2001 From: "Five (Xer)" Date: Sat, 23 May 2020 00:18:36 +0200 Subject: [PATCH 55/76] Snapshot 20w21a --- .../api/network/ProtocolVersion.java | 3 +- proxy/build.gradle | 1 + .../client/ClientPlaySessionHandler.java | 8 +- .../proxy/protocol/ProtocolUtils.java | 82 +++++++++++++++++ .../proxy/protocol/StateRegistry.java | 7 +- .../proxy/protocol/packet/Chat.java | 30 +++++-- .../proxy/protocol/packet/JoinGame.java | 90 ++++++++++++++++--- .../proxy/protocol/packet/Respawn.java | 74 +++++++++++++-- .../protocol/packet/ServerLoginSuccess.java | 12 ++- .../brigadier/ArgumentPropertyRegistry.java | 1 + 10 files changed, 281 insertions(+), 27 deletions(-) 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 4d6dcd85d..07b35926b 100644 --- a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java +++ b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java @@ -34,7 +34,8 @@ public enum ProtocolVersion { MINECRAFT_1_14_4(498, "1.14.4"), MINECRAFT_1_15(573, "1.15"), MINECRAFT_1_15_1(575, "1.15.1"), - MINECRAFT_1_15_2(578, "1.15.2"); + MINECRAFT_1_15_2(578, "1.15.2"), + MINECRAFT_1_16(718, "1.16"); private final int protocol; private final String name; diff --git a/proxy/build.gradle b/proxy/build.gradle index b4a59f90d..91a434a11 100644 --- a/proxy/build.gradle +++ b/proxy/build.gradle @@ -63,6 +63,7 @@ dependencies { compile 'it.unimi.dsi:fastutil:8.2.2' compile 'net.kyori:event-method-asm:3.0.0' + compile 'net.kyori:nbt:1.12-1.0.0-SNAPSHOT' compile 'com.mojang:brigadier:1.0.17' 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 aa5ff5329..d0d21cbd8 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 @@ -337,10 +337,14 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { int tempDim = joinGame.getDimension() == 0 ? -1 : 0; player.getMinecraftConnection().delayedWrite( new Respawn(tempDim, joinGame.getPartialHashedSeed(), joinGame.getDifficulty(), - joinGame.getGamemode(), joinGame.getLevelType())); + joinGame.getGamemode(), joinGame.getLevelType(), joinGame.getShouldKeepPlayerData(), + joinGame.getIsDebug(), joinGame.getIsFlat(), + joinGame.getDimensionRegistryName())); player.getMinecraftConnection().delayedWrite( new Respawn(joinGame.getDimension(), joinGame.getPartialHashedSeed(), - joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType())); + joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(), + joinGame.getShouldKeepPlayerData(), joinGame.getIsDebug(), joinGame.getIsFlat(), + joinGame.getDimensionRegistryName())); } // Remove previous boss bars. These don't get cleared when sending JoinGame, thus the need to 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..c0cbc136d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java @@ -6,13 +6,20 @@ import static com.google.common.base.Preconditions.checkState; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.util.GameProfile; import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufInputStream; +import io.netty.buffer.ByteBufOutputStream; import io.netty.buffer.ByteBufUtil; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.UUID; +import net.kyori.nbt.CompoundTag; + public enum ProtocolUtils { ; private static final int DEFAULT_MAX_STRING_SIZE = 65536; // 64KiB @@ -153,6 +160,81 @@ public enum ProtocolUtils { buf.writeLong(uuid.getLeastSignificantBits()); } + /** + * Reads an UUID stored as an Integer Array from the {@code buf}. + * @param buf the buffer to read from + * @return the UUID from the buffer + */ + public static UUID readUuidIntArray(ByteBuf buf) { + long msbHigh = (long) buf.readInt() << 32; + long msbLow = (long) buf.readInt() & 0xFFFFFFFFL; + long msb = msbHigh | msbLow; + long lsbHigh = (long) buf.readInt() << 32; + long lsbLow = (long) buf.readInt() & 0xFFFFFFFFL; + long lsb = lsbHigh | lsbLow; + return new UUID(msb, lsb); + } + + /** + * Writes an UUID as an Integer Array to the {@code buf}. + * @param buf the buffer to write to + * @param uuid the UUID to write + */ + public static void writeUuidIntArray(ByteBuf buf, UUID uuid) { + buf.writeInt((int) (uuid.getMostSignificantBits() >> 32)); + buf.writeInt((int) uuid.getMostSignificantBits()); + buf.writeInt((int) (uuid.getLeastSignificantBits() >> 32)); + buf.writeInt((int) uuid.getLeastSignificantBits()); + } + + /** + * Reads a {@link net.kyori.nbt.CompoundTag} from the {@code buf}. + * @param buf the buffer to read from + * @return {@link net.kyori.nbt.CompoundTag} the CompoundTag from the buffer + */ + public static CompoundTag readCompoundTag(ByteBuf buf) { + int indexBefore = buf.readerIndex(); + byte startType = buf.readByte(); + if (startType == 0) { + return null; + } else { + buf.readerIndex(indexBefore); + try { + DataInput input = new ByteBufInputStream(buf); + byte type = input.readByte(); + if (type != 10) { + return null; + } + input.readUTF(); + CompoundTag ret = new CompoundTag(); + ret.read(input, 0); + return ret; + } catch (IOException e) { + return null; + } + } + } + + /** + * Writes a CompoundTag to the {@code buf}. + * @param buf the buffer to write to + * @param compoundTag the CompoundTag to write + */ + public static void writeCompoundTag(ByteBuf buf, CompoundTag compoundTag) { + if (compoundTag == null) { + buf.writeByte(0); + } else { + try { + DataOutput output = new ByteBufOutputStream(buf); + output.writeByte(10); + output.writeUTF(""); + compoundTag.write(output); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + /** * Writes a list of {@link com.velocitypowered.api.util.GameProfile.Property} to the buffer. * @param buf the buffer to write to 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 bae659122..07f174b51 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -6,6 +6,7 @@ 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_15; +import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_16; 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; @@ -113,13 +114,15 @@ public enum StateRegistry { map(0x0C, MINECRAFT_1_12, false), map(0x0B, MINECRAFT_1_12_1, false), map(0x0E, MINECRAFT_1_13, false), - map(0x0F, MINECRAFT_1_14, false)); + map(0x0F, MINECRAFT_1_14, false), + map(0x10, MINECRAFT_1_16, false)); serverbound.register(ResourcePackResponse.class, ResourcePackResponse::new, map(0x19, MINECRAFT_1_8, false), map(0x16, MINECRAFT_1_9, false), map(0x18, MINECRAFT_1_12, false), map(0x1D, MINECRAFT_1_13, false), - map(0x1F, MINECRAFT_1_14, false)); + map(0x1F, MINECRAFT_1_14, false), + map(0x20, MINECRAFT_1_16, false)); clientbound.register(BossBar.class, BossBar::new, map(0x0C, MINECRAFT_1_9, false), 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..b80e0f855 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 @@ -10,20 +10,25 @@ import net.kyori.text.Component; import net.kyori.text.serializer.gson.GsonComponentSerializer; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.UUID; + public class Chat implements MinecraftPacket { public static final byte CHAT_TYPE = (byte) 0; public static final int MAX_SERVERBOUND_MESSAGE_LENGTH = 256; + public static final UUID EMPTY_SENDER = new UUID(0, 0); private @Nullable String message; private byte type; + private UUID sender; public Chat() { } - public Chat(String message, byte type) { + public Chat(String message, byte type, UUID sender) { this.message = message; this.type = type; + this.sender = sender; } public String getMessage() { @@ -45,11 +50,20 @@ public class Chat implements MinecraftPacket { this.type = type; } + public UUID getSenderUuid() { + return sender; + } + + public void setSenderUuid(UUID sender) { + this.sender = sender; + } + @Override public String toString() { return "Chat{" + "message='" + message + '\'' + ", type=" + type + + ", sender=" + sender + '}'; } @@ -58,6 +72,9 @@ public class Chat implements MinecraftPacket { message = ProtocolUtils.readString(buf); if (direction == ProtocolUtils.Direction.CLIENTBOUND) { type = buf.readByte(); + if(version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { + sender = ProtocolUtils.readUuid(buf); + } } } @@ -69,6 +86,9 @@ public class Chat implements MinecraftPacket { ProtocolUtils.writeString(buf, message); if (direction == ProtocolUtils.Direction.CLIENTBOUND) { buf.writeByte(type); + if(version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { + ProtocolUtils.writeUuid(buf, sender == null ? new UUID(0,0) : sender); + } } } @@ -78,15 +98,15 @@ public class Chat implements MinecraftPacket { } public static Chat createClientbound(Component component) { - return createClientbound(component, CHAT_TYPE); + return createClientbound(component, CHAT_TYPE, EMPTY_SENDER); } - public static Chat createClientbound(Component component, byte type) { + public static Chat createClientbound(Component component, byte type, UUID sender) { Preconditions.checkNotNull(component, "component"); - return new Chat(GsonComponentSerializer.INSTANCE.serialize(component), type); + return new Chat(GsonComponentSerializer.INSTANCE.serialize(component), type, sender); } public static Chat createServerbound(String message) { - return new Chat(message, CHAT_TYPE); + return new Chat(message, CHAT_TYPE, EMPTY_SENDER); } } 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 b422d9876..d036e6531 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 @@ -5,6 +5,7 @@ import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; +import net.kyori.nbt.CompoundTag; import org.checkerframework.checker.nullness.qual.Nullable; public class JoinGame implements MinecraftPacket { @@ -19,6 +20,11 @@ public class JoinGame implements MinecraftPacket { private int viewDistance; //1.14+ private boolean reducedDebugInfo; private boolean showRespawnScreen; + private boolean shouldKeepPlayerData; + private boolean isDebug; + private boolean isFlat; + private String dimensionRegistryName; + private CompoundTag dimensionRegistry; public int getEntityId() { return entityId; @@ -91,6 +97,46 @@ public class JoinGame implements MinecraftPacket { this.reducedDebugInfo = reducedDebugInfo; } + public boolean getShouldKeepPlayerData() { + return shouldKeepPlayerData; + } + + public void setShouldKeepPlayerData(boolean shouldKeepPlayerData) { + this.shouldKeepPlayerData = shouldKeepPlayerData; + } + + public boolean getIsDebug() { + return isDebug; + } + + public void setIsDebug(boolean isDebug) { + this.isDebug = isDebug; + } + + public boolean getIsFlat() { + return isFlat; + } + + public void setIsFlat(boolean isFlat) { + this.isFlat = isFlat; + } + + public String getDimensionRegistryName() { + return dimensionRegistryName; + } + + public void setDimensionRegistryName(String dimensionRegistryName) { + this.dimensionRegistryName = dimensionRegistryName; + } + + public CompoundTag getDimensionRegistry() { + return dimensionRegistry; + } + + public void setDimensionRegistry(CompoundTag dimensionRegistry) { + this.dimensionRegistry = dimensionRegistry; + } + @Override public String toString() { return "JoinGame{" @@ -110,10 +156,15 @@ public class JoinGame implements MinecraftPacket { public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { this.entityId = buf.readInt(); this.gamemode = buf.readUnsignedByte(); - if (version.compareTo(ProtocolVersion.MINECRAFT_1_9_1) >= 0) { - this.dimension = buf.readInt(); + if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { + this.dimensionRegistry = ProtocolUtils.readCompoundTag(buf); + this.dimensionRegistryName = ProtocolUtils.readString(buf); } else { - this.dimension = buf.readByte(); + if (version.compareTo(ProtocolVersion.MINECRAFT_1_9_1) >= 0) { + this.dimension = buf.readInt(); + } else { + this.dimension = buf.readByte(); + } } if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) { this.difficulty = buf.readUnsignedByte(); @@ -122,7 +173,11 @@ public class JoinGame implements MinecraftPacket { this.partialHashedSeed = buf.readLong(); } this.maxPlayers = buf.readUnsignedByte(); - this.levelType = ProtocolUtils.readString(buf, 16); + if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) < 0) { + this.levelType = ProtocolUtils.readString(buf, 16); + } else { + this.levelType = "default"; // I didn't have the courage to rework this yet. + } if (version.compareTo(ProtocolVersion.MINECRAFT_1_14) >= 0) { this.viewDistance = ProtocolUtils.readVarInt(buf); } @@ -130,16 +185,25 @@ public class JoinGame implements MinecraftPacket { if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) { this.showRespawnScreen = buf.readBoolean(); } + if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { + isDebug = buf.readBoolean(); + isFlat = buf.readBoolean(); + } } @Override public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { buf.writeInt(entityId); buf.writeByte(gamemode); - if (version.compareTo(ProtocolVersion.MINECRAFT_1_9_1) >= 0) { - buf.writeInt(dimension); + if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { + ProtocolUtils.writeCompoundTag(buf, dimensionRegistry); + ProtocolUtils.writeString(buf, dimensionRegistryName); } else { - buf.writeByte(dimension); + if (version.compareTo(ProtocolVersion.MINECRAFT_1_9_1) >= 0) { + buf.writeInt(dimension); + } else { + buf.writeByte(dimension); + } } if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) { buf.writeByte(difficulty); @@ -148,10 +212,12 @@ public class JoinGame implements MinecraftPacket { buf.writeLong(partialHashedSeed); } buf.writeByte(maxPlayers); - if (levelType == null) { - throw new IllegalStateException("No level type specified."); + if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) < 0) { + if (levelType == null) { + throw new IllegalStateException("No level type specified."); + } + ProtocolUtils.writeString(buf, levelType); } - ProtocolUtils.writeString(buf, levelType); if (version.compareTo(ProtocolVersion.MINECRAFT_1_14) >= 0) { ProtocolUtils.writeVarInt(buf,viewDistance); } @@ -159,6 +225,10 @@ public class JoinGame implements MinecraftPacket { if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) { buf.writeBoolean(showRespawnScreen); } + if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { + buf.writeBoolean(isDebug); + buf.writeBoolean(isFlat); + } } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java index 847a722c0..6829d0340 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java @@ -13,17 +13,25 @@ public class Respawn implements MinecraftPacket { private short difficulty; private short gamemode; private String levelType = ""; + private boolean shouldKeepPlayerData; + private boolean isDebug; + private boolean isFlat; + private String dimensionRegistryName; public Respawn() { } public Respawn(int dimension, long partialHashedSeed, short difficulty, short gamemode, - String levelType) { + String levelType, boolean shouldKeepPlayerData, boolean isDebug, boolean isFlat, String dimensionRegistryName) { this.dimension = dimension; this.partialHashedSeed = partialHashedSeed; this.difficulty = difficulty; this.gamemode = gamemode; this.levelType = levelType; + this.shouldKeepPlayerData = shouldKeepPlayerData; + this.isDebug = isDebug; + this.isFlat = isFlat; + this.dimensionRegistryName = dimensionRegistryName; } public int getDimension() { @@ -66,6 +74,38 @@ public class Respawn implements MinecraftPacket { this.levelType = levelType; } + public boolean getShouldKeepPlayerData() { + return shouldKeepPlayerData; + } + + public void setShouldKeepPlayerData(boolean shouldKeepPlayerData) { + this.shouldKeepPlayerData = shouldKeepPlayerData; + } + + public boolean getIsDebug() { + return isDebug; + } + + public void setIsDebug(boolean isDebug) { + this.isDebug = isDebug; + } + + public boolean getIsFlat() { + return isFlat; + } + + public void setIsFlat(boolean isFlat) { + this.isFlat = isFlat; + } + + public String getDimensionRegistryName() { + return dimensionRegistryName; + } + + public void setDimensionRegistryName(String dimensionRegistryName) { + this.dimensionRegistryName = dimensionRegistryName; + } + @Override public String toString() { return "Respawn{" @@ -74,12 +114,20 @@ public class Respawn implements MinecraftPacket { + ", difficulty=" + difficulty + ", gamemode=" + gamemode + ", levelType='" + levelType + '\'' + + ", shouldKeepPlayerData=" + shouldKeepPlayerData + + ", isDebug=" + isDebug + + ", isFlat='" + isFlat + + ", dimensionRegistryName='" + dimensionRegistryName + '\'' + '}'; } @Override public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - this.dimension = buf.readInt(); + if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { + this.dimensionRegistryName = ProtocolUtils.readString(buf); // Not sure what the cap on that is + } else { + this.dimension = buf.readInt(); + } if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) { this.difficulty = buf.readUnsignedByte(); } @@ -87,12 +135,22 @@ public class Respawn implements MinecraftPacket { this.partialHashedSeed = buf.readLong(); } this.gamemode = buf.readUnsignedByte(); - this.levelType = ProtocolUtils.readString(buf, 16); + if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { + isDebug = buf.readBoolean(); + isFlat = buf.readBoolean(); + shouldKeepPlayerData = buf.readBoolean(); + } else { + this.levelType = ProtocolUtils.readString(buf, 16); + } } @Override public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - buf.writeInt(dimension); + if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { + ProtocolUtils.writeString(buf, dimensionRegistryName); + } else { + buf.writeInt(dimension); + } if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) { buf.writeByte(difficulty); } @@ -100,7 +158,13 @@ public class Respawn implements MinecraftPacket { buf.writeLong(partialHashedSeed); } buf.writeByte(gamemode); - ProtocolUtils.writeString(buf, levelType); + if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { + buf.writeBoolean(isDebug); + buf.writeBoolean(isFlat); + buf.writeBoolean(shouldKeepPlayerData); + } else { + ProtocolUtils.writeString(buf, levelType); + } } @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..4ff3e132b 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 @@ -45,7 +45,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_16) >= 0) { + uuid = ProtocolUtils.readUuidIntArray(buf); + } else { + uuid = UUID.fromString(ProtocolUtils.readString(buf, 36)); + } username = ProtocolUtils.readString(buf, 16); } @@ -54,7 +58,11 @@ public class ServerLoginSuccess implements MinecraftPacket { if (uuid == null) { throw new IllegalStateException("No UUID specified!"); } - ProtocolUtils.writeString(buf, uuid.toString()); + if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { + ProtocolUtils.writeUuidIntArray(buf, uuid); + } else { + ProtocolUtils.writeString(buf, uuid.toString()); + } if (username == null) { throw new IllegalStateException("No username specified!"); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java index e7847ec8e..56c2a06c5 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java @@ -134,5 +134,6 @@ public class ArgumentPropertyRegistry { dummy("minecraft:int_range", DUMMY); dummy("minecraft:float_range", DUMMY); dummy("minecraft:time", DUMMY); // added in 1.14 + dummy("minecraft:uuid", DUMMY); } } From fca73bae675d396cb584fc48e2c0548cc6595e58 Mon Sep 17 00:00:00 2001 From: "Five (Xer)" Date: Sat, 23 May 2020 11:43:03 +0200 Subject: [PATCH 56/76] Some minor touch-ups --- .../proxy/protocol/ProtocolUtils.java | 49 ++++++++++--------- .../proxy/protocol/packet/Chat.java | 6 +-- .../proxy/protocol/packet/JoinGame.java | 32 ++++++------ .../proxy/protocol/packet/Respawn.java | 8 +-- .../brigadier/ArgumentPropertyRegistry.java | 2 +- 5 files changed, 49 insertions(+), 48 deletions(-) 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 c0cbc136d..628ec3249 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java @@ -18,6 +18,8 @@ import java.util.ArrayList; import java.util.List; import java.util.UUID; +import io.netty.handler.codec.DecoderException; +import io.netty.handler.codec.EncoderException; import net.kyori.nbt.CompoundTag; public enum ProtocolUtils { @@ -196,22 +198,21 @@ public enum ProtocolUtils { int indexBefore = buf.readerIndex(); byte startType = buf.readByte(); if (startType == 0) { - return null; - } else { - buf.readerIndex(indexBefore); - try { - DataInput input = new ByteBufInputStream(buf); - byte type = input.readByte(); - if (type != 10) { - return null; - } - input.readUTF(); - CompoundTag ret = new CompoundTag(); - ret.read(input, 0); - return ret; - } catch (IOException e) { - return null; + throw new DecoderException("Invalid NBT start-type (end/empty)"); + } + buf.readerIndex(indexBefore); + try { + DataInput input = new ByteBufInputStream(buf); + byte type = input.readByte(); + if (type != 10) { + throw new DecoderException("NBTTag is not a CompoundTag"); } + input.readUTF(); // Head-padding + CompoundTag compoundTag = new CompoundTag(); + compoundTag.read(input, 0); + return compoundTag; + } catch (IOException e) { + throw new DecoderException("Unable to decode NBT CompoundTag at " + indexBefore); } } @@ -223,15 +224,15 @@ public enum ProtocolUtils { public static void writeCompoundTag(ByteBuf buf, CompoundTag compoundTag) { if (compoundTag == null) { buf.writeByte(0); - } else { - try { - DataOutput output = new ByteBufOutputStream(buf); - output.writeByte(10); - output.writeUTF(""); - compoundTag.write(output); - } catch (IOException e) { - e.printStackTrace(); - } + return; + } + try { + DataOutput output = new ByteBufOutputStream(buf); + output.writeByte(10); // Type 10 - CompoundTag + output.writeUTF(""); // Head-padding + compoundTag.write(output); + } catch (IOException e) { + throw new EncoderException("Unable to encode NBT CompoundTag"); } } 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 b80e0f855..61619d0e3 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 @@ -20,7 +20,7 @@ public class Chat implements MinecraftPacket { private @Nullable String message; private byte type; - private UUID sender; + private @Nullable UUID sender; public Chat() { } @@ -72,7 +72,7 @@ public class Chat implements MinecraftPacket { message = ProtocolUtils.readString(buf); if (direction == ProtocolUtils.Direction.CLIENTBOUND) { type = buf.readByte(); - if(version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { + if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { sender = ProtocolUtils.readUuid(buf); } } @@ -87,7 +87,7 @@ public class Chat implements MinecraftPacket { if (direction == ProtocolUtils.Direction.CLIENTBOUND) { buf.writeByte(type); if(version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { - ProtocolUtils.writeUuid(buf, sender == null ? new UUID(0,0) : sender); + ProtocolUtils.writeUuid(buf, sender == null ? EMPTY_SENDER : sender); } } } 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 d036e6531..ef5315701 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 @@ -17,14 +17,14 @@ public class JoinGame implements MinecraftPacket { private short difficulty; private short maxPlayers; private @Nullable String levelType; - private int viewDistance; //1.14+ + private int viewDistance; // 1.14+ private boolean reducedDebugInfo; private boolean showRespawnScreen; - private boolean shouldKeepPlayerData; - private boolean isDebug; - private boolean isFlat; - private String dimensionRegistryName; - private CompoundTag dimensionRegistry; + private boolean shouldKeepPlayerData; // 1.16+ + private boolean isDebug; // 1.16+ + private boolean isFlat; // 1.16+ + private String dimensionRegistryName; // 1.16+ + private CompoundTag dimensionRegistry; // 1.16+ public int getEntityId() { return entityId; @@ -149,6 +149,10 @@ public class JoinGame implements MinecraftPacket { + ", levelType='" + levelType + '\'' + ", viewDistance=" + viewDistance + ", reducedDebugInfo=" + reducedDebugInfo + + ", shouldKeepPlayerData=" + shouldKeepPlayerData + + ", isDebug=" + isDebug + + ", isFlat='" + isFlat + + ", dimensionRegistryName='" + dimensionRegistryName + '\'' + '}'; } @@ -159,12 +163,10 @@ public class JoinGame implements MinecraftPacket { if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { this.dimensionRegistry = ProtocolUtils.readCompoundTag(buf); this.dimensionRegistryName = ProtocolUtils.readString(buf); + } else if (version.compareTo(ProtocolVersion.MINECRAFT_1_9_1) >= 0) { + this.dimension = buf.readInt(); } else { - if (version.compareTo(ProtocolVersion.MINECRAFT_1_9_1) >= 0) { - this.dimension = buf.readInt(); - } else { - this.dimension = buf.readByte(); - } + this.dimension = buf.readByte(); } if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) { this.difficulty = buf.readUnsignedByte(); @@ -198,12 +200,10 @@ public class JoinGame implements MinecraftPacket { if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { ProtocolUtils.writeCompoundTag(buf, dimensionRegistry); ProtocolUtils.writeString(buf, dimensionRegistryName); + } else if (version.compareTo(ProtocolVersion.MINECRAFT_1_9_1) >= 0) { + buf.writeInt(dimension); } else { - if (version.compareTo(ProtocolVersion.MINECRAFT_1_9_1) >= 0) { - buf.writeInt(dimension); - } else { - buf.writeByte(dimension); - } + buf.writeByte(dimension); } if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) { buf.writeByte(difficulty); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java index 6829d0340..0f988a5f9 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java @@ -13,10 +13,10 @@ public class Respawn implements MinecraftPacket { private short difficulty; private short gamemode; private String levelType = ""; - private boolean shouldKeepPlayerData; - private boolean isDebug; - private boolean isFlat; - private String dimensionRegistryName; + private boolean shouldKeepPlayerData; // 1.16+ + private boolean isDebug; // 1.16+ + private boolean isFlat; // 1.16+ + private String dimensionRegistryName; // 1.16+ public Respawn() { } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java index 56c2a06c5..7c9b8fb9c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java @@ -134,6 +134,6 @@ public class ArgumentPropertyRegistry { dummy("minecraft:int_range", DUMMY); dummy("minecraft:float_range", DUMMY); dummy("minecraft:time", DUMMY); // added in 1.14 - dummy("minecraft:uuid", DUMMY); + dummy("minecraft:uuid", DUMMY); // added in 1.16 } } From 197bc4f288f4b01f3348e98d84e5facb0f224518 Mon Sep 17 00:00:00 2001 From: "Five (Xer)" Date: Sat, 23 May 2020 11:49:27 +0200 Subject: [PATCH 57/76] Make checkstyle happy again --- .../com/velocitypowered/proxy/protocol/ProtocolUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 628ec3249..cfedec888 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java @@ -9,6 +9,8 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufInputStream; import io.netty.buffer.ByteBufOutputStream; import io.netty.buffer.ByteBufUtil; +import io.netty.handler.codec.DecoderException; +import io.netty.handler.codec.EncoderException; import java.io.DataInput; import java.io.DataOutput; @@ -18,8 +20,6 @@ import java.util.ArrayList; import java.util.List; import java.util.UUID; -import io.netty.handler.codec.DecoderException; -import io.netty.handler.codec.EncoderException; import net.kyori.nbt.CompoundTag; public enum ProtocolUtils { From 38487c5bba0d2376312f663c47bbea906c32779e Mon Sep 17 00:00:00 2001 From: "Five (Xer)" Date: Sat, 23 May 2020 13:03:33 +0200 Subject: [PATCH 58/76] Server-change mechanics update --- .../client/ClientPlaySessionHandler.java | 15 +++++++++------ .../proxy/protocol/packet/Chat.java | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) 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 d0d21cbd8..d8e3d3718 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 @@ -334,12 +334,15 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { // to perform entity ID rewrites, eliminating potential issues from rewriting packets and // improving compatibility with mods. player.getMinecraftConnection().delayedWrite(joinGame); - int tempDim = joinGame.getDimension() == 0 ? -1 : 0; - player.getMinecraftConnection().delayedWrite( - new Respawn(tempDim, joinGame.getPartialHashedSeed(), joinGame.getDifficulty(), - joinGame.getGamemode(), joinGame.getLevelType(), joinGame.getShouldKeepPlayerData(), - joinGame.getIsDebug(), joinGame.getIsFlat(), - joinGame.getDimensionRegistryName())); + if (player.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_16) < 0) { + int tempDim = joinGame.getDimension() == 0 ? -1 : 0; + player.getMinecraftConnection().delayedWrite( + new Respawn(tempDim, joinGame.getPartialHashedSeed(), joinGame.getDifficulty(), + joinGame.getGamemode(), joinGame.getLevelType(), + joinGame.getShouldKeepPlayerData(), + joinGame.getIsDebug(), joinGame.getIsFlat(), + joinGame.getDimensionRegistryName())); + } player.getMinecraftConnection().delayedWrite( new Respawn(joinGame.getDimension(), joinGame.getPartialHashedSeed(), joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(), 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 61619d0e3..7905d7cab 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 @@ -86,7 +86,7 @@ public class Chat implements MinecraftPacket { ProtocolUtils.writeString(buf, message); if (direction == ProtocolUtils.Direction.CLIENTBOUND) { buf.writeByte(type); - if(version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { + if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { ProtocolUtils.writeUuid(buf, sender == null ? EMPTY_SENDER : sender); } } From 18e595397660ea6651bb4c86e1bb3a6addf1dd24 Mon Sep 17 00:00:00 2001 From: Lechner Markus Date: Thu, 4 Jun 2020 15:36:58 +0200 Subject: [PATCH 59/76] Save progress --- .../backend/VelocityServerConnection.java | 9 ++ .../client/ClientPlaySessionHandler.java | 55 +++++++-- .../proxy/protocol/DimensionInfo.java | 40 +++++++ .../proxy/protocol/DimensionRegistry.java | 113 ++++++++++++++++++ .../proxy/protocol/ProtocolUtils.java | 33 ++++- .../proxy/protocol/packet/JoinGame.java | 78 +++++------- .../proxy/protocol/packet/Respawn.java | 56 +++------ 7 files changed, 289 insertions(+), 95 deletions(-) create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionInfo.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionRegistry.java 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 a8b97c028..7d1d5832f 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 @@ -23,6 +23,7 @@ import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl; +import com.velocitypowered.proxy.protocol.DimensionRegistry; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; @@ -53,6 +54,7 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, private BackendConnectionPhase connectionPhase = BackendConnectionPhases.UNKNOWN; private long lastPingId; private long lastPingSent; + private @Nullable DimensionRegistry activeDimensionRegistry; /** * Initializes a new server connection. @@ -297,4 +299,11 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, return hasCompletedJoin; } + public DimensionRegistry getActiveDimensionRegistry() { + return activeDimensionRegistry; + } + + public void setActiveDimensionRegistry(DimensionRegistry activeDimensionRegistry) { + this.activeDimensionRegistry = activeDimensionRegistry; + } } 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 d8e3d3718..5a0820cd9 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 @@ -13,6 +13,8 @@ import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.backend.BackendConnectionPhases; import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; +import com.velocitypowered.proxy.protocol.DimensionInfo; +import com.velocitypowered.proxy.protocol.DimensionRegistry; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.packet.BossBar; @@ -313,8 +315,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { if (!spawned) { // Nothing special to do with regards to spawning the player spawned = true; + destination.setActiveDimensionRegistry(joinGame.getDimensionRegistry()); // 1.16 player.getMinecraftConnection().delayedWrite(joinGame); - // Required for Legacy Forge player.getPhase().onFirstJoin(player); } else { @@ -334,20 +336,59 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { // to perform entity ID rewrites, eliminating potential issues from rewriting packets and // improving compatibility with mods. player.getMinecraftConnection().delayedWrite(joinGame); + int tempDim = joinGame.getDimension() == 0 ? -1 : 0; + // Since 1.16 this dynamic changed: + // The respawn packet has a keepMetadata flag which should + // be true for dimension switches, so by double switching + // we can keep the flow of the game + // There is a problem here though: By only sending one dimension + // in the registry we can't do that, so we need to run an *unclean* switch. + // NOTE! We can't just send a fake dimension in the registry either + // to get two dimensions, as modded games will break with this. + final DimensionRegistry dimensionRegistry = joinGame.getDimensionRegistry(); + DimensionInfo dimensionInfo = joinGame.getDimensionInfo(); // 1.16+ + // The doubleSwitch variable doubles as keepMetadata flag for an unclean switch as + // well as to indicate the second switch. + boolean doubleSwitch; + // This is not ONE if because this will all be null in < 1.16 if (player.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_16) < 0) { - int tempDim = joinGame.getDimension() == 0 ? -1 : 0; + if(dimensionRegistry.getWorldNames().size() > 1 && dimensionRegistry.getDimensionRegistry().size() > 1){ + String tmpDimLevelName = null; + for(String s : dimensionRegistry.getWorldNames()){ + if(!s.equals(dimensionInfo.getDimensionLevelName())){ + tmpDimLevelName = s; + break; + } + } + String tmpDimIdentifier = null; + for(String s : dimensionRegistry.getDimensionRegistry().keySet()){ + if(!s.equals(dimensionInfo.getDimensionIdentifier())){ + tmpDimIdentifier = s; + break; + } + } + dimensionInfo = new DimensionInfo(tmpDimIdentifier, tmpDimLevelName, true, false); + doubleSwitch = true; + } else { + doubleSwitch = false; + // We should add a warning here. + } + } else { + doubleSwitch = true; + } + if(doubleSwitch) { player.getMinecraftConnection().delayedWrite( new Respawn(tempDim, joinGame.getPartialHashedSeed(), joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(), - joinGame.getShouldKeepPlayerData(), - joinGame.getIsDebug(), joinGame.getIsFlat(), - joinGame.getDimensionRegistryName())); + false, dimensionInfo)); } + player.getMinecraftConnection().delayedWrite( new Respawn(joinGame.getDimension(), joinGame.getPartialHashedSeed(), joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(), - joinGame.getShouldKeepPlayerData(), joinGame.getIsDebug(), joinGame.getIsFlat(), - joinGame.getDimensionRegistryName())); + doubleSwitch, joinGame.getDimensionInfo())); + + destination.setActiveDimensionRegistry(joinGame.getDimensionRegistry()); // 1.16 } // Remove previous boss bars. These don't get cleared when sending JoinGame, thus the need to diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionInfo.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionInfo.java new file mode 100644 index 000000000..3e98da366 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionInfo.java @@ -0,0 +1,40 @@ +package com.velocitypowered.proxy.protocol; + +import javax.annotation.Nonnull; + +public class DimensionInfo { + + private final @Nonnull String dimensionIdentifier; + private final @Nonnull String dimensionLevelName; + private final boolean isFlat; + private final boolean isDebugType; + + public DimensionInfo(@Nonnull String dimensionIdentifier, @Nonnull String dimensionLevelName, boolean isFlat, boolean isDebugType) { + if(dimensionIdentifier == null || dimensionIdentifier.isEmpty() || dimensionIdentifier.isBlank()) { + throw new IllegalArgumentException("DimensionRegistryName may not be empty or null"); + } + this.dimensionIdentifier = dimensionIdentifier; + if(dimensionLevelName == null || dimensionLevelName.isEmpty() || dimensionLevelName.isBlank()) { + throw new IllegalArgumentException("DimensionLevelName may not be empty or null"); + } + this.dimensionLevelName = dimensionLevelName; + this.isFlat = isFlat; + this.isDebugType = isDebugType; + } + + public boolean isDebugType() { + return isDebugType; + } + + public boolean isFlat() { + return isFlat; + } + + public @Nonnull String getDimensionLevelName() { + return dimensionLevelName; + } + + public @Nonnull String getDimensionIdentifier() { + return dimensionIdentifier; + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionRegistry.java new file mode 100644 index 000000000..d4305f540 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionRegistry.java @@ -0,0 +1,113 @@ +package com.velocitypowered.proxy.protocol; + +import net.kyori.nbt.CompoundTag; +import net.kyori.nbt.ListTag; +import net.kyori.nbt.Tag; +import net.kyori.nbt.TagType; + +import javax.annotation.Nonnull; +import java.util.*; + +public class DimensionRegistry { + + private final @Nonnull Map dimensionRegistry; + private final @Nonnull Set worldNames; + + public DimensionRegistry(Map dimensionRegistry, Set worldNames) { + if(dimensionRegistry == null || dimensionRegistry.isEmpty() || worldNames == null || worldNames.isEmpty()) { + throw new IllegalArgumentException("DimensionRegistry requires valid arguments, not null and not empty"); + } + this.dimensionRegistry = dimensionRegistry; + this.worldNames = worldNames; + } + + public @Nonnull Map getDimensionRegistry() { + return dimensionRegistry; + } + + public @Nonnull Set getWorldNames() { + return worldNames; + } + + public @Nonnull String getDimensionIdentifier(@Nonnull String dimensionName) { + if (dimensionName == null) { + throw new IllegalArgumentException("DimensionName cannot be null!"); + } + if (dimensionName == null || !dimensionRegistry.containsKey(dimensionName)) { + throw new NoSuchElementException("DimensionName " + dimensionName + " doesn't exist in this Registry!"); + } + return dimensionRegistry.get(dimensionName); + } + + public @Nonnull String getDimensionName(@Nonnull String dimensionIdentifier) { + if (dimensionIdentifier == null) { + throw new IllegalArgumentException("DimensionIdentifier cannot be null!"); + } + for (Map.Entry entry : dimensionRegistry.entrySet()){ + if(entry.getValue().equals(dimensionIdentifier)){ + return entry.getKey(); + } + } + throw new NoSuchElementException("DimensionIdentifier " + dimensionIdentifier + " doesn't exist in this Registry!"); + } + + public boolean isValidFor(@Nonnull DimensionInfo toValidate) { + if(toValidate == null) { + throw new IllegalArgumentException("DimensionInfo cannot be null"); + } + try{ + if (!worldNames.contains(toValidate.getDimensionLevelName())){ + return false; + } + getDimensionName(toValidate.getDimensionIdentifier()); + return true; + + } catch(NoSuchElementException thrown){ + return false; + } + + } + + public CompoundTag encodeToCompoundTag(){ + CompoundTag ret = new CompoundTag(); + ListTag list = new ListTag(TagType.COMPOUND); + for(Map.Entry entry : dimensionRegistry.entrySet()){ + CompoundTag item = new CompoundTag(); + item.putString("key", entry.getKey()); + item.putString("element", entry.getValue()); + list.add(item); + } + ret.put("dimension", list); + return ret; + } + + public static Map parseToMapping(@Nonnull CompoundTag toParse){ + if(toParse == null) { + throw new IllegalArgumentException("CompoundTag cannot be null"); + } + if(!toParse.contains("dimension", TagType.LIST)){ + throw new IllegalStateException("CompoundTag does not contain a dimension List"); + } + ListTag dimensions = toParse.getList("dimension"); + Map mappings = new HashMap(); + for(Tag iter : dimensions){ + if(iter instanceof CompoundTag){ + throw new IllegalStateException("DimensionList in CompoundTag contains an invalid entry"); + } + CompoundTag mapping = (CompoundTag) iter; + String key = mapping.getString("key", null); + String element = mapping.getString("element", null); + if(element == null || key == null){ + throw new IllegalStateException("DimensionList in CompoundTag contains an mapping"); + } + if(mappings.containsKey(key) || mappings.containsValue(element)) { + throw new IllegalStateException("Dimension mappings may not have identifier/name duplicates"); + } + mappings.put(key, element); + } + if(mappings.isEmpty()){ + throw new IllegalStateException("Dimension mapping cannot be empty"); + } + return mappings; + } +} 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 cfedec888..e2b55c544 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.UUID; import net.kyori.nbt.CompoundTag; +import net.kyori.nbt.TagType; public enum ProtocolUtils { ; @@ -204,7 +205,7 @@ public enum ProtocolUtils { try { DataInput input = new ByteBufInputStream(buf); byte type = input.readByte(); - if (type != 10) { + if (type != TagType.COMPOUND.id()) { throw new DecoderException("NBTTag is not a CompoundTag"); } input.readUTF(); // Head-padding @@ -236,6 +237,36 @@ public enum ProtocolUtils { } } + /** + * Reads a String array from the {@code buf}. + * @param buf the buffer to read from + * @return the String array from the buffer + */ + public static String[] readStringArray(ByteBuf buf) { + int length = readVarInt(buf); + String[] ret = new String[length]; + for(int i = 0; i < length; i++) { + ret[i] = readString(buf); + } + return ret; + } + + /** + * Writes a String Array to the {@code buf}. + * @param buf the buffer to write to + * @param stringArray the array to write + */ + public static void writeStringArray(ByteBuf buf, String[] stringArray) { + if (stringArray == null) { + writeVarInt(buf, 0); + return; + } + writeVarInt(buf, stringArray.length); + for(int i = 0; i < stringArray.length; i++) { + writeString(buf, stringArray[i]); + } + } + /** * Writes a list of {@link com.velocitypowered.api.util.GameProfile.Property} to the buffer. * @param buf the buffer to write to 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 ef5315701..8ea5cbeb6 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 @@ -2,12 +2,17 @@ package com.velocitypowered.proxy.protocol.packet; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.DimensionInfo; +import com.velocitypowered.proxy.protocol.DimensionRegistry; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; import net.kyori.nbt.CompoundTag; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Map; +import java.util.Set; + public class JoinGame implements MinecraftPacket { private int entityId; @@ -20,11 +25,8 @@ public class JoinGame implements MinecraftPacket { private int viewDistance; // 1.14+ private boolean reducedDebugInfo; private boolean showRespawnScreen; - private boolean shouldKeepPlayerData; // 1.16+ - private boolean isDebug; // 1.16+ - private boolean isFlat; // 1.16+ - private String dimensionRegistryName; // 1.16+ - private CompoundTag dimensionRegistry; // 1.16+ + private DimensionRegistry dimensionRegistry; // 1.16+ + private DimensionInfo dimensionInfo; // 1.16+ public int getEntityId() { return entityId; @@ -97,43 +99,19 @@ public class JoinGame implements MinecraftPacket { this.reducedDebugInfo = reducedDebugInfo; } - public boolean getShouldKeepPlayerData() { - return shouldKeepPlayerData; + public DimensionInfo getDimensionInfo() { + return dimensionInfo; } - public void setShouldKeepPlayerData(boolean shouldKeepPlayerData) { - this.shouldKeepPlayerData = shouldKeepPlayerData; + public void setDimensionInfo(DimensionInfo dimensionInfo) { + this.dimensionInfo = dimensionInfo; } - public boolean getIsDebug() { - return isDebug; - } - - public void setIsDebug(boolean isDebug) { - this.isDebug = isDebug; - } - - public boolean getIsFlat() { - return isFlat; - } - - public void setIsFlat(boolean isFlat) { - this.isFlat = isFlat; - } - - public String getDimensionRegistryName() { - return dimensionRegistryName; - } - - public void setDimensionRegistryName(String dimensionRegistryName) { - this.dimensionRegistryName = dimensionRegistryName; - } - - public CompoundTag getDimensionRegistry() { + public DimensionRegistry getDimensionRegistry() { return dimensionRegistry; } - public void setDimensionRegistry(CompoundTag dimensionRegistry) { + public void setDimensionRegistry(DimensionRegistry dimensionRegistry) { this.dimensionRegistry = dimensionRegistry; } @@ -149,10 +127,8 @@ public class JoinGame implements MinecraftPacket { + ", levelType='" + levelType + '\'' + ", viewDistance=" + viewDistance + ", reducedDebugInfo=" + reducedDebugInfo - + ", shouldKeepPlayerData=" + shouldKeepPlayerData - + ", isDebug=" + isDebug - + ", isFlat='" + isFlat - + ", dimensionRegistryName='" + dimensionRegistryName + '\'' + + ", dimensionRegistry='" + dimensionRegistry.toString() + '\'' + + ", dimensionInfo='" + dimensionInfo.toString() + '\'' + '}'; } @@ -160,9 +136,14 @@ public class JoinGame implements MinecraftPacket { public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { this.entityId = buf.readInt(); this.gamemode = buf.readUnsignedByte(); + String dimensionIdentifier = null; + String levelName = null; if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { - this.dimensionRegistry = ProtocolUtils.readCompoundTag(buf); - this.dimensionRegistryName = ProtocolUtils.readString(buf); + String levelNames[] = ProtocolUtils.readStringArray(buf); + Map dimensionMapping = DimensionRegistry.parseToMapping(ProtocolUtils.readCompoundTag(buf)); + this.dimensionRegistry = new DimensionRegistry(dimensionMapping, Set.of(levelNames)); + dimensionIdentifier = ProtocolUtils.readString(buf); + levelName = ProtocolUtils.readString(buf); } else if (version.compareTo(ProtocolVersion.MINECRAFT_1_9_1) >= 0) { this.dimension = buf.readInt(); } else { @@ -188,8 +169,9 @@ public class JoinGame implements MinecraftPacket { this.showRespawnScreen = buf.readBoolean(); } if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { - isDebug = buf.readBoolean(); - isFlat = buf.readBoolean(); + boolean isDebug = buf.readBoolean(); + boolean isFlat = buf.readBoolean(); + this.dimensionInfo = new DimensionInfo(dimensionIdentifier, levelName, isFlat, isDebug); } } @@ -198,8 +180,10 @@ public class JoinGame implements MinecraftPacket { buf.writeInt(entityId); buf.writeByte(gamemode); if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { - ProtocolUtils.writeCompoundTag(buf, dimensionRegistry); - ProtocolUtils.writeString(buf, dimensionRegistryName); + ProtocolUtils.writeStringArray(buf, dimensionRegistry.getWorldNames().toArray(new String[dimensionRegistry.getWorldNames().size()])); + ProtocolUtils.writeCompoundTag(buf, dimensionRegistry.encodeToCompoundTag()); + ProtocolUtils.writeString(buf, dimensionInfo.getDimensionIdentifier()); + ProtocolUtils.writeString(buf, dimensionInfo.getDimensionLevelName()); } else if (version.compareTo(ProtocolVersion.MINECRAFT_1_9_1) >= 0) { buf.writeInt(dimension); } else { @@ -226,8 +210,8 @@ public class JoinGame implements MinecraftPacket { buf.writeBoolean(showRespawnScreen); } if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { - buf.writeBoolean(isDebug); - buf.writeBoolean(isFlat); + buf.writeBoolean(dimensionInfo.isDebugType()); + buf.writeBoolean(dimensionInfo.isFlat()); } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java index 0f988a5f9..67fafb7f7 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java @@ -2,6 +2,7 @@ package com.velocitypowered.proxy.protocol.packet; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.DimensionInfo; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; @@ -14,24 +15,20 @@ public class Respawn implements MinecraftPacket { private short gamemode; private String levelType = ""; private boolean shouldKeepPlayerData; // 1.16+ - private boolean isDebug; // 1.16+ - private boolean isFlat; // 1.16+ - private String dimensionRegistryName; // 1.16+ + private DimensionInfo dimensionInfo; public Respawn() { } public Respawn(int dimension, long partialHashedSeed, short difficulty, short gamemode, - String levelType, boolean shouldKeepPlayerData, boolean isDebug, boolean isFlat, String dimensionRegistryName) { + String levelType, boolean shouldKeepPlayerData, DimensionInfo dimensionInfo) { this.dimension = dimension; this.partialHashedSeed = partialHashedSeed; this.difficulty = difficulty; this.gamemode = gamemode; this.levelType = levelType; this.shouldKeepPlayerData = shouldKeepPlayerData; - this.isDebug = isDebug; - this.isFlat = isFlat; - this.dimensionRegistryName = dimensionRegistryName; + this.dimensionInfo = dimensionInfo; } public int getDimension() { @@ -82,30 +79,6 @@ public class Respawn implements MinecraftPacket { this.shouldKeepPlayerData = shouldKeepPlayerData; } - public boolean getIsDebug() { - return isDebug; - } - - public void setIsDebug(boolean isDebug) { - this.isDebug = isDebug; - } - - public boolean getIsFlat() { - return isFlat; - } - - public void setIsFlat(boolean isFlat) { - this.isFlat = isFlat; - } - - public String getDimensionRegistryName() { - return dimensionRegistryName; - } - - public void setDimensionRegistryName(String dimensionRegistryName) { - this.dimensionRegistryName = dimensionRegistryName; - } - @Override public String toString() { return "Respawn{" @@ -115,16 +88,17 @@ public class Respawn implements MinecraftPacket { + ", gamemode=" + gamemode + ", levelType='" + levelType + '\'' + ", shouldKeepPlayerData=" + shouldKeepPlayerData - + ", isDebug=" + isDebug - + ", isFlat='" + isFlat - + ", dimensionRegistryName='" + dimensionRegistryName + '\'' + + ", dimensionRegistryName='" + dimensionInfo.toString() + '\'' + '}'; } @Override public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { + String dimensionIdentifier = null; + String levelName = null; if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { - this.dimensionRegistryName = ProtocolUtils.readString(buf); // Not sure what the cap on that is + dimensionIdentifier = ProtocolUtils.readString(buf); + levelName = ProtocolUtils.readString(buf); } else { this.dimension = buf.readInt(); } @@ -136,8 +110,9 @@ public class Respawn implements MinecraftPacket { } this.gamemode = buf.readUnsignedByte(); if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { - isDebug = buf.readBoolean(); - isFlat = buf.readBoolean(); + boolean isDebug = buf.readBoolean(); + boolean isFlat = buf.readBoolean(); + this.dimensionInfo = new DimensionInfo(dimensionIdentifier, levelName, isFlat, isDebug); shouldKeepPlayerData = buf.readBoolean(); } else { this.levelType = ProtocolUtils.readString(buf, 16); @@ -147,7 +122,8 @@ public class Respawn implements MinecraftPacket { @Override public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { - ProtocolUtils.writeString(buf, dimensionRegistryName); + ProtocolUtils.writeString(buf, dimensionInfo.getDimensionIdentifier()); + ProtocolUtils.writeString(buf, dimensionInfo.getDimensionLevelName()); } else { buf.writeInt(dimension); } @@ -159,8 +135,8 @@ public class Respawn implements MinecraftPacket { } buf.writeByte(gamemode); if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { - buf.writeBoolean(isDebug); - buf.writeBoolean(isFlat); + buf.writeBoolean(dimensionInfo.isDebugType()); + buf.writeBoolean(dimensionInfo.isFlat()); buf.writeBoolean(shouldKeepPlayerData); } else { ProtocolUtils.writeString(buf, levelType); From 6734ef3a087ed3f068ee9ce809d93ce31ce844bc Mon Sep 17 00:00:00 2001 From: "Five (Xer)" Date: Thu, 4 Jun 2020 19:13:10 +0200 Subject: [PATCH 60/76] Checkstyle-auto --- .../client/ClientPlaySessionHandler.java | 13 +- .../proxy/protocol/DimensionInfo.java | 57 +++--- .../proxy/protocol/DimensionRegistry.java | 186 +++++++++--------- .../proxy/protocol/ProtocolUtils.java | 4 +- 4 files changed, 135 insertions(+), 125 deletions(-) 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 5a0820cd9..7dc865355 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 @@ -352,17 +352,18 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { boolean doubleSwitch; // This is not ONE if because this will all be null in < 1.16 if (player.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_16) < 0) { - if(dimensionRegistry.getWorldNames().size() > 1 && dimensionRegistry.getDimensionRegistry().size() > 1){ + if (dimensionRegistry.getWorldNames().size() > 1 + && dimensionRegistry.getDimensionRegistry().size() > 1) { String tmpDimLevelName = null; - for(String s : dimensionRegistry.getWorldNames()){ - if(!s.equals(dimensionInfo.getDimensionLevelName())){ + for (String s : dimensionRegistry.getWorldNames()) { + if (!s.equals(dimensionInfo.getDimensionLevelName())) { tmpDimLevelName = s; break; } } String tmpDimIdentifier = null; - for(String s : dimensionRegistry.getDimensionRegistry().keySet()){ - if(!s.equals(dimensionInfo.getDimensionIdentifier())){ + for (String s : dimensionRegistry.getDimensionRegistry().keySet()) { + if (!s.equals(dimensionInfo.getDimensionIdentifier())) { tmpDimIdentifier = s; break; } @@ -376,7 +377,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { } else { doubleSwitch = true; } - if(doubleSwitch) { + if (doubleSwitch) { player.getMinecraftConnection().delayedWrite( new Respawn(tempDim, joinGame.getPartialHashedSeed(), joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(), diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionInfo.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionInfo.java index 3e98da366..1a5a5245b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionInfo.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionInfo.java @@ -4,37 +4,40 @@ import javax.annotation.Nonnull; public class DimensionInfo { - private final @Nonnull String dimensionIdentifier; - private final @Nonnull String dimensionLevelName; - private final boolean isFlat; - private final boolean isDebugType; + private final @Nonnull String dimensionIdentifier; + private final @Nonnull String dimensionLevelName; + private final boolean isFlat; + private final boolean isDebugType; - public DimensionInfo(@Nonnull String dimensionIdentifier, @Nonnull String dimensionLevelName, boolean isFlat, boolean isDebugType) { - if(dimensionIdentifier == null || dimensionIdentifier.isEmpty() || dimensionIdentifier.isBlank()) { - throw new IllegalArgumentException("DimensionRegistryName may not be empty or null"); - } - this.dimensionIdentifier = dimensionIdentifier; - if(dimensionLevelName == null || dimensionLevelName.isEmpty() || dimensionLevelName.isBlank()) { - throw new IllegalArgumentException("DimensionLevelName may not be empty or null"); - } - this.dimensionLevelName = dimensionLevelName; - this.isFlat = isFlat; - this.isDebugType = isDebugType; + public DimensionInfo(@Nonnull String dimensionIdentifier, @Nonnull String dimensionLevelName, + boolean isFlat, boolean isDebugType) { + if (dimensionIdentifier == null || dimensionIdentifier.isEmpty() + || dimensionIdentifier.isBlank()) { + throw new IllegalArgumentException("DimensionRegistryName may not be empty or null"); } + this.dimensionIdentifier = dimensionIdentifier; + if (dimensionLevelName == null || dimensionLevelName.isEmpty() + || dimensionLevelName.isBlank()) { + throw new IllegalArgumentException("DimensionLevelName may not be empty or null"); + } + this.dimensionLevelName = dimensionLevelName; + this.isFlat = isFlat; + this.isDebugType = isDebugType; + } - public boolean isDebugType() { - return isDebugType; - } + public boolean isDebugType() { + return isDebugType; + } - public boolean isFlat() { - return isFlat; - } + public boolean isFlat() { + return isFlat; + } - public @Nonnull String getDimensionLevelName() { - return dimensionLevelName; - } + public @Nonnull String getDimensionLevelName() { + return dimensionLevelName; + } - public @Nonnull String getDimensionIdentifier() { - return dimensionIdentifier; - } + public @Nonnull String getDimensionIdentifier() { + return dimensionIdentifier; + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionRegistry.java index d4305f540..70ea76e26 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionRegistry.java @@ -1,113 +1,119 @@ package com.velocitypowered.proxy.protocol; +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import javax.annotation.Nonnull; import net.kyori.nbt.CompoundTag; import net.kyori.nbt.ListTag; import net.kyori.nbt.Tag; import net.kyori.nbt.TagType; -import javax.annotation.Nonnull; -import java.util.*; - public class DimensionRegistry { - private final @Nonnull Map dimensionRegistry; - private final @Nonnull Set worldNames; + private final @Nonnull Map dimensionRegistry; + private final @Nonnull Set worldNames; - public DimensionRegistry(Map dimensionRegistry, Set worldNames) { - if(dimensionRegistry == null || dimensionRegistry.isEmpty() || worldNames == null || worldNames.isEmpty()) { - throw new IllegalArgumentException("DimensionRegistry requires valid arguments, not null and not empty"); - } - this.dimensionRegistry = dimensionRegistry; - this.worldNames = worldNames; + public DimensionRegistry(Map dimensionRegistry, + Set worldNames) { + if (dimensionRegistry == null || dimensionRegistry.isEmpty() + || worldNames == null || worldNames.isEmpty()) { + throw new IllegalArgumentException( + "DimensionRegistry requires valid arguments, not null and not empty"); } + this.dimensionRegistry = dimensionRegistry; + this.worldNames = worldNames; + } - public @Nonnull Map getDimensionRegistry() { - return dimensionRegistry; + public @Nonnull Map getDimensionRegistry() { + return dimensionRegistry; + } + + public @Nonnull Set getWorldNames() { + return worldNames; + } + + public @Nonnull String getDimensionIdentifier(@Nonnull String dimensionName) { + if (dimensionName == null) { + throw new IllegalArgumentException("DimensionName cannot be null!"); } - - public @Nonnull Set getWorldNames() { - return worldNames; + if (dimensionName == null || !dimensionRegistry.containsKey(dimensionName)) { + throw new NoSuchElementException("DimensionName " + dimensionName + + " doesn't exist in this Registry!"); } + return dimensionRegistry.get(dimensionName); + } - public @Nonnull String getDimensionIdentifier(@Nonnull String dimensionName) { - if (dimensionName == null) { - throw new IllegalArgumentException("DimensionName cannot be null!"); - } - if (dimensionName == null || !dimensionRegistry.containsKey(dimensionName)) { - throw new NoSuchElementException("DimensionName " + dimensionName + " doesn't exist in this Registry!"); - } - return dimensionRegistry.get(dimensionName); + public @Nonnull String getDimensionName(@Nonnull String dimensionIdentifier) { + if (dimensionIdentifier == null) { + throw new IllegalArgumentException("DimensionIdentifier cannot be null!"); } - - public @Nonnull String getDimensionName(@Nonnull String dimensionIdentifier) { - if (dimensionIdentifier == null) { - throw new IllegalArgumentException("DimensionIdentifier cannot be null!"); - } - for (Map.Entry entry : dimensionRegistry.entrySet()){ - if(entry.getValue().equals(dimensionIdentifier)){ - return entry.getKey(); - } - } - throw new NoSuchElementException("DimensionIdentifier " + dimensionIdentifier + " doesn't exist in this Registry!"); + for (Map.Entry entry : dimensionRegistry.entrySet()) { + if (entry.getValue().equals(dimensionIdentifier)) { + return entry.getKey(); + } } + throw new NoSuchElementException("DimensionIdentifier " + dimensionIdentifier + + " doesn't exist in this Registry!"); + } - public boolean isValidFor(@Nonnull DimensionInfo toValidate) { - if(toValidate == null) { - throw new IllegalArgumentException("DimensionInfo cannot be null"); - } - try{ - if (!worldNames.contains(toValidate.getDimensionLevelName())){ - return false; - } - getDimensionName(toValidate.getDimensionIdentifier()); - return true; - - } catch(NoSuchElementException thrown){ - return false; - } - + public boolean isValidFor(@Nonnull DimensionInfo toValidate) { + if (toValidate == null) { + throw new IllegalArgumentException("DimensionInfo cannot be null"); } - - public CompoundTag encodeToCompoundTag(){ - CompoundTag ret = new CompoundTag(); - ListTag list = new ListTag(TagType.COMPOUND); - for(Map.Entry entry : dimensionRegistry.entrySet()){ - CompoundTag item = new CompoundTag(); - item.putString("key", entry.getKey()); - item.putString("element", entry.getValue()); - list.add(item); - } - ret.put("dimension", list); - return ret; + try { + if (!worldNames.contains(toValidate.getDimensionLevelName())) { + return false; + } + getDimensionName(toValidate.getDimensionIdentifier()); + return true; + } catch (NoSuchElementException thrown) { + return false; } + } - public static Map parseToMapping(@Nonnull CompoundTag toParse){ - if(toParse == null) { - throw new IllegalArgumentException("CompoundTag cannot be null"); - } - if(!toParse.contains("dimension", TagType.LIST)){ - throw new IllegalStateException("CompoundTag does not contain a dimension List"); - } - ListTag dimensions = toParse.getList("dimension"); - Map mappings = new HashMap(); - for(Tag iter : dimensions){ - if(iter instanceof CompoundTag){ - throw new IllegalStateException("DimensionList in CompoundTag contains an invalid entry"); - } - CompoundTag mapping = (CompoundTag) iter; - String key = mapping.getString("key", null); - String element = mapping.getString("element", null); - if(element == null || key == null){ - throw new IllegalStateException("DimensionList in CompoundTag contains an mapping"); - } - if(mappings.containsKey(key) || mappings.containsValue(element)) { - throw new IllegalStateException("Dimension mappings may not have identifier/name duplicates"); - } - mappings.put(key, element); - } - if(mappings.isEmpty()){ - throw new IllegalStateException("Dimension mapping cannot be empty"); - } - return mappings; + public CompoundTag encodeToCompoundTag() { + CompoundTag ret = new CompoundTag(); + ListTag list = new ListTag(TagType.COMPOUND); + for (Map.Entry entry : dimensionRegistry.entrySet()) { + CompoundTag item = new CompoundTag(); + item.putString("key", entry.getKey()); + item.putString("element", entry.getValue()); + list.add(item); } + ret.put("dimension", list); + return ret; + } + + public static Map parseToMapping(@Nonnull CompoundTag toParse) { + if (toParse == null) { + throw new IllegalArgumentException("CompoundTag cannot be null"); + } + if (!toParse.contains("dimension", TagType.LIST)) { + throw new IllegalStateException("CompoundTag does not contain a dimension List"); + } + ListTag dimensions = toParse.getList("dimension"); + Map mappings = new HashMap(); + for (Tag iter : dimensions) { + if (iter instanceof CompoundTag) { + throw new IllegalStateException("DimensionList in CompoundTag contains an invalid entry"); + } + CompoundTag mapping = (CompoundTag) iter; + String key = mapping.getString("key", null); + String element = mapping.getString("element", null); + if (element == null || key == null) { + throw new IllegalStateException("DimensionList in CompoundTag contains an mapping"); + } + if (mappings.containsKey(key) || mappings.containsValue(element)) { + throw new IllegalStateException( + "Dimension mappings may not have identifier/name duplicates"); + } + mappings.put(key, element); + } + if (mappings.isEmpty()) { + throw new IllegalStateException("Dimension mapping cannot be empty"); + } + return mappings; + } } 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 e2b55c544..a272c8023 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java @@ -245,7 +245,7 @@ public enum ProtocolUtils { public static String[] readStringArray(ByteBuf buf) { int length = readVarInt(buf); String[] ret = new String[length]; - for(int i = 0; i < length; i++) { + for (int i = 0; i < length; i++) { ret[i] = readString(buf); } return ret; @@ -262,7 +262,7 @@ public enum ProtocolUtils { return; } writeVarInt(buf, stringArray.length); - for(int i = 0; i < stringArray.length; i++) { + for (int i = 0; i < stringArray.length; i++) { writeString(buf, stringArray[i]); } } From 009f207883738e3ee1b1fb51adf614fec89b8a4c Mon Sep 17 00:00:00 2001 From: "Five (Xer)" Date: Thu, 4 Jun 2020 21:21:54 +0200 Subject: [PATCH 61/76] More progress --- .../api/network/ProtocolVersion.java | 2 +- .../proxy/protocol/DimensionInfo.java | 9 +++ .../proxy/protocol/DimensionRegistry.java | 62 ++++++++++++++----- .../proxy/protocol/StateRegistry.java | 39 ++++++++---- 4 files changed, 84 insertions(+), 28 deletions(-) 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 07b35926b..3f86aced0 100644 --- a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java +++ b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java @@ -35,7 +35,7 @@ public enum ProtocolVersion { MINECRAFT_1_15(573, "1.15"), MINECRAFT_1_15_1(575, "1.15.1"), MINECRAFT_1_15_2(578, "1.15.2"), - MINECRAFT_1_16(718, "1.16"); + MINECRAFT_1_16(721, "1.16"); private final int protocol; private final String name; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionInfo.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionInfo.java index 1a5a5245b..803f011a1 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionInfo.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionInfo.java @@ -1,5 +1,7 @@ package com.velocitypowered.proxy.protocol; +import com.velocitypowered.proxy.connection.MinecraftConnection; + import javax.annotation.Nonnull; public class DimensionInfo { @@ -9,6 +11,13 @@ public class DimensionInfo { private final boolean isFlat; private final boolean isDebugType; + /** + * Initializes a new {@link DimensionInfo} instance. + * @param dimensionIdentifier the identifier for the dimension from the registry + * @param dimensionLevelName the level name as displayed in the F3 menu and logs + * @param isFlat if true will set world lighting below surface-level to not display fog + * @param isDebugType if true constrains the world to the very limited debug-type world + */ public DimensionInfo(@Nonnull String dimensionIdentifier, @Nonnull String dimensionLevelName, boolean isFlat, boolean isDebugType) { if (dimensionIdentifier == null || dimensionIdentifier.isEmpty() diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionRegistry.java index 70ea76e26..2c917d29f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionRegistry.java @@ -12,15 +12,26 @@ import net.kyori.nbt.TagType; public class DimensionRegistry { + // Mapping: + // dimensionIdentifier (Client connection refers to this), + // dimensionType (The game refers to this). private final @Nonnull Map dimensionRegistry; private final @Nonnull Set worldNames; + /** + * Initializes a new {@link DimensionRegistry} instance. + * This registry is required for 1.16+ clients/servers to communicate, + * it constrains the dimension types and names the client can be sent + * in a Respawn action (dimension change). + * @param dimensionRegistry a populated map containing dimensionIdentifier and dimensionType sets + * @param worldNames a populated {@link Set} of the dimension level names the server offers + */ public DimensionRegistry(Map dimensionRegistry, Set worldNames) { if (dimensionRegistry == null || dimensionRegistry.isEmpty() || worldNames == null || worldNames.isEmpty()) { throw new IllegalArgumentException( - "DimensionRegistry requires valid arguments, not null and not empty"); + "Dimension registry requires valid arguments, not null and not empty"); } this.dimensionRegistry = dimensionRegistry; this.worldNames = worldNames; @@ -34,45 +45,64 @@ public class DimensionRegistry { return worldNames; } - public @Nonnull String getDimensionIdentifier(@Nonnull String dimensionName) { - if (dimensionName == null) { - throw new IllegalArgumentException("DimensionName cannot be null!"); + /** + * Returns the internal dimension type as used by the game. + * @param dimensionIdentifier how the type is identified by the connection + * @return game internal dimension type + */ + public @Nonnull String getDimensionType(@Nonnull String dimensionIdentifier) { + if (dimensionIdentifier == null) { + throw new IllegalArgumentException("Dimension identifier cannot be null!"); } - if (dimensionName == null || !dimensionRegistry.containsKey(dimensionName)) { - throw new NoSuchElementException("DimensionName " + dimensionName + if (dimensionIdentifier == null || !dimensionRegistry.containsKey(dimensionIdentifier)) { + throw new NoSuchElementException("Dimension with identifier " + dimensionIdentifier + " doesn't exist in this Registry!"); } - return dimensionRegistry.get(dimensionName); + return dimensionRegistry.get(dimensionIdentifier); } - public @Nonnull String getDimensionName(@Nonnull String dimensionIdentifier) { - if (dimensionIdentifier == null) { - throw new IllegalArgumentException("DimensionIdentifier cannot be null!"); + /** + * Returns the dimension identifier as used by the client. + * @param dimensionType the internal dimension type + * @return game dimension identifier + */ + public @Nonnull String getDimensionIdentifier(@Nonnull String dimensionType) { + if (dimensionType == null) { + throw new IllegalArgumentException("Dimension type cannot be null!"); } for (Map.Entry entry : dimensionRegistry.entrySet()) { - if (entry.getValue().equals(dimensionIdentifier)) { + if (entry.getValue().equals(dimensionType)) { return entry.getKey(); } } - throw new NoSuchElementException("DimensionIdentifier " + dimensionIdentifier + throw new NoSuchElementException("Dimension type " + dimensionType + " doesn't exist in this Registry!"); } + /** + * Checks a {@link DimensionInfo} against this registry. + * @param toValidate the {@link DimensionInfo} to validate + * @return true: the dimension information is valid for this registry + */ public boolean isValidFor(@Nonnull DimensionInfo toValidate) { if (toValidate == null) { - throw new IllegalArgumentException("DimensionInfo cannot be null"); + throw new IllegalArgumentException("Dimension info cannot be null"); } try { if (!worldNames.contains(toValidate.getDimensionLevelName())) { return false; } - getDimensionName(toValidate.getDimensionIdentifier()); + getDimensionType(toValidate.getDimensionIdentifier()); return true; } catch (NoSuchElementException thrown) { return false; } } + /** + * Encodes the stored Dimension registry as CompoundTag. + * @return the CompoundTag containing identifier:type mappings + */ public CompoundTag encodeToCompoundTag() { CompoundTag ret = new CompoundTag(); ListTag list = new ListTag(TagType.COMPOUND); @@ -86,6 +116,10 @@ public class DimensionRegistry { return ret; } + /** + * Decodes a CompoundTag storing dimension mappings to a Map identifier:type. + * @param toParse CompoundTag containing a dimension registry + */ public static Map parseToMapping(@Nonnull CompoundTag toParse) { if (toParse == null) { throw new IllegalArgumentException("CompoundTag cannot be null"); 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 07f174b51..658c818d7 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -126,44 +126,52 @@ public enum StateRegistry { clientbound.register(BossBar.class, BossBar::new, map(0x0C, MINECRAFT_1_9, false), - map(0x0D, MINECRAFT_1_15, false)); + map(0x0D, MINECRAFT_1_15, false), + map(0x0C, MINECRAFT_1_16, false)); clientbound.register(Chat.class, Chat::new, map(0x02, MINECRAFT_1_8, true), map(0x0F, MINECRAFT_1_9, true), map(0x0E, MINECRAFT_1_13, true), - map(0x0F, MINECRAFT_1_15, true)); + map(0x0F, MINECRAFT_1_15, true), + map(0x0E, MINECRAFT_1_16, true)); clientbound.register(TabCompleteResponse.class, TabCompleteResponse::new, map(0x3A, MINECRAFT_1_8, false), map(0x0E, MINECRAFT_1_9, false), map(0x10, MINECRAFT_1_13, false), - map(0x11, MINECRAFT_1_15, false)); + map(0x11, MINECRAFT_1_15, false), + map(0x10, MINECRAFT_1_16, false)); clientbound.register(AvailableCommands.class, AvailableCommands::new, map(0x11, MINECRAFT_1_13, false), - map(0x12, MINECRAFT_1_15, false)); + map(0x12, MINECRAFT_1_15, false), + map(0x11, MINECRAFT_1_16, false)); clientbound.register(PluginMessage.class, PluginMessage::new, map(0x3F, MINECRAFT_1_8, false), map(0x18, MINECRAFT_1_9, false), map(0x19, MINECRAFT_1_13, false), map(0x18, MINECRAFT_1_14, false), - map(0x19, MINECRAFT_1_15, false)); + map(0x19, MINECRAFT_1_15, false), + map(0x18, MINECRAFT_1_16, false)); clientbound.register(Disconnect.class, Disconnect::new, map(0x40, MINECRAFT_1_8, false), map(0x1A, MINECRAFT_1_9, false), map(0x1B, MINECRAFT_1_13, false), map(0x1A, MINECRAFT_1_14, false), - map(0x1B, MINECRAFT_1_15, false)); + map(0x1B, MINECRAFT_1_15, false), + map(0x1A, MINECRAFT_1_16, false)); clientbound.register(KeepAlive.class, KeepAlive::new, map(0x00, MINECRAFT_1_8, false), map(0x1F, MINECRAFT_1_9, false), map(0x21, MINECRAFT_1_13, false), map(0x20, MINECRAFT_1_14, false), - map(0x21, MINECRAFT_1_15, false)); + map(0x21, MINECRAFT_1_15, false), + map(0x20, MINECRAFT_1_16, false)); clientbound.register(JoinGame.class, JoinGame::new, map(0x01, MINECRAFT_1_8, false), map(0x23, MINECRAFT_1_9, false), map(0x25, MINECRAFT_1_13, false), map(0x25, MINECRAFT_1_14, false), - map(0x26, MINECRAFT_1_15, false)); + map(0x26, MINECRAFT_1_15, false), + map(0x25, MINECRAFT_1_16, false)); clientbound.register(Respawn.class, Respawn::new, map(0x07, MINECRAFT_1_8, true), map(0x33, MINECRAFT_1_9, true), @@ -171,7 +179,8 @@ public enum StateRegistry { map(0x35, MINECRAFT_1_12_1, true), map(0x38, MINECRAFT_1_13, true), map(0x3A, MINECRAFT_1_14, true), - map(0x3B, MINECRAFT_1_15, true)); + map(0x3B, MINECRAFT_1_15, true), + map(0x3A, MINECRAFT_1_16, true)); clientbound.register(ResourcePackRequest.class, ResourcePackRequest::new, map(0x48, MINECRAFT_1_8, true), map(0x32, MINECRAFT_1_9, true), @@ -179,7 +188,8 @@ public enum StateRegistry { map(0x34, MINECRAFT_1_12_1, true), map(0x37, MINECRAFT_1_13, true), map(0x39, MINECRAFT_1_14, true), - map(0x3A, MINECRAFT_1_15, true)); + map(0x3A, MINECRAFT_1_15, true), + map(0x39, MINECRAFT_1_16, true)); clientbound.register(HeaderAndFooter.class, HeaderAndFooter::new, map(0x47, MINECRAFT_1_8, true), map(0x48, MINECRAFT_1_9, true), @@ -188,7 +198,8 @@ public enum StateRegistry { map(0x4A, MINECRAFT_1_12_1, true), map(0x4E, MINECRAFT_1_13, true), map(0x53, MINECRAFT_1_14, true), - map(0x54, MINECRAFT_1_15, true)); + map(0x54, MINECRAFT_1_15, true), + map(0x53, MINECRAFT_1_16, true)); clientbound.register(TitlePacket.class, TitlePacket::new, map(0x45, MINECRAFT_1_8, true), map(0x45, MINECRAFT_1_9, true), @@ -196,14 +207,16 @@ public enum StateRegistry { map(0x48, MINECRAFT_1_12_1, true), map(0x4B, MINECRAFT_1_13, true), map(0x4F, MINECRAFT_1_14, true), - map(0x50, MINECRAFT_1_15, true)); + map(0x50, MINECRAFT_1_15, true), + map(0x4F, MINECRAFT_1_16, true)); clientbound.register(PlayerListItem.class, PlayerListItem::new, map(0x38, MINECRAFT_1_8, false), map(0x2D, MINECRAFT_1_9, false), map(0x2E, MINECRAFT_1_12_1, false), map(0x30, MINECRAFT_1_13, false), map(0x33, MINECRAFT_1_14, false), - map(0x34, MINECRAFT_1_15, false)); + map(0x34, MINECRAFT_1_15, false), + map(0x33, MINECRAFT_1_16, false)); } }, LOGIN { From 368d50b4555a87f0fb5ca734947f098e86ccac96 Mon Sep 17 00:00:00 2001 From: Lechner Markus Date: Fri, 5 Jun 2020 15:22:55 +0200 Subject: [PATCH 62/76] Rework Dimension Registry --- .../api/network/ProtocolVersion.java | 2 +- .../client/ClientPlaySessionHandler.java | 48 +------- .../proxy/protocol/DimensionData.java | 106 +++++++++++++++++ .../proxy/protocol/DimensionInfo.java | 18 ++- .../proxy/protocol/DimensionRegistry.java | 109 +++++++----------- .../proxy/protocol/packet/JoinGame.java | 15 +-- .../proxy/protocol/packet/Respawn.java | 2 +- 7 files changed, 166 insertions(+), 134 deletions(-) create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionData.java 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 3f86aced0..92326cc62 100644 --- a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java +++ b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java @@ -35,7 +35,7 @@ public enum ProtocolVersion { MINECRAFT_1_15(573, "1.15"), MINECRAFT_1_15_1(575, "1.15.1"), MINECRAFT_1_15_2(578, "1.15.2"), - MINECRAFT_1_16(721, "1.16"); + MINECRAFT_1_16(722, "1.16"); private final int protocol; private final String name; 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 7dc865355..07132bac0 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 @@ -34,11 +34,9 @@ import io.netty.buffer.ByteBuf; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; -import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Queue; -import java.util.Set; import java.util.UUID; import net.kyori.text.TextComponent; import net.kyori.text.format.TextColor; @@ -336,58 +334,20 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { // to perform entity ID rewrites, eliminating potential issues from rewriting packets and // improving compatibility with mods. player.getMinecraftConnection().delayedWrite(joinGame); - int tempDim = joinGame.getDimension() == 0 ? -1 : 0; // Since 1.16 this dynamic changed: - // The respawn packet has a keepMetadata flag which should - // be true for dimension switches, so by double switching - // we can keep the flow of the game - // There is a problem here though: By only sending one dimension - // in the registry we can't do that, so we need to run an *unclean* switch. - // NOTE! We can't just send a fake dimension in the registry either - // to get two dimensions, as modded games will break with this. - final DimensionRegistry dimensionRegistry = joinGame.getDimensionRegistry(); - DimensionInfo dimensionInfo = joinGame.getDimensionInfo(); // 1.16+ - // The doubleSwitch variable doubles as keepMetadata flag for an unclean switch as - // well as to indicate the second switch. - boolean doubleSwitch; - // This is not ONE if because this will all be null in < 1.16 + // We don't need to send two dimension swiches anymore! if (player.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_16) < 0) { - if (dimensionRegistry.getWorldNames().size() > 1 - && dimensionRegistry.getDimensionRegistry().size() > 1) { - String tmpDimLevelName = null; - for (String s : dimensionRegistry.getWorldNames()) { - if (!s.equals(dimensionInfo.getDimensionLevelName())) { - tmpDimLevelName = s; - break; - } - } - String tmpDimIdentifier = null; - for (String s : dimensionRegistry.getDimensionRegistry().keySet()) { - if (!s.equals(dimensionInfo.getDimensionIdentifier())) { - tmpDimIdentifier = s; - break; - } - } - dimensionInfo = new DimensionInfo(tmpDimIdentifier, tmpDimLevelName, true, false); - doubleSwitch = true; - } else { - doubleSwitch = false; - // We should add a warning here. - } - } else { - doubleSwitch = true; - } - if (doubleSwitch) { + int tempDim = joinGame.getDimension() == 0 ? -1 : 0; player.getMinecraftConnection().delayedWrite( new Respawn(tempDim, joinGame.getPartialHashedSeed(), joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(), - false, dimensionInfo)); + false, joinGame.getDimensionInfo())); } player.getMinecraftConnection().delayedWrite( new Respawn(joinGame.getDimension(), joinGame.getPartialHashedSeed(), joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(), - doubleSwitch, joinGame.getDimensionInfo())); + false, joinGame.getDimensionInfo())); destination.setActiveDimensionRegistry(joinGame.getDimensionRegistry()); // 1.16 } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionData.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionData.java new file mode 100644 index 000000000..9ecfebe15 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionData.java @@ -0,0 +1,106 @@ +package com.velocitypowered.proxy.protocol; + +import net.kyori.nbt.CompoundTag; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class DimensionData { + private final @Nonnull String registryIdentifier; + private final boolean isNatural; + private final float ambientLight; + private final boolean isShrunk; + private final boolean isUltrawarm; + private final boolean hasCeiling; + private final boolean hasSkylight; + private final @Nullable Long fixedTime; + private final @Nullable Boolean hasEnderdragonFight; + + public DimensionData(@Nonnull String registryIdentifier, boolean isNatural, + float ambientLight, boolean isShrunk, boolean isUltrawarm, + boolean hasCeiling, boolean hasSkylight, + @Nullable Long fixedTime, @Nullable Boolean hasEnderdragonFight) { + this.registryIdentifier = registryIdentifier; + this.isNatural = isNatural; + this.ambientLight = ambientLight; + this.isShrunk = isShrunk; + this.isUltrawarm = isUltrawarm; + this.hasCeiling = hasCeiling; + this.hasSkylight = hasSkylight; + this.fixedTime = fixedTime; + this.hasEnderdragonFight = hasEnderdragonFight; + } + + public @Nonnull String getRegistryIdentifier() { + return registryIdentifier; + } + + public boolean isNatural() { + return isNatural; + } + + public float getAmbientLight() { + return ambientLight; + } + + public boolean isShrunk() { + return isShrunk; + } + + public boolean isUltrawarm() { + return isUltrawarm; + } + + public boolean isHasCeiling() { + return hasCeiling; + } + + public boolean isHasSkylight() { + return hasSkylight; + } + + public @Nullable Long getFixedTime() { + return fixedTime; + } + + public @Nullable Boolean getHasEnderdragonFight() { + return hasEnderdragonFight; + } + + public static DimensionData fromNBT(@Nonnull CompoundTag toRead) { + if (toRead == null){ + throw new IllegalArgumentException("CompoundTag cannot be null"); + } + String registryIdentifier = toRead.getString("key"); + CompoundTag values = toRead.getCompound("element"); + boolean isNatural = values.getBoolean("natural"); + float ambientLight = values.getFloat("ambient_light"); + boolean isShrunk = values.getBoolean("shrunk"); + boolean isUltrawarm = values.getBoolean("ultrawarm"); + boolean hasCeiling = values.getBoolean("has_ceiling"); + boolean hasSkylight = values.getBoolean("has_skylight"); + Long fixedTime = values.contains("fixed_time") ? values.getLong("fixed_time") : null; + Boolean hasEnderdragonFight = values.contains("has_enderdragon_fight") ? values.getBoolean("has_enderdragon_fight") : null; + return new DimensionData(registryIdentifier, isNatural, ambientLight, isShrunk, isUltrawarm, hasCeiling, hasSkylight, fixedTime, hasEnderdragonFight); + } + + public CompoundTag encode() { + CompoundTag ret = new CompoundTag(); + ret.putString("key", registryIdentifier); + CompoundTag values = new CompoundTag(); + values.putBoolean("natural", isNatural); + values.putFloat("ambient_light", ambientLight); + values.putBoolean("shrunk", isShrunk); + values.putBoolean("ultrawarm", isUltrawarm); + values.putBoolean("has_ceiling", hasCeiling); + values.putBoolean("has_skylight", hasSkylight); + if (fixedTime != null) { + values.putLong("fixed_time", fixedTime); + } + if (hasEnderdragonFight != null) { + values.putBoolean("has_enderdragon_fight", hasEnderdragonFight); + } + ret.put("element", values); + return ret; + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionInfo.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionInfo.java index 803f011a1..787c7a948 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionInfo.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionInfo.java @@ -1,35 +1,33 @@ package com.velocitypowered.proxy.protocol; -import com.velocitypowered.proxy.connection.MinecraftConnection; - import javax.annotation.Nonnull; public class DimensionInfo { private final @Nonnull String dimensionIdentifier; - private final @Nonnull String dimensionLevelName; + private final @Nonnull String levelName; private final boolean isFlat; private final boolean isDebugType; /** * Initializes a new {@link DimensionInfo} instance. * @param dimensionIdentifier the identifier for the dimension from the registry - * @param dimensionLevelName the level name as displayed in the F3 menu and logs + * @param levelName the level name as displayed in the F3 menu and logs * @param isFlat if true will set world lighting below surface-level to not display fog * @param isDebugType if true constrains the world to the very limited debug-type world */ - public DimensionInfo(@Nonnull String dimensionIdentifier, @Nonnull String dimensionLevelName, + public DimensionInfo(@Nonnull String dimensionIdentifier, @Nonnull String levelName, boolean isFlat, boolean isDebugType) { if (dimensionIdentifier == null || dimensionIdentifier.isEmpty() || dimensionIdentifier.isBlank()) { throw new IllegalArgumentException("DimensionRegistryName may not be empty or null"); } this.dimensionIdentifier = dimensionIdentifier; - if (dimensionLevelName == null || dimensionLevelName.isEmpty() - || dimensionLevelName.isBlank()) { + if (levelName == null || levelName.isEmpty() + || levelName.isBlank()) { throw new IllegalArgumentException("DimensionLevelName may not be empty or null"); } - this.dimensionLevelName = dimensionLevelName; + this.levelName = levelName; this.isFlat = isFlat; this.isDebugType = isDebugType; } @@ -42,8 +40,8 @@ public class DimensionInfo { return isFlat; } - public @Nonnull String getDimensionLevelName() { - return dimensionLevelName; + public @Nonnull String getLevelName() { + return levelName; } public @Nonnull String getDimensionIdentifier() { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionRegistry.java index 2c917d29f..606f53cc7 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionRegistry.java @@ -1,10 +1,9 @@ package com.velocitypowered.proxy.protocol; -import java.util.HashMap; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Set; +import java.util.*; import javax.annotation.Nonnull; + +import com.google.inject.internal.asm.$TypePath; import net.kyori.nbt.CompoundTag; import net.kyori.nbt.ListTag; import net.kyori.nbt.Tag; @@ -12,70 +11,51 @@ import net.kyori.nbt.TagType; public class DimensionRegistry { - // Mapping: - // dimensionIdentifier (Client connection refers to this), - // dimensionType (The game refers to this). - private final @Nonnull Map dimensionRegistry; - private final @Nonnull Set worldNames; + private final @Nonnull Set dimensionRegistry; + private final @Nonnull String[] levelNames; /** * Initializes a new {@link DimensionRegistry} instance. * This registry is required for 1.16+ clients/servers to communicate, * it constrains the dimension types and names the client can be sent * in a Respawn action (dimension change). - * @param dimensionRegistry a populated map containing dimensionIdentifier and dimensionType sets - * @param worldNames a populated {@link Set} of the dimension level names the server offers + * @param dimensionRegistry a populated set containing dimension data types + * @param levelNames a populated {@link Set} of the dimension level names the server offers */ - public DimensionRegistry(Map dimensionRegistry, - Set worldNames) { + public DimensionRegistry(Set dimensionRegistry, + String[] levelNames) { if (dimensionRegistry == null || dimensionRegistry.isEmpty() - || worldNames == null || worldNames.isEmpty()) { + || levelNames == null || levelNames.length == 0) { throw new IllegalArgumentException( "Dimension registry requires valid arguments, not null and not empty"); } this.dimensionRegistry = dimensionRegistry; - this.worldNames = worldNames; + this.levelNames = levelNames; } - public @Nonnull Map getDimensionRegistry() { + public @Nonnull Set getDimensionRegistry() { return dimensionRegistry; } - public @Nonnull Set getWorldNames() { - return worldNames; + public @Nonnull String[] getLevelNames() { + return levelNames; } /** - * Returns the internal dimension type as used by the game. - * @param dimensionIdentifier how the type is identified by the connection - * @return game internal dimension type + * Returns the internal dimension data type as used by the game. + * @param dimensionIdentifier how the dimension is identified by the connection + * @return game dimension data */ - public @Nonnull String getDimensionType(@Nonnull String dimensionIdentifier) { + public @Nonnull DimensionData getDimensionData(@Nonnull String dimensionIdentifier) { if (dimensionIdentifier == null) { throw new IllegalArgumentException("Dimension identifier cannot be null!"); } - if (dimensionIdentifier == null || !dimensionRegistry.containsKey(dimensionIdentifier)) { - throw new NoSuchElementException("Dimension with identifier " + dimensionIdentifier - + " doesn't exist in this Registry!"); - } - return dimensionRegistry.get(dimensionIdentifier); - } - - /** - * Returns the dimension identifier as used by the client. - * @param dimensionType the internal dimension type - * @return game dimension identifier - */ - public @Nonnull String getDimensionIdentifier(@Nonnull String dimensionType) { - if (dimensionType == null) { - throw new IllegalArgumentException("Dimension type cannot be null!"); - } - for (Map.Entry entry : dimensionRegistry.entrySet()) { - if (entry.getValue().equals(dimensionType)) { - return entry.getKey(); + for (DimensionData iter : dimensionRegistry) { + if(iter.getRegistryIdentifier().equals(dimensionIdentifier)) { + return iter; } } - throw new NoSuchElementException("Dimension type " + dimensionType + throw new NoSuchElementException("Dimension with identifier " + dimensionIdentifier + " doesn't exist in this Registry!"); } @@ -89,11 +69,13 @@ public class DimensionRegistry { throw new IllegalArgumentException("Dimension info cannot be null"); } try { - if (!worldNames.contains(toValidate.getDimensionLevelName())) { - return false; + getDimensionData(toValidate.getDimensionIdentifier()); + for(int i = 0; i < levelNames.length; i++) { + if(levelNames[i].equals(toValidate.getDimensionIdentifier())) { + return true; + } } - getDimensionType(toValidate.getDimensionIdentifier()); - return true; + return false; } catch (NoSuchElementException thrown) { return false; } @@ -103,51 +85,42 @@ public class DimensionRegistry { * Encodes the stored Dimension registry as CompoundTag. * @return the CompoundTag containing identifier:type mappings */ - public CompoundTag encodeToCompoundTag() { + public CompoundTag encodeRegistry() { CompoundTag ret = new CompoundTag(); ListTag list = new ListTag(TagType.COMPOUND); - for (Map.Entry entry : dimensionRegistry.entrySet()) { - CompoundTag item = new CompoundTag(); - item.putString("key", entry.getKey()); - item.putString("element", entry.getValue()); - list.add(item); + for (DimensionData iter : dimensionRegistry) { + list.add(iter.encode()); } ret.put("dimension", list); return ret; } /** - * Decodes a CompoundTag storing dimension mappings to a Map identifier:type. + * Decodes a CompoundTag storing a dimension registry * @param toParse CompoundTag containing a dimension registry + * @param levelNames world level names */ - public static Map parseToMapping(@Nonnull CompoundTag toParse) { + public static DimensionRegistry fromGameData(@Nonnull CompoundTag toParse, @Nonnull String[] levelNames) { if (toParse == null) { throw new IllegalArgumentException("CompoundTag cannot be null"); } + if (levelNames == null || levelNames.length == 0) { + throw new IllegalArgumentException("Level names cannot be null or empty"); + } if (!toParse.contains("dimension", TagType.LIST)) { throw new IllegalStateException("CompoundTag does not contain a dimension List"); } ListTag dimensions = toParse.getList("dimension"); - Map mappings = new HashMap(); + Set mappings = new HashSet(); for (Tag iter : dimensions) { - if (iter instanceof CompoundTag) { + if (!(iter instanceof CompoundTag)) { throw new IllegalStateException("DimensionList in CompoundTag contains an invalid entry"); } - CompoundTag mapping = (CompoundTag) iter; - String key = mapping.getString("key", null); - String element = mapping.getString("element", null); - if (element == null || key == null) { - throw new IllegalStateException("DimensionList in CompoundTag contains an mapping"); - } - if (mappings.containsKey(key) || mappings.containsValue(element)) { - throw new IllegalStateException( - "Dimension mappings may not have identifier/name duplicates"); - } - mappings.put(key, element); + mappings.add(DimensionData.fromNBT((CompoundTag) iter)); } if (mappings.isEmpty()) { throw new IllegalStateException("Dimension mapping cannot be empty"); } - return mappings; + return new DimensionRegistry(mappings, levelNames); } } 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 8ea5cbeb6..64104ebfa 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 @@ -2,12 +2,8 @@ package com.velocitypowered.proxy.protocol.packet; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; -import com.velocitypowered.proxy.protocol.DimensionInfo; -import com.velocitypowered.proxy.protocol.DimensionRegistry; -import com.velocitypowered.proxy.protocol.MinecraftPacket; -import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.protocol.*; import io.netty.buffer.ByteBuf; -import net.kyori.nbt.CompoundTag; import org.checkerframework.checker.nullness.qual.Nullable; import java.util.Map; @@ -140,8 +136,7 @@ public class JoinGame implements MinecraftPacket { String levelName = null; if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { String levelNames[] = ProtocolUtils.readStringArray(buf); - Map dimensionMapping = DimensionRegistry.parseToMapping(ProtocolUtils.readCompoundTag(buf)); - this.dimensionRegistry = new DimensionRegistry(dimensionMapping, Set.of(levelNames)); + this.dimensionRegistry = DimensionRegistry.fromGameData(ProtocolUtils.readCompoundTag(buf), levelNames); dimensionIdentifier = ProtocolUtils.readString(buf); levelName = ProtocolUtils.readString(buf); } else if (version.compareTo(ProtocolVersion.MINECRAFT_1_9_1) >= 0) { @@ -180,10 +175,10 @@ public class JoinGame implements MinecraftPacket { buf.writeInt(entityId); buf.writeByte(gamemode); if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { - ProtocolUtils.writeStringArray(buf, dimensionRegistry.getWorldNames().toArray(new String[dimensionRegistry.getWorldNames().size()])); - ProtocolUtils.writeCompoundTag(buf, dimensionRegistry.encodeToCompoundTag()); + ProtocolUtils.writeStringArray(buf, dimensionRegistry.getLevelNames()); + ProtocolUtils.writeCompoundTag(buf, dimensionRegistry.encodeRegistry()); ProtocolUtils.writeString(buf, dimensionInfo.getDimensionIdentifier()); - ProtocolUtils.writeString(buf, dimensionInfo.getDimensionLevelName()); + ProtocolUtils.writeString(buf, dimensionInfo.getLevelName()); } else if (version.compareTo(ProtocolVersion.MINECRAFT_1_9_1) >= 0) { buf.writeInt(dimension); } else { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java index 67fafb7f7..74832b5d8 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java @@ -123,7 +123,7 @@ public class Respawn implements MinecraftPacket { public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { ProtocolUtils.writeString(buf, dimensionInfo.getDimensionIdentifier()); - ProtocolUtils.writeString(buf, dimensionInfo.getDimensionLevelName()); + ProtocolUtils.writeString(buf, dimensionInfo.getLevelName()); } else { buf.writeInt(dimension); } From aa4a8de2fd474325927be5e221534a4f98a7a046 Mon Sep 17 00:00:00 2001 From: Lechner Markus Date: Fri, 5 Jun 2020 15:45:11 +0200 Subject: [PATCH 63/76] Stylize --- .../proxy/protocol/DimensionData.java | 37 +++++++++++++++---- .../proxy/protocol/DimensionInfo.java | 20 +++++----- .../proxy/protocol/DimensionRegistry.java | 22 ++++++----- .../proxy/protocol/packet/JoinGame.java | 5 +-- .../proxy/protocol/packet/Respawn.java | 2 +- 5 files changed, 54 insertions(+), 32 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionData.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionData.java index 9ecfebe15..ed6867e03 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionData.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionData.java @@ -1,9 +1,8 @@ package com.velocitypowered.proxy.protocol; -import net.kyori.nbt.CompoundTag; - import javax.annotation.Nonnull; import javax.annotation.Nullable; +import net.kyori.nbt.CompoundTag; public class DimensionData { private final @Nonnull String registryIdentifier; @@ -16,6 +15,18 @@ public class DimensionData { private final @Nullable Long fixedTime; private final @Nullable Boolean hasEnderdragonFight; + /** + * Initializes a new {@link DimensionData} instance. + * @param registryIdentifier the identifier for the dimension from the registry. + * @param isNatural indicates if the dimension use natural world generation (e.g. overworld) + * @param ambientLight the light level the client sees without external lighting + * @param isShrunk indicates if the world is shrunk, aka not the full 256 blocks (e.g. nether) + * @param isUltrawarm internal dimension warmth flag + * @param hasCeiling indicates if the dimension has a ceiling layer + * @param hasSkylight indicates if the dimension should display the sun + * @param fixedTime optional. If set to any game daytime value will deactivate time cycle + * @param hasEnderdragonFight optional. Internal flag used in the end dimension + */ public DimensionData(@Nonnull String registryIdentifier, boolean isNatural, float ambientLight, boolean isShrunk, boolean isUltrawarm, boolean hasCeiling, boolean hasSkylight, @@ -67,8 +78,13 @@ public class DimensionData { return hasEnderdragonFight; } - public static DimensionData fromNBT(@Nonnull CompoundTag toRead) { - if (toRead == null){ + /** + * Parses a given CompoundTag to a DimensionData instance. + * @param toRead the compound from the registry to read + * @return game dimension data + */ + public static DimensionData fromCompoundTag(@Nonnull CompoundTag toRead) { + if (toRead == null) { throw new IllegalArgumentException("CompoundTag cannot be null"); } String registryIdentifier = toRead.getString("key"); @@ -80,11 +96,18 @@ public class DimensionData { boolean hasCeiling = values.getBoolean("has_ceiling"); boolean hasSkylight = values.getBoolean("has_skylight"); Long fixedTime = values.contains("fixed_time") ? values.getLong("fixed_time") : null; - Boolean hasEnderdragonFight = values.contains("has_enderdragon_fight") ? values.getBoolean("has_enderdragon_fight") : null; - return new DimensionData(registryIdentifier, isNatural, ambientLight, isShrunk, isUltrawarm, hasCeiling, hasSkylight, fixedTime, hasEnderdragonFight); + Boolean hasEnderdragonFight = values.contains("has_enderdragon_fight") + ? values.getBoolean("has_enderdragon_fight") : null; + return new DimensionData( + registryIdentifier, isNatural, ambientLight, isShrunk, + isUltrawarm, hasCeiling, hasSkylight, fixedTime, hasEnderdragonFight); } - public CompoundTag encode() { + /** + * Encodes the Dimension data as CompoundTag. + * @return compound containing the dimension data + */ + public CompoundTag encodeAsCompundTag() { CompoundTag ret = new CompoundTag(); ret.putString("key", registryIdentifier); CompoundTag values = new CompoundTag(); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionInfo.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionInfo.java index 787c7a948..42f2980cd 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionInfo.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionInfo.java @@ -4,28 +4,28 @@ import javax.annotation.Nonnull; public class DimensionInfo { - private final @Nonnull String dimensionIdentifier; + private final @Nonnull String registryIdentifier; private final @Nonnull String levelName; private final boolean isFlat; private final boolean isDebugType; /** * Initializes a new {@link DimensionInfo} instance. - * @param dimensionIdentifier the identifier for the dimension from the registry + * @param registryIdentifier the identifier for the dimension from the registry * @param levelName the level name as displayed in the F3 menu and logs * @param isFlat if true will set world lighting below surface-level to not display fog * @param isDebugType if true constrains the world to the very limited debug-type world */ - public DimensionInfo(@Nonnull String dimensionIdentifier, @Nonnull String levelName, + public DimensionInfo(@Nonnull String registryIdentifier, @Nonnull String levelName, boolean isFlat, boolean isDebugType) { - if (dimensionIdentifier == null || dimensionIdentifier.isEmpty() - || dimensionIdentifier.isBlank()) { - throw new IllegalArgumentException("DimensionRegistryName may not be empty or null"); + if (registryIdentifier == null || registryIdentifier.isEmpty() + || registryIdentifier.isBlank()) { + throw new IllegalArgumentException("Dimension registry identifier may not be empty or null"); } - this.dimensionIdentifier = dimensionIdentifier; + this.registryIdentifier = registryIdentifier; if (levelName == null || levelName.isEmpty() || levelName.isBlank()) { - throw new IllegalArgumentException("DimensionLevelName may not be empty or null"); + throw new IllegalArgumentException("dimensions level name may not be empty or null"); } this.levelName = levelName; this.isFlat = isFlat; @@ -44,7 +44,7 @@ public class DimensionInfo { return levelName; } - public @Nonnull String getDimensionIdentifier() { - return dimensionIdentifier; + public @Nonnull String getRegistryIdentifier() { + return registryIdentifier; } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionRegistry.java index 606f53cc7..2bf53ffc1 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionRegistry.java @@ -1,9 +1,10 @@ package com.velocitypowered.proxy.protocol; -import java.util.*; +import java.util.HashSet; +import java.util.NoSuchElementException; +import java.util.Set; import javax.annotation.Nonnull; -import com.google.inject.internal.asm.$TypePath; import net.kyori.nbt.CompoundTag; import net.kyori.nbt.ListTag; import net.kyori.nbt.Tag; @@ -51,7 +52,7 @@ public class DimensionRegistry { throw new IllegalArgumentException("Dimension identifier cannot be null!"); } for (DimensionData iter : dimensionRegistry) { - if(iter.getRegistryIdentifier().equals(dimensionIdentifier)) { + if (iter.getRegistryIdentifier().equals(dimensionIdentifier)) { return iter; } } @@ -69,9 +70,9 @@ public class DimensionRegistry { throw new IllegalArgumentException("Dimension info cannot be null"); } try { - getDimensionData(toValidate.getDimensionIdentifier()); - for(int i = 0; i < levelNames.length; i++) { - if(levelNames[i].equals(toValidate.getDimensionIdentifier())) { + getDimensionData(toValidate.getRegistryIdentifier()); + for (int i = 0; i < levelNames.length; i++) { + if (levelNames[i].equals(toValidate.getRegistryIdentifier())) { return true; } } @@ -89,18 +90,19 @@ public class DimensionRegistry { CompoundTag ret = new CompoundTag(); ListTag list = new ListTag(TagType.COMPOUND); for (DimensionData iter : dimensionRegistry) { - list.add(iter.encode()); + list.add(iter.encodeAsCompundTag()); } ret.put("dimension", list); return ret; } /** - * Decodes a CompoundTag storing a dimension registry + * Decodes a CompoundTag storing a dimension registry. * @param toParse CompoundTag containing a dimension registry * @param levelNames world level names */ - public static DimensionRegistry fromGameData(@Nonnull CompoundTag toParse, @Nonnull String[] levelNames) { + public static DimensionRegistry fromGameData( + @Nonnull CompoundTag toParse, @Nonnull String[] levelNames) { if (toParse == null) { throw new IllegalArgumentException("CompoundTag cannot be null"); } @@ -116,7 +118,7 @@ public class DimensionRegistry { if (!(iter instanceof CompoundTag)) { throw new IllegalStateException("DimensionList in CompoundTag contains an invalid entry"); } - mappings.add(DimensionData.fromNBT((CompoundTag) iter)); + mappings.add(DimensionData.fromCompoundTag((CompoundTag) iter)); } if (mappings.isEmpty()) { throw new IllegalStateException("Dimension mapping cannot be empty"); 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 64104ebfa..56e1d8006 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 @@ -6,9 +6,6 @@ import com.velocitypowered.proxy.protocol.*; import io.netty.buffer.ByteBuf; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.Map; -import java.util.Set; - public class JoinGame implements MinecraftPacket { private int entityId; @@ -177,7 +174,7 @@ public class JoinGame implements MinecraftPacket { if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { ProtocolUtils.writeStringArray(buf, dimensionRegistry.getLevelNames()); ProtocolUtils.writeCompoundTag(buf, dimensionRegistry.encodeRegistry()); - ProtocolUtils.writeString(buf, dimensionInfo.getDimensionIdentifier()); + ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier()); ProtocolUtils.writeString(buf, dimensionInfo.getLevelName()); } else if (version.compareTo(ProtocolVersion.MINECRAFT_1_9_1) >= 0) { buf.writeInt(dimension); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java index 74832b5d8..e3a1a99e0 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java @@ -122,7 +122,7 @@ public class Respawn implements MinecraftPacket { @Override public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { - ProtocolUtils.writeString(buf, dimensionInfo.getDimensionIdentifier()); + ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier()); ProtocolUtils.writeString(buf, dimensionInfo.getLevelName()); } else { buf.writeInt(dimension); From 6368b47e78a499b7c1cfada8f1d0913a1ca59c8c Mon Sep 17 00:00:00 2001 From: Lechner Markus Date: Fri, 5 Jun 2020 15:58:34 +0200 Subject: [PATCH 64/76] Old sins --- .../java/com/velocitypowered/proxy/protocol/packet/Respawn.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java index e3a1a99e0..ba12ee9a3 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java @@ -113,7 +113,7 @@ public class Respawn implements MinecraftPacket { boolean isDebug = buf.readBoolean(); boolean isFlat = buf.readBoolean(); this.dimensionInfo = new DimensionInfo(dimensionIdentifier, levelName, isFlat, isDebug); - shouldKeepPlayerData = buf.readBoolean(); + this.shouldKeepPlayerData = buf.readBoolean(); } else { this.levelType = ProtocolUtils.readString(buf, 16); } From 0377a6829f7b280aa35bb78219cfb86b0321ab64 Mon Sep 17 00:00:00 2001 From: Lechner Markus Date: Fri, 5 Jun 2020 16:00:51 +0200 Subject: [PATCH 65/76] Move to Registry --- .../proxy/connection/backend/VelocityServerConnection.java | 3 +-- .../proxy/connection/client/ClientPlaySessionHandler.java | 2 -- .../proxy/{protocol => connection/registry}/DimensionData.java | 2 +- .../proxy/{protocol => connection/registry}/DimensionInfo.java | 2 +- .../{protocol => connection/registry}/DimensionRegistry.java | 2 +- .../com/velocitypowered/proxy/protocol/packet/JoinGame.java | 2 ++ .../com/velocitypowered/proxy/protocol/packet/Respawn.java | 2 +- 7 files changed, 7 insertions(+), 8 deletions(-) rename proxy/src/main/java/com/velocitypowered/proxy/{protocol => connection/registry}/DimensionData.java (98%) rename proxy/src/main/java/com/velocitypowered/proxy/{protocol => connection/registry}/DimensionInfo.java (96%) rename proxy/src/main/java/com/velocitypowered/proxy/{protocol => connection/registry}/DimensionRegistry.java (98%) 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 7d1d5832f..dd3fd6e02 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 @@ -12,7 +12,6 @@ import static com.velocitypowered.proxy.network.Connections.READ_TIMEOUT; import com.google.common.base.Preconditions; import com.velocitypowered.api.network.ProtocolVersion; -import com.velocitypowered.api.proxy.ConnectionRequestBuilder; import com.velocitypowered.api.proxy.ServerConnection; import com.velocitypowered.api.proxy.messages.ChannelIdentifier; import com.velocitypowered.api.proxy.server.ServerInfo; @@ -23,7 +22,7 @@ import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl; -import com.velocitypowered.proxy.protocol.DimensionRegistry; +import com.velocitypowered.proxy.connection.registry.DimensionRegistry; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; 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 07132bac0..4f64edff7 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 @@ -13,8 +13,6 @@ import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.backend.BackendConnectionPhases; import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; -import com.velocitypowered.proxy.protocol.DimensionInfo; -import com.velocitypowered.proxy.protocol.DimensionRegistry; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.packet.BossBar; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionData.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionData.java similarity index 98% rename from proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionData.java rename to proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionData.java index ed6867e03..cb5df5f2e 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionData.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionData.java @@ -1,4 +1,4 @@ -package com.velocitypowered.proxy.protocol; +package com.velocitypowered.proxy.connection.registry; import javax.annotation.Nonnull; import javax.annotation.Nullable; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionInfo.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionInfo.java similarity index 96% rename from proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionInfo.java rename to proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionInfo.java index 42f2980cd..c1c1874d4 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionInfo.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionInfo.java @@ -1,4 +1,4 @@ -package com.velocitypowered.proxy.protocol; +package com.velocitypowered.proxy.connection.registry; import javax.annotation.Nonnull; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionRegistry.java similarity index 98% rename from proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionRegistry.java rename to proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionRegistry.java index 2bf53ffc1..303ce3aa1 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/DimensionRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionRegistry.java @@ -1,4 +1,4 @@ -package com.velocitypowered.proxy.protocol; +package com.velocitypowered.proxy.connection.registry; import java.util.HashSet; import java.util.NoSuchElementException; 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 56e1d8006..3e1bfe5a6 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 @@ -2,6 +2,8 @@ package com.velocitypowered.proxy.protocol.packet; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.connection.registry.DimensionInfo; +import com.velocitypowered.proxy.connection.registry.DimensionRegistry; import com.velocitypowered.proxy.protocol.*; import io.netty.buffer.ByteBuf; import org.checkerframework.checker.nullness.qual.Nullable; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java index ba12ee9a3..5f883c538 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java @@ -2,7 +2,7 @@ package com.velocitypowered.proxy.protocol.packet; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; -import com.velocitypowered.proxy.protocol.DimensionInfo; +import com.velocitypowered.proxy.connection.registry.DimensionInfo; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; From f868cea5830fac6fec03fa360fd6650d65de0e20 Mon Sep 17 00:00:00 2001 From: "Five (Xer)" Date: Sun, 7 Jun 2020 00:14:23 +0200 Subject: [PATCH 66/76] Move to proper API --- .../backend/VelocityServerConnection.java | 6 +- .../connection/registry/DimensionData.java | 55 +++++---- .../connection/registry/DimensionInfo.java | 34 +++--- .../registry/DimensionRegistry.java | 109 ++++++++---------- 4 files changed, 97 insertions(+), 107 deletions(-) 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 dd3fd6e02..525b5cf26 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 @@ -21,8 +21,8 @@ import com.velocitypowered.proxy.connection.ConnectionTypes; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; -import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl; import com.velocitypowered.proxy.connection.registry.DimensionRegistry; +import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; @@ -40,6 +40,8 @@ import io.netty.handler.flow.FlowControlHandler; import io.netty.handler.timeout.ReadTimeoutHandler; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; + +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.Nullable; public class VelocityServerConnection implements MinecraftConnectionAssociation, ServerConnection { @@ -53,7 +55,7 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation, private BackendConnectionPhase connectionPhase = BackendConnectionPhases.UNKNOWN; private long lastPingId; private long lastPingSent; - private @Nullable DimensionRegistry activeDimensionRegistry; + private @MonotonicNonNull DimensionRegistry activeDimensionRegistry; /** * Initializes a new server connection. diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionData.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionData.java index cb5df5f2e..f6ced21ce 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionData.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionData.java @@ -1,19 +1,19 @@ package com.velocitypowered.proxy.connection.registry; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; import net.kyori.nbt.CompoundTag; -public class DimensionData { - private final @Nonnull String registryIdentifier; +public final class DimensionData { + private final String registryIdentifier; private final boolean isNatural; private final float ambientLight; private final boolean isShrunk; private final boolean isUltrawarm; private final boolean hasCeiling; private final boolean hasSkylight; - private final @Nullable Long fixedTime; - private final @Nullable Boolean hasEnderdragonFight; + private final Optional fixedTime; + private final Optional hasEnderdragonFight; /** * Initializes a new {@link DimensionData} instance. @@ -27,10 +27,14 @@ public class DimensionData { * @param fixedTime optional. If set to any game daytime value will deactivate time cycle * @param hasEnderdragonFight optional. Internal flag used in the end dimension */ - public DimensionData(@Nonnull String registryIdentifier, boolean isNatural, + public DimensionData(String registryIdentifier, boolean isNatural, float ambientLight, boolean isShrunk, boolean isUltrawarm, boolean hasCeiling, boolean hasSkylight, - @Nullable Long fixedTime, @Nullable Boolean hasEnderdragonFight) { + Optional fixedTime, Optional hasEnderdragonFight) { + Preconditions.checkNotNull( + registryIdentifier, "registryIdentifier cannot be null"); + Preconditions.checkArgument(registryIdentifier.length() > 0 && !registryIdentifier.isBlank(), + "registryIdentifier cannot be empty"); this.registryIdentifier = registryIdentifier; this.isNatural = isNatural; this.ambientLight = ambientLight; @@ -38,11 +42,13 @@ public class DimensionData { this.isUltrawarm = isUltrawarm; this.hasCeiling = hasCeiling; this.hasSkylight = hasSkylight; - this.fixedTime = fixedTime; - this.hasEnderdragonFight = hasEnderdragonFight; + this.fixedTime = Preconditions.checkNotNull( + fixedTime, "fixedTime optional object cannot be null"); + this.hasEnderdragonFight = Preconditions.checkNotNull( + hasEnderdragonFight, "hasEnderdragonFight optional object cannot be null"); } - public @Nonnull String getRegistryIdentifier() { + public String getRegistryIdentifier() { return registryIdentifier; } @@ -70,11 +76,11 @@ public class DimensionData { return hasSkylight; } - public @Nullable Long getFixedTime() { + public Optional getFixedTime() { return fixedTime; } - public @Nullable Boolean getHasEnderdragonFight() { + public Optional getHasEnderdragonFight() { return hasEnderdragonFight; } @@ -83,10 +89,8 @@ public class DimensionData { * @param toRead the compound from the registry to read * @return game dimension data */ - public static DimensionData fromCompoundTag(@Nonnull CompoundTag toRead) { - if (toRead == null) { - throw new IllegalArgumentException("CompoundTag cannot be null"); - } + public static DimensionData decodeCompoundTag(CompoundTag toRead) { + Preconditions.checkNotNull(toRead, "CompoundTag cannot be null"); String registryIdentifier = toRead.getString("key"); CompoundTag values = toRead.getCompound("element"); boolean isNatural = values.getBoolean("natural"); @@ -95,9 +99,12 @@ public class DimensionData { boolean isUltrawarm = values.getBoolean("ultrawarm"); boolean hasCeiling = values.getBoolean("has_ceiling"); boolean hasSkylight = values.getBoolean("has_skylight"); - Long fixedTime = values.contains("fixed_time") ? values.getLong("fixed_time") : null; - Boolean hasEnderdragonFight = values.contains("has_enderdragon_fight") - ? values.getBoolean("has_enderdragon_fight") : null; + Optional fixedTime = Optional.fromNullable( + values.contains("fixed_time") + ? values.getLong("fixed_time") : null); + Optional hasEnderdragonFight = Optional.fromNullable( + values.contains("has_enderdragon_fight") + ? values.getBoolean("has_enderdragon_fight") : null); return new DimensionData( registryIdentifier, isNatural, ambientLight, isShrunk, isUltrawarm, hasCeiling, hasSkylight, fixedTime, hasEnderdragonFight); @@ -117,11 +124,11 @@ public class DimensionData { values.putBoolean("ultrawarm", isUltrawarm); values.putBoolean("has_ceiling", hasCeiling); values.putBoolean("has_skylight", hasSkylight); - if (fixedTime != null) { - values.putLong("fixed_time", fixedTime); + if (fixedTime.isPresent()) { + values.putLong("fixed_time", fixedTime.get()); } - if (hasEnderdragonFight != null) { - values.putBoolean("has_enderdragon_fight", hasEnderdragonFight); + if (hasEnderdragonFight.isPresent()) { + values.putBoolean("has_enderdragon_fight", hasEnderdragonFight.get()); } ret.put("element", values); return ret; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionInfo.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionInfo.java index c1c1874d4..38500ed3a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionInfo.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionInfo.java @@ -1,11 +1,11 @@ package com.velocitypowered.proxy.connection.registry; -import javax.annotation.Nonnull; +import com.google.common.base.Preconditions; -public class DimensionInfo { +public final class DimensionInfo { - private final @Nonnull String registryIdentifier; - private final @Nonnull String levelName; + private final String registryIdentifier; + private final String levelName; private final boolean isFlat; private final boolean isDebugType; @@ -16,18 +16,18 @@ public class DimensionInfo { * @param isFlat if true will set world lighting below surface-level to not display fog * @param isDebugType if true constrains the world to the very limited debug-type world */ - public DimensionInfo(@Nonnull String registryIdentifier, @Nonnull String levelName, + public DimensionInfo(String registryIdentifier, String levelName, boolean isFlat, boolean isDebugType) { - if (registryIdentifier == null || registryIdentifier.isEmpty() - || registryIdentifier.isBlank()) { - throw new IllegalArgumentException("Dimension registry identifier may not be empty or null"); - } - this.registryIdentifier = registryIdentifier; - if (levelName == null || levelName.isEmpty() - || levelName.isBlank()) { - throw new IllegalArgumentException("dimensions level name may not be empty or null"); - } - this.levelName = levelName; + this.registryIdentifier = Preconditions.checkNotNull( + registryIdentifier, "registryIdentifier cannot be null"); + Preconditions.checkArgument( + registryIdentifier.length() > 0 && registryIdentifier.isBlank(), + "registryIdentifier cannot be empty"); + this.levelName = Preconditions.checkNotNull( + levelName, "levelName cannot be null"); + Preconditions.checkArgument( + levelName.length() > 0 && levelName.isBlank(), + "registryIdentifier cannot be empty"); this.isFlat = isFlat; this.isDebugType = isDebugType; } @@ -40,11 +40,11 @@ public class DimensionInfo { return isFlat; } - public @Nonnull String getLevelName() { + public String getLevelName() { return levelName; } - public @Nonnull String getRegistryIdentifier() { + public String getRegistryIdentifier() { return registryIdentifier; } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionRegistry.java index 303ce3aa1..9e5f1af62 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionRegistry.java @@ -1,63 +1,65 @@ package com.velocitypowered.proxy.connection.registry; -import java.util.HashSet; -import java.util.NoSuchElementException; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; + +import java.util.Map; import java.util.Set; -import javax.annotation.Nonnull; import net.kyori.nbt.CompoundTag; import net.kyori.nbt.ListTag; import net.kyori.nbt.Tag; import net.kyori.nbt.TagType; +import org.checkerframework.checker.nullness.qual.Nullable; -public class DimensionRegistry { - private final @Nonnull Set dimensionRegistry; - private final @Nonnull String[] levelNames; +public final class DimensionRegistry { + + private final Map registeredDimensions; + private final ImmutableSet levelNames; /** * Initializes a new {@link DimensionRegistry} instance. * This registry is required for 1.16+ clients/servers to communicate, * it constrains the dimension types and names the client can be sent * in a Respawn action (dimension change). - * @param dimensionRegistry a populated set containing dimension data types - * @param levelNames a populated {@link Set} of the dimension level names the server offers + * This WILL raise an IllegalArgumentException if the following is not met: + * - At least one valid DimensionData instance is provided + * - At least one valid world name is provided + * @param registeredDimensions a populated {@link ImmutableSet} containing dimension data types + * @param levelNames a populated {@link ImmutableSet} of the level (world) names the server offers */ - public DimensionRegistry(Set dimensionRegistry, - String[] levelNames) { - if (dimensionRegistry == null || dimensionRegistry.isEmpty() - || levelNames == null || levelNames.length == 0) { - throw new IllegalArgumentException( - "Dimension registry requires valid arguments, not null and not empty"); - } - this.dimensionRegistry = dimensionRegistry; + public DimensionRegistry(ImmutableSet registeredDimensions, + ImmutableSet levelNames) { + Preconditions.checkNotNull(registeredDimensions, + "registeredDimensions cannot be null"); + Preconditions.checkNotNull(levelNames, + "levelNames cannot be null"); + Preconditions.checkArgument(registeredDimensions.size() > 0, + "registeredDimensions needs to be populated"); + Preconditions.checkArgument(levelNames.size() > 0, + "levelNames needs to populated"); + this.registeredDimensions = Maps.uniqueIndex( + registeredDimensions, DimensionData::getRegistryIdentifier); this.levelNames = levelNames; } - public @Nonnull Set getDimensionRegistry() { - return dimensionRegistry; + public Map getRegisteredDimensions() { + return registeredDimensions; } - public @Nonnull String[] getLevelNames() { + public Set getLevelNames() { return levelNames; } /** * Returns the internal dimension data type as used by the game. * @param dimensionIdentifier how the dimension is identified by the connection - * @return game dimension data + * @return game dimension data or null if not registered */ - public @Nonnull DimensionData getDimensionData(@Nonnull String dimensionIdentifier) { - if (dimensionIdentifier == null) { - throw new IllegalArgumentException("Dimension identifier cannot be null!"); - } - for (DimensionData iter : dimensionRegistry) { - if (iter.getRegistryIdentifier().equals(dimensionIdentifier)) { - return iter; - } - } - throw new NoSuchElementException("Dimension with identifier " + dimensionIdentifier - + " doesn't exist in this Registry!"); + public @Nullable DimensionData getDimensionData(String dimensionIdentifier) { + return registeredDimensions.getOrDefault(dimensionIdentifier, null); } /** @@ -65,21 +67,12 @@ public class DimensionRegistry { * @param toValidate the {@link DimensionInfo} to validate * @return true: the dimension information is valid for this registry */ - public boolean isValidFor(@Nonnull DimensionInfo toValidate) { + public boolean isValidFor(DimensionInfo toValidate) { if (toValidate == null) { - throw new IllegalArgumentException("Dimension info cannot be null"); - } - try { - getDimensionData(toValidate.getRegistryIdentifier()); - for (int i = 0; i < levelNames.length; i++) { - if (levelNames[i].equals(toValidate.getRegistryIdentifier())) { - return true; - } - } - return false; - } catch (NoSuchElementException thrown) { return false; } + return registeredDimensions.containsKey(toValidate.getRegistryIdentifier()) + && levelNames.contains(toValidate.getLevelName()); } /** @@ -89,7 +82,7 @@ public class DimensionRegistry { public CompoundTag encodeRegistry() { CompoundTag ret = new CompoundTag(); ListTag list = new ListTag(TagType.COMPOUND); - for (DimensionData iter : dimensionRegistry) { + for (DimensionData iter : registeredDimensions.values()) { list.add(iter.encodeAsCompundTag()); } ret.put("dimension", list); @@ -99,30 +92,18 @@ public class DimensionRegistry { /** * Decodes a CompoundTag storing a dimension registry. * @param toParse CompoundTag containing a dimension registry - * @param levelNames world level names */ - public static DimensionRegistry fromGameData( - @Nonnull CompoundTag toParse, @Nonnull String[] levelNames) { - if (toParse == null) { - throw new IllegalArgumentException("CompoundTag cannot be null"); - } - if (levelNames == null || levelNames.length == 0) { - throw new IllegalArgumentException("Level names cannot be null or empty"); - } - if (!toParse.contains("dimension", TagType.LIST)) { - throw new IllegalStateException("CompoundTag does not contain a dimension List"); - } + public static Set fromGameData(CompoundTag toParse) { + Preconditions.checkNotNull(toParse, "CompoundTag cannot be null"); + Preconditions.checkArgument(toParse.contains("dimension", TagType.LIST), + "CompoundTag does not contain a dimension list"); ListTag dimensions = toParse.getList("dimension"); - Set mappings = new HashSet(); + ImmutableSet.Builder mappings = ImmutableSet.builder(); for (Tag iter : dimensions) { - if (!(iter instanceof CompoundTag)) { - throw new IllegalStateException("DimensionList in CompoundTag contains an invalid entry"); + if (iter instanceof CompoundTag) { + mappings.add(DimensionData.decodeCompoundTag((CompoundTag) iter)); } - mappings.add(DimensionData.fromCompoundTag((CompoundTag) iter)); } - if (mappings.isEmpty()) { - throw new IllegalStateException("Dimension mapping cannot be empty"); - } - return new DimensionRegistry(mappings, levelNames); + return mappings.build(); } } From ef5b9cf183da0eda038374f01dc342bbea5d2d40 Mon Sep 17 00:00:00 2001 From: "Five (Xer)" Date: Sun, 7 Jun 2020 00:22:11 +0200 Subject: [PATCH 67/76] Sync to IDE --- .../proxy/connection/registry/DimensionRegistry.java | 2 +- .../proxy/protocol/packet/JoinGame.java | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionRegistry.java index 9e5f1af62..4122d09e3 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionRegistry.java @@ -93,7 +93,7 @@ public final class DimensionRegistry { * Decodes a CompoundTag storing a dimension registry. * @param toParse CompoundTag containing a dimension registry */ - public static Set fromGameData(CompoundTag toParse) { + public static ImmutableSet fromGameData(CompoundTag toParse) { Preconditions.checkNotNull(toParse, "CompoundTag cannot be null"); Preconditions.checkArgument(toParse.contains("dimension", TagType.LIST), "CompoundTag does not contain a dimension list"); 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 3e1bfe5a6..d000320ac 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 @@ -1,7 +1,9 @@ package com.velocitypowered.proxy.protocol.packet; +import com.google.common.collect.ImmutableSet; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.connection.registry.DimensionData; import com.velocitypowered.proxy.connection.registry.DimensionInfo; import com.velocitypowered.proxy.connection.registry.DimensionRegistry; import com.velocitypowered.proxy.protocol.*; @@ -134,8 +136,9 @@ public class JoinGame implements MinecraftPacket { String dimensionIdentifier = null; String levelName = null; if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { - String levelNames[] = ProtocolUtils.readStringArray(buf); - this.dimensionRegistry = DimensionRegistry.fromGameData(ProtocolUtils.readCompoundTag(buf), levelNames); + ImmutableSet levelNames = ImmutableSet.copyOf(ProtocolUtils.readStringArray(buf)); + ImmutableSet readData = DimensionRegistry.fromGameData(ProtocolUtils.readCompoundTag(buf)); + this.dimensionRegistry = new DimensionRegistry(readData, levelNames); dimensionIdentifier = ProtocolUtils.readString(buf); levelName = ProtocolUtils.readString(buf); } else if (version.compareTo(ProtocolVersion.MINECRAFT_1_9_1) >= 0) { @@ -174,7 +177,8 @@ public class JoinGame implements MinecraftPacket { buf.writeInt(entityId); buf.writeByte(gamemode); if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { - ProtocolUtils.writeStringArray(buf, dimensionRegistry.getLevelNames()); + ProtocolUtils.writeStringArray(buf, dimensionRegistry.getLevelNames().toArray( + new String[dimensionRegistry.getLevelNames().size()])); ProtocolUtils.writeCompoundTag(buf, dimensionRegistry.encodeRegistry()); ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier()); ProtocolUtils.writeString(buf, dimensionInfo.getLevelName()); From 3ed5e7718c918bbdded6c9654dc656110966244b Mon Sep 17 00:00:00 2001 From: "Five (Xer)" Date: Sun, 7 Jun 2020 00:33:06 +0200 Subject: [PATCH 68/76] Fix logic error --- .../proxy/connection/registry/DimensionInfo.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionInfo.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionInfo.java index 38500ed3a..fdfc3dc5d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionInfo.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionInfo.java @@ -21,12 +21,12 @@ public final class DimensionInfo { this.registryIdentifier = Preconditions.checkNotNull( registryIdentifier, "registryIdentifier cannot be null"); Preconditions.checkArgument( - registryIdentifier.length() > 0 && registryIdentifier.isBlank(), + registryIdentifier.length() > 0 && !registryIdentifier.isBlank(), "registryIdentifier cannot be empty"); this.levelName = Preconditions.checkNotNull( levelName, "levelName cannot be null"); Preconditions.checkArgument( - levelName.length() > 0 && levelName.isBlank(), + levelName.length() > 0 && !levelName.isBlank(), "registryIdentifier cannot be empty"); this.isFlat = isFlat; this.isDebugType = isDebugType; From 4e5f708bede9c73a74c6eee8bbbaa6bdfc38490e Mon Sep 17 00:00:00 2001 From: "Five (Xer)" Date: Sun, 7 Jun 2020 00:51:21 +0200 Subject: [PATCH 69/76] Resolve review --- .../connection/registry/DimensionData.java | 36 +++++++++---------- .../registry/DimensionRegistry.java | 2 +- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionData.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionData.java index f6ced21ce..4de63a84c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionData.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionData.java @@ -1,8 +1,8 @@ package com.velocitypowered.proxy.connection.registry; -import com.google.common.base.Optional; import com.google.common.base.Preconditions; import net.kyori.nbt.CompoundTag; +import org.checkerframework.checker.nullness.qual.Nullable; public final class DimensionData { private final String registryIdentifier; @@ -12,8 +12,8 @@ public final class DimensionData { private final boolean isUltrawarm; private final boolean hasCeiling; private final boolean hasSkylight; - private final Optional fixedTime; - private final Optional hasEnderdragonFight; + private final @Nullable Long fixedTime; + private final @Nullable Boolean hasEnderdragonFight; /** * Initializes a new {@link DimensionData} instance. @@ -30,7 +30,7 @@ public final class DimensionData { public DimensionData(String registryIdentifier, boolean isNatural, float ambientLight, boolean isShrunk, boolean isUltrawarm, boolean hasCeiling, boolean hasSkylight, - Optional fixedTime, Optional hasEnderdragonFight) { + @Nullable Long fixedTime, @Nullable Boolean hasEnderdragonFight) { Preconditions.checkNotNull( registryIdentifier, "registryIdentifier cannot be null"); Preconditions.checkArgument(registryIdentifier.length() > 0 && !registryIdentifier.isBlank(), @@ -42,10 +42,8 @@ public final class DimensionData { this.isUltrawarm = isUltrawarm; this.hasCeiling = hasCeiling; this.hasSkylight = hasSkylight; - this.fixedTime = Preconditions.checkNotNull( - fixedTime, "fixedTime optional object cannot be null"); - this.hasEnderdragonFight = Preconditions.checkNotNull( - hasEnderdragonFight, "hasEnderdragonFight optional object cannot be null"); + this.fixedTime = fixedTime; + this.hasEnderdragonFight = hasEnderdragonFight; } public String getRegistryIdentifier() { @@ -76,11 +74,11 @@ public final class DimensionData { return hasSkylight; } - public Optional getFixedTime() { + public @Nullable Long getFixedTime() { return fixedTime; } - public Optional getHasEnderdragonFight() { + public @Nullable Boolean getHasEnderdragonFight() { return hasEnderdragonFight; } @@ -99,12 +97,10 @@ public final class DimensionData { boolean isUltrawarm = values.getBoolean("ultrawarm"); boolean hasCeiling = values.getBoolean("has_ceiling"); boolean hasSkylight = values.getBoolean("has_skylight"); - Optional fixedTime = Optional.fromNullable( - values.contains("fixed_time") - ? values.getLong("fixed_time") : null); - Optional hasEnderdragonFight = Optional.fromNullable( - values.contains("has_enderdragon_fight") - ? values.getBoolean("has_enderdragon_fight") : null); + Long fixedTime = values.contains("fixed_time") + ? values.getLong("fixed_time") : null; + Boolean hasEnderdragonFight = values.contains("has_enderdragon_fight") + ? values.getBoolean("has_enderdragon_fight") : null; return new DimensionData( registryIdentifier, isNatural, ambientLight, isShrunk, isUltrawarm, hasCeiling, hasSkylight, fixedTime, hasEnderdragonFight); @@ -124,11 +120,11 @@ public final class DimensionData { values.putBoolean("ultrawarm", isUltrawarm); values.putBoolean("has_ceiling", hasCeiling); values.putBoolean("has_skylight", hasSkylight); - if (fixedTime.isPresent()) { - values.putLong("fixed_time", fixedTime.get()); + if (fixedTime != null) { + values.putLong("fixed_time", fixedTime); } - if (hasEnderdragonFight.isPresent()) { - values.putBoolean("has_enderdragon_fight", hasEnderdragonFight.get()); + if (hasEnderdragonFight != null) { + values.putBoolean("has_enderdragon_fight", hasEnderdragonFight); } ret.put("element", values); return ret; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionRegistry.java index 4122d09e3..22bf61977 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionRegistry.java @@ -59,7 +59,7 @@ public final class DimensionRegistry { * @return game dimension data or null if not registered */ public @Nullable DimensionData getDimensionData(String dimensionIdentifier) { - return registeredDimensions.getOrDefault(dimensionIdentifier, null); + return registeredDimensions.get(dimensionIdentifier); } /** From 101a6a58dd7a1c2af13edd897eade28ac92599ba Mon Sep 17 00:00:00 2001 From: "Five (Xer)" Date: Wed, 10 Jun 2020 21:00:18 +0200 Subject: [PATCH 70/76] Changes 1.16-pre3 --- .../api/network/ProtocolVersion.java | 2 +- .../connection/registry/DimensionData.java | 127 +++++++++++++----- .../proxy/protocol/ProtocolUtils.java | 21 +-- 3 files changed, 101 insertions(+), 49 deletions(-) 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 92326cc62..4782feb31 100644 --- a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java +++ b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java @@ -35,7 +35,7 @@ public enum ProtocolVersion { MINECRAFT_1_15(573, "1.15"), MINECRAFT_1_15_1(575, "1.15.1"), MINECRAFT_1_15_2(578, "1.15.2"), - MINECRAFT_1_16(722, "1.16"); + MINECRAFT_1_16(725, "1.16"); private final int protocol; private final String name; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionData.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionData.java index 4de63a84c..ca1f8ed80 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionData.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionData.java @@ -12,8 +12,14 @@ public final class DimensionData { private final boolean isUltrawarm; private final boolean hasCeiling; private final boolean hasSkylight; + private final boolean isPiglinSafe; + private final boolean doBedsWork; + private final boolean doRespawnAnchorsWork; + private final boolean hasRaids; + private final int logicalHeight; + private final String burningBehaviourIdentifier; private final @Nullable Long fixedTime; - private final @Nullable Boolean hasEnderdragonFight; + private final @Nullable Boolean createDragonFight; /** * Initializes a new {@link DimensionData} instance. @@ -24,17 +30,32 @@ public final class DimensionData { * @param isUltrawarm internal dimension warmth flag * @param hasCeiling indicates if the dimension has a ceiling layer * @param hasSkylight indicates if the dimension should display the sun + * @param isPiglinSafe indicates if piglins should naturally zombify in this dimension + * @param doBedsWork indicates if players should be able to sleep in beds in this dimension + * @param doRespawnAnchorsWork indicates if player respawn points can be used in this dimension + * @param hasRaids indicates if raids can be spawned in the dimension + * @param logicalHeight the natural max height for the given dimension + * @param burningBehaviourIdentifier the identifier for how burning blocks work in the dimension * @param fixedTime optional. If set to any game daytime value will deactivate time cycle - * @param hasEnderdragonFight optional. Internal flag used in the end dimension + * @param createDragonFight optional. Internal flag used in the end dimension */ public DimensionData(String registryIdentifier, boolean isNatural, float ambientLight, boolean isShrunk, boolean isUltrawarm, boolean hasCeiling, boolean hasSkylight, - @Nullable Long fixedTime, @Nullable Boolean hasEnderdragonFight) { + boolean isPiglinSafe, boolean doBedsWork, + boolean doRespawnAnchorsWork, boolean hasRaids, + int logicalHeight, String burningBehaviourIdentifier, + @Nullable Long fixedTime, @Nullable Boolean createDragonFight) { Preconditions.checkNotNull( registryIdentifier, "registryIdentifier cannot be null"); Preconditions.checkArgument(registryIdentifier.length() > 0 && !registryIdentifier.isBlank(), "registryIdentifier cannot be empty"); + Preconditions.checkArgument(logicalHeight >= 0, "localHeight must be >= 0"); + Preconditions.checkNotNull( + burningBehaviourIdentifier, "burningBehaviourIdentifier cannot be null"); + Preconditions.checkArgument(burningBehaviourIdentifier.length() > 0 + && !burningBehaviourIdentifier.isBlank(), + "burningBehaviourIdentifier cannot be empty"); this.registryIdentifier = registryIdentifier; this.isNatural = isNatural; this.ambientLight = ambientLight; @@ -42,8 +63,14 @@ public final class DimensionData { this.isUltrawarm = isUltrawarm; this.hasCeiling = hasCeiling; this.hasSkylight = hasSkylight; + this.isPiglinSafe = isPiglinSafe; + this.doBedsWork = doBedsWork; + this.doRespawnAnchorsWork = doRespawnAnchorsWork; + this.hasRaids = hasRaids; + this.logicalHeight = logicalHeight; + this.burningBehaviourIdentifier = burningBehaviourIdentifier; this.fixedTime = fixedTime; - this.hasEnderdragonFight = hasEnderdragonFight; + this.createDragonFight = createDragonFight; } public String getRegistryIdentifier() { @@ -66,20 +93,44 @@ public final class DimensionData { return isUltrawarm; } - public boolean isHasCeiling() { + public boolean hasCeiling() { return hasCeiling; } - public boolean isHasSkylight() { + public boolean hasSkylight() { return hasSkylight; } + public boolean isPiglinSafe() { + return isPiglinSafe; + } + + public boolean doBedsWork() { + return doBedsWork; + } + + public boolean doRespawnAnchorsWork() { + return doRespawnAnchorsWork; + } + + public boolean hasRaids() { + return hasRaids; + } + + public int getLogicalHeight() { + return logicalHeight; + } + + public String getBurningBehaviourIdentifier() { + return burningBehaviourIdentifier; + } + public @Nullable Long getFixedTime() { return fixedTime; } - public @Nullable Boolean getHasEnderdragonFight() { - return hasEnderdragonFight; + public @Nullable Boolean getCreateDragonFight() { + return createDragonFight; } /** @@ -89,21 +140,27 @@ public final class DimensionData { */ public static DimensionData decodeCompoundTag(CompoundTag toRead) { Preconditions.checkNotNull(toRead, "CompoundTag cannot be null"); - String registryIdentifier = toRead.getString("key"); - CompoundTag values = toRead.getCompound("element"); - boolean isNatural = values.getBoolean("natural"); - float ambientLight = values.getFloat("ambient_light"); - boolean isShrunk = values.getBoolean("shrunk"); - boolean isUltrawarm = values.getBoolean("ultrawarm"); - boolean hasCeiling = values.getBoolean("has_ceiling"); - boolean hasSkylight = values.getBoolean("has_skylight"); - Long fixedTime = values.contains("fixed_time") - ? values.getLong("fixed_time") : null; - Boolean hasEnderdragonFight = values.contains("has_enderdragon_fight") - ? values.getBoolean("has_enderdragon_fight") : null; + String registryIdentifier = toRead.getString("name"); + boolean isNatural = toRead.getBoolean("natural"); + float ambientLight = toRead.getFloat("ambient_light"); + boolean isShrunk = toRead.getBoolean("shrunk"); + boolean isUltrawarm = toRead.getBoolean("ultrawarm"); + boolean hasCeiling = toRead.getBoolean("has_ceiling"); + boolean hasSkylight = toRead.getBoolean("has_skylight"); + boolean isPiglinSafe = toRead.getBoolean("piglin_safe"); + boolean doBedsWork = toRead.getBoolean("bed_works"); + boolean doRespawnAnchorsWork = toRead.getBoolean("respawn_anchor_works"); + boolean hasRaids = toRead.getBoolean("has_raids"); + int logicalHeight = toRead.getInt("logical_height"); + String burningBehaviourIdentifier = toRead.getString("infiniburn"); + Long fixedTime = toRead.contains("fixed_time") + ? toRead.getLong("fixed_time") : null; + Boolean hasEnderdragonFight = toRead.contains("has_enderdragon_fight") + ? toRead.getBoolean("has_enderdragon_fight") : null; return new DimensionData( registryIdentifier, isNatural, ambientLight, isShrunk, - isUltrawarm, hasCeiling, hasSkylight, fixedTime, hasEnderdragonFight); + isUltrawarm, hasCeiling, hasSkylight, isPiglinSafe, doBedsWork, doRespawnAnchorsWork, + hasRaids, logicalHeight, burningBehaviourIdentifier, fixedTime, hasEnderdragonFight); } /** @@ -112,21 +169,25 @@ public final class DimensionData { */ public CompoundTag encodeAsCompundTag() { CompoundTag ret = new CompoundTag(); - ret.putString("key", registryIdentifier); - CompoundTag values = new CompoundTag(); - values.putBoolean("natural", isNatural); - values.putFloat("ambient_light", ambientLight); - values.putBoolean("shrunk", isShrunk); - values.putBoolean("ultrawarm", isUltrawarm); - values.putBoolean("has_ceiling", hasCeiling); - values.putBoolean("has_skylight", hasSkylight); + ret.putString("name", registryIdentifier); + ret.putBoolean("natural", isNatural); + ret.putFloat("ambient_light", ambientLight); + ret.putBoolean("shrunk", isShrunk); + ret.putBoolean("ultrawarm", isUltrawarm); + ret.putBoolean("has_ceiling", hasCeiling); + ret.putBoolean("has_skylight", hasSkylight); + ret.putBoolean("piglin_safe", isPiglinSafe); + ret.putBoolean("bed_works", doBedsWork); + ret.putBoolean("respawn_anchor_works", doRespawnAnchorsWork); + ret.putBoolean("has_raids", hasRaids); + ret.putInt("logical_height", logicalHeight); + ret.putString("infiniburn", burningBehaviourIdentifier); if (fixedTime != null) { - values.putLong("fixed_time", fixedTime); + ret.putLong("fixed_time", fixedTime); } - if (hasEnderdragonFight != null) { - values.putBoolean("has_enderdragon_fight", hasEnderdragonFight); + if (createDragonFight != null) { + ret.putBoolean("has_enderdragon_fight", createDragonFight); } - ret.put("element", values); return ret; } } 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 a272c8023..bb7516aa3 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.UUID; import net.kyori.nbt.CompoundTag; +import net.kyori.nbt.TagIO; import net.kyori.nbt.TagType; public enum ProtocolUtils { @@ -203,17 +204,10 @@ public enum ProtocolUtils { } buf.readerIndex(indexBefore); try { - DataInput input = new ByteBufInputStream(buf); - byte type = input.readByte(); - if (type != TagType.COMPOUND.id()) { - throw new DecoderException("NBTTag is not a CompoundTag"); - } - input.readUTF(); // Head-padding - CompoundTag compoundTag = new CompoundTag(); - compoundTag.read(input, 0); - return compoundTag; - } catch (IOException e) { - throw new DecoderException("Unable to decode NBT CompoundTag at " + indexBefore); + return TagIO.readDataInput(new ByteBufInputStream(buf)); + } catch (IOException thrown) { + throw new DecoderException( + "Unable to parse NBT CompoundTag, full error: " + thrown.getMessage()); } } @@ -228,10 +222,7 @@ public enum ProtocolUtils { return; } try { - DataOutput output = new ByteBufOutputStream(buf); - output.writeByte(10); // Type 10 - CompoundTag - output.writeUTF(""); // Head-padding - compoundTag.write(output); + TagIO.writeDataOutput(compoundTag, new ByteBufOutputStream(buf)); } catch (IOException e) { throw new EncoderException("Unable to encode NBT CompoundTag"); } From 78b442a852a14633ea16fa09f3f27b28fd44ea3f Mon Sep 17 00:00:00 2001 From: "Five (Xer)" Date: Thu, 11 Jun 2020 23:39:16 +0200 Subject: [PATCH 71/76] Changes 1.16-pre4 and Logic fixes --- .../velocitypowered/api/network/ProtocolVersion.java | 2 +- .../proxy/connection/client/LoginSessionHandler.java | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) 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 4782feb31..6fda2d2b3 100644 --- a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java +++ b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java @@ -35,7 +35,7 @@ public enum ProtocolVersion { MINECRAFT_1_15(573, "1.15"), MINECRAFT_1_15_1(575, "1.15.1"), MINECRAFT_1_15_2(578, "1.15.2"), - MINECRAFT_1_16(725, "1.16"); + MINECRAFT_1_16(727, "1.16"); private final int protocol; private final String name; 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 d0707588c..cfe5f4f92 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 @@ -18,7 +18,10 @@ import com.velocitypowered.api.event.permission.PermissionsSetupEvent; import com.velocitypowered.api.event.player.GameProfileRequestEvent; import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.util.GameProfile; +import com.velocitypowered.api.util.UuidUtils; import com.velocitypowered.proxy.VelocityServer; +import com.velocitypowered.proxy.config.PlayerInfoForwarding; +import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; @@ -41,6 +44,7 @@ import java.security.GeneralSecurityException; import java.security.KeyPair; import java.util.Arrays; import java.util.Optional; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ThreadLocalRandom; import net.kyori.text.Component; @@ -253,10 +257,14 @@ public class LoginSessionHandler implements MinecraftSessionHandler { mcConnection.write(new SetCompression(threshold)); mcConnection.setCompressionThreshold(threshold); } - + VelocityConfiguration configuration = server.getConfiguration(); + UUID playerUniqueId = player.getUniqueId(); + if (configuration.getPlayerInfoForwardingMode() == PlayerInfoForwarding.NONE) { + playerUniqueId = UuidUtils.generateOfflinePlayerUuid(player.getUsername()); + } ServerLoginSuccess success = new ServerLoginSuccess(); success.setUsername(player.getUsername()); - success.setUuid(player.getUniqueId()); + success.setUuid(playerUniqueId); mcConnection.write(success); mcConnection.setAssociation(player); From 6577b08bdd6c6286681b3c784bf8664681b4697a Mon Sep 17 00:00:00 2001 From: "Five (Xer)" Date: Sat, 13 Jun 2020 11:26:51 +0200 Subject: [PATCH 72/76] Changes 1.16-pre5 --- .../java/com/velocitypowered/api/network/ProtocolVersion.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 6fda2d2b3..46d515ed7 100644 --- a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java +++ b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java @@ -35,7 +35,7 @@ public enum ProtocolVersion { MINECRAFT_1_15(573, "1.15"), MINECRAFT_1_15_1(575, "1.15.1"), MINECRAFT_1_15_2(578, "1.15.2"), - MINECRAFT_1_16(727, "1.16"); + MINECRAFT_1_16(729, "1.16"); private final int protocol; private final String name; From a1ab29186b26145c88f07552f6a1ad81bdd2cc22 Mon Sep 17 00:00:00 2001 From: "Five (Xer)" Date: Tue, 16 Jun 2020 17:56:56 +0200 Subject: [PATCH 73/76] Changes 1.16-pre6 --- .../api/network/ProtocolVersion.java | 2 +- .../client/ClientPlaySessionHandler.java | 4 ++-- .../proxy/protocol/packet/JoinGame.java | 21 ++++++++++++------- .../proxy/protocol/packet/Respawn.java | 20 +++++++++++++++--- 4 files changed, 34 insertions(+), 13 deletions(-) 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 46d515ed7..fbdb2b9b9 100644 --- a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java +++ b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java @@ -35,7 +35,7 @@ public enum ProtocolVersion { MINECRAFT_1_15(573, "1.15"), MINECRAFT_1_15_1(575, "1.15.1"), MINECRAFT_1_15_2(578, "1.15.2"), - MINECRAFT_1_16(729, "1.16"); + MINECRAFT_1_16(730, "1.16"); private final int protocol; private final String name; 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 4f64edff7..0e6dbe729 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 @@ -339,13 +339,13 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { player.getMinecraftConnection().delayedWrite( new Respawn(tempDim, joinGame.getPartialHashedSeed(), joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(), - false, joinGame.getDimensionInfo())); + false, joinGame.getDimensionInfo(), joinGame.getPreviousGamemode())); } player.getMinecraftConnection().delayedWrite( new Respawn(joinGame.getDimension(), joinGame.getPartialHashedSeed(), joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(), - false, joinGame.getDimensionInfo())); + false, joinGame.getDimensionInfo(), joinGame.getPreviousGamemode())); destination.setActiveDimensionRegistry(joinGame.getDimensionRegistry()); // 1.16 } 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 d000320ac..630a60de2 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 @@ -24,6 +24,7 @@ public class JoinGame implements MinecraftPacket { private boolean showRespawnScreen; private DimensionRegistry dimensionRegistry; // 1.16+ private DimensionInfo dimensionInfo; // 1.16+ + private short previousGamemode; // 1.16+ public int getEntityId() { return entityId; @@ -69,10 +70,7 @@ public class JoinGame implements MinecraftPacket { this.maxPlayers = maxPlayers; } - public String getLevelType() { - if (levelType == null) { - throw new IllegalStateException("No level type specified."); - } + public @Nullable String getLevelType() { return levelType; } @@ -112,6 +110,14 @@ public class JoinGame implements MinecraftPacket { this.dimensionRegistry = dimensionRegistry; } + public short getPreviousGamemode() { + return previousGamemode; + } + + public void setPreviousGamemode(short previousGamemode) { + this.previousGamemode = previousGamemode; + } + @Override public String toString() { return "JoinGame{" @@ -126,16 +132,18 @@ public class JoinGame implements MinecraftPacket { + ", reducedDebugInfo=" + reducedDebugInfo + ", dimensionRegistry='" + dimensionRegistry.toString() + '\'' + ", dimensionInfo='" + dimensionInfo.toString() + '\'' + + ", previousGamemode=" + previousGamemode + '}'; } @Override public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { this.entityId = buf.readInt(); - this.gamemode = buf.readUnsignedByte(); + this.gamemode = buf.readByte(); String dimensionIdentifier = null; String levelName = null; if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { + this.previousGamemode = buf.readByte(); ImmutableSet levelNames = ImmutableSet.copyOf(ProtocolUtils.readStringArray(buf)); ImmutableSet readData = DimensionRegistry.fromGameData(ProtocolUtils.readCompoundTag(buf)); this.dimensionRegistry = new DimensionRegistry(readData, levelNames); @@ -155,8 +163,6 @@ public class JoinGame implements MinecraftPacket { this.maxPlayers = buf.readUnsignedByte(); if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) < 0) { this.levelType = ProtocolUtils.readString(buf, 16); - } else { - this.levelType = "default"; // I didn't have the courage to rework this yet. } if (version.compareTo(ProtocolVersion.MINECRAFT_1_14) >= 0) { this.viewDistance = ProtocolUtils.readVarInt(buf); @@ -177,6 +183,7 @@ public class JoinGame implements MinecraftPacket { buf.writeInt(entityId); buf.writeByte(gamemode); if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { + buf.writeByte(previousGamemode); ProtocolUtils.writeStringArray(buf, dimensionRegistry.getLevelNames().toArray( new String[dimensionRegistry.getLevelNames().size()])); ProtocolUtils.writeCompoundTag(buf, dimensionRegistry.encodeRegistry()); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java index 5f883c538..d14e9f8c8 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/Respawn.java @@ -15,13 +15,15 @@ public class Respawn implements MinecraftPacket { private short gamemode; private String levelType = ""; private boolean shouldKeepPlayerData; // 1.16+ - private DimensionInfo dimensionInfo; + private DimensionInfo dimensionInfo; // 1.16+ + private short previousGamemode; // 1.16+ public Respawn() { } public Respawn(int dimension, long partialHashedSeed, short difficulty, short gamemode, - String levelType, boolean shouldKeepPlayerData, DimensionInfo dimensionInfo) { + String levelType, boolean shouldKeepPlayerData, DimensionInfo dimensionInfo, + short previousGamemode) { this.dimension = dimension; this.partialHashedSeed = partialHashedSeed; this.difficulty = difficulty; @@ -29,6 +31,7 @@ public class Respawn implements MinecraftPacket { this.levelType = levelType; this.shouldKeepPlayerData = shouldKeepPlayerData; this.dimensionInfo = dimensionInfo; + this.previousGamemode = previousGamemode; } public int getDimension() { @@ -79,6 +82,14 @@ public class Respawn implements MinecraftPacket { this.shouldKeepPlayerData = shouldKeepPlayerData; } + public short getPreviousGamemode() { + return previousGamemode; + } + + public void setPreviousGamemode(short previousGamemode) { + this.previousGamemode = previousGamemode; + } + @Override public String toString() { return "Respawn{" @@ -89,6 +100,7 @@ public class Respawn implements MinecraftPacket { + ", levelType='" + levelType + '\'' + ", shouldKeepPlayerData=" + shouldKeepPlayerData + ", dimensionRegistryName='" + dimensionInfo.toString() + '\'' + + ", previousGamemode=" + previousGamemode + '}'; } @@ -108,8 +120,9 @@ public class Respawn implements MinecraftPacket { if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) { this.partialHashedSeed = buf.readLong(); } - this.gamemode = buf.readUnsignedByte(); + this.gamemode = buf.readByte(); if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { + this.previousGamemode = buf.readByte(); boolean isDebug = buf.readBoolean(); boolean isFlat = buf.readBoolean(); this.dimensionInfo = new DimensionInfo(dimensionIdentifier, levelName, isFlat, isDebug); @@ -135,6 +148,7 @@ public class Respawn implements MinecraftPacket { } buf.writeByte(gamemode); if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { + buf.writeByte(previousGamemode); buf.writeBoolean(dimensionInfo.isDebugType()); buf.writeBoolean(dimensionInfo.isFlat()); buf.writeBoolean(shouldKeepPlayerData); From ee64b97b8ee1b217686687f3c4c6326cf0ff9c14 Mon Sep 17 00:00:00 2001 From: "Five (Xer)" Date: Tue, 16 Jun 2020 18:39:51 +0200 Subject: [PATCH 74/76] Changes 1.16-pre7 --- .../java/com/velocitypowered/api/network/ProtocolVersion.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 fbdb2b9b9..0d05720a9 100644 --- a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java +++ b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java @@ -35,7 +35,7 @@ public enum ProtocolVersion { MINECRAFT_1_15(573, "1.15"), MINECRAFT_1_15_1(575, "1.15.1"), MINECRAFT_1_15_2(578, "1.15.2"), - MINECRAFT_1_16(730, "1.16"); + MINECRAFT_1_16(732, "1.16"); private final int protocol; private final String name; From 83ba7d6051ad8efcbc3ce56ebc0fc2caec2efdff Mon Sep 17 00:00:00 2001 From: "FivePB (Xer)" Date: Thu, 18 Jun 2020 18:24:39 +0200 Subject: [PATCH 75/76] Changes 1.16-rc1 --- .../java/com/velocitypowered/api/network/ProtocolVersion.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 0d05720a9..0a727a036 100644 --- a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java +++ b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java @@ -35,7 +35,7 @@ public enum ProtocolVersion { MINECRAFT_1_15(573, "1.15"), MINECRAFT_1_15_1(575, "1.15.1"), MINECRAFT_1_15_2(578, "1.15.2"), - MINECRAFT_1_16(732, "1.16"); + MINECRAFT_1_16(734, "1.16"); private final int protocol; private final String name; From f8e20ab3a7e75936e67c76a71b8fbc88c06c6bbd Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Fri, 19 Jun 2020 05:22:19 -0400 Subject: [PATCH 76/76] Fix tab complete using proper vanilla limit. --- .../proxy/protocol/packet/TabCompleteRequest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 91a83915d..37788eb0e 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 @@ -13,6 +13,8 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class TabCompleteRequest implements MinecraftPacket { + private static final int VANILLA_MAX_TAB_COMPLETE_LEN = 2048; + private @Nullable String command; private int transactionId; private boolean assumeCommand; @@ -77,9 +79,9 @@ public class TabCompleteRequest implements MinecraftPacket { public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { if (version.compareTo(MINECRAFT_1_13) >= 0) { this.transactionId = ProtocolUtils.readVarInt(buf); - this.command = ProtocolUtils.readString(buf, Chat.MAX_SERVERBOUND_MESSAGE_LENGTH); + this.command = ProtocolUtils.readString(buf, VANILLA_MAX_TAB_COMPLETE_LEN); } else { - this.command = ProtocolUtils.readString(buf, Chat.MAX_SERVERBOUND_MESSAGE_LENGTH); + this.command = ProtocolUtils.readString(buf, VANILLA_MAX_TAB_COMPLETE_LEN); if (version.compareTo(MINECRAFT_1_9) >= 0) { this.assumeCommand = buf.readBoolean(); }