From afd8b55fb52daa36e59f10b3ceb1fa953c29c485 Mon Sep 17 00:00:00 2001 From: booky10 <53302036+booky10@users.noreply.github.com> Date: Fri, 10 May 2024 03:56:03 +0200 Subject: [PATCH 01/11] Fix permission for velocity dump command (#1318) --- .../velocitypowered/proxy/command/builtin/VelocityCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java index 26f99ecd2..7904a06a0 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java @@ -74,7 +74,7 @@ public final class VelocityCommand { @SuppressWarnings("checkstyle:MissingJavadocMethod") public static BrigadierCommand create(final VelocityServer server) { final LiteralCommandNode dump = BrigadierCommand.literalArgumentBuilder("dump") - .requires(source -> source.getPermissionValue("velocity.command.plugins") == Tristate.TRUE) + .requires(source -> source.getPermissionValue("velocity.command.dump") == Tristate.TRUE) .executes(new Dump(server)) .build(); final LiteralCommandNode heap = BrigadierCommand.literalArgumentBuilder("heap") From a02b601b6c5d343475767685c0e5f39780e09c6e Mon Sep 17 00:00:00 2001 From: Gijs de Jong <14833076+oxkitsune@users.noreply.github.com> Date: Thu, 23 May 2024 20:09:53 +0200 Subject: [PATCH 02/11] Pass through packets in config state (#1326) --- .../connection/client/ClientConfigSessionHandler.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java index c1ed4a87a..d9c5295db 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java @@ -128,17 +128,20 @@ public class ClientConfigSessionHandler implements MinecraftSessionHandler { public boolean handle(PingIdentifyPacket packet) { if (player.getConnectionInFlight() != null) { player.getConnectionInFlight().ensureConnected().write(packet); + return true; } - return true; + + return false; } @Override public boolean handle(KnownPacksPacket packet) { if (player.getConnectionInFlight() != null) { player.getConnectionInFlight().ensureConnected().write(packet); + return true; } - return true; + return false; } @Override From 71bb0246a8bf7507b570e6a82a99ce77a33ad19d Mon Sep 17 00:00:00 2001 From: "Pantera (Mad_Daniel)" <89838384+Pantera07@users.noreply.github.com> Date: Fri, 24 May 2024 03:12:28 +0900 Subject: [PATCH 03/11] Fix query listener reload bug (#1322) --- .../main/java/com/velocitypowered/proxy/VelocityServer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index 1a9d71cb4..ef8217c69 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -469,11 +469,11 @@ public class VelocityServer implements ProxyServer, ForwardingAudience { boolean queryPortChanged = newConfiguration.getQueryPort() != configuration.getQueryPort(); boolean queryAlreadyEnabled = configuration.isQueryEnabled(); boolean queryEnabled = newConfiguration.isQueryEnabled(); - if ((!queryEnabled && queryAlreadyEnabled) || queryPortChanged) { + if (queryAlreadyEnabled && (!queryEnabled || queryPortChanged)) { this.cm.close(new InetSocketAddress( configuration.getBind().getHostString(), configuration.getQueryPort())); } - if (queryEnabled && queryPortChanged) { + if (queryEnabled && (!queryAlreadyEnabled || queryPortChanged)) { this.cm.queryBind(newConfiguration.getBind().getHostString(), newConfiguration.getQueryPort()); } From 42d42883341c240b4ecf059707429c8aaa8d17cf Mon Sep 17 00:00:00 2001 From: Gero Date: Sun, 26 May 2024 14:19:37 +0200 Subject: [PATCH 04/11] Fix tab completions not being forwarded to backend if proxy command exists but is inaccessible (#1329) --- .../velocitypowered/api/command/CommandManager.java | 12 ++++++++++++ .../proxy/command/VelocityCommandManager.java | 13 ++++++++++++- .../connection/client/ClientPlaySessionHandler.java | 2 +- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/api/src/main/java/com/velocitypowered/api/command/CommandManager.java b/api/src/main/java/com/velocitypowered/api/command/CommandManager.java index 5ece4e7d6..6cf90a791 100644 --- a/api/src/main/java/com/velocitypowered/api/command/CommandManager.java +++ b/api/src/main/java/com/velocitypowered/api/command/CommandManager.java @@ -10,6 +10,7 @@ package com.velocitypowered.api.command; import com.velocitypowered.api.event.command.CommandExecuteEvent; import java.util.Collection; import java.util.concurrent.CompletableFuture; +import java.util.function.Predicate; import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -126,4 +127,15 @@ public interface CommandManager { * @return true if the alias is registered; false otherwise */ boolean hasCommand(String alias); + + /** + * Returns whether the given alias is registered on this manager + * and can be used by the given {@link CommandSource}. + * See {@link com.mojang.brigadier.builder.ArgumentBuilder#requires(Predicate)} + * + * @param alias the command alias to check + * @param source the command source + * @return true if the alias is registered and usable; false otherwise + */ + boolean hasCommand(String alias, CommandSource source); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java index b92c4f500..a4fab9321 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java @@ -347,8 +347,19 @@ public class VelocityCommandManager implements CommandManager { @Override public boolean hasCommand(final String alias) { + return getCommand(alias) != null; + } + + @Override + public boolean hasCommand(String alias, CommandSource source) { + Preconditions.checkNotNull(source, "source"); + CommandNode command = getCommand(alias); + return command != null && command.canUse(source); + } + + CommandNode getCommand(final String alias) { Preconditions.checkNotNull(alias, "alias"); - return dispatcher.getRoot().getChild(alias.toLowerCase(Locale.ENGLISH)) != null; + return dispatcher.getRoot().getChild(alias.toLowerCase(Locale.ENGLISH)); } @VisibleForTesting // this constitutes unsafe publication 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 ef769b168..f5e6e880f 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 @@ -630,7 +630,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { } String commandLabel = command.substring(0, commandEndPosition); - if (!server.getCommandManager().hasCommand(commandLabel)) { + if (!server.getCommandManager().hasCommand(commandLabel, player)) { if (player.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_13)) { // Outstanding tab completes are recorded for use with 1.12 clients and below to provide // additional tab completion support. From 1c36b66dcbf2f1a42c60eea0c5d9aee8fc506020 Mon Sep 17 00:00:00 2001 From: R00tB33rMan <36140389+R00tB33rMan@users.noreply.github.com> Date: Tue, 28 May 2024 09:53:20 -0400 Subject: [PATCH 05/11] Fix `null` or empty has handling in `LegacyResourcepackHandler` (#1331) --- .../player/resourcepack/LegacyResourcePackHandler.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/LegacyResourcePackHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/LegacyResourcePackHandler.java index b2b803991..b1b325484 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/LegacyResourcePackHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/LegacyResourcePackHandler.java @@ -109,7 +109,7 @@ public sealed class LegacyResourcePackHandler extends ResourcePackHandler break; } onResourcePackResponse(new ResourcePackResponseBundle(queued.getId(), - new String(queued.getHash()), + queued.getHash() == null ? "" : new String(queued.getHash()), PlayerResourcePackStatusEvent.Status.DECLINED)); queued = null; } @@ -172,6 +172,10 @@ public sealed class LegacyResourcePackHandler extends ResourcePackHandler @Override public boolean hasPackAppliedByHash(final byte[] hash) { + if (hash == null) { + return false; + } + return this.appliedResourcePack != null && Arrays.equals(this.appliedResourcePack.getHash(), hash); } From deacdb622818b43fdb3de426a98b7b869afbb542 Mon Sep 17 00:00:00 2001 From: Gero Date: Thu, 30 May 2024 13:40:07 +0200 Subject: [PATCH 06/11] Fix StackOverflowError in CommandGraphInjector when using redirects (#1335) --- .../proxy/command/CommandGraphInjector.java | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/CommandGraphInjector.java b/proxy/src/main/java/com/velocitypowered/proxy/command/CommandGraphInjector.java index 16e3119a6..848d76d13 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/CommandGraphInjector.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/CommandGraphInjector.java @@ -27,6 +27,8 @@ import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.LiteralCommandNode; import com.mojang.brigadier.tree.RootCommandNode; import com.velocitypowered.proxy.command.brigadier.VelocityArgumentCommandNode; +import java.util.IdentityHashMap; +import java.util.Map; import java.util.concurrent.locks.Lock; import org.checkerframework.checker.lock.qual.GuardedBy; import org.checkerframework.checker.nullness.qual.Nullable; @@ -66,6 +68,7 @@ public final class CommandGraphInjector { public void inject(final RootCommandNode dest, final S source) { lock.lock(); try { + final Map, CommandNode> done = new IdentityHashMap<>(); final RootCommandNode origin = this.dispatcher.getRoot(); final CommandContextBuilder rootContext = new CommandContextBuilder<>(this.dispatcher, source, origin, 0); @@ -88,7 +91,7 @@ public final class CommandGraphInjector { VelocityCommands.getArgumentsNode(asLiteral); if (argsNode == null) { // This literal is associated to a BrigadierCommand, filter normally. - this.copyChildren(node, copy, source); + this.copyChildren(node, copy, source, done); } else { // Copy all children nodes (arguments node and hints) for (final CommandNode child : node.getChildren()) { @@ -102,7 +105,10 @@ public final class CommandGraphInjector { } } - private @Nullable CommandNode filterNode(final CommandNode node, final S source) { + private @Nullable CommandNode filterNode(final CommandNode node, final S source, final Map, CommandNode> done) { + if (done.containsKey(node)) { + return done.get(node); + } // We only check the non-context requirement when filtering alias nodes. // Otherwise, we would need to manually craft context builder and reader instances, // which is both incorrect and inefficient. The reason why we can do so for alias @@ -116,18 +122,18 @@ public final class CommandGraphInjector { // Redirects to non-Brigadier commands are not supported. Luckily, // we don't expose the root node to API users, so they can't access // nodes associated to other commands. - final CommandNode target = this.filterNode(node.getRedirect(), source); + final CommandNode target = this.filterNode(node.getRedirect(), source, done); builder.forward(target, builder.getRedirectModifier(), builder.isFork()); } final CommandNode result = builder.build(); - this.copyChildren(node, result, source); + done.put(node, result); + this.copyChildren(node, result, source, done); return result; } - private void copyChildren(final CommandNode parent, final CommandNode dest, - final S source) { + private void copyChildren(final CommandNode parent, final CommandNode dest, final S source, final Map, CommandNode> done) { for (final CommandNode child : parent.getChildren()) { - final CommandNode filtered = this.filterNode(child, source); + final CommandNode filtered = this.filterNode(child, source, done); if (filtered != null) { dest.addChild(filtered); } From 07f1f9e7bd0789a6bb1eef134a050772c261cebd Mon Sep 17 00:00:00 2001 From: Luccboy <58391278+Luccboy@users.noreply.github.com> Date: Thu, 30 May 2024 17:52:30 +0200 Subject: [PATCH 07/11] Add Cookie API (#1313) --- .../api/event/player/CookieReceiveEvent.java | 155 ++++++++++++++++++ .../api/event/player/CookieRequestEvent.java | 127 ++++++++++++++ .../api/event/player/CookieStoreEvent.java | 154 +++++++++++++++++ .../com/velocitypowered/api/proxy/Player.java | 25 +++ .../connection/MinecraftSessionHandler.java | 15 ++ .../backend/BackendPlaySessionHandler.java | 38 +++++ .../backend/ConfigSessionHandler.java | 39 +++++ .../backend/LoginSessionHandler.java | 24 +++ .../connection/client/AuthSessionHandler.java | 20 +++ .../client/ClientConfigSessionHandler.java | 25 +++ .../client/ClientPlaySessionHandler.java | 25 +++ .../connection/client/ConnectedPlayer.java | 49 ++++++ .../proxy/protocol/StateRegistry.java | 27 +++ .../ClientboundCookieRequestPacket.java | 57 +++++++ .../packet/ClientboundStoreCookiePacket.java | 65 ++++++++ .../ServerboundCookieResponsePacket.java | 72 ++++++++ 16 files changed, 917 insertions(+) create mode 100644 api/src/main/java/com/velocitypowered/api/event/player/CookieReceiveEvent.java create mode 100644 api/src/main/java/com/velocitypowered/api/event/player/CookieRequestEvent.java create mode 100644 api/src/main/java/com/velocitypowered/api/event/player/CookieStoreEvent.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientboundCookieRequestPacket.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientboundStoreCookiePacket.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerboundCookieResponsePacket.java diff --git a/api/src/main/java/com/velocitypowered/api/event/player/CookieReceiveEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/CookieReceiveEvent.java new file mode 100644 index 000000000..d6ef0d556 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/player/CookieReceiveEvent.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2024 Velocity Contributors + * + * The Velocity API is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package com.velocitypowered.api.event.player; + +import com.google.common.base.Preconditions; +import com.velocitypowered.api.event.ResultedEvent; +import com.velocitypowered.api.event.annotation.AwaitingEvent; +import com.velocitypowered.api.proxy.Player; +import java.util.Arrays; +import net.kyori.adventure.key.Key; +import org.jetbrains.annotations.Nullable; + +/** + * This event is fired when a cookie response from a client is received by the proxy. + * This usually happens after either a proxy plugin or a backend server requested a cookie. + * Velocity will wait on this event to finish firing before discarding the + * received cookie (if handled) or forwarding it to the backend server. + */ +@AwaitingEvent +public final class CookieReceiveEvent implements ResultedEvent { + + private final Player player; + private final Key originalKey; + private final byte @Nullable [] originalData; + private ForwardResult result; + + /** + * Creates a new instance. + * + * @param player the player who sent the cookie response + * @param key the identifier of the cookie + * @param data the data of the cookie + */ + public CookieReceiveEvent(final Player player, final Key key, final byte @Nullable [] data) { + this.player = Preconditions.checkNotNull(player, "player"); + this.originalKey = Preconditions.checkNotNull(key, "key"); + this.originalData = data; + this.result = ForwardResult.forward(); + } + + @Override + public ForwardResult getResult() { + return result; + } + + @Override + public void setResult(ForwardResult result) { + this.result = Preconditions.checkNotNull(result, "result"); + } + + public Player getPlayer() { + return player; + } + + public Key getOriginalKey() { + return originalKey; + } + + public byte @Nullable [] getOriginalData() { + return originalData; + } + + @Override + public String toString() { + return "CookieReceiveEvent{" + + ", originalKey=" + originalKey + + ", originalData=" + Arrays.toString(originalData) + + ", result=" + result + + '}'; + } + + /** + * A result determining whether or not to forward the cookie response on. + */ + public static final class ForwardResult implements ResultedEvent.Result { + + private static final ForwardResult ALLOWED = new ForwardResult(true, null, null); + private static final ForwardResult DENIED = new ForwardResult(false, null, null); + + private final boolean status; + private final Key key; + private final byte[] data; + + private ForwardResult(final boolean status, final Key key, final byte[] data) { + this.status = status; + this.key = key; + this.data = data; + } + + @Override + public boolean isAllowed() { + return status; + } + + public Key getKey() { + return key; + } + + public byte[] getData() { + return data; + } + + @Override + public String toString() { + return status ? "forward to backend server" : "handled by proxy"; + } + + /** + * Allows the cookie response to be forwarded to the backend server. + * + * @return the forward result + */ + public static ForwardResult forward() { + return ALLOWED; + } + + /** + * Prevents the cookie response from being forwarded to the backend server, the cookie response + * is handled by the proxy. + * + * @return the handled result + */ + public static ForwardResult handled() { + return DENIED; + } + + /** + * Allows the cookie response to be forwarded to the backend server, but silently replaces the + * identifier of the cookie with another. + * + * @param key the identifier to use instead + * @return a result with a new key + */ + public static ForwardResult key(final Key key) { + Preconditions.checkNotNull(key, "key"); + return new ForwardResult(true, key, null); + } + + /** + * Allows the cookie response to be forwarded to the backend server, but silently replaces the + * data of the cookie with another. + * + * @param data the data of the cookie to use instead + * @return a result with new data + */ + public static ForwardResult data(final byte[] data) { + return new ForwardResult(true, null, data); + } + } +} diff --git a/api/src/main/java/com/velocitypowered/api/event/player/CookieRequestEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/CookieRequestEvent.java new file mode 100644 index 000000000..021fd7e63 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/player/CookieRequestEvent.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2024 Velocity Contributors + * + * The Velocity API is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package com.velocitypowered.api.event.player; + +import com.google.common.base.Preconditions; +import com.velocitypowered.api.event.ResultedEvent; +import com.velocitypowered.api.event.annotation.AwaitingEvent; +import com.velocitypowered.api.proxy.Player; +import net.kyori.adventure.key.Key; + +/** + * This event is fired when a cookie is requested from a client either by a proxy plugin or + * by a backend server. Velocity will wait on this event to finish firing before discarding the + * received cookie (if handled) or forwarding it to the backend server. + */ +@AwaitingEvent +public final class CookieRequestEvent implements ResultedEvent { + + private final Player player; + private final Key originalKey; + private ForwardResult result; + + /** + * Creates a new instance. + * + * @param player the player from whom the cookies is requested + * @param key the identifier of the cookie + */ + public CookieRequestEvent(final Player player, final Key key) { + this.player = Preconditions.checkNotNull(player, "player"); + this.originalKey = Preconditions.checkNotNull(key, "key"); + this.result = ForwardResult.forward(); + } + + @Override + public ForwardResult getResult() { + return result; + } + + @Override + public void setResult(ForwardResult result) { + this.result = Preconditions.checkNotNull(result, "result"); + } + + public Player getPlayer() { + return player; + } + + public Key getOriginalKey() { + return originalKey; + } + + @Override + public String toString() { + return "CookieRequestEvent{" + + ", originalKey=" + originalKey + + ", result=" + result + + '}'; + } + + /** + * A result determining whether or not to forward the cookie request on. + */ + public static final class ForwardResult implements Result { + + private static final ForwardResult ALLOWED = new ForwardResult(true, null); + private static final ForwardResult DENIED = new ForwardResult(false, null); + + private final boolean status; + private final Key key; + + private ForwardResult(final boolean status, final Key key) { + this.status = status; + this.key = key; + } + + @Override + public boolean isAllowed() { + return status; + } + + public Key getKey() { + return key; + } + + @Override + public String toString() { + return status ? "forward to client" : "handled by proxy"; + } + + /** + * Allows the cookie request to be forwarded to the client. + * + * @return the forward result + */ + public static ForwardResult forward() { + return ALLOWED; + } + + /** + * Prevents the cookie request from being forwarded to the client, the cookie request is + * handled by the proxy. + * + * @return the handled result + */ + public static ForwardResult handled() { + return DENIED; + } + + /** + * Allows the cookie response to be forwarded to the client, but silently replaces the + * identifier of the cookie with another. + * + * @param key the identifier to use instead + * @return a result with a new key + */ + public static ForwardResult key(final Key key) { + Preconditions.checkNotNull(key, "key"); + return new ForwardResult(true, key); + } + } +} diff --git a/api/src/main/java/com/velocitypowered/api/event/player/CookieStoreEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/CookieStoreEvent.java new file mode 100644 index 000000000..0bcb4dcd1 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/player/CookieStoreEvent.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2024 Velocity Contributors + * + * The Velocity API is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package com.velocitypowered.api.event.player; + +import com.google.common.base.Preconditions; +import com.velocitypowered.api.event.ResultedEvent; +import com.velocitypowered.api.event.annotation.AwaitingEvent; +import com.velocitypowered.api.proxy.Player; +import java.util.Arrays; +import net.kyori.adventure.key.Key; + +/** + * This event is fired when a cookie should be stored on a player's client. This process can be + * initiated either by a proxy plugin or by a backend server. Velocity will wait on this event + * to finish firing before discarding the cookie (if handled) or forwarding it to the client so + * that it can store the cookie. + */ +@AwaitingEvent +public final class CookieStoreEvent implements ResultedEvent { + + private final Player player; + private final Key originalKey; + private final byte[] originalData; + private ForwardResult result; + + /** + * Creates a new instance. + * + * @param player the player who should store the cookie + * @param key the identifier of the cookie + * @param data the data of the cookie + */ + public CookieStoreEvent(final Player player, final Key key, final byte[] data) { + this.player = Preconditions.checkNotNull(player, "player"); + this.originalKey = Preconditions.checkNotNull(key, "key"); + this.originalData = Preconditions.checkNotNull(data, "data"); + this.result = ForwardResult.forward(); + } + + @Override + public ForwardResult getResult() { + return result; + } + + @Override + public void setResult(ForwardResult result) { + this.result = Preconditions.checkNotNull(result, "result"); + } + + public Player getPlayer() { + return player; + } + + public Key getOriginalKey() { + return originalKey; + } + + public byte[] getOriginalData() { + return originalData; + } + + @Override + public String toString() { + return "CookieStoreEvent{" + + ", originalKey=" + originalKey + + ", originalData=" + Arrays.toString(originalData) + + ", result=" + result + + '}'; + } + + /** + * A result determining whether or not to forward the cookie on. + */ + public static final class ForwardResult implements Result { + + private static final ForwardResult ALLOWED = new ForwardResult(true, null, null); + private static final ForwardResult DENIED = new ForwardResult(false, null, null); + + private final boolean status; + private final Key key; + private final byte[] data; + + private ForwardResult(final boolean status, final Key key, final byte[] data) { + this.status = status; + this.key = key; + this.data = data; + } + + @Override + public boolean isAllowed() { + return status; + } + + public Key getKey() { + return key; + } + + public byte[] getData() { + return data; + } + + @Override + public String toString() { + return status ? "forward to client" : "handled by proxy"; + } + + /** + * Allows the cookie to be forwarded to the client so that it can store it. + * + * @return the forward result + */ + public static ForwardResult forward() { + return ALLOWED; + } + + /** + * Prevents the cookie from being forwarded to the client, the cookie is handled by the proxy. + * + * @return the handled result + */ + public static ForwardResult handled() { + return DENIED; + } + + /** + * Allows the cookie to be forwarded to the client so that it can store it, but silently + * replaces the identifier of the cookie with another. + * + * @param key the identifier to use instead + * @return a result with a new key + */ + public static ForwardResult key(final Key key) { + Preconditions.checkNotNull(key, "key"); + return new ForwardResult(true, key, null); + } + + /** + * Allows the cookie to be forwarded to the client so that it can store it, but silently + * replaces the data of the cookie with another. + * + * @param data the data of the cookie to use instead + * @return a result with new data + */ + public static ForwardResult data(final byte[] data) { + Preconditions.checkNotNull(data, "data"); + return new ForwardResult(true, null, data); + } + } +} diff --git a/api/src/main/java/com/velocitypowered/api/proxy/Player.java b/api/src/main/java/com/velocitypowered/api/proxy/Player.java index 57b307575..dfe9a2bc7 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/Player.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/Player.java @@ -8,6 +8,7 @@ package com.velocitypowered.api.proxy; import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.event.player.CookieReceiveEvent; import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent; import com.velocitypowered.api.proxy.crypto.KeyIdentifiable; import com.velocitypowered.api.proxy.messages.ChannelIdentifier; @@ -436,4 +437,28 @@ public interface Player extends * @since 3.3.0 */ void transferToHost(@NotNull InetSocketAddress address); + + /** + * Stores a cookie with arbitrary data on the player's client. + * + * @param key the identifier of the cookie + * @param data the data of the cookie + * @throws IllegalArgumentException if the player is from a version lower than 1.20.5 + * @since 3.3.0 + * @sinceMinecraft 1.20.5 + */ + void storeCookie(Key key, byte[] data); + + /** + * Requests a previously stored cookie from the player's client. + * Calling this method causes the client to send the cookie to the proxy. + * To retrieve the actual data of the requested cookie, you have to use the + * {@link CookieReceiveEvent}. + * + * @param key the identifier of the cookie + * @throws IllegalArgumentException if the player is from a version lower than 1.20.5 + * @since 3.3.0 + * @sinceMinecraft 1.20.5 + */ + void requestCookie(Key key); } \ No newline at end of file diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java index 67cd9d11b..288e667e1 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java @@ -22,6 +22,8 @@ import com.velocitypowered.proxy.protocol.packet.AvailableCommandsPacket; import com.velocitypowered.proxy.protocol.packet.BossBarPacket; import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket; import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket; +import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket; +import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket; import com.velocitypowered.proxy.protocol.packet.DisconnectPacket; import com.velocitypowered.proxy.protocol.packet.EncryptionRequestPacket; import com.velocitypowered.proxy.protocol.packet.EncryptionResponsePacket; @@ -45,6 +47,7 @@ import com.velocitypowered.proxy.protocol.packet.RespawnPacket; import com.velocitypowered.proxy.protocol.packet.ServerDataPacket; import com.velocitypowered.proxy.protocol.packet.ServerLoginPacket; import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccessPacket; +import com.velocitypowered.proxy.protocol.packet.ServerboundCookieResponsePacket; import com.velocitypowered.proxy.protocol.packet.SetCompressionPacket; import com.velocitypowered.proxy.protocol.packet.StatusPingPacket; import com.velocitypowered.proxy.protocol.packet.StatusRequestPacket; @@ -339,4 +342,16 @@ public interface MinecraftSessionHandler { default boolean handle(KnownPacksPacket packet) { return false; } + + default boolean handle(ClientboundStoreCookiePacket packet) { + return false; + } + + default boolean handle(ClientboundCookieRequestPacket packet) { + return false; + } + + default boolean handle(ServerboundCookieResponsePacket packet) { + return false; + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java index c5d1aa108..5b83e6549 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java @@ -26,6 +26,8 @@ import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.event.command.PlayerAvailableCommandsEvent; import com.velocitypowered.api.event.connection.PluginMessageEvent; import com.velocitypowered.api.event.connection.PreTransferEvent; +import com.velocitypowered.api.event.player.CookieRequestEvent; +import com.velocitypowered.api.event.player.CookieStoreEvent; import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent; import com.velocitypowered.api.event.player.ServerResourcePackSendEvent; import com.velocitypowered.api.event.proxy.ProxyPingEvent; @@ -48,6 +50,8 @@ import com.velocitypowered.proxy.protocol.packet.AvailableCommandsPacket; import com.velocitypowered.proxy.protocol.packet.BossBarPacket; import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket; import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket; +import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket; +import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket; import com.velocitypowered.proxy.protocol.packet.DisconnectPacket; import com.velocitypowered.proxy.protocol.packet.KeepAlivePacket; import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItemPacket; @@ -70,6 +74,7 @@ import io.netty.channel.Channel; import io.netty.handler.timeout.ReadTimeoutException; import java.net.InetSocketAddress; import java.util.regex.Pattern; +import net.kyori.adventure.key.Key; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -390,6 +395,39 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { return true; } + @Override + public boolean handle(ClientboundStoreCookiePacket packet) { + server.getEventManager() + .fire(new CookieStoreEvent(serverConn.getPlayer(), packet.getKey(), packet.getPayload())) + .thenAcceptAsync(event -> { + if (event.getResult().isAllowed()) { + final Key resultedKey = event.getResult().getKey() == null + ? event.getOriginalKey() : event.getResult().getKey(); + final byte[] resultedData = event.getResult().getData() == null + ? event.getOriginalData() : event.getResult().getData(); + + playerConnection.write(new ClientboundStoreCookiePacket(resultedKey, resultedData)); + } + }, playerConnection.eventLoop()); + + return true; + } + + @Override + public boolean handle(ClientboundCookieRequestPacket packet) { + server.getEventManager().fire(new CookieRequestEvent(serverConn.getPlayer(), packet.getKey())) + .thenAcceptAsync(event -> { + if (event.getResult().isAllowed()) { + final Key resultedKey = event.getResult().getKey() == null + ? event.getOriginalKey() : event.getResult().getKey(); + + playerConnection.write(new ClientboundCookieRequestPacket(resultedKey)); + } + }, playerConnection.eventLoop()); + + return true; + } + @Override public void handleGeneric(MinecraftPacket packet) { if (packet instanceof PluginMessagePacket) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java index 31d1dedb0..baff6017b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java @@ -18,6 +18,8 @@ package com.velocitypowered.proxy.connection.backend; import com.velocitypowered.api.event.connection.PreTransferEvent; +import com.velocitypowered.api.event.player.CookieRequestEvent; +import com.velocitypowered.api.event.player.CookieStoreEvent; import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent; import com.velocitypowered.api.event.player.ServerResourcePackSendEvent; import com.velocitypowered.api.network.ProtocolVersion; @@ -34,6 +36,8 @@ import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; +import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket; +import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket; import com.velocitypowered.proxy.protocol.packet.DisconnectPacket; import com.velocitypowered.proxy.protocol.packet.KeepAlivePacket; import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket; @@ -48,6 +52,7 @@ import com.velocitypowered.proxy.protocol.util.PluginMessageUtil; import java.io.IOException; import java.net.InetSocketAddress; import java.util.concurrent.CompletableFuture; +import net.kyori.adventure.key.Key; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -251,6 +256,40 @@ public class ConfigSessionHandler implements MinecraftSessionHandler { return true; } + @Override + public boolean handle(ClientboundStoreCookiePacket packet) { + server.getEventManager() + .fire(new CookieStoreEvent(serverConn.getPlayer(), packet.getKey(), packet.getPayload())) + .thenAcceptAsync(event -> { + if (event.getResult().isAllowed()) { + final Key resultedKey = event.getResult().getKey() == null + ? event.getOriginalKey() : event.getResult().getKey(); + final byte[] resultedData = event.getResult().getData() == null + ? event.getOriginalData() : event.getResult().getData(); + + serverConn.getPlayer().getConnection() + .write(new ClientboundStoreCookiePacket(resultedKey, resultedData)); + } + }, serverConn.ensureConnected().eventLoop()); + + return true; + } + + @Override + public boolean handle(ClientboundCookieRequestPacket packet) { + server.getEventManager().fire(new CookieRequestEvent(serverConn.getPlayer(), packet.getKey())) + .thenAcceptAsync(event -> { + if (event.getResult().isAllowed()) { + final Key resultedKey = event.getResult().getKey() == null + ? event.getOriginalKey() : event.getResult().getKey(); + + serverConn.getPlayer().getConnection().write(new ClientboundCookieRequestPacket(resultedKey)); + } + }, serverConn.ensureConnected().eventLoop()); + + return true; + } + @Override public void disconnected() { resultFuture.completeExceptionally( diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java index 0c0e5e553..a672c9174 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java @@ -17,6 +17,7 @@ package com.velocitypowered.proxy.connection.backend; +import com.velocitypowered.api.event.player.CookieRequestEvent; import com.velocitypowered.api.event.player.ServerLoginPluginMessageEvent; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; @@ -31,6 +32,8 @@ import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl; import com.velocitypowered.proxy.protocol.StateRegistry; +import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket; +import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket; import com.velocitypowered.proxy.protocol.packet.DisconnectPacket; import com.velocitypowered.proxy.protocol.packet.EncryptionRequestPacket; import com.velocitypowered.proxy.protocol.packet.LoginAcknowledgedPacket; @@ -43,6 +46,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import java.util.concurrent.CompletableFuture; +import net.kyori.adventure.key.Key; import net.kyori.adventure.text.Component; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -174,6 +178,26 @@ public class LoginSessionHandler implements MinecraftSessionHandler { return true; } + @Override + public boolean handle(ClientboundStoreCookiePacket packet) { + throw new IllegalStateException("Can only store cookie in CONFIGURATION or PLAY protocol"); + } + + @Override + public boolean handle(ClientboundCookieRequestPacket packet) { + server.getEventManager().fire(new CookieRequestEvent(serverConn.getPlayer(), packet.getKey())) + .thenAcceptAsync(event -> { + if (event.getResult().isAllowed()) { + final Key resultedKey = event.getResult().getKey() == null + ? event.getOriginalKey() : event.getResult().getKey(); + + serverConn.getPlayer().getConnection().write(new ClientboundCookieRequestPacket(resultedKey)); + } + }, serverConn.ensureConnected().eventLoop()); + + return true; + } + @Override public void exception(Throwable throwable) { resultFuture.completeExceptionally(throwable); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/AuthSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/AuthSessionHandler.java index 4315edf7a..77290584d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/AuthSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/AuthSessionHandler.java @@ -24,6 +24,7 @@ import com.velocitypowered.api.event.connection.DisconnectEvent; import com.velocitypowered.api.event.connection.LoginEvent; import com.velocitypowered.api.event.connection.PostLoginEvent; import com.velocitypowered.api.event.permission.PermissionsSetupEvent; +import com.velocitypowered.api.event.player.CookieReceiveEvent; import com.velocitypowered.api.event.player.GameProfileRequestEvent; import com.velocitypowered.api.event.player.PlayerChooseInitialServerEvent; import com.velocitypowered.api.network.ProtocolVersion; @@ -41,6 +42,7 @@ import com.velocitypowered.proxy.crypto.IdentifiedKeyImpl; import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.packet.LoginAcknowledgedPacket; import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccessPacket; +import com.velocitypowered.proxy.protocol.packet.ServerboundCookieResponsePacket; import com.velocitypowered.proxy.protocol.packet.SetCompressionPacket; import io.netty.buffer.ByteBuf; import java.util.Objects; @@ -188,6 +190,24 @@ public class AuthSessionHandler implements MinecraftSessionHandler { return true; } + @Override + public boolean handle(ServerboundCookieResponsePacket packet) { + server.getEventManager() + .fire(new CookieReceiveEvent(connectedPlayer, packet.getKey(), packet.getPayload())) + .thenAcceptAsync(event -> { + if (event.getResult().isAllowed()) { + // The received cookie must have been requested by a proxy plugin in login phase, + // because if a backend server requests a cookie in login phase, the client is already + // in config phase. Therefore, the only way, we receive a CookieResponsePacket from a + // client in login phase is when a proxy plugin requested a cookie in login phase. + throw new IllegalStateException( + "A cookie was requested by a proxy plugin in login phase but the response wasn't handled"); + } + }, mcConnection.eventLoop()); + + return true; + } + private void completeLoginProtocolPhaseAndInitialize(ConnectedPlayer player) { mcConnection.setAssociation(player); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java index d9c5295db..52b67f7b5 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java @@ -17,6 +17,7 @@ package com.velocitypowered.proxy.connection.client; +import com.velocitypowered.api.event.player.CookieReceiveEvent; import com.velocitypowered.api.event.player.PlayerClientBrandEvent; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.connection.MinecraftConnection; @@ -32,6 +33,7 @@ import com.velocitypowered.proxy.protocol.packet.KeepAlivePacket; import com.velocitypowered.proxy.protocol.packet.PingIdentifyPacket; import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket; import com.velocitypowered.proxy.protocol.packet.ResourcePackResponsePacket; +import com.velocitypowered.proxy.protocol.packet.ServerboundCookieResponsePacket; import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket; import com.velocitypowered.proxy.protocol.packet.config.KnownPacksPacket; import com.velocitypowered.proxy.protocol.util.PluginMessageUtil; @@ -39,6 +41,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; +import net.kyori.adventure.key.Key; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.apache.logging.log4j.LogManager; @@ -144,6 +147,28 @@ public class ClientConfigSessionHandler implements MinecraftSessionHandler { return false; } + @Override + public boolean handle(ServerboundCookieResponsePacket packet) { + server.getEventManager() + .fire(new CookieReceiveEvent(player, packet.getKey(), packet.getPayload())) + .thenAcceptAsync(event -> { + if (event.getResult().isAllowed()) { + final VelocityServerConnection serverConnection = player.getConnectionInFlight(); + if (serverConnection != null) { + final Key resultedKey = event.getResult().getKey() == null + ? event.getOriginalKey() : event.getResult().getKey(); + final byte[] resultedData = event.getResult().getData() == null + ? event.getOriginalData() : event.getResult().getData(); + + serverConnection.ensureConnected() + .write(new ServerboundCookieResponsePacket(resultedKey, resultedData)); + } + } + }, player.getConnection().eventLoop()); + + return true; + } + @Override public void handleGeneric(MinecraftPacket packet) { VelocityServerConnection serverConnection = player.getConnectedServer(); 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 f5e6e880f..2a595b167 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 @@ -23,6 +23,7 @@ import com.google.common.collect.ImmutableList; import com.mojang.brigadier.suggestion.Suggestion; import com.velocitypowered.api.command.VelocityBrigadierMessage; import com.velocitypowered.api.event.connection.PluginMessageEvent; +import com.velocitypowered.api.event.player.CookieReceiveEvent; import com.velocitypowered.api.event.player.PlayerChannelRegisterEvent; import com.velocitypowered.api.event.player.PlayerClientBrandEvent; import com.velocitypowered.api.event.player.TabCompleteEvent; @@ -48,6 +49,7 @@ import com.velocitypowered.proxy.protocol.packet.KeepAlivePacket; import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket; import com.velocitypowered.proxy.protocol.packet.ResourcePackResponsePacket; import com.velocitypowered.proxy.protocol.packet.RespawnPacket; +import com.velocitypowered.proxy.protocol.packet.ServerboundCookieResponsePacket; import com.velocitypowered.proxy.protocol.packet.TabCompleteRequestPacket; import com.velocitypowered.proxy.protocol.packet.TabCompleteResponsePacket; import com.velocitypowered.proxy.protocol.packet.TabCompleteResponsePacket.Offer; @@ -83,6 +85,7 @@ import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.TimeUnit; +import net.kyori.adventure.key.Key; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.apache.logging.log4j.LogManager; @@ -418,6 +421,28 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { return true; } + @Override + public boolean handle(ServerboundCookieResponsePacket packet) { + server.getEventManager() + .fire(new CookieReceiveEvent(player, packet.getKey(), packet.getPayload())) + .thenAcceptAsync(event -> { + if (event.getResult().isAllowed()) { + final VelocityServerConnection serverConnection = player.getConnectedServer(); + if (serverConnection != null) { + final Key resultedKey = event.getResult().getKey() == null + ? event.getOriginalKey() : event.getResult().getKey(); + final byte[] resultedData = event.getResult().getData() == null + ? event.getOriginalData() : event.getResult().getData(); + + serverConnection.ensureConnected() + .write(new ServerboundCookieResponsePacket(resultedKey, resultedData)); + } + } + }, player.getConnection().eventLoop()); + + return true; + } + @Override public void handleGeneric(MinecraftPacket packet) { VelocityServerConnection serverConnection = player.getConnectedServer(); 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 edefbc2d8..a471e2b53 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 @@ -27,6 +27,8 @@ import com.google.gson.JsonObject; import com.velocitypowered.api.event.connection.DisconnectEvent; import com.velocitypowered.api.event.connection.DisconnectEvent.LoginStatus; import com.velocitypowered.api.event.connection.PreTransferEvent; +import com.velocitypowered.api.event.player.CookieRequestEvent; +import com.velocitypowered.api.event.player.CookieStoreEvent; import com.velocitypowered.api.event.player.KickedFromServerEvent; import com.velocitypowered.api.event.player.KickedFromServerEvent.DisconnectPlayer; import com.velocitypowered.api.event.player.KickedFromServerEvent.Notify; @@ -65,6 +67,8 @@ import com.velocitypowered.proxy.connection.util.VelocityInboundConnection; import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder; import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket; +import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket; +import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket; import com.velocitypowered.proxy.protocol.packet.DisconnectPacket; import com.velocitypowered.proxy.protocol.packet.HeaderAndFooterPacket; import com.velocitypowered.proxy.protocol.packet.KeepAlivePacket; @@ -105,6 +109,7 @@ import java.util.concurrent.ThreadLocalRandom; import net.kyori.adventure.audience.MessageType; import net.kyori.adventure.bossbar.BossBar; import net.kyori.adventure.identity.Identity; +import net.kyori.adventure.key.Key; import net.kyori.adventure.permission.PermissionChecker; import net.kyori.adventure.platform.facet.FacetPointers; import net.kyori.adventure.platform.facet.FacetPointers.Type; @@ -1008,6 +1013,50 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, }); } + @Override + public void storeCookie(final Key key, final byte[] data) { + Preconditions.checkNotNull(key); + Preconditions.checkNotNull(data); + Preconditions.checkArgument( + this.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_20_5), + "Player version must be at least 1.20.5 to be able to store cookies"); + + if (connection.getState() != StateRegistry.PLAY + && connection.getState() != StateRegistry.CONFIG) { + throw new IllegalStateException("Can only store cookie in CONFIGURATION or PLAY protocol"); + } + + server.getEventManager().fire(new CookieStoreEvent(this, key, data)) + .thenAcceptAsync(event -> { + if (event.getResult().isAllowed()) { + final Key resultedKey = event.getResult().getKey() == null + ? event.getOriginalKey() : event.getResult().getKey(); + final byte[] resultedData = event.getResult().getData() == null + ? event.getOriginalData() : event.getResult().getData(); + + connection.write(new ClientboundStoreCookiePacket(resultedKey, resultedData)); + } + }, connection.eventLoop()); + } + + @Override + public void requestCookie(final Key key) { + Preconditions.checkNotNull(key); + Preconditions.checkArgument( + this.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_20_5), + "Player version must be at least 1.20.5 to be able to retrieve cookies"); + + server.getEventManager().fire(new CookieRequestEvent(this, key)) + .thenAcceptAsync(event -> { + if (event.getResult().isAllowed()) { + final Key resultedKey = event.getResult().getKey() == null + ? event.getOriginalKey() : event.getResult().getKey(); + + connection.write(new ClientboundCookieRequestPacket(resultedKey)); + } + }, connection.eventLoop()); + } + @Override public void addCustomChatCompletions(@NotNull Collection completions) { Preconditions.checkNotNull(completions, "completions"); 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 b0ee89f75..07a7dab48 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -52,6 +52,8 @@ import com.velocitypowered.proxy.protocol.packet.AvailableCommandsPacket; import com.velocitypowered.proxy.protocol.packet.BossBarPacket; import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket; import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket; +import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket; +import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket; import com.velocitypowered.proxy.protocol.packet.DisconnectPacket; import com.velocitypowered.proxy.protocol.packet.EncryptionRequestPacket; import com.velocitypowered.proxy.protocol.packet.EncryptionResponsePacket; @@ -73,6 +75,7 @@ import com.velocitypowered.proxy.protocol.packet.RespawnPacket; import com.velocitypowered.proxy.protocol.packet.ServerDataPacket; import com.velocitypowered.proxy.protocol.packet.ServerLoginPacket; import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccessPacket; +import com.velocitypowered.proxy.protocol.packet.ServerboundCookieResponsePacket; import com.velocitypowered.proxy.protocol.packet.SetCompressionPacket; import com.velocitypowered.proxy.protocol.packet.StatusPingPacket; import com.velocitypowered.proxy.protocol.packet.StatusRequestPacket; @@ -146,6 +149,9 @@ public enum StateRegistry { serverbound.register( ClientSettingsPacket.class, ClientSettingsPacket::new, map(0x00, MINECRAFT_1_20_2, false)); + serverbound.register( + ServerboundCookieResponsePacket.class, ServerboundCookieResponsePacket::new, + map(0x01, MINECRAFT_1_20_5, false)); serverbound.register( PluginMessagePacket.class, PluginMessagePacket::new, map(0x01, MINECRAFT_1_20_2, false), @@ -171,6 +177,9 @@ public enum StateRegistry { KnownPacksPacket::new, map(0x07, MINECRAFT_1_20_5, false)); + clientbound.register( + ClientboundCookieRequestPacket.class, ClientboundCookieRequestPacket::new, + map(0x00, MINECRAFT_1_20_5, false)); clientbound.register( PluginMessagePacket.class, PluginMessagePacket::new, map(0x00, MINECRAFT_1_20_2, false), @@ -202,6 +211,9 @@ public enum StateRegistry { map(0x06, MINECRAFT_1_20_2, false), map(0x07, MINECRAFT_1_20_3, false), map(0x09, MINECRAFT_1_20_5, false)); + clientbound.register( + ClientboundStoreCookiePacket.class, ClientboundStoreCookiePacket::new, + map(0x0A, MINECRAFT_1_20_5, false)); clientbound.register(TransferPacket.class, TransferPacket::new, map(0x0B, MINECRAFT_1_20_5, false)); clientbound.register(ActiveFeaturesPacket.class, ActiveFeaturesPacket::new, @@ -276,6 +288,9 @@ public enum StateRegistry { map(0x08, MINECRAFT_1_19_4, false), map(0x09, MINECRAFT_1_20_2, false), map(0x0A, MINECRAFT_1_20_5, false)); + serverbound.register( + ServerboundCookieResponsePacket.class, ServerboundCookieResponsePacket::new, + map(0x11, MINECRAFT_1_20_5, false)); serverbound.register( PluginMessagePacket.class, PluginMessagePacket::new, @@ -374,6 +389,9 @@ public enum StateRegistry { map(0x0E, MINECRAFT_1_19_3, false), map(0x10, MINECRAFT_1_19_4, false), map(0x11, MINECRAFT_1_20_2, false)); + clientbound.register( + ClientboundCookieRequestPacket.class, ClientboundCookieRequestPacket::new, + map(0x16, MINECRAFT_1_20_5, false)); clientbound.register( PluginMessagePacket.class, PluginMessagePacket::new, @@ -595,6 +613,9 @@ public enum StateRegistry { map(0x3A, MINECRAFT_1_19_4, false), map(0x3C, MINECRAFT_1_20_2, false), map(0x3E, MINECRAFT_1_20_5, false)); + clientbound.register( + ClientboundStoreCookiePacket.class, ClientboundStoreCookiePacket::new, + map(0x6B, MINECRAFT_1_20_5, false)); clientbound.register( SystemChatPacket.class, SystemChatPacket::new, @@ -654,6 +675,9 @@ public enum StateRegistry { serverbound.register( LoginAcknowledgedPacket.class, LoginAcknowledgedPacket::new, map(0x03, MINECRAFT_1_20_2, false)); + serverbound.register( + ServerboundCookieResponsePacket.class, ServerboundCookieResponsePacket::new, + map(0x04, MINECRAFT_1_20_5, false)); clientbound.register( DisconnectPacket.class, () -> new DisconnectPacket(this), @@ -671,6 +695,9 @@ public enum StateRegistry { LoginPluginMessagePacket.class, LoginPluginMessagePacket::new, map(0x04, MINECRAFT_1_13, false)); + clientbound.register( + ClientboundCookieRequestPacket.class, ClientboundCookieRequestPacket::new, + map(0x05, MINECRAFT_1_20_5, false)); } }; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientboundCookieRequestPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientboundCookieRequestPacket.java new file mode 100644 index 000000000..fd558b29c --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientboundCookieRequestPacket.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2024 Velocity Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.velocitypowered.proxy.protocol.packet; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; +import io.netty.buffer.ByteBuf; +import net.kyori.adventure.key.Key; + +public class ClientboundCookieRequestPacket implements MinecraftPacket { + + private Key key; + + public Key getKey() { + return key; + } + + public ClientboundCookieRequestPacket() { + } + + public ClientboundCookieRequestPacket(final Key key) { + this.key = key; + } + + @Override + public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { + this.key = ProtocolUtils.readKey(buf); + } + + @Override + public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { + ProtocolUtils.writeKey(buf, key); + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientboundStoreCookiePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientboundStoreCookiePacket.java new file mode 100644 index 000000000..7823b5584 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientboundStoreCookiePacket.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2024 Velocity Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.velocitypowered.proxy.protocol.packet; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; +import io.netty.buffer.ByteBuf; +import net.kyori.adventure.key.Key; + +public class ClientboundStoreCookiePacket implements MinecraftPacket { + + private Key key; + private byte[] payload; + + public Key getKey() { + return key; + } + + public byte[] getPayload() { + return payload; + } + + public ClientboundStoreCookiePacket() { + } + + public ClientboundStoreCookiePacket(final Key key, final byte[] payload) { + this.key = key; + this.payload = payload; + } + + @Override + public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { + this.key = ProtocolUtils.readKey(buf); + this.payload = ProtocolUtils.readByteArray(buf, 5120); + } + + @Override + public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { + ProtocolUtils.writeKey(buf, key); + ProtocolUtils.writeByteArray(buf, payload); + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerboundCookieResponsePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerboundCookieResponsePacket.java new file mode 100644 index 000000000..bee12b802 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerboundCookieResponsePacket.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2024 Velocity Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.velocitypowered.proxy.protocol.packet; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; +import io.netty.buffer.ByteBuf; +import net.kyori.adventure.key.Key; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class ServerboundCookieResponsePacket implements MinecraftPacket { + + private Key key; + private byte @Nullable [] payload; + + public Key getKey() { + return key; + } + + public byte @Nullable [] getPayload() { + return payload; + } + + public ServerboundCookieResponsePacket() { + } + + public ServerboundCookieResponsePacket(final Key key, final byte @Nullable [] payload) { + this.key = key; + this.payload = payload; + } + + @Override + public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { + this.key = ProtocolUtils.readKey(buf); + if (buf.readBoolean()) { + this.payload = ProtocolUtils.readByteArray(buf, 5120); + } + } + + @Override + public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { + ProtocolUtils.writeKey(buf, key); + final boolean hasPayload = payload != null && payload.length > 0; + buf.writeBoolean(hasPayload); + if (hasPayload) { + ProtocolUtils.writeByteArray(buf, payload); + } + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } +} From 46cf9be297e366bbab2a2941d35246a85d1591e9 Mon Sep 17 00:00:00 2001 From: Luccboy <58391278+Luccboy@users.noreply.github.com> Date: Thu, 30 May 2024 18:21:26 +0200 Subject: [PATCH 08/11] [ci skip] Fix Javadocs of CookieRequestEvent (#1339) --- .../api/event/player/CookieRequestEvent.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/src/main/java/com/velocitypowered/api/event/player/CookieRequestEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/CookieRequestEvent.java index 021fd7e63..2f3db4e4c 100644 --- a/api/src/main/java/com/velocitypowered/api/event/player/CookieRequestEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/player/CookieRequestEvent.java @@ -14,9 +14,9 @@ import com.velocitypowered.api.proxy.Player; import net.kyori.adventure.key.Key; /** - * This event is fired when a cookie is requested from a client either by a proxy plugin or + * This event is fired when a cookie from a client is requested either by a proxy plugin or * by a backend server. Velocity will wait on this event to finish firing before discarding the - * received cookie (if handled) or forwarding it to the backend server. + * cookie request (if handled) or forwarding it to the client. */ @AwaitingEvent public final class CookieRequestEvent implements ResultedEvent { @@ -113,7 +113,7 @@ public final class CookieRequestEvent implements ResultedEvent Date: Sat, 8 Jun 2024 14:28:33 -0500 Subject: [PATCH 09/11] Add support for canceling ProxyPingEvent (#1264) * Added support for canceling ProxyPingEvent --- .../api/event/proxy/ProxyPingEvent.java | 55 +++++++++++++++++-- .../client/StatusSessionHandler.java | 22 +++++--- 2 files changed, 65 insertions(+), 12 deletions(-) diff --git a/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyPingEvent.java b/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyPingEvent.java index 0e04acfee..635554366 100644 --- a/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyPingEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyPingEvent.java @@ -8,9 +8,11 @@ package com.velocitypowered.api.event.proxy; import com.google.common.base.Preconditions; +import com.velocitypowered.api.event.ResultedEvent; import com.velocitypowered.api.event.annotation.AwaitingEvent; import com.velocitypowered.api.proxy.InboundConnection; import com.velocitypowered.api.proxy.server.ServerPing; +import org.jetbrains.annotations.NotNull; /** * This event is fired when a request for server information is sent by a remote client, or when the @@ -20,28 +22,71 @@ import com.velocitypowered.api.proxy.server.ServerPing; * amount of ping packets a client can send. */ @AwaitingEvent -public final class ProxyPingEvent { +public final class ProxyPingEvent implements ResultedEvent { private final InboundConnection connection; private ServerPing ping; + private GenericResult result = GenericResult.allowed(); - public ProxyPingEvent(InboundConnection connection, ServerPing ping) { + public ProxyPingEvent(final InboundConnection connection, final ServerPing ping) { this.connection = Preconditions.checkNotNull(connection, "connection"); this.ping = Preconditions.checkNotNull(ping, "ping"); } + /** + * Obtain the connection to which the corresponding ServerPing will be sent. + * + * @return the connection that has sent the ServerPing request + */ public InboundConnection getConnection() { - return connection; + return this.connection; } + /** + * Get the ServerPing to send to the connection. + * + * @return the ServerPing to send + */ public ServerPing getPing() { - return ping; + return this.ping; } - public void setPing(ServerPing ping) { + /** + * Sets the ServerPing to send to the connection. + * + * @param ping sets the ServerPing to send + */ + public void setPing(final @NotNull ServerPing ping) { this.ping = Preconditions.checkNotNull(ping, "ping"); } + /** + * Gets whether to avoid sending a ping response to the connection. + * + * @return if a ping response to the connection will be avoided + * @apiNote For the ProxyPingEvent executed to obtain the MOTD for the ServerData + * sent to players of versions higher than 1.19.1, + * the cancellation of this event will have no effect. + */ + @Override + public GenericResult getResult() { + return this.result; + } + + /** + * Sets whether to avoid sending a ping response to the connection. + * This will automatically close the connection. + * + * @param result if a ping response to the connection will be avoided + * @apiNote For the ProxyPingEvent executed to obtain the MOTD for the ServerData + * sent to players of versions higher than 1.19.1, + * the cancellation of this event will have no effect. + */ + @Override + public void setResult(final @NotNull GenericResult result) { + this.result = Preconditions.checkNotNull(result, "result"); + } + @Override public String toString() { return "ProxyPingEvent{" diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/StatusSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/StatusSessionHandler.java index 0f7c43b84..2c2d7984f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/StatusSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/StatusSessionHandler.java @@ -68,9 +68,13 @@ public class StatusSessionHandler implements MinecraftSessionHandler { this.pingReceived = true; server.getServerListPingHandler().getInitialPing(this.inbound) .thenCompose(ping -> server.getEventManager().fire(new ProxyPingEvent(inbound, ping))) - .thenAcceptAsync(event -> connection.closeWith( - LegacyDisconnect.fromServerPing(event.getPing(), packet.getVersion())), - connection.eventLoop()) + .thenAcceptAsync(event -> { + if (event.getResult().isAllowed()) { + connection.closeWith(LegacyDisconnect.fromServerPing(event.getPing(), packet.getVersion())); + } else { + connection.close(); + } + }, connection.eventLoop()) .exceptionally((ex) -> { logger.error("Exception while handling legacy ping {}", packet, ex); return null; @@ -95,10 +99,14 @@ public class StatusSessionHandler implements MinecraftSessionHandler { .thenCompose(ping -> server.getEventManager().fire(new ProxyPingEvent(inbound, ping))) .thenAcceptAsync( (event) -> { - final StringBuilder json = new StringBuilder(); - VelocityServer.getPingGsonInstance(connection.getProtocolVersion()) - .toJson(event.getPing(), json); - connection.write(new StatusResponsePacket(json)); + if (event.getResult().isAllowed()) { + final StringBuilder json = new StringBuilder(); + VelocityServer.getPingGsonInstance(connection.getProtocolVersion()) + .toJson(event.getPing(), json); + connection.write(new StatusResponsePacket(json)); + } else { + connection.close(); + } }, connection.eventLoop()) .exceptionally((ex) -> { From 350ea7f31eb40b2c7b28aed0ba070453f48b6e7e Mon Sep 17 00:00:00 2001 From: Gero Date: Wed, 12 Jun 2024 20:33:01 +0200 Subject: [PATCH 10/11] 1.21 support (#1338) --- .../api/network/ProtocolVersion.java | 3 +- .../connection/MinecraftSessionHandler.java | 10 +++ .../backend/ConfigSessionHandler.java | 14 +++ .../proxy/protocol/StateRegistry.java | 11 +++ .../packet/ServerLoginSuccessPacket.java | 4 +- .../ClientboundCustomReportDetailsPacket.java | 67 ++++++++++++++ .../config/ClientboundServerLinksPacket.java | 88 +++++++++++++++++++ 7 files changed, 194 insertions(+), 3 deletions(-) create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/ClientboundCustomReportDetailsPacket.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/ClientboundServerLinksPacket.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 8723d8884..e968b0255 100644 --- a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java +++ b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java @@ -86,7 +86,8 @@ public enum ProtocolVersion implements Ordered { MINECRAFT_1_20(763, "1.20", "1.20.1"), MINECRAFT_1_20_2(764, "1.20.2"), MINECRAFT_1_20_3(765, "1.20.3", "1.20.4"), - MINECRAFT_1_20_5(766, "1.20.5", "1.20.6"); + MINECRAFT_1_20_5(766, "1.20.5", "1.20.6"), + MINECRAFT_1_21(767, "1.21"); private static final int SNAPSHOT_BIT = 30; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java index 288e667e1..b36d9f0ab 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java @@ -65,6 +65,8 @@ import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyChatPacket; import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerChatPacket; import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerCommandPacket; import com.velocitypowered.proxy.protocol.packet.config.ActiveFeaturesPacket; +import com.velocitypowered.proxy.protocol.packet.config.ClientboundCustomReportDetailsPacket; +import com.velocitypowered.proxy.protocol.packet.config.ClientboundServerLinksPacket; import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket; import com.velocitypowered.proxy.protocol.packet.config.KnownPacksPacket; import com.velocitypowered.proxy.protocol.packet.config.RegistrySyncPacket; @@ -354,4 +356,12 @@ public interface MinecraftSessionHandler { default boolean handle(ServerboundCookieResponsePacket packet) { return false; } + + default boolean handle(ClientboundCustomReportDetailsPacket packet) { + return false; + } + + default boolean handle(ClientboundServerLinksPacket packet) { + return false; + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java index baff6017b..3f4325e52 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java @@ -44,6 +44,8 @@ import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket; import com.velocitypowered.proxy.protocol.packet.ResourcePackRequestPacket; import com.velocitypowered.proxy.protocol.packet.ResourcePackResponsePacket; import com.velocitypowered.proxy.protocol.packet.TransferPacket; +import com.velocitypowered.proxy.protocol.packet.config.ClientboundCustomReportDetailsPacket; +import com.velocitypowered.proxy.protocol.packet.config.ClientboundServerLinksPacket; import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket; import com.velocitypowered.proxy.protocol.packet.config.RegistrySyncPacket; import com.velocitypowered.proxy.protocol.packet.config.StartUpdatePacket; @@ -116,6 +118,18 @@ public class ConfigSessionHandler implements MinecraftSessionHandler { return true; } + @Override + public boolean handle(ClientboundCustomReportDetailsPacket packet) { + serverConn.getPlayer().getConnection().write(packet); + return true; + } + + @Override + public boolean handle(ClientboundServerLinksPacket packet) { + serverConn.getPlayer().getConnection().write(packet); + return true; + } + @Override public boolean handle(KeepAlivePacket packet) { serverConn.ensureConnected().write(packet); 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 07a7dab48..41d444a36 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -36,6 +36,7 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_4; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_2; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_3; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_5; +import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_7_2; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9; @@ -94,6 +95,8 @@ import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerChatP import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerCommandPacket; import com.velocitypowered.proxy.protocol.packet.chat.session.UnsignedPlayerCommandPacket; import com.velocitypowered.proxy.protocol.packet.config.ActiveFeaturesPacket; +import com.velocitypowered.proxy.protocol.packet.config.ClientboundCustomReportDetailsPacket; +import com.velocitypowered.proxy.protocol.packet.config.ClientboundServerLinksPacket; import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket; import com.velocitypowered.proxy.protocol.packet.config.KnownPacksPacket; import com.velocitypowered.proxy.protocol.packet.config.RegistrySyncPacket; @@ -226,6 +229,10 @@ public enum StateRegistry { map(0x0D, MINECRAFT_1_20_5, false)); clientbound.register(KnownPacksPacket.class, KnownPacksPacket::new, map(0x0E, MINECRAFT_1_20_5, false)); + clientbound.register(ClientboundCustomReportDetailsPacket.class, ClientboundCustomReportDetailsPacket::new, + map(0x0F, MINECRAFT_1_21, false)); + clientbound.register(ClientboundServerLinksPacket.class, ClientboundServerLinksPacket::new, + map(0x10, MINECRAFT_1_21, false)); } }, PLAY { @@ -659,6 +666,10 @@ public enum StateRegistry { TransferPacket::new, map(0x73, MINECRAFT_1_20_5, false) ); + clientbound.register(ClientboundCustomReportDetailsPacket.class, ClientboundCustomReportDetailsPacket::new, + map(0x7A, MINECRAFT_1_21, false)); + clientbound.register(ClientboundServerLinksPacket.class, ClientboundServerLinksPacket::new, + map(0x7B, MINECRAFT_1_21, false)); } }, LOGIN { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLoginSuccessPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLoginSuccessPacket.java index f4fa6bc30..1e70568f6 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLoginSuccessPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLoginSuccessPacket.java @@ -92,7 +92,7 @@ public class ServerLoginSuccessPacket implements MinecraftPacket { if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) { properties = ProtocolUtils.readProperties(buf); } - if (version == ProtocolVersion.MINECRAFT_1_20_5) { + if (version == ProtocolVersion.MINECRAFT_1_20_5 || version == ProtocolVersion.MINECRAFT_1_21) { buf.readBoolean(); } } @@ -123,7 +123,7 @@ public class ServerLoginSuccessPacket implements MinecraftPacket { ProtocolUtils.writeProperties(buf, properties); } } - if (version == ProtocolVersion.MINECRAFT_1_20_5) { + if (version == ProtocolVersion.MINECRAFT_1_20_5 || version == ProtocolVersion.MINECRAFT_1_21) { buf.writeBoolean(strictErrorHandling); } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/ClientboundCustomReportDetailsPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/ClientboundCustomReportDetailsPacket.java new file mode 100644 index 000000000..6a3618cb7 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/ClientboundCustomReportDetailsPacket.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2024 Velocity Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.velocitypowered.proxy.protocol.packet.config; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; + +public class ClientboundCustomReportDetailsPacket implements MinecraftPacket { + + private Map details; + + public ClientboundCustomReportDetailsPacket() { + } + + public ClientboundCustomReportDetailsPacket(Map details) { + this.details = details; + } + + @Override + public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + int detailsCount = ProtocolUtils.readVarInt(buf); + + this.details = new HashMap<>(detailsCount); + for (int i = 0; i < detailsCount; i++) { + details.put(ProtocolUtils.readString(buf), ProtocolUtils.readString(buf)); + } + } + + @Override + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + ProtocolUtils.writeVarInt(buf, details.size()); + + details.forEach((key, detail) -> { + ProtocolUtils.writeString(buf, key); + ProtocolUtils.writeString(buf, detail); + }); + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } + + public Map getDetails() { + return details; + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/ClientboundServerLinksPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/ClientboundServerLinksPacket.java new file mode 100644 index 000000000..bee080ee8 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/ClientboundServerLinksPacket.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2024 Velocity Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.velocitypowered.proxy.protocol.packet.config; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; +import io.netty.buffer.ByteBuf; +import java.util.ArrayList; +import java.util.List; + +public class ClientboundServerLinksPacket implements MinecraftPacket { + + private List serverLinks; + + public ClientboundServerLinksPacket() { + } + + public ClientboundServerLinksPacket(List serverLinks) { + this.serverLinks = serverLinks; + } + + @Override + public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { + int linksCount = ProtocolUtils.readVarInt(buf); + + this.serverLinks = new ArrayList<>(linksCount); + for (int i = 0; i < linksCount; i++) { + serverLinks.add(ServerLink.read(buf, version)); + } + } + + @Override + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + ProtocolUtils.writeVarInt(buf, serverLinks.size()); + + for (ServerLink serverLink : serverLinks) { + serverLink.write(buf); + } + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } + + public List getServerLinks() { + return serverLinks; + } + + public record ServerLink(int id, ComponentHolder displayName, String url) { + private static ServerLink read(ByteBuf buf, ProtocolVersion version) { + if (buf.readBoolean()) { + return new ServerLink(ProtocolUtils.readVarInt(buf), null, ProtocolUtils.readString(buf)); + } else { + return new ServerLink(-1, ComponentHolder.read(buf, version), ProtocolUtils.readString(buf)); + } + } + + private void write(ByteBuf buf) { + if (id >= 0) { + buf.writeBoolean(true); + ProtocolUtils.writeVarInt(buf, id); + } else { + buf.writeBoolean(false); + displayName.write(buf); + } + ProtocolUtils.writeString(buf, url); + } + } +} From e60e2063a87c8904b5ad89680aa51698b1ef8a0c Mon Sep 17 00:00:00 2001 From: Adrian <68704415+4drian3d@users.noreply.github.com> Date: Sun, 16 Jun 2024 10:56:09 -0500 Subject: [PATCH 11/11] Add Configuration State API (#1261) * Implement Configuration State events * Implement PlayerFinishedConfigurationEvent * Fixed PlayerFinishConfigurationEvent execution * Apply suggestions * Fix keep alive when blocking PlayerFinishConfigurationEvent * Add ServerConnection to configuration events * Separate PlayPacketQueueHandler to fix AutoReadHolderHandler --------- Co-authored-by: Gero --- .../PlayerEnterConfigurationEvent.java | 27 ++++++ .../PlayerFinishConfigurationEvent.java | 26 +++++ .../PlayerFinishedConfigurationEvent.java | 26 +++++ .../velocitypowered/proxy/VelocityServer.java | 2 +- .../proxy/connection/MinecraftConnection.java | 29 +++--- .../backend/BackendPlaySessionHandler.java | 4 +- .../backend/BungeeCordMessageResponder.java | 8 +- .../backend/ConfigSessionHandler.java | 28 +++--- .../backend/LoginSessionHandler.java | 7 +- .../client/ClientConfigSessionHandler.java | 10 +- .../client/ClientPlaySessionHandler.java | 4 +- .../connection/client/ConnectedPlayer.java | 39 ++++---- .../bundle}/BundleDelimiterHandler.java | 3 +- .../VelocityResourcePackInfo.java | 2 +- .../Legacy117ResourcePackHandler.java | 2 +- .../LegacyResourcePackHandler.java | 3 +- .../ModernResourcePackHandler.java | 3 +- .../{ => handler}/ResourcePackHandler.java | 5 +- .../proxy/network/Connections.java | 3 +- .../protocol/netty/MinecraftDecoder.java | 7 +- .../netty/PlayPacketQueueInboundHandler.java | 94 +++++++++++++++++++ ...va => PlayPacketQueueOutboundHandler.java} | 20 ++-- .../packet/ResourcePackRequestPacket.java | 2 +- 23 files changed, 264 insertions(+), 90 deletions(-) create mode 100644 api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerEnterConfigurationEvent.java create mode 100644 api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerFinishConfigurationEvent.java create mode 100644 api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerFinishedConfigurationEvent.java rename proxy/src/main/java/com/velocitypowered/proxy/connection/{client => player/bundle}/BundleDelimiterHandler.java (95%) rename proxy/src/main/java/com/velocitypowered/proxy/connection/player/{ => resourcepack}/VelocityResourcePackInfo.java (98%) rename proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/{ => handler}/Legacy117ResourcePackHandler.java (99%) rename proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/{ => handler}/LegacyResourcePackHandler.java (98%) rename proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/{ => handler}/ModernResourcePackHandler.java (98%) rename proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/{ => handler}/ResourcePackHandler.java (97%) create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/PlayPacketQueueInboundHandler.java rename proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/{PlayPacketQueueHandler.java => PlayPacketQueueOutboundHandler.java} (86%) diff --git a/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerEnterConfigurationEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerEnterConfigurationEvent.java new file mode 100644 index 000000000..3b108c6a7 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerEnterConfigurationEvent.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2024 Velocity Contributors + * + * The Velocity API is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package com.velocitypowered.api.event.player.configuration; + +import com.velocitypowered.api.network.ProtocolState; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ServerConnection; +import org.jetbrains.annotations.NotNull; + +/** + * This event is executed when a player with version 1.20.2 or higher enters the configuration phase. + *

From this moment on, until the {@link PlayerFinishedConfigurationEvent} is executed, + * the {@linkplain Player#getProtocolState()} method is guaranteed + * to return {@link ProtocolState#CONFIGURATION}.

+ * + * @param player The player that has entered the configuration phase. + * @param server The server that will now (re-)configure the player. + * @since 3.3.0 + * @sinceMinecraft 1.20.2 + */ +public record PlayerEnterConfigurationEvent(@NotNull Player player, ServerConnection server) { +} diff --git a/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerFinishConfigurationEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerFinishConfigurationEvent.java new file mode 100644 index 000000000..f6249b897 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerFinishConfigurationEvent.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 Velocity Contributors + * + * The Velocity API is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package com.velocitypowered.api.event.player.configuration; + +import com.velocitypowered.api.event.annotation.AwaitingEvent; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ServerConnection; +import org.jetbrains.annotations.NotNull; + +/** + * This event is executed when the player is about to finish the Configuration state. + *

Velocity will wait for this event to finish the configuration phase on the client.

+ * + * @param player The player who is about to complete the configuration phase. + * @param server The server that is currently (re-)configuring the player. + * @since 3.3.0 + * @sinceMinecraft 1.20.2 + */ +@AwaitingEvent +public record PlayerFinishConfigurationEvent(@NotNull Player player, @NotNull ServerConnection server) { +} diff --git a/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerFinishedConfigurationEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerFinishedConfigurationEvent.java new file mode 100644 index 000000000..09e76104f --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerFinishedConfigurationEvent.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 Velocity Contributors + * + * The Velocity API is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package com.velocitypowered.api.event.player.configuration; + +import com.velocitypowered.api.network.ProtocolState; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ServerConnection; +import org.jetbrains.annotations.NotNull; + +/** + * Event executed when a player of version 1.20.2 or higher finishes the Configuration state. + *

From this moment on, the {@link Player#getProtocolState()} method + * will return {@link ProtocolState#PLAY}.

+ * + * @param player The player who has completed the Configuration state + * @param server The server that has (re-)configured the player. + * @since 3.3.0 + * @sinceMinecraft 1.20.2 + */ +public record PlayerFinishedConfigurationEvent(@NotNull Player player, @NotNull ServerConnection server) { +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index ef8217c69..004243616 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -45,7 +45,7 @@ import com.velocitypowered.proxy.command.builtin.ShutdownCommand; import com.velocitypowered.proxy.command.builtin.VelocityCommand; import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; -import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo; +import com.velocitypowered.proxy.connection.player.resourcepack.VelocityResourcePackInfo; import com.velocitypowered.proxy.connection.util.ServerListPingHandler; import com.velocitypowered.proxy.console.VelocityConsole; import com.velocitypowered.proxy.crypto.EncryptionUtils; 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 b1c91ebdb..c91739078 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftConnection.java @@ -47,7 +47,8 @@ import com.velocitypowered.proxy.protocol.netty.MinecraftCompressorAndLengthEnco import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder; import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder; -import com.velocitypowered.proxy.protocol.netty.PlayPacketQueueHandler; +import com.velocitypowered.proxy.protocol.netty.PlayPacketQueueInboundHandler; +import com.velocitypowered.proxy.protocol.netty.PlayPacketQueueOutboundHandler; import com.velocitypowered.proxy.protocol.packet.SetCompressionPacket; import com.velocitypowered.proxy.util.except.QuietDecoderException; import io.netty.buffer.ByteBuf; @@ -148,13 +149,11 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { return; } - if (msg instanceof MinecraftPacket) { - MinecraftPacket pkt = (MinecraftPacket) msg; + if (msg instanceof MinecraftPacket pkt) { if (!pkt.handle(activeSessionHandler)) { activeSessionHandler.handleGeneric((MinecraftPacket) msg); } - } else if (msg instanceof HAProxyMessage) { - HAProxyMessage proxyMessage = (HAProxyMessage) msg; + } else if (msg instanceof HAProxyMessage proxyMessage) { this.remoteAddress = new InetSocketAddress(proxyMessage.sourceAddress(), proxyMessage.sourcePort()); } else if (msg instanceof ByteBuf) { @@ -383,9 +382,14 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { if (state == StateRegistry.CONFIG) { // Activate the play packet queue addPlayPacketQueueHandler(); - } else if (this.channel.pipeline().get(Connections.PLAY_PACKET_QUEUE) != null) { + } else { // Remove the queue - this.channel.pipeline().remove(Connections.PLAY_PACKET_QUEUE); + if (this.channel.pipeline().get(Connections.PLAY_PACKET_QUEUE_OUTBOUND) != null) { + this.channel.pipeline().remove(Connections.PLAY_PACKET_QUEUE_OUTBOUND); + } + if (this.channel.pipeline().get(Connections.PLAY_PACKET_QUEUE_INBOUND) != null) { + this.channel.pipeline().remove(Connections.PLAY_PACKET_QUEUE_INBOUND); + } } } @@ -393,10 +397,13 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter { * Adds the play packet queue handler. */ public void addPlayPacketQueueHandler() { - if (this.channel.pipeline().get(Connections.PLAY_PACKET_QUEUE) == null) { - this.channel.pipeline().addAfter(Connections.MINECRAFT_ENCODER, Connections.PLAY_PACKET_QUEUE, - new PlayPacketQueueHandler(this.protocolVersion, - channel.pipeline().get(MinecraftEncoder.class).getDirection())); + if (this.channel.pipeline().get(Connections.PLAY_PACKET_QUEUE_OUTBOUND) == null) { + this.channel.pipeline().addAfter(Connections.MINECRAFT_ENCODER, Connections.PLAY_PACKET_QUEUE_OUTBOUND, + new PlayPacketQueueOutboundHandler(this.protocolVersion, channel.pipeline().get(MinecraftEncoder.class).getDirection())); + } + if (this.channel.pipeline().get(Connections.PLAY_PACKET_QUEUE_INBOUND) == null) { + this.channel.pipeline().addAfter(Connections.MINECRAFT_DECODER, Connections.PLAY_PACKET_QUEUE_INBOUND, + new PlayPacketQueueInboundHandler(this.protocolVersion, channel.pipeline().get(MinecraftDecoder.class).getDirection())); } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java index 5b83e6549..14989b1a3 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java @@ -40,8 +40,8 @@ import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; -import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo; -import com.velocitypowered.proxy.connection.player.resourcepack.ResourcePackHandler; +import com.velocitypowered.proxy.connection.player.resourcepack.VelocityResourcePackInfo; +import com.velocitypowered.proxy.connection.player.resourcepack.handler.ResourcePackHandler; import com.velocitypowered.proxy.connection.util.ConnectionMessages; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.StateRegistry; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BungeeCordMessageResponder.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BungeeCordMessageResponder.java index 3c2691781..ea28d2c97 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BungeeCordMessageResponder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BungeeCordMessageResponder.java @@ -37,7 +37,6 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import java.util.Optional; import java.util.StringJoiner; -import net.kyori.adventure.identity.Identity; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.ComponentSerializer; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; @@ -143,7 +142,7 @@ public class BungeeCordMessageResponder { out.writeUTF("PlayerList"); out.writeUTF(info.getServerInfo().getName()); - StringJoiner joiner = new StringJoiner(", "); + final StringJoiner joiner = new StringJoiner(", "); for (Player online : info.getPlayersConnected()) { joiner.add(online.getUsername()); } @@ -187,10 +186,9 @@ public class BungeeCordMessageResponder { Component messageComponent = serializer.deserialize(message); if (target.equals("ALL")) { - proxy.sendMessage(Identity.nil(), messageComponent); + proxy.sendMessage(messageComponent); } else { - proxy.getPlayer(target).ifPresent(player -> player.sendMessage(Identity.nil(), - messageComponent)); + proxy.getPlayer(target).ifPresent(player -> player.sendMessage(messageComponent)); } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java index 3f4325e52..a19854eb9 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java @@ -29,7 +29,7 @@ import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.client.ClientConfigSessionHandler; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; -import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo; +import com.velocitypowered.proxy.connection.player.resourcepack.VelocityResourcePackInfo; import com.velocitypowered.proxy.connection.util.ConnectionMessages; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl; @@ -132,7 +132,8 @@ public class ConfigSessionHandler implements MinecraftSessionHandler { @Override public boolean handle(KeepAlivePacket packet) { - serverConn.ensureConnected().write(packet); + serverConn.getPendingPings().put(packet.getRandomId(), System.nanoTime()); + serverConn.getPlayer().getConnection().write(packet); return true; } @@ -193,30 +194,25 @@ public class ConfigSessionHandler implements MinecraftSessionHandler { @Override public boolean handle(FinishedUpdatePacket packet) { - MinecraftConnection smc = serverConn.ensureConnected(); - ConnectedPlayer player = serverConn.getPlayer(); - ClientConfigSessionHandler configHandler = - (ClientConfigSessionHandler) player.getConnection().getActiveSessionHandler(); + final MinecraftConnection smc = serverConn.ensureConnected(); + final ConnectedPlayer player = serverConn.getPlayer(); + final ClientConfigSessionHandler configHandler = (ClientConfigSessionHandler) player.getConnection().getActiveSessionHandler(); - smc.setAutoReading(false); - // Even when not auto reading messages are still decoded. Decode them with the correct state smc.getChannel().pipeline().get(MinecraftDecoder.class).setState(StateRegistry.PLAY); - configHandler.handleBackendFinishUpdate(serverConn).thenAcceptAsync((unused) -> { + //noinspection DataFlowIssue + configHandler.handleBackendFinishUpdate(serverConn).thenRunAsync(() -> { + smc.write(FinishedUpdatePacket.INSTANCE); if (serverConn == player.getConnectedServer()) { smc.setActiveSessionHandler(StateRegistry.PLAY); - player.sendPlayerListHeaderAndFooter( - player.getPlayerListHeader(), player.getPlayerListFooter()); + player.sendPlayerListHeaderAndFooter(player.getPlayerListHeader(), player.getPlayerListFooter()); // The client cleared the tab list. TODO: Restore changes done via TabList API player.getTabList().clearAllSilent(); } else { - smc.setActiveSessionHandler(StateRegistry.PLAY, - new TransitionSessionHandler(server, serverConn, resultFuture)); + smc.setActiveSessionHandler(StateRegistry.PLAY, new TransitionSessionHandler(server, serverConn, resultFuture)); } - if (player.resourcePackHandler().getFirstAppliedPack() == null - && resourcePackToApply != null) { + if (player.resourcePackHandler().getFirstAppliedPack() == null && resourcePackToApply != null) { player.resourcePackHandler().queueResourcePack(resourcePackToApply); } - smc.setAutoReading(true); }, smc.eventLoop()); return true; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java index a672c9174..a2d2205ef 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java @@ -166,12 +166,9 @@ public class LoginSessionHandler implements MinecraftSessionHandler { if (player.getClientSettingsPacket() != null) { smc.write(player.getClientSettingsPacket()); } - if (player.getConnection().getActiveSessionHandler() instanceof ClientPlaySessionHandler) { + if (player.getConnection().getActiveSessionHandler() instanceof ClientPlaySessionHandler clientPlaySessionHandler) { smc.setAutoReading(false); - ((ClientPlaySessionHandler) player.getConnection() - .getActiveSessionHandler()).doSwitch().thenAcceptAsync((unused) -> { - smc.setAutoReading(true); - }, smc.eventLoop()); + clientPlaySessionHandler.doSwitch().thenAcceptAsync((unused) -> smc.setAutoReading(true), smc.eventLoop()); } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java index 52b67f7b5..3d955e903 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java @@ -19,6 +19,8 @@ package com.velocitypowered.proxy.connection.client; import com.velocitypowered.api.event.player.CookieReceiveEvent; import com.velocitypowered.api.event.player.PlayerClientBrandEvent; +import com.velocitypowered.api.event.player.configuration.PlayerFinishConfigurationEvent; +import com.velocitypowered.api.event.player.configuration.PlayerFinishedConfigurationEvent; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; @@ -246,13 +248,11 @@ public class ClientConfigSessionHandler implements MinecraftSessionHandler { smc.write(brandPacket); } - player.getConnection().eventLoop().execute(() -> { + server.getEventManager().fire(new PlayerFinishConfigurationEvent(player, serverConn)).thenAcceptAsync(event -> { player.getConnection().write(FinishedUpdatePacket.INSTANCE); player.getConnection().getChannel().pipeline().get(MinecraftEncoder.class).setState(StateRegistry.PLAY); - }); - - smc.write(FinishedUpdatePacket.INSTANCE); - smc.getChannel().pipeline().get(MinecraftEncoder.class).setState(StateRegistry.PLAY); + server.getEventManager().fireAndForget(new PlayerFinishedConfigurationEvent(player, serverConn)); + }, player.getConnection().eventLoop()); return configSwitchFuture; } 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 2a595b167..a76f054e7 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 @@ -27,6 +27,7 @@ import com.velocitypowered.api.event.player.CookieReceiveEvent; import com.velocitypowered.api.event.player.PlayerChannelRegisterEvent; import com.velocitypowered.api.event.player.PlayerClientBrandEvent; import com.velocitypowered.api.event.player.TabCompleteEvent; +import com.velocitypowered.api.event.player.configuration.PlayerEnterConfigurationEvent; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.proxy.messages.ChannelIdentifier; import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier; @@ -406,6 +407,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { // Complete client switch player.getConnection().setActiveSessionHandler(StateRegistry.CONFIG); VelocityServerConnection serverConnection = player.getConnectedServer(); + server.getEventManager().fireAndForget(new PlayerEnterConfigurationEvent(player, serverConnection)); if (serverConnection != null) { MinecraftConnection smc = serverConnection.ensureConnected(); CompletableFuture.runAsync(() -> { @@ -512,7 +514,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { * @return a future that completes when the switch is complete */ public CompletableFuture doSwitch() { - VelocityServerConnection existingConnection = player.getConnectedServer(); + final VelocityServerConnection existingConnection = player.getConnectedServer(); if (existingConnection != null) { // Shut down the existing server connection. 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 a471e2b53..de828382e 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 @@ -37,6 +37,7 @@ import com.velocitypowered.api.event.player.KickedFromServerEvent.ServerKickResu import com.velocitypowered.api.event.player.PlayerModInfoEvent; import com.velocitypowered.api.event.player.PlayerSettingsChangedEvent; import com.velocitypowered.api.event.player.ServerPreConnectEvent; +import com.velocitypowered.api.event.player.configuration.PlayerEnterConfigurationEvent; import com.velocitypowered.api.network.ProtocolState; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.permission.PermissionFunction; @@ -59,8 +60,9 @@ import com.velocitypowered.proxy.adventure.VelocityBossBarImplementation; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation; import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; -import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo; -import com.velocitypowered.proxy.connection.player.resourcepack.ResourcePackHandler; +import com.velocitypowered.proxy.connection.player.bundle.BundleDelimiterHandler; +import com.velocitypowered.proxy.connection.player.resourcepack.VelocityResourcePackInfo; +import com.velocitypowered.proxy.connection.player.resourcepack.handler.ResourcePackHandler; import com.velocitypowered.proxy.connection.util.ConnectionMessages; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl; import com.velocitypowered.proxy.connection.util.VelocityInboundConnection; @@ -806,7 +808,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, }, connection.eventLoop()); } else if (event.getResult() instanceof final Notify res) { if (event.kickedDuringServerConnect() && previousConnection != null) { - sendMessage(Identity.nil(), res.getMessageComponent()); + sendMessage(res.getMessageComponent()); } else { disconnect(res.getMessageComponent()); } @@ -1224,11 +1226,12 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, CompletableFuture.runAsync(() -> { connection.write(StartUpdatePacket.INSTANCE); connection.getChannel().pipeline() - .get(MinecraftEncoder.class).setState(StateRegistry.CONFIG); + .get(MinecraftEncoder.class).setState(StateRegistry.CONFIG); // Make sure we don't send any play packets to the player after update start connection.addPlayPacketQueueHandler(); + server.getEventManager().fireAndForget(new PlayerEnterConfigurationEvent(this, connectionInFlight)); }, connection.eventLoop()).exceptionally((ex) -> { - logger.error("Error switching player connection to config state:", ex); + logger.error("Error switching player connection to config state", ex); return null; }); } @@ -1363,24 +1366,20 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, } switch (status.getStatus()) { - case ALREADY_CONNECTED: - sendMessage(Identity.nil(), ConnectionMessages.ALREADY_CONNECTED); - break; - case CONNECTION_IN_PROGRESS: - sendMessage(Identity.nil(), ConnectionMessages.IN_PROGRESS); - break; - case CONNECTION_CANCELLED: + case ALREADY_CONNECTED -> sendMessage(ConnectionMessages.ALREADY_CONNECTED); + case CONNECTION_IN_PROGRESS -> sendMessage(ConnectionMessages.IN_PROGRESS); + case CONNECTION_CANCELLED -> { // Ignored; the plugin probably already handled this. - break; - case SERVER_DISCONNECTED: - Component reason = status.getReasonComponent() - .orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR); + } + case SERVER_DISCONNECTED -> { + final Component reason = status.getReasonComponent() + .orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR); handleConnectionException(toConnect, - DisconnectPacket.create(reason, getProtocolVersion(), connection.getState()), status.isSafe()); - break; - default: + DisconnectPacket.create(reason, getProtocolVersion(), connection.getState()), status.isSafe()); + } + default -> { // The only remaining value is successful (no need to do anything!) - break; + } } }, connection.eventLoop()).thenApply(Result::isSuccessful); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/BundleDelimiterHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/bundle/BundleDelimiterHandler.java similarity index 95% rename from proxy/src/main/java/com/velocitypowered/proxy/connection/client/BundleDelimiterHandler.java rename to proxy/src/main/java/com/velocitypowered/proxy/connection/player/bundle/BundleDelimiterHandler.java index e92d10587..d5d52ef03 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/BundleDelimiterHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/bundle/BundleDelimiterHandler.java @@ -15,10 +15,11 @@ * along with this program. If not, see . */ -package com.velocitypowered.proxy.connection.client; +package com.velocitypowered.proxy.connection.player.bundle; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; +import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket; import java.util.concurrent.CompletableFuture; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/VelocityResourcePackInfo.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/VelocityResourcePackInfo.java similarity index 98% rename from proxy/src/main/java/com/velocitypowered/proxy/connection/player/VelocityResourcePackInfo.java rename to proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/VelocityResourcePackInfo.java index 67f90fd15..4292710e0 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/VelocityResourcePackInfo.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/VelocityResourcePackInfo.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.velocitypowered.proxy.connection.player; +package com.velocitypowered.proxy.connection.player.resourcepack; import com.google.common.base.Preconditions; import com.velocitypowered.api.proxy.player.ResourcePackInfo; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/Legacy117ResourcePackHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/Legacy117ResourcePackHandler.java similarity index 99% rename from proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/Legacy117ResourcePackHandler.java rename to proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/Legacy117ResourcePackHandler.java index 385bdce4c..a1af567dd 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/Legacy117ResourcePackHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/Legacy117ResourcePackHandler.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.velocitypowered.proxy.connection.player.resourcepack; +package com.velocitypowered.proxy.connection.player.resourcepack.handler; import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent; import com.velocitypowered.proxy.VelocityServer; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/LegacyResourcePackHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/LegacyResourcePackHandler.java similarity index 98% rename from proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/LegacyResourcePackHandler.java rename to proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/LegacyResourcePackHandler.java index b1b325484..53fa2421c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/LegacyResourcePackHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/LegacyResourcePackHandler.java @@ -15,13 +15,14 @@ * along with this program. If not, see . */ -package com.velocitypowered.proxy.connection.player.resourcepack; +package com.velocitypowered.proxy.connection.player.resourcepack.handler; import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.proxy.player.ResourcePackInfo; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; +import com.velocitypowered.proxy.connection.player.resourcepack.ResourcePackResponseBundle; import java.util.ArrayDeque; import java.util.Arrays; import java.util.Collection; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/ModernResourcePackHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/ModernResourcePackHandler.java similarity index 98% rename from proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/ModernResourcePackHandler.java rename to proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/ModernResourcePackHandler.java index 5ba74a273..077ce701d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/ModernResourcePackHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/ModernResourcePackHandler.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.velocitypowered.proxy.connection.player.resourcepack; +package com.velocitypowered.proxy.connection.player.resourcepack.handler; import com.google.common.collect.ListMultimap; import com.google.common.collect.Multimaps; @@ -23,6 +23,7 @@ import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent; import com.velocitypowered.api.proxy.player.ResourcePackInfo; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; +import com.velocitypowered.proxy.connection.player.resourcepack.ResourcePackResponseBundle; import java.util.Arrays; import java.util.Collection; import java.util.LinkedList; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/ResourcePackHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/ResourcePackHandler.java similarity index 97% rename from proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/ResourcePackHandler.java rename to proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/ResourcePackHandler.java index 4e6e72505..e5df9f659 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/ResourcePackHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/ResourcePackHandler.java @@ -15,14 +15,15 @@ * along with this program. If not, see . */ -package com.velocitypowered.proxy.connection.player.resourcepack; +package com.velocitypowered.proxy.connection.player.resourcepack.handler; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.proxy.player.ResourcePackInfo; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; -import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo; +import com.velocitypowered.proxy.connection.player.resourcepack.ResourcePackResponseBundle; +import com.velocitypowered.proxy.connection.player.resourcepack.VelocityResourcePackInfo; import com.velocitypowered.proxy.protocol.packet.ResourcePackRequestPacket; import com.velocitypowered.proxy.protocol.packet.ResourcePackResponsePacket; import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/Connections.java b/proxy/src/main/java/com/velocitypowered/proxy/network/Connections.java index 27ec4ba8b..ca4e6b1b2 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/network/Connections.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/Connections.java @@ -35,7 +35,8 @@ public class Connections { public static final String MINECRAFT_DECODER = "minecraft-decoder"; public static final String MINECRAFT_ENCODER = "minecraft-encoder"; public static final String READ_TIMEOUT = "read-timeout"; - public static final String PLAY_PACKET_QUEUE = "play-packet-queue"; + public static final String PLAY_PACKET_QUEUE_OUTBOUND = "play-packet-queue-outbound"; + public static final String PLAY_PACKET_QUEUE_INBOUND = "play-packet-queue-inbound"; private Connections() { throw new AssertionError(); 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 f8362cc09..d75cb46ca 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 @@ -56,8 +56,7 @@ public class MinecraftDecoder extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - if (msg instanceof ByteBuf) { - ByteBuf buf = (ByteBuf) msg; + if (msg instanceof ByteBuf buf) { tryDecode(ctx, buf); } else { ctx.fireChannelRead(msg); @@ -147,4 +146,8 @@ public class MinecraftDecoder extends ChannelInboundHandlerAdapter { this.state = state; this.setProtocolVersion(registry.version); } + + public ProtocolUtils.Direction getDirection() { + return direction; + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/PlayPacketQueueInboundHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/PlayPacketQueueInboundHandler.java new file mode 100644 index 000000000..fe553f76a --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/PlayPacketQueueInboundHandler.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2023 Velocity Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.velocitypowered.proxy.protocol.netty; + +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 io.netty.channel.ChannelDuplexHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.util.ReferenceCountUtil; +import io.netty.util.internal.PlatformDependent; +import java.util.Queue; +import org.jetbrains.annotations.NotNull; + +/** + * Queues up any pending PLAY packets while the client is in the CONFIG state. + * + *

Much of the Velocity API (i.e. chat messages) utilize PLAY packets, however the client is + * incapable of receiving these packets during the CONFIG state. Certain events such as the + * ServerPreConnectEvent may be called during this time, and we need to ensure that any API that + * uses these packets will work as expected. + * + *

This handler will queue up any packets that are sent to the client during this time, and send + * them once the client has (re)entered the PLAY state. + */ +public class PlayPacketQueueInboundHandler extends ChannelDuplexHandler { + + private final StateRegistry.PacketRegistry.ProtocolRegistry registry; + private final Queue queue = PlatformDependent.newMpscQueue(); + + /** + * Provides registries for client & server bound packets. + * + * @param version the protocol version + */ + public PlayPacketQueueInboundHandler(ProtocolVersion version, ProtocolUtils.Direction direction) { + this.registry = StateRegistry.CONFIG.getProtocolRegistry(direction, version); + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + if (msg instanceof final MinecraftPacket packet) { + // If the packet exists in the CONFIG state, we want to always + // ensure that it gets handled by the current handler + if (this.registry.containsPacket(packet)) { + ctx.fireChannelRead(msg); + return; + } + } + + // Otherwise, queue the packet + this.queue.offer(msg); + } + + @Override + public void channelInactive(@NotNull ChannelHandlerContext ctx) throws Exception { + this.releaseQueue(ctx, false); + + super.channelInactive(ctx); + } + + @Override + public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { + this.releaseQueue(ctx, ctx.channel().isActive()); + } + + private void releaseQueue(ChannelHandlerContext ctx, boolean active) { + // Handle all the queued packets + Object msg; + while ((msg = this.queue.poll()) != null) { + if (active) { + ctx.fireChannelRead(msg); + } else { + ReferenceCountUtil.release(msg); + } + } + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/PlayPacketQueueHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/PlayPacketQueueOutboundHandler.java similarity index 86% rename from proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/PlayPacketQueueHandler.java rename to proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/PlayPacketQueueOutboundHandler.java index 990985c25..d5764ef6a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/PlayPacketQueueHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/PlayPacketQueueOutboundHandler.java @@ -40,7 +40,7 @@ import org.jetbrains.annotations.NotNull; *

This handler will queue up any packets that are sent to the client during this time, and send * them once the client has (re)entered the PLAY state. */ -public class PlayPacketQueueHandler extends ChannelDuplexHandler { +public class PlayPacketQueueOutboundHandler extends ChannelDuplexHandler { private final StateRegistry.PacketRegistry.ProtocolRegistry registry; private final Queue queue = PlatformDependent.newMpscQueue(); @@ -50,28 +50,26 @@ public class PlayPacketQueueHandler extends ChannelDuplexHandler { * * @param version the protocol version */ - public PlayPacketQueueHandler(ProtocolVersion version, ProtocolUtils.Direction direction) { - this.registry = - StateRegistry.CONFIG.getProtocolRegistry(direction, version); + public PlayPacketQueueOutboundHandler(ProtocolVersion version, ProtocolUtils.Direction direction) { + this.registry = StateRegistry.CONFIG.getProtocolRegistry(direction, version); } @Override - public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) - throws Exception { - if (!(msg instanceof MinecraftPacket)) { + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + if (!(msg instanceof final MinecraftPacket packet)) { ctx.write(msg, promise); return; } // If the packet exists in the CONFIG state, we want to always // ensure that it gets sent out to the client - if (this.registry.containsPacket(((MinecraftPacket) msg))) { + if (this.registry.containsPacket(packet)) { ctx.write(msg, promise); return; } // Otherwise, queue the packet - this.queue.offer((MinecraftPacket) msg); + this.queue.offer(packet); } @Override @@ -87,10 +85,6 @@ public class PlayPacketQueueHandler extends ChannelDuplexHandler { } private void releaseQueue(ChannelHandlerContext ctx, boolean active) { - if (this.queue.isEmpty()) { - return; - } - // Send out all the queued packets MinecraftPacket packet; while ((packet = this.queue.poll()) != null) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ResourcePackRequestPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ResourcePackRequestPacket.java index 07f9f776a..a0f86aed1 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ResourcePackRequestPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ResourcePackRequestPacket.java @@ -21,7 +21,7 @@ import com.google.common.base.Preconditions; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.proxy.player.ResourcePackInfo; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; -import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo; +import com.velocitypowered.proxy.connection.player.resourcepack.VelocityResourcePackInfo; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;