3
0
Mirror von https://github.com/PaperMC/Velocity.git synchronisiert 2024-09-29 06:30:16 +02:00

Merge branch 'PaperMC:dev/3.0.0' into dev/3.0.0

Dieser Commit ist enthalten in:
Loganius 2024-06-16 22:05:42 -05:00 committet von GitHub
Commit b3c131f96b
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: B5690EEEBB952194
43 geänderte Dateien mit 1490 neuen und 119 gelöschten Zeilen

Datei anzeigen

@ -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);
}

Datei anzeigen

@ -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<CookieReceiveEvent.ForwardResult> {
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);
}
}
}

Datei anzeigen

@ -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 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
* cookie request (if handled) or forwarding it to the client.
*/
@AwaitingEvent
public final class CookieRequestEvent implements ResultedEvent<CookieRequestEvent.ForwardResult> {
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 request 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);
}
}
}

Datei anzeigen

@ -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<CookieStoreEvent.ForwardResult> {
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);
}
}
}

Datei anzeigen

@ -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.
* <p>From this moment on, until the {@link PlayerFinishedConfigurationEvent} is executed,
* the {@linkplain Player#getProtocolState()} method is guaranteed
* to return {@link ProtocolState#CONFIGURATION}.</p>
*
* @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) {
}

Datei anzeigen

@ -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.
* <p>Velocity will wait for this event to finish the configuration phase on the client.</p>
*
* @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) {
}

Datei anzeigen

@ -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.
* <p>From this moment on, the {@link Player#getProtocolState()} method
* will return {@link ProtocolState#PLAY}.</p>
*
* @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) {
}

Datei anzeigen

@ -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<ResultedEvent.GenericResult> {
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{"

Datei anzeigen

@ -86,7 +86,8 @@ public enum ProtocolVersion implements Ordered<ProtocolVersion> {
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;

Datei anzeigen

@ -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);
}

Datei anzeigen

@ -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;
@ -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());
}

Datei anzeigen

@ -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<S> {
public void inject(final RootCommandNode<S> dest, final S source) {
lock.lock();
try {
final Map<CommandNode<S>, CommandNode<S>> done = new IdentityHashMap<>();
final RootCommandNode<S> origin = this.dispatcher.getRoot();
final CommandContextBuilder<S> rootContext =
new CommandContextBuilder<>(this.dispatcher, source, origin, 0);
@ -88,7 +91,7 @@ public final class CommandGraphInjector<S> {
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<S> child : node.getChildren()) {
@ -102,7 +105,10 @@ public final class CommandGraphInjector<S> {
}
}
private @Nullable CommandNode<S> filterNode(final CommandNode<S> node, final S source) {
private @Nullable CommandNode<S> filterNode(final CommandNode<S> node, final S source, final Map<CommandNode<S>, CommandNode<S>> 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<S> {
// 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<S> target = this.filterNode(node.getRedirect(), source);
final CommandNode<S> target = this.filterNode(node.getRedirect(), source, done);
builder.forward(target, builder.getRedirectModifier(), builder.isFork());
}
final CommandNode<S> 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<S> parent, final CommandNode<S> dest,
final S source) {
private void copyChildren(final CommandNode<S> parent, final CommandNode<S> dest, final S source, final Map<CommandNode<S>, CommandNode<S>> done) {
for (final CommandNode<S> child : parent.getChildren()) {
final CommandNode<S> filtered = this.filterNode(child, source);
final CommandNode<S> filtered = this.filterNode(child, source, done);
if (filtered != null) {
dest.addChild(filtered);
}

Datei anzeigen

@ -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<CommandSource> command = getCommand(alias);
return command != null && command.canUse(source);
}
CommandNode<CommandSource> 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

Datei anzeigen

@ -74,7 +74,7 @@ public final class VelocityCommand {
@SuppressWarnings("checkstyle:MissingJavadocMethod")
public static BrigadierCommand create(final VelocityServer server) {
final LiteralCommandNode<CommandSource> 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<CommandSource> heap = BrigadierCommand.literalArgumentBuilder("heap")

Datei anzeigen

@ -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()));
}
}

Datei anzeigen

@ -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;
@ -62,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;
@ -339,4 +344,24 @@ 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;
}
default boolean handle(ClientboundCustomReportDetailsPacket packet) {
return false;
}
default boolean handle(ClientboundServerLinksPacket packet) {
return false;
}
}

Datei anzeigen

@ -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;
@ -38,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;
@ -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) {

Datei anzeigen

@ -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));
}
}

Datei anzeigen

@ -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;
@ -27,19 +29,23 @@ 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;
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;
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;
@ -48,6 +54,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;
@ -111,9 +118,22 @@ 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);
serverConn.getPendingPings().put(packet.getRandomId(), System.nanoTime());
serverConn.getPlayer().getConnection().write(packet);
return true;
}
@ -174,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;
}
@ -251,6 +266,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(

Datei anzeigen

@ -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;
@ -162,18 +166,35 @@ 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());
}
}
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);

Datei anzeigen

@ -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);

Datei anzeigen

@ -17,7 +17,10 @@
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;
@ -32,6 +35,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 +43,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;
@ -128,16 +133,41 @@ 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 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;
}
@ -218,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;
}

Datei anzeigen

@ -23,9 +23,11 @@ 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;
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;
@ -48,6 +50,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 +86,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;
@ -403,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(() -> {
@ -418,6 +423,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();
@ -487,7 +514,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
* @return a future that completes when the switch is complete
*/
public CompletableFuture<Void> doSwitch() {
VelocityServerConnection existingConnection = player.getConnectedServer();
final VelocityServerConnection existingConnection = player.getConnectedServer();
if (existingConnection != null) {
// Shut down the existing server connection.
@ -630,7 +657,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.

Datei anzeigen

@ -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;
@ -35,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;
@ -57,14 +60,17 @@ 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;
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 +111,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;
@ -801,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());
}
@ -1008,6 +1015,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<String> completions) {
Preconditions.checkNotNull(completions, "completions");
@ -1175,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;
});
}
@ -1314,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);
}

Datei anzeigen

@ -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) -> {

Datei anzeigen

@ -15,10 +15,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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;

Datei anzeigen

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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;

Datei anzeigen

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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;

Datei anzeigen

@ -15,13 +15,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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;
@ -109,7 +110,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 +173,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);
}

Datei anzeigen

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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;

Datei anzeigen

@ -15,14 +15,15 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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;

Datei anzeigen

@ -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();

Datei anzeigen

@ -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;
@ -52,6 +53,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 +76,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;
@ -91,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;
@ -146,6 +152,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 +180,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 +214,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,
@ -214,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 {
@ -276,6 +295,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 +396,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 +620,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,
@ -638,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 {
@ -654,6 +686,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 +706,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));
}
};

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -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 <https://www.gnu.org/licenses/>.
*/
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.
*
* <p>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.
*
* <p>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<Object> queue = PlatformDependent.newMpscQueue();
/**
* Provides registries for client &amp; 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);
}
}
}
}

Datei anzeigen

@ -40,7 +40,7 @@ import org.jetbrains.annotations.NotNull;
* <p>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<MinecraftPacket> 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) {

Datei anzeigen

@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}

Datei anzeigen

@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}

Datei anzeigen

@ -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;

Datei anzeigen

@ -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);
}
}

Datei anzeigen

@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}

Datei anzeigen

@ -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 <https://www.gnu.org/licenses/>.
*/
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<String, String> details;
public ClientboundCustomReportDetailsPacket() {
}
public ClientboundCustomReportDetailsPacket(Map<String, String> 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<String, String> getDetails() {
return details;
}
}

Datei anzeigen

@ -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 <https://www.gnu.org/licenses/>.
*/
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<ServerLink> serverLinks;
public ClientboundServerLinksPacket() {
}
public ClientboundServerLinksPacket(List<ServerLink> 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<ServerLink> 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);
}
}
}