Merge remote-tracking branch 'github/dev/3.0.0'
All checks were successful
SteamWarCI Build successful
All checks were successful
SteamWarCI Build successful
# Conflicts: # proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionPlayerCommandPacket.java
This commit is contained in:
@ -51,7 +51,11 @@ tasks {
|
||||
exclude("it/unimi/dsi/fastutil/ints/*Int2Short*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/*Int2Reference*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/IntAVL*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/IntArray*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/IntArrayF*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/IntArrayI*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/IntArrayL*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/IntArrayP*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/IntArraySet*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/*IntBi*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/Int*Pair")
|
||||
exclude("it/unimi/dsi/fastutil/ints/IntLinked*")
|
||||
|
||||
@ -65,7 +65,8 @@ public class Metrics {
|
||||
logger::info,
|
||||
config.isLogErrorsEnabled(),
|
||||
config.isLogSentDataEnabled(),
|
||||
config.isLogResponseStatusTextEnabled()
|
||||
config.isLogResponseStatusTextEnabled(),
|
||||
false
|
||||
);
|
||||
|
||||
if (!config.didExistBefore()) {
|
||||
|
||||
@ -236,6 +236,15 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
||||
|
||||
registerTranslations();
|
||||
|
||||
// Yes, you're reading that correctly. We're generating a 1024-bit RSA keypair. Sounds
|
||||
// dangerous, right? We're well within the realm of factoring such a key...
|
||||
//
|
||||
// You can blame Mojang. For the record, we also don't consider the Minecraft protocol
|
||||
// encryption scheme to be secure, and it has reached the point where any serious cryptographic
|
||||
// protocol needs a refresh. There are multiple obvious weaknesses, and this is far from the
|
||||
// most serious.
|
||||
//
|
||||
// If you are using Minecraft in a security-sensitive application, *I don't know what to say.*
|
||||
serverKeyPair = EncryptionUtils.createRsaKeyPair(1024);
|
||||
|
||||
cm.logChannelInformation();
|
||||
@ -794,6 +803,11 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
||||
public VelocityChannelRegistrar getChannelRegistrar() {
|
||||
return channelRegistrar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShuttingDown() {
|
||||
return shutdownInProgress.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getBoundAddress() {
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
|
||||
package com.velocitypowered.proxy.command.builtin;
|
||||
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import com.mojang.brigadier.Command;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
@ -25,8 +26,9 @@ import com.velocitypowered.api.command.BrigadierCommand;
|
||||
import com.velocitypowered.api.command.CommandSource;
|
||||
import com.velocitypowered.api.proxy.ConsoleCommandSource;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||
|
||||
/**
|
||||
* Shuts down the proxy.
|
||||
@ -53,11 +55,22 @@ public final class ShutdownCommand {
|
||||
StringArgumentType.greedyString())
|
||||
.executes(context -> {
|
||||
String reason = context.getArgument("reason", String.class);
|
||||
server.shutdown(true, MiniMessage.miniMessage().deserialize(
|
||||
MiniMessage.miniMessage().serialize(
|
||||
LegacyComponentSerializer.legacy('&').deserialize(reason)
|
||||
)
|
||||
));
|
||||
Component reasonComponent = null;
|
||||
|
||||
if (reason.startsWith("{") || reason.startsWith("[") || reason.startsWith("\"")) {
|
||||
try {
|
||||
reasonComponent = GsonComponentSerializer.gson()
|
||||
.deserializeOrNull(reason);
|
||||
} catch (JsonSyntaxException expected) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (reasonComponent == null) {
|
||||
reasonComponent = MiniMessage.miniMessage().deserialize(reason);
|
||||
}
|
||||
|
||||
server.shutdown(true, reasonComponent);
|
||||
return Command.SINGLE_SUCCESS;
|
||||
})
|
||||
).build());
|
||||
|
||||
@ -82,7 +82,7 @@ public final class VelocityCommand {
|
||||
.executes(new Heap())
|
||||
.build();
|
||||
final LiteralCommandNode<CommandSource> info = BrigadierCommand.literalArgumentBuilder("info")
|
||||
.requires(source -> source.getPermissionValue("velocity.command.info") != Tristate.FALSE)
|
||||
.requires(source -> source.getPermissionValue("velocity.command.info") == Tristate.TRUE)
|
||||
.executes(new Info(server))
|
||||
.build();
|
||||
final LiteralCommandNode<CommandSource> plugins = BrigadierCommand
|
||||
|
||||
@ -407,6 +407,10 @@ public class VelocityConfiguration implements ProxyConfig {
|
||||
return forceKeyAuthentication;
|
||||
}
|
||||
|
||||
public boolean isEnableReusePort() {
|
||||
return advanced.isEnableReusePort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
@ -716,6 +720,8 @@ public class VelocityConfiguration implements ProxyConfig {
|
||||
private boolean logPlayerConnections = true;
|
||||
@Expose
|
||||
private boolean acceptTransfers = false;
|
||||
@Expose
|
||||
private boolean enableReusePort = false;
|
||||
|
||||
private Advanced() {
|
||||
}
|
||||
@ -741,6 +747,7 @@ public class VelocityConfiguration implements ProxyConfig {
|
||||
this.logCommandExecutions = config.getOrElse("log-command-executions", false);
|
||||
this.logPlayerConnections = config.getOrElse("log-player-connections", true);
|
||||
this.acceptTransfers = config.getOrElse("accepts-transfers", false);
|
||||
this.enableReusePort = config.getOrElse("enable-reuse-port", false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -804,6 +811,10 @@ public class VelocityConfiguration implements ProxyConfig {
|
||||
return this.acceptTransfers;
|
||||
}
|
||||
|
||||
public boolean isEnableReusePort() {
|
||||
return enableReusePort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Advanced{"
|
||||
@ -821,6 +832,7 @@ public class VelocityConfiguration implements ProxyConfig {
|
||||
+ ", logCommandExecutions=" + logCommandExecutions
|
||||
+ ", logPlayerConnections=" + logPlayerConnections
|
||||
+ ", acceptTransfers=" + acceptTransfers
|
||||
+ ", enableReusePort=" + enableReusePort
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
|
||||
@ -301,6 +301,21 @@ public class BungeeCordMessageResponder {
|
||||
}
|
||||
}
|
||||
|
||||
private void processGetPlayerServer(ByteBufDataInput in) {
|
||||
proxy.getPlayer(in.readUTF()).ifPresent(player -> {
|
||||
player.getCurrentServer().ifPresent(server -> {
|
||||
ByteBuf buf = Unpooled.buffer();
|
||||
ByteBufDataOutput out = new ByteBufDataOutput(buf);
|
||||
|
||||
out.writeUTF("GetPlayerServer");
|
||||
out.writeUTF(player.getUsername());
|
||||
out.writeUTF(server.getServerInfo().getName());
|
||||
|
||||
sendResponseOnConnection(buf);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static String getBungeeCordChannel(ProtocolVersion version) {
|
||||
return version.noLessThan(ProtocolVersion.MINECRAFT_1_13) ? MODERN_CHANNEL.getId()
|
||||
: LEGACY_CHANNEL.getId();
|
||||
@ -331,6 +346,9 @@ public class BungeeCordMessageResponder {
|
||||
ByteBufDataInput in = new ByteBufDataInput(message.content());
|
||||
String subChannel = in.readUTF();
|
||||
switch (subChannel) {
|
||||
case "GetPlayerServer":
|
||||
this.processGetPlayerServer(in);
|
||||
break;
|
||||
case "ForwardToPlayer":
|
||||
this.processForwardToPlayer(in);
|
||||
break;
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
|
||||
package com.velocitypowered.proxy.connection.backend;
|
||||
|
||||
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;
|
||||
@ -24,6 +25,7 @@ import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent;
|
||||
import com.velocitypowered.api.event.player.ServerResourcePackRemoveEvent;
|
||||
import com.velocitypowered.api.event.player.ServerResourcePackSendEvent;
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.player.ResourcePackInfo;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
@ -54,6 +56,8 @@ import com.velocitypowered.proxy.protocol.packet.config.RegistrySyncPacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.config.StartUpdatePacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.config.TagsUpdatePacket;
|
||||
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
@ -261,7 +265,29 @@ public class ConfigSessionHandler implements MinecraftSessionHandler {
|
||||
PluginMessageUtil.rewriteMinecraftBrand(packet, server.getVersion(),
|
||||
serverConn.getPlayer().getProtocolVersion()));
|
||||
} else {
|
||||
serverConn.getPlayer().getConnection().write(packet.retain());
|
||||
byte[] bytes = ByteBufUtil.getBytes(packet.content());
|
||||
ChannelIdentifier id = this.server.getChannelRegistrar().getFromId(packet.getChannel());
|
||||
|
||||
if (id == null) {
|
||||
serverConn.getPlayer().getConnection().write(packet.retain());
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handling this stuff async means that we should probably pause
|
||||
// the connection while we toss this off into another pool
|
||||
this.serverConn.getConnection().setAutoReading(false);
|
||||
this.server.getEventManager()
|
||||
.fire(new PluginMessageEvent(serverConn, serverConn.getPlayer(), id, bytes))
|
||||
.thenAcceptAsync(pme -> {
|
||||
if (pme.getResult().isAllowed() && !serverConn.getPlayer().getConnection().isClosed()) {
|
||||
serverConn.getPlayer().getConnection().write(new PluginMessagePacket(
|
||||
pme.getIdentifier().getId(), Unpooled.wrappedBuffer(bytes)));
|
||||
}
|
||||
this.serverConn.getConnection().setAutoReading(true);
|
||||
}, serverConn.ensureConnected().eventLoop()).exceptionally((ex) -> {
|
||||
logger.error("Exception while handling plugin message {}", packet, ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -97,7 +97,7 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
|
||||
// Initiate a regular connection and move over to it.
|
||||
ConnectedPlayer player = new ConnectedPlayer(server, profileEvent.getGameProfile(),
|
||||
mcConnection, inbound.getVirtualHost().orElse(null), inbound.getRawVirtualHost().orElse(null), onlineMode,
|
||||
inbound.getIdentifiedKey());
|
||||
inbound.getHandshakeIntent(), inbound.getIdentifiedKey());
|
||||
this.connectedPlayer = player;
|
||||
if (!server.canRegisterConnection(player)) {
|
||||
player.disconnect0(
|
||||
@ -106,7 +106,9 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
logger.info("{} has connected", player);
|
||||
if (server.getConfiguration().isLogPlayerConnections()) {
|
||||
logger.info("{} has connected", player);
|
||||
}
|
||||
|
||||
return server.getEventManager()
|
||||
.fire(new PermissionsSetupEvent(player, ConnectedPlayer.DEFAULT_PERMISSIONS))
|
||||
|
||||
@ -17,14 +17,17 @@
|
||||
|
||||
package com.velocitypowered.proxy.connection.client;
|
||||
|
||||
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
||||
import com.velocitypowered.api.event.player.CookieReceiveEvent;
|
||||
import com.velocitypowered.api.event.player.PlayerClientBrandEvent;
|
||||
import com.velocitypowered.api.event.player.configuration.PlayerConfigurationEvent;
|
||||
import com.velocitypowered.api.event.player.configuration.PlayerFinishConfigurationEvent;
|
||||
import com.velocitypowered.api.event.player.configuration.PlayerFinishedConfigurationEvent;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.connection.backend.BungeeCordMessageResponder;
|
||||
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
||||
import com.velocitypowered.proxy.connection.player.resourcepack.ResourcePackResponseBundle;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
@ -41,6 +44,7 @@ import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.config.KnownPacksPacket;
|
||||
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@ -123,8 +127,32 @@ public class ClientConfigSessionHandler implements MinecraftSessionHandler {
|
||||
brandChannel = packet.getChannel();
|
||||
// Client sends `minecraft:brand` packet immediately after Login,
|
||||
// but at this time the backend server may not be ready
|
||||
} else if (BungeeCordMessageResponder.isBungeeCordMessage(packet)) {
|
||||
return true;
|
||||
} else if (serverConn != null) {
|
||||
serverConn.ensureConnected().write(packet.retain());
|
||||
byte[] bytes = ByteBufUtil.getBytes(packet.content());
|
||||
ChannelIdentifier id = this.server.getChannelRegistrar().getFromId(packet.getChannel());
|
||||
|
||||
if (id == null) {
|
||||
serverConn.ensureConnected().write(packet.retain());
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handling this stuff async means that we should probably pause
|
||||
// the connection while we toss this off into another pool
|
||||
serverConn.getPlayer().getConnection().setAutoReading(false);
|
||||
this.server.getEventManager()
|
||||
.fire(new PluginMessageEvent(serverConn.getPlayer(), serverConn, id, bytes))
|
||||
.thenAcceptAsync(pme -> {
|
||||
if (pme.getResult().isAllowed() && serverConn.getConnection() != null) {
|
||||
serverConn.ensureConnected().write(new PluginMessagePacket(
|
||||
pme.getIdentifier().getId(), Unpooled.wrappedBuffer(bytes)));
|
||||
}
|
||||
serverConn.getPlayer().getConnection().setAutoReading(true);
|
||||
}, player.getConnection().eventLoop()).exceptionally((ex) -> {
|
||||
logger.error("Exception while handling plugin message packet for {}", player, ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -170,6 +170,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
@Override
|
||||
public void deactivated() {
|
||||
player.discardChatQueue();
|
||||
for (PluginMessagePacket message : loginPluginMessages) {
|
||||
ReferenceCountUtil.release(message);
|
||||
}
|
||||
@ -426,6 +427,13 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(JoinGamePacket packet) {
|
||||
// Forward the packet as normal, but discard any chat state we have queued - the client will do this too
|
||||
player.discardChatQueue();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleGeneric(MinecraftPacket packet) {
|
||||
VelocityServerConnection serverConnection = player.getConnectedServer();
|
||||
|
||||
@ -38,6 +38,7 @@ 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.HandshakeIntent;
|
||||
import com.velocitypowered.api.network.ProtocolState;
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.api.permission.PermissionFunction;
|
||||
@ -156,6 +157,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
private final MinecraftConnection connection;
|
||||
private final @Nullable InetSocketAddress virtualHost;
|
||||
private final @Nullable String rawVirtualHost;
|
||||
private final HandshakeIntent handshakeIntent;
|
||||
private GameProfile profile;
|
||||
private PermissionFunction permissionFunction;
|
||||
private int tryIndex = 0;
|
||||
@ -188,17 +190,18 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
private @Nullable Locale effectiveLocale;
|
||||
private final @Nullable IdentifiedKey playerKey;
|
||||
private @Nullable ClientSettingsPacket clientSettingsPacket;
|
||||
private final ChatQueue chatQueue;
|
||||
private volatile ChatQueue chatQueue;
|
||||
private final ChatBuilderFactory chatBuilderFactory;
|
||||
|
||||
ConnectedPlayer(VelocityServer server, GameProfile profile, MinecraftConnection connection,
|
||||
@Nullable InetSocketAddress virtualHost, @Nullable String rawVirtualHost, boolean onlineMode,
|
||||
@Nullable IdentifiedKey playerKey) {
|
||||
HandshakeIntent handshakeIntent, @Nullable IdentifiedKey playerKey) {
|
||||
this.server = server;
|
||||
this.profile = profile;
|
||||
this.connection = connection;
|
||||
this.virtualHost = virtualHost;
|
||||
this.rawVirtualHost = rawVirtualHost;
|
||||
this.handshakeIntent = handshakeIntent;
|
||||
this.permissionFunction = PermissionFunction.ALWAYS_UNDEFINED;
|
||||
this.connectionPhase = connection.getType().getInitialClientPhase();
|
||||
this.onlineMode = onlineMode;
|
||||
@ -233,6 +236,17 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
return chatQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discards any messages still being processed by the {@link ChatQueue}, and creates a fresh state for future packets.
|
||||
* This should be used on server switches, or whenever the client resets its own 'last seen' state.
|
||||
*/
|
||||
public void discardChatQueue() {
|
||||
// No need for atomic swap, should only be called from event loop
|
||||
final ChatQueue oldChatQueue = chatQueue;
|
||||
chatQueue = new ChatQueue(this);
|
||||
oldChatQueue.close();
|
||||
}
|
||||
|
||||
public BundleDelimiterHandler getBundleHandler() {
|
||||
return this.bundleHandler;
|
||||
}
|
||||
@ -1087,8 +1101,14 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
throw new IllegalStateException("Can only send server links in CONFIGURATION or PLAY protocol");
|
||||
}
|
||||
|
||||
connection.write(new ClientboundServerLinksPacket(List.copyOf(links).stream()
|
||||
.map(l -> new ClientboundServerLinksPacket.ServerLink(l, getProtocolVersion())).toList()));
|
||||
connection.write(new ClientboundServerLinksPacket(links.stream()
|
||||
.map(l -> new ClientboundServerLinksPacket.ServerLink(
|
||||
l.getBuiltInType().map(Enum::ordinal).orElse(-1),
|
||||
l.getCustomLabel()
|
||||
.map(c -> new ComponentHolder(getProtocolVersion(), translateMessage(c)))
|
||||
.orElse(null),
|
||||
l.getUrl().toString()))
|
||||
.toList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1284,6 +1304,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
public void switchToConfigState() {
|
||||
server.getEventManager().fire(new PlayerEnterConfigurationEvent(this, getConnectionInFlightOrConnectedServer()))
|
||||
.completeOnTimeout(null, 5, TimeUnit.SECONDS).thenRunAsync(() -> {
|
||||
// if the connection was closed earlier, there is a risk that the player is no longer connected
|
||||
if (!connection.getChannel().isActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (bundleHandler.isInBundleSession()) {
|
||||
bundleHandler.toggleBundleSession();
|
||||
connection.write(BundleDelimiterPacket.INSTANCE);
|
||||
@ -1335,6 +1360,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
return connection.getState().toProtocolState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandshakeIntent getHandshakeIntent() {
|
||||
return handshakeIntent;
|
||||
}
|
||||
|
||||
private final class ConnectionRequestBuilderImpl implements ConnectionRequestBuilder {
|
||||
|
||||
private final RegisteredServer toConnect;
|
||||
|
||||
@ -277,5 +277,10 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
||||
public ProtocolState getProtocolState() {
|
||||
return connection.getState().toProtocolState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandshakeIntent getHandshakeIntent() {
|
||||
return HandshakeIntent.STATUS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
|
||||
package com.velocitypowered.proxy.connection.client;
|
||||
|
||||
import com.velocitypowered.api.network.HandshakeIntent;
|
||||
import com.velocitypowered.api.network.ProtocolState;
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.api.proxy.InboundConnection;
|
||||
@ -98,6 +99,11 @@ public final class InitialInboundConnection implements VelocityInboundConnection
|
||||
return connection.getState().toProtocolState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandshakeIntent getHandshakeIntent() {
|
||||
return handshake.getIntent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects the connection from the server.
|
||||
*
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
|
||||
package com.velocitypowered.proxy.connection.client;
|
||||
|
||||
import com.velocitypowered.api.network.HandshakeIntent;
|
||||
import com.velocitypowered.api.network.ProtocolState;
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.api.proxy.LoginPhaseConnection;
|
||||
@ -177,4 +178,9 @@ public class LoginInboundConnection implements LoginPhaseConnection, KeyIdentifi
|
||||
public ProtocolState getProtocolState() {
|
||||
return delegate.getProtocolState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandshakeIntent getHandshakeIntent() {
|
||||
return delegate.getHandshakeIntent();
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,6 +64,7 @@ class LegacyForgeUtil {
|
||||
if (discriminator == MOD_LIST_DISCRIMINATOR) {
|
||||
ImmutableList.Builder<ModInfo.Mod> mods = ImmutableList.builder();
|
||||
int modCount = ProtocolUtils.readVarInt(contents);
|
||||
Preconditions.checkArgument(modCount < 1024, "Oversized mods list");
|
||||
|
||||
for (int index = 0; index < modCount; index++) {
|
||||
String id = ProtocolUtils.readString(contents);
|
||||
|
||||
@ -63,7 +63,7 @@ public class ServerListPingHandler {
|
||||
}
|
||||
|
||||
private CompletableFuture<ServerPing> attemptPingPassthrough(VelocityInboundConnection connection,
|
||||
PingPassthroughMode mode, List<String> servers, ProtocolVersion responseProtocolVersion) {
|
||||
PingPassthroughMode mode, List<String> servers, ProtocolVersion responseProtocolVersion, String virtualHostStr) {
|
||||
ServerPing fallback = constructLocalPing(connection.getProtocolVersion());
|
||||
List<CompletableFuture<ServerPing>> pings = new ArrayList<>();
|
||||
for (String s : servers) {
|
||||
@ -73,7 +73,7 @@ public class ServerListPingHandler {
|
||||
}
|
||||
VelocityRegisteredServer vrs = (VelocityRegisteredServer) rs.get();
|
||||
pings.add(vrs.ping(connection.getConnection().eventLoop(), PingOptions.builder()
|
||||
.version(responseProtocolVersion).build()));
|
||||
.version(responseProtocolVersion).virtualHost(virtualHostStr).build()));
|
||||
}
|
||||
if (pings.isEmpty()) {
|
||||
return CompletableFuture.completedFuture(fallback);
|
||||
@ -155,7 +155,7 @@ public class ServerListPingHandler {
|
||||
.orElse("");
|
||||
List<String> serversToTry = server.getConfiguration().getForcedHosts().getOrDefault(
|
||||
virtualHostStr, server.getConfiguration().getAttemptConnectionOrder());
|
||||
return attemptPingPassthrough(connection, passthroughMode, serversToTry, shownVersion);
|
||||
return attemptPingPassthrough(connection, passthroughMode, serversToTry, shownVersion, virtualHostStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,8 +23,6 @@ import com.velocitypowered.proxy.util.except.QuietDecoderException;
|
||||
import it.unimi.dsi.fastutil.Pair;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.Key;
|
||||
@ -111,42 +109,6 @@ public enum EncryptionUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a signature for input data.
|
||||
*
|
||||
* @param algorithm the signature algorithm
|
||||
* @param base the private key to sign with
|
||||
* @param toSign the byte array(s) of data to sign
|
||||
* @return the generated signature
|
||||
*/
|
||||
public static byte[] generateSignature(String algorithm, PrivateKey base, byte[]... toSign) {
|
||||
Preconditions.checkArgument(toSign.length > 0);
|
||||
try {
|
||||
Signature construct = Signature.getInstance(algorithm);
|
||||
construct.initSign(base);
|
||||
for (byte[] bytes : toSign) {
|
||||
construct.update(bytes);
|
||||
}
|
||||
return construct.sign();
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new IllegalArgumentException("Invalid signature parameters");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a long array as Big-endian byte array.
|
||||
*
|
||||
* @param bits the long (array) of numbers to encode
|
||||
* @return the encoded bytes
|
||||
*/
|
||||
public static byte[] longToBigEndianByteArray(long... bits) {
|
||||
ByteBuffer ret = ByteBuffer.allocate(8 * bits.length).order(ByteOrder.BIG_ENDIAN);
|
||||
for (long put : bits) {
|
||||
ret.putLong(put);
|
||||
}
|
||||
return ret.array();
|
||||
}
|
||||
|
||||
public static String encodeUrlEncoded(byte[] data) {
|
||||
return MIME_SPECIAL_ENCODER.encodeToString(data);
|
||||
}
|
||||
@ -155,22 +117,6 @@ public enum EncryptionUtils {
|
||||
return Base64.getMimeDecoder().decode(toParse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a cer-encoded RSA key into its key bytes.
|
||||
*
|
||||
* @param toParse the cer-encoded key String
|
||||
* @param descriptors the type of key
|
||||
* @return the parsed key bytes
|
||||
*/
|
||||
public static byte[] parsePemEncoded(String toParse, Pair<String, String> descriptors) {
|
||||
int startIdx = toParse.indexOf(descriptors.first());
|
||||
Preconditions.checkArgument(startIdx >= 0);
|
||||
int firstLen = descriptors.first().length();
|
||||
int endIdx = toParse.indexOf(descriptors.second(), firstLen + startIdx) + 1;
|
||||
Preconditions.checkArgument(endIdx > 0);
|
||||
return decodeUrlEncoded(toParse.substring(startIdx + firstLen, endIdx));
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes an RSA key as String cer format.
|
||||
*
|
||||
|
||||
@ -350,8 +350,9 @@ public class VelocityEventManager implements EventManager {
|
||||
asyncType = AsyncType.ALWAYS;
|
||||
}
|
||||
|
||||
// The default value of 0 will fall back to PostOrder, the default PostOrder (NORMAL) is also 0
|
||||
final short order;
|
||||
if (subscribe.order() == PostOrder.CUSTOM) {
|
||||
if (subscribe.priority() != 0) {
|
||||
order = subscribe.priority();
|
||||
} else {
|
||||
order = (short) POST_ORDER_MAP.get(subscribe.order());
|
||||
|
||||
@ -18,6 +18,8 @@
|
||||
package com.velocitypowered.proxy.network;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.velocitypowered.api.event.proxy.ListenerBoundEvent;
|
||||
import com.velocitypowered.api.event.proxy.ListenerCloseEvent;
|
||||
import com.velocitypowered.api.network.ListenerType;
|
||||
@ -28,14 +30,17 @@ import com.velocitypowered.proxy.protocol.netty.GameSpyQueryHandler;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.WriteBufferWaterMark;
|
||||
import io.netty.channel.unix.UnixChannelOption;
|
||||
import io.netty.util.concurrent.GlobalEventExecutor;
|
||||
import io.netty.util.concurrent.MultithreadEventExecutorGroup;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.http.HttpClient;
|
||||
import java.util.HashMap;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@ -49,7 +54,7 @@ public final class ConnectionManager {
|
||||
private static final WriteBufferWaterMark SERVER_WRITE_MARK = new WriteBufferWaterMark(1 << 20,
|
||||
1 << 21);
|
||||
private static final Logger LOGGER = LogManager.getLogger(ConnectionManager.class);
|
||||
private final Map<InetSocketAddress, Endpoint> endpoints = new HashMap<>();
|
||||
private final Multimap<InetSocketAddress, Endpoint> endpoints = HashMultimap.create();
|
||||
private final TransportType transportType;
|
||||
private final EventLoopGroup bossGroup;
|
||||
private final EventLoopGroup workerGroup;
|
||||
@ -93,7 +98,6 @@ public final class ConnectionManager {
|
||||
public void bind(final InetSocketAddress address) {
|
||||
final ServerBootstrap bootstrap = new ServerBootstrap()
|
||||
.channelFactory(this.transportType.serverSocketChannelFactory)
|
||||
.group(this.bossGroup, this.workerGroup)
|
||||
.childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, SERVER_WRITE_MARK)
|
||||
.childHandler(this.serverChannelInitializer.get())
|
||||
.childOption(ChannelOption.TCP_NODELAY, true)
|
||||
@ -104,26 +108,50 @@ public final class ConnectionManager {
|
||||
bootstrap.option(ChannelOption.TCP_FASTOPEN, 3);
|
||||
}
|
||||
|
||||
bootstrap.bind()
|
||||
.addListener((ChannelFutureListener) future -> {
|
||||
final Channel channel = future.channel();
|
||||
if (future.isSuccess()) {
|
||||
this.endpoints.put(address, new Endpoint(channel, ListenerType.MINECRAFT));
|
||||
|
||||
// Warn people with console access that HAProxy is in use, see PR: #1436
|
||||
if (this.server.getConfiguration().isProxyProtocol()) {
|
||||
LOGGER.warn("Using HAProxy and listening on {}, please ensure this listener is adequately firewalled.", channel.localAddress());
|
||||
if (server.getConfiguration().isEnableReusePort()) {
|
||||
// We don't need a boss group, since each worker will bind to the socket
|
||||
bootstrap.option(UnixChannelOption.SO_REUSEPORT, true)
|
||||
.group(this.workerGroup);
|
||||
} else {
|
||||
bootstrap.group(this.bossGroup, this.workerGroup);
|
||||
}
|
||||
|
||||
final int binds = server.getConfiguration().isEnableReusePort()
|
||||
? ((MultithreadEventExecutorGroup) this.workerGroup).executorCount() : 1;
|
||||
|
||||
for (int bind = 0; bind < binds; bind++) {
|
||||
// Wait for each bind to open. If we encounter any errors, don't try to bind again.
|
||||
int finalBind = bind;
|
||||
ChannelFuture f = bootstrap.bind()
|
||||
.addListener((ChannelFutureListener) future -> {
|
||||
final Channel channel = future.channel();
|
||||
if (future.isSuccess()) {
|
||||
this.endpoints.put(address, new Endpoint(channel, ListenerType.MINECRAFT));
|
||||
|
||||
LOGGER.info("Listening on {}", channel.localAddress());
|
||||
|
||||
if (finalBind == 0) {
|
||||
// Warn people with console access that HAProxy is in use, see PR: #1436
|
||||
if (this.server.getConfiguration().isProxyProtocol()) {
|
||||
LOGGER.warn(
|
||||
"Using HAProxy and listening on {}, please ensure this listener is adequately firewalled.",
|
||||
channel.localAddress());
|
||||
}
|
||||
|
||||
// Fire the proxy bound event after the socket is bound
|
||||
server.getEventManager().fireAndForget(
|
||||
new ListenerBoundEvent(address, ListenerType.MINECRAFT));
|
||||
}
|
||||
} else {
|
||||
LOGGER.error("Can't bind to {}", address, future.cause());
|
||||
}
|
||||
});
|
||||
f.syncUninterruptibly();
|
||||
|
||||
LOGGER.info("Listening on {}", channel.localAddress());
|
||||
|
||||
// Fire the proxy bound event after the socket is bound
|
||||
server.getEventManager().fireAndForget(
|
||||
new ListenerBoundEvent(address, ListenerType.MINECRAFT));
|
||||
} else {
|
||||
LOGGER.error("Can't bind to {}", address, future.cause());
|
||||
}
|
||||
});
|
||||
if (!f.isSuccess()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -181,17 +209,20 @@ public final class ConnectionManager {
|
||||
* @param oldBind the endpoint to close
|
||||
*/
|
||||
public void close(InetSocketAddress oldBind) {
|
||||
Endpoint endpoint = endpoints.remove(oldBind);
|
||||
Collection<Endpoint> endpoints = this.endpoints.removeAll(oldBind);
|
||||
Preconditions.checkState(!endpoints.isEmpty(), "Endpoint was not registered");
|
||||
|
||||
ListenerType type = endpoints.iterator().next().getType();
|
||||
|
||||
// Fire proxy close event to notify plugins of socket close. We block since plugins
|
||||
// should have a chance to be notified before the server stops accepting connections.
|
||||
server.getEventManager().fire(new ListenerCloseEvent(oldBind, endpoint.getType())).join();
|
||||
server.getEventManager().fire(new ListenerCloseEvent(oldBind, type)).join();
|
||||
|
||||
Channel serverChannel = endpoint.getChannel();
|
||||
|
||||
Preconditions.checkState(serverChannel != null, "Endpoint %s not registered", oldBind);
|
||||
LOGGER.info("Closing endpoint {}", serverChannel.localAddress());
|
||||
serverChannel.close().syncUninterruptibly();
|
||||
for (Endpoint endpoint : endpoints) {
|
||||
Channel serverChannel = endpoint.getChannel();
|
||||
LOGGER.info("Closing endpoint {}", serverChannel.localAddress());
|
||||
serverChannel.close().syncUninterruptibly();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -200,24 +231,28 @@ public final class ConnectionManager {
|
||||
* @param interrupt should closing forward interruptions
|
||||
*/
|
||||
public void closeEndpoints(boolean interrupt) {
|
||||
for (final Map.Entry<InetSocketAddress, Endpoint> entry : this.endpoints.entrySet()) {
|
||||
for (final Map.Entry<InetSocketAddress, Collection<Endpoint>> entry : this.endpoints.asMap()
|
||||
.entrySet()) {
|
||||
final InetSocketAddress address = entry.getKey();
|
||||
final Endpoint endpoint = entry.getValue();
|
||||
final Collection<Endpoint> endpoints = entry.getValue();
|
||||
ListenerType type = endpoints.iterator().next().getType();
|
||||
|
||||
// Fire proxy close event to notify plugins of socket close. We block since plugins
|
||||
// should have a chance to be notified before the server stops accepting connections.
|
||||
server.getEventManager().fire(new ListenerCloseEvent(address, endpoint.getType())).join();
|
||||
server.getEventManager().fire(new ListenerCloseEvent(address, type)).join();
|
||||
|
||||
LOGGER.info("Closing endpoint {}", address);
|
||||
if (interrupt) {
|
||||
try {
|
||||
endpoint.getChannel().close().sync();
|
||||
} catch (final InterruptedException e) {
|
||||
LOGGER.info("Interrupted whilst closing endpoint", e);
|
||||
Thread.currentThread().interrupt();
|
||||
for (Endpoint endpoint : endpoints) {
|
||||
LOGGER.info("Closing endpoint {}", address);
|
||||
if (interrupt) {
|
||||
try {
|
||||
endpoint.getChannel().close().sync();
|
||||
} catch (final InterruptedException e) {
|
||||
LOGGER.info("Interrupted whilst closing endpoint", e);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
} else {
|
||||
endpoint.getChannel().close().syncUninterruptibly();
|
||||
}
|
||||
} else {
|
||||
endpoint.getChannel().close().syncUninterruptibly();
|
||||
}
|
||||
}
|
||||
this.endpoints.clear();
|
||||
|
||||
@ -39,6 +39,7 @@ 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_21_2;
|
||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_4;
|
||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_5;
|
||||
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;
|
||||
@ -377,7 +378,8 @@ public enum StateRegistry {
|
||||
map(0x0D, MINECRAFT_1_17, false),
|
||||
map(0x0A, MINECRAFT_1_19, false),
|
||||
map(0x0B, MINECRAFT_1_19_4, false),
|
||||
map(0x0A, MINECRAFT_1_20_2, false));
|
||||
map(0x0A, MINECRAFT_1_20_2, false),
|
||||
map(0x09, MINECRAFT_1_21_5, false));
|
||||
clientbound.register(
|
||||
LegacyChatPacket.class,
|
||||
LegacyChatPacket::new,
|
||||
@ -398,7 +400,8 @@ public enum StateRegistry {
|
||||
map(0x0E, MINECRAFT_1_19, false),
|
||||
map(0x0D, MINECRAFT_1_19_3, false),
|
||||
map(0x0F, MINECRAFT_1_19_4, false),
|
||||
map(0x10, MINECRAFT_1_20_2, false));
|
||||
map(0x10, MINECRAFT_1_20_2, false),
|
||||
map(0x0F, MINECRAFT_1_21_5, false));
|
||||
clientbound.register(
|
||||
AvailableCommandsPacket.class,
|
||||
AvailableCommandsPacket::new,
|
||||
@ -410,10 +413,12 @@ public enum StateRegistry {
|
||||
map(0x0F, MINECRAFT_1_19, false),
|
||||
map(0x0E, MINECRAFT_1_19_3, false),
|
||||
map(0x10, MINECRAFT_1_19_4, false),
|
||||
map(0x11, MINECRAFT_1_20_2, false));
|
||||
map(0x11, MINECRAFT_1_20_2, false),
|
||||
map(0x10, MINECRAFT_1_21_5, false));
|
||||
clientbound.register(
|
||||
ClientboundCookieRequestPacket.class, ClientboundCookieRequestPacket::new,
|
||||
map(0x16, MINECRAFT_1_20_5, false));
|
||||
map(0x16, MINECRAFT_1_20_5, false),
|
||||
map(0x15, MINECRAFT_1_21_5, false));
|
||||
clientbound.register(
|
||||
PluginMessagePacket.class,
|
||||
PluginMessagePacket::new,
|
||||
@ -430,7 +435,8 @@ public enum StateRegistry {
|
||||
map(0x15, MINECRAFT_1_19_3, false),
|
||||
map(0x17, MINECRAFT_1_19_4, false),
|
||||
map(0x18, MINECRAFT_1_20_2, false),
|
||||
map(0x19, MINECRAFT_1_20_5, false));
|
||||
map(0x19, MINECRAFT_1_20_5, false),
|
||||
map(0x18, MINECRAFT_1_21_5, false));
|
||||
clientbound.register(
|
||||
DisconnectPacket.class,
|
||||
() -> new DisconnectPacket(this),
|
||||
@ -447,7 +453,8 @@ public enum StateRegistry {
|
||||
map(0x17, MINECRAFT_1_19_3, false),
|
||||
map(0x1A, MINECRAFT_1_19_4, false),
|
||||
map(0x1B, MINECRAFT_1_20_2, false),
|
||||
map(0x1D, MINECRAFT_1_20_5, false));
|
||||
map(0x1D, MINECRAFT_1_20_5, false),
|
||||
map(0x1C, MINECRAFT_1_21_5, false));
|
||||
clientbound.register(
|
||||
KeepAlivePacket.class,
|
||||
KeepAlivePacket::new,
|
||||
@ -465,7 +472,8 @@ public enum StateRegistry {
|
||||
map(0x23, MINECRAFT_1_19_4, false),
|
||||
map(0x24, MINECRAFT_1_20_2, false),
|
||||
map(0x26, MINECRAFT_1_20_5, false),
|
||||
map(0x27, MINECRAFT_1_21_2, false));
|
||||
map(0x27, MINECRAFT_1_21_2, false),
|
||||
map(0x26, MINECRAFT_1_21_5, false));
|
||||
clientbound.register(
|
||||
JoinGamePacket.class,
|
||||
JoinGamePacket::new,
|
||||
@ -483,7 +491,8 @@ public enum StateRegistry {
|
||||
map(0x28, MINECRAFT_1_19_4, false),
|
||||
map(0x29, MINECRAFT_1_20_2, false),
|
||||
map(0x2B, MINECRAFT_1_20_5, false),
|
||||
map(0x2C, MINECRAFT_1_21_2, false));
|
||||
map(0x2C, MINECRAFT_1_21_2, false),
|
||||
map(0x2B, MINECRAFT_1_21_5, false));
|
||||
clientbound.register(
|
||||
RespawnPacket.class,
|
||||
RespawnPacket::new,
|
||||
@ -504,13 +513,15 @@ public enum StateRegistry {
|
||||
map(0x43, MINECRAFT_1_20_2, true),
|
||||
map(0x45, MINECRAFT_1_20_3, true),
|
||||
map(0x47, MINECRAFT_1_20_5, true),
|
||||
map(0x4C, MINECRAFT_1_21_2, true));
|
||||
map(0x4C, MINECRAFT_1_21_2, true),
|
||||
map(0x4B, MINECRAFT_1_21_5, true));
|
||||
clientbound.register(
|
||||
RemoveResourcePackPacket.class,
|
||||
RemoveResourcePackPacket::new,
|
||||
map(0x43, MINECRAFT_1_20_3, false),
|
||||
map(0x45, MINECRAFT_1_20_5, false),
|
||||
map(0x4A, MINECRAFT_1_21_2, false));
|
||||
map(0x4A, MINECRAFT_1_21_2, false),
|
||||
map(0x49, MINECRAFT_1_21_5, false));
|
||||
clientbound.register(
|
||||
ResourcePackRequestPacket.class,
|
||||
ResourcePackRequestPacket::new,
|
||||
@ -531,7 +542,8 @@ public enum StateRegistry {
|
||||
map(0x42, MINECRAFT_1_20_2, false),
|
||||
map(0x44, MINECRAFT_1_20_3, false),
|
||||
map(0x46, MINECRAFT_1_20_5, false),
|
||||
map(0x4B, MINECRAFT_1_21_2, false));
|
||||
map(0x4B, MINECRAFT_1_21_2, false),
|
||||
map(0x4A, MINECRAFT_1_21_5, false));
|
||||
clientbound.register(
|
||||
HeaderAndFooterPacket.class,
|
||||
HeaderAndFooterPacket::new,
|
||||
@ -553,7 +565,8 @@ public enum StateRegistry {
|
||||
map(0x68, MINECRAFT_1_20_2, true),
|
||||
map(0x6A, MINECRAFT_1_20_3, true),
|
||||
map(0x6D, MINECRAFT_1_20_5, true),
|
||||
map(0x74, MINECRAFT_1_21_2, true));
|
||||
map(0x74, MINECRAFT_1_21_2, true),
|
||||
map(0x73, MINECRAFT_1_21_5, true));
|
||||
clientbound.register(
|
||||
LegacyTitlePacket.class,
|
||||
LegacyTitlePacket::new,
|
||||
@ -574,7 +587,8 @@ public enum StateRegistry {
|
||||
map(0x5F, MINECRAFT_1_20_2, true),
|
||||
map(0x61, MINECRAFT_1_20_3, true),
|
||||
map(0x63, MINECRAFT_1_20_5, true),
|
||||
map(0x6A, MINECRAFT_1_21_2, true));
|
||||
map(0x6A, MINECRAFT_1_21_2, true),
|
||||
map(0x69, MINECRAFT_1_21_5, true));
|
||||
clientbound.register(
|
||||
TitleTextPacket.class,
|
||||
TitleTextPacket::new,
|
||||
@ -586,7 +600,8 @@ public enum StateRegistry {
|
||||
map(0x61, MINECRAFT_1_20_2, true),
|
||||
map(0x63, MINECRAFT_1_20_3, true),
|
||||
map(0x65, MINECRAFT_1_20_5, true),
|
||||
map(0x6C, MINECRAFT_1_21_2, true));
|
||||
map(0x6C, MINECRAFT_1_21_2, true),
|
||||
map(0x6B, MINECRAFT_1_21_5, true));
|
||||
clientbound.register(
|
||||
TitleActionbarPacket.class,
|
||||
TitleActionbarPacket::new,
|
||||
@ -598,7 +613,8 @@ public enum StateRegistry {
|
||||
map(0x48, MINECRAFT_1_20_2, true),
|
||||
map(0x4A, MINECRAFT_1_20_3, true),
|
||||
map(0x4C, MINECRAFT_1_20_5, true),
|
||||
map(0x51, MINECRAFT_1_21_2, true));
|
||||
map(0x51, MINECRAFT_1_21_2, true),
|
||||
map(0x50, MINECRAFT_1_21_5, true));
|
||||
clientbound.register(
|
||||
TitleTimesPacket.class,
|
||||
TitleTimesPacket::new,
|
||||
@ -610,7 +626,8 @@ public enum StateRegistry {
|
||||
map(0x62, MINECRAFT_1_20_2, true),
|
||||
map(0x64, MINECRAFT_1_20_3, true),
|
||||
map(0x66, MINECRAFT_1_20_5, true),
|
||||
map(0x6D, MINECRAFT_1_21_2, true));
|
||||
map(0x6D, MINECRAFT_1_21_2, true),
|
||||
map(0x6C, MINECRAFT_1_21_5, true));
|
||||
clientbound.register(
|
||||
TitleClearPacket.class,
|
||||
TitleClearPacket::new,
|
||||
@ -618,7 +635,8 @@ public enum StateRegistry {
|
||||
map(0x0D, MINECRAFT_1_19, true),
|
||||
map(0x0C, MINECRAFT_1_19_3, true),
|
||||
map(0x0E, MINECRAFT_1_19_4, true),
|
||||
map(0x0F, MINECRAFT_1_20_2, true));
|
||||
map(0x0F, MINECRAFT_1_20_2, true),
|
||||
map(0x0E, MINECRAFT_1_21_5, true));
|
||||
clientbound.register(
|
||||
LegacyPlayerListItemPacket.class,
|
||||
LegacyPlayerListItemPacket::new,
|
||||
@ -638,7 +656,8 @@ public enum StateRegistry {
|
||||
map(0x39, MINECRAFT_1_19_4, false),
|
||||
map(0x3B, MINECRAFT_1_20_2, false),
|
||||
map(0x3D, MINECRAFT_1_20_5, false),
|
||||
map(0x3F, MINECRAFT_1_21_2, false));
|
||||
map(0x3F, MINECRAFT_1_21_2, false),
|
||||
map(0x3E, MINECRAFT_1_21_5, false));
|
||||
clientbound.register(
|
||||
UpsertPlayerInfoPacket.class,
|
||||
UpsertPlayerInfoPacket::new,
|
||||
@ -646,11 +665,13 @@ public enum StateRegistry {
|
||||
map(0x3A, MINECRAFT_1_19_4, false),
|
||||
map(0x3C, MINECRAFT_1_20_2, false),
|
||||
map(0x3E, MINECRAFT_1_20_5, false),
|
||||
map(0x40, MINECRAFT_1_21_2, false));
|
||||
map(0x40, MINECRAFT_1_21_2, false),
|
||||
map(0x3F, MINECRAFT_1_21_5, false));
|
||||
clientbound.register(
|
||||
ClientboundStoreCookiePacket.class, ClientboundStoreCookiePacket::new,
|
||||
map(0x6B, MINECRAFT_1_20_5, false),
|
||||
map(0x72, MINECRAFT_1_21_2, false));
|
||||
map(0x72, MINECRAFT_1_21_2, false),
|
||||
map(0x71, MINECRAFT_1_21_5, false));
|
||||
clientbound.register(
|
||||
SystemChatPacket.class,
|
||||
SystemChatPacket::new,
|
||||
@ -661,7 +682,8 @@ public enum StateRegistry {
|
||||
map(0x67, MINECRAFT_1_20_2, true),
|
||||
map(0x69, MINECRAFT_1_20_3, true),
|
||||
map(0x6C, MINECRAFT_1_20_5, true),
|
||||
map(0x73, MINECRAFT_1_21_2, true));
|
||||
map(0x73, MINECRAFT_1_21_2, true),
|
||||
map(0x72, MINECRAFT_1_21_5, true));
|
||||
clientbound.register(
|
||||
PlayerChatCompletionPacket.class,
|
||||
PlayerChatCompletionPacket::new,
|
||||
@ -669,7 +691,8 @@ public enum StateRegistry {
|
||||
map(0x14, MINECRAFT_1_19_3, true),
|
||||
map(0x16, MINECRAFT_1_19_4, true),
|
||||
map(0x17, MINECRAFT_1_20_2, true),
|
||||
map(0x18, MINECRAFT_1_20_5, true));
|
||||
map(0x18, MINECRAFT_1_20_5, true),
|
||||
map(0x17, MINECRAFT_1_21_5, true));
|
||||
clientbound.register(
|
||||
ServerDataPacket.class,
|
||||
ServerDataPacket::new,
|
||||
@ -680,14 +703,16 @@ public enum StateRegistry {
|
||||
map(0x47, MINECRAFT_1_20_2, false),
|
||||
map(0x49, MINECRAFT_1_20_3, false),
|
||||
map(0x4B, MINECRAFT_1_20_5, false),
|
||||
map(0x50, MINECRAFT_1_21_2, false));
|
||||
map(0x50, MINECRAFT_1_21_2, false),
|
||||
map(0x4F, MINECRAFT_1_21_5, false));
|
||||
clientbound.register(
|
||||
StartUpdatePacket.class,
|
||||
() -> StartUpdatePacket.INSTANCE,
|
||||
map(0x65, MINECRAFT_1_20_2, false),
|
||||
map(0x67, MINECRAFT_1_20_3, false),
|
||||
map(0x69, MINECRAFT_1_20_5, false),
|
||||
map(0x70, MINECRAFT_1_21_2, false));
|
||||
map(0x70, MINECRAFT_1_21_2, false),
|
||||
map(0x6F, MINECRAFT_1_21_5, false));
|
||||
clientbound.register(
|
||||
BundleDelimiterPacket.class,
|
||||
() -> BundleDelimiterPacket.INSTANCE,
|
||||
|
||||
@ -52,6 +52,9 @@ public class MinecraftCompressDecoder extends MessageToMessageDecoder<ByteBuf> {
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||
int claimedUncompressedSize = ProtocolUtils.readVarInt(in);
|
||||
if (claimedUncompressedSize == 0) {
|
||||
int actualUncompressedSize = in.readableBytes();
|
||||
checkFrame(actualUncompressedSize < threshold, "Actual uncompressed size %s is greater than"
|
||||
+ " threshold %s", actualUncompressedSize, threshold);
|
||||
// This message is not compressed.
|
||||
out.add(in.retain());
|
||||
return;
|
||||
|
||||
@ -58,6 +58,13 @@ public class ArgumentIdentifier {
|
||||
this.versionById = ImmutableMap.copyOf(temp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ArgumentIdentifier{" +
|
||||
"identifier='" + identifier + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
public String getIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
@ -22,6 +22,7 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_3;
|
||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_4;
|
||||
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_5;
|
||||
import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.id;
|
||||
import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.mapSet;
|
||||
import static com.velocitypowered.proxy.protocol.packet.brigadier.DoubleArgumentPropertySerializer.DOUBLE;
|
||||
@ -254,17 +255,20 @@ public class ArgumentPropertyRegistry {
|
||||
register(id("minecraft:resource_key", mapSet(MINECRAFT_1_20_5, 46), mapSet(MINECRAFT_1_20_3, 45), mapSet(MINECRAFT_1_19_3, 44)),
|
||||
RegistryKeyArgumentList.ResourceKey.class,
|
||||
RegistryKeyArgumentList.ResourceKey.Serializer.REGISTRY);
|
||||
register(id("minecraft:resource_selector", mapSet(MINECRAFT_1_21_5, 47)),
|
||||
RegistryKeyArgumentList.ResourceSelector.class,
|
||||
RegistryKeyArgumentList.ResourceSelector.Serializer.REGISTRY);
|
||||
|
||||
empty(id("minecraft:template_mirror", mapSet(MINECRAFT_1_20_5, 47), mapSet(MINECRAFT_1_20_3, 46), mapSet(MINECRAFT_1_19, 45))); // 1.19
|
||||
empty(id("minecraft:template_rotation", mapSet(MINECRAFT_1_20_5, 48), mapSet(MINECRAFT_1_20_3, 47), mapSet(MINECRAFT_1_19, 46))); // 1.19
|
||||
empty(id("minecraft:heightmap", mapSet(MINECRAFT_1_20_3, 49), mapSet(MINECRAFT_1_19_4, 47))); // 1.19.4
|
||||
empty(id("minecraft:template_mirror", mapSet(MINECRAFT_1_21_5, 48), mapSet(MINECRAFT_1_20_5, 47), mapSet(MINECRAFT_1_20_3, 46), mapSet(MINECRAFT_1_19, 45))); // 1.19
|
||||
empty(id("minecraft:template_rotation", mapSet(MINECRAFT_1_21_5, 49), mapSet(MINECRAFT_1_20_5, 48), mapSet(MINECRAFT_1_20_3, 47), mapSet(MINECRAFT_1_19, 46))); // 1.19
|
||||
empty(id("minecraft:heightmap", mapSet(MINECRAFT_1_21_5, 50), mapSet(MINECRAFT_1_20_3, 49), mapSet(MINECRAFT_1_19_4, 47))); // 1.19.4
|
||||
|
||||
empty(id("minecraft:uuid", mapSet(MINECRAFT_1_20_5, 53), mapSet(MINECRAFT_1_20_3, 48), mapSet(MINECRAFT_1_19_4, 48),
|
||||
empty(id("minecraft:uuid", mapSet(MINECRAFT_1_21_5, 54),mapSet(MINECRAFT_1_20_5, 53), mapSet(MINECRAFT_1_20_3, 48), mapSet(MINECRAFT_1_19_4, 48),
|
||||
mapSet(MINECRAFT_1_19, 47))); // added in 1.16
|
||||
|
||||
empty(id("minecraft:loot_table", mapSet(MINECRAFT_1_20_5, 50)));
|
||||
empty(id("minecraft:loot_predicate", mapSet(MINECRAFT_1_20_5, 51)));
|
||||
empty(id("minecraft:loot_modifier", mapSet(MINECRAFT_1_20_5, 52)));
|
||||
empty(id("minecraft:loot_table", mapSet(MINECRAFT_1_21_5, 51), mapSet(MINECRAFT_1_20_5, 50)));
|
||||
empty(id("minecraft:loot_predicate", mapSet(MINECRAFT_1_21_5, 52), mapSet(MINECRAFT_1_20_5, 51)));
|
||||
empty(id("minecraft:loot_modifier", mapSet(MINECRAFT_1_21_5, 53), mapSet(MINECRAFT_1_20_5, 52)));
|
||||
|
||||
// Crossstitch support
|
||||
register(id("crossstitch:mod_argument", mapSet(MINECRAFT_1_19, -256)), ModArgumentProperty.class, MOD);
|
||||
|
||||
@ -67,23 +67,23 @@ public final class RegistryKeyArgumentList {
|
||||
}
|
||||
}
|
||||
|
||||
public static class Resource extends RegistryKeyArgument {
|
||||
public static class ResourceSelector extends RegistryKeyArgument {
|
||||
|
||||
public Resource(String identifier) {
|
||||
public ResourceSelector(String identifier) {
|
||||
super(identifier);
|
||||
}
|
||||
|
||||
public static class Serializer implements ArgumentPropertySerializer<Resource> {
|
||||
public static class Serializer implements ArgumentPropertySerializer<ResourceSelector> {
|
||||
|
||||
static final Resource.Serializer REGISTRY = new Resource.Serializer();
|
||||
static final ResourceSelector.Serializer REGISTRY = new ResourceSelector.Serializer();
|
||||
|
||||
@Override
|
||||
public Resource deserialize(ByteBuf buf, ProtocolVersion protocolVersion) {
|
||||
return new Resource(ProtocolUtils.readString(buf));
|
||||
public ResourceSelector deserialize(ByteBuf buf, ProtocolVersion protocolVersion) {
|
||||
return new ResourceSelector(ProtocolUtils.readString(buf));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(Resource object, ByteBuf buf, ProtocolVersion protocolVersion) {
|
||||
public void serialize(ResourceSelector object, ByteBuf buf, ProtocolVersion protocolVersion) {
|
||||
ProtocolUtils.writeString(buf, object.getIdentifier());
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,13 +32,15 @@ import java.util.function.Function;
|
||||
* A precisely ordered queue which allows for outside entries into the ordered queue through
|
||||
* piggybacking timestamps.
|
||||
*/
|
||||
public class ChatQueue {
|
||||
public class ChatQueue implements AutoCloseable {
|
||||
|
||||
private final Object internalLock = new Object();
|
||||
private final ConnectedPlayer player;
|
||||
private final ChatState chatState = new ChatState();
|
||||
private CompletableFuture<Void> head = CompletableFuture.completedFuture(null);
|
||||
|
||||
private volatile boolean closed;
|
||||
|
||||
/**
|
||||
* Instantiates a {@link ChatQueue} for a specific {@link ConnectedPlayer}.
|
||||
*
|
||||
@ -50,8 +52,14 @@ public class ChatQueue {
|
||||
|
||||
private void queueTask(Task task) {
|
||||
synchronized (internalLock) {
|
||||
if (closed) {
|
||||
throw new IllegalStateException("ChatQueue has already been closed");
|
||||
}
|
||||
MinecraftConnection smc = player.ensureAndGetCurrentServer().ensureConnected();
|
||||
head = head.thenCompose(v -> {
|
||||
if (closed) {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
try {
|
||||
return task.update(chatState, smc).exceptionally(ignored -> null);
|
||||
} catch (Throwable ignored) {
|
||||
@ -102,9 +110,9 @@ public class ChatQueue {
|
||||
});
|
||||
}
|
||||
|
||||
private static <T extends MinecraftPacket> CompletableFuture<Void> writePacket(T packet, MinecraftConnection smc) {
|
||||
private <T extends MinecraftPacket> CompletableFuture<Void> writePacket(T packet, MinecraftConnection smc) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
if (!smc.isClosed()) {
|
||||
if (!closed && !smc.isClosed()) {
|
||||
ChannelFuture future = smc.write(packet);
|
||||
if (future != null) {
|
||||
future.awaitUninterruptibly();
|
||||
@ -113,6 +121,11 @@ public class ChatQueue {
|
||||
}, smc.eventLoop());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
closed = true;
|
||||
}
|
||||
|
||||
private interface Task {
|
||||
CompletableFuture<Void> update(ChatState chatState, MinecraftConnection smc);
|
||||
}
|
||||
@ -174,7 +187,7 @@ public class ChatQueue {
|
||||
}
|
||||
|
||||
public LastSeenMessages createLastSeen() {
|
||||
return new LastSeenMessages(0, lastSeenMessages);
|
||||
return new LastSeenMessages(0, lastSeenMessages, (byte) 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,7 +43,6 @@ import net.kyori.adventure.nbt.LongBinaryTag;
|
||||
import net.kyori.adventure.nbt.ShortBinaryTag;
|
||||
import net.kyori.adventure.nbt.StringBinaryTag;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
@ -106,16 +105,14 @@ public class ComponentHolder {
|
||||
public BinaryTag getBinaryTag() {
|
||||
if (binaryTag == null) {
|
||||
// TODO: replace this with adventure-text-serializer-nbt
|
||||
binaryTag = serialize(GsonComponentSerializer.gson().serializeToTree(getComponent()));
|
||||
binaryTag = serialize(ProtocolUtils.getJsonChatSerializer(version).serializeToTree(getComponent()));
|
||||
}
|
||||
return binaryTag;
|
||||
}
|
||||
|
||||
public static BinaryTag serialize(JsonElement json) {
|
||||
if (json instanceof JsonPrimitive) {
|
||||
JsonPrimitive jsonPrimitive = (JsonPrimitive) json;
|
||||
|
||||
if (jsonPrimitive.isNumber()) {
|
||||
if (json instanceof JsonPrimitive jsonPrimitive) {
|
||||
if (jsonPrimitive.isNumber()) {
|
||||
Number number = json.getAsNumber();
|
||||
|
||||
if (number instanceof Byte) {
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
|
||||
package com.velocitypowered.proxy.protocol.packet.chat;
|
||||
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import java.util.Arrays;
|
||||
@ -26,30 +27,38 @@ public class LastSeenMessages {
|
||||
|
||||
public static final int WINDOW_SIZE = 20;
|
||||
private static final int DIV_FLOOR = -Math.floorDiv(-WINDOW_SIZE, 8);
|
||||
private int offset;
|
||||
private BitSet acknowledged;
|
||||
private final int offset;
|
||||
private final BitSet acknowledged;
|
||||
private byte checksum;
|
||||
|
||||
public LastSeenMessages() {
|
||||
this.offset = 0;
|
||||
this.acknowledged = new BitSet();
|
||||
this(0, new BitSet(), (byte) 0);
|
||||
}
|
||||
|
||||
public LastSeenMessages(int offset, BitSet acknowledged) {
|
||||
public LastSeenMessages(int offset, BitSet acknowledged, byte checksum) {
|
||||
this.offset = offset;
|
||||
this.acknowledged = acknowledged;
|
||||
this.checksum = checksum;
|
||||
}
|
||||
|
||||
public LastSeenMessages(ByteBuf buf) {
|
||||
public LastSeenMessages(ByteBuf buf, ProtocolVersion protocolVersion) {
|
||||
this.offset = ProtocolUtils.readVarInt(buf);
|
||||
|
||||
byte[] bytes = new byte[DIV_FLOOR];
|
||||
buf.readBytes(bytes);
|
||||
this.acknowledged = BitSet.valueOf(bytes);
|
||||
|
||||
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_21_5)) {
|
||||
this.checksum = buf.readByte();
|
||||
}
|
||||
}
|
||||
|
||||
public void encode(ByteBuf buf) {
|
||||
public void encode(ByteBuf buf, ProtocolVersion protocolVersion) {
|
||||
ProtocolUtils.writeVarInt(buf, offset);
|
||||
buf.writeBytes(Arrays.copyOf(acknowledged.toByteArray(), DIV_FLOOR));
|
||||
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_21_5)) {
|
||||
buf.writeByte(this.checksum);
|
||||
}
|
||||
}
|
||||
|
||||
public int getOffset() {
|
||||
@ -61,14 +70,15 @@ public class LastSeenMessages {
|
||||
}
|
||||
|
||||
public LastSeenMessages offset(final int offset) {
|
||||
return new LastSeenMessages(this.offset + offset, acknowledged);
|
||||
return new LastSeenMessages(this.offset + offset, acknowledged, checksum);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LastSeenMessages{" +
|
||||
"offset=" + offset +
|
||||
", acknowledged=" + acknowledged +
|
||||
'}';
|
||||
"offset=" + offset +
|
||||
", acknowledged=" + acknowledged +
|
||||
", checksum=" + checksum +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,7 +91,8 @@ public class LegacyChatPacket implements MinecraftPacket {
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
|
||||
message = ProtocolUtils.readString(buf);
|
||||
message = ProtocolUtils.readString(buf, direction == ProtocolUtils.Direction.CLIENTBOUND
|
||||
? 262144 : version.noLessThan(ProtocolVersion.MINECRAFT_1_11) ? 256 : 100);
|
||||
if (direction == ProtocolUtils.Direction.CLIENTBOUND
|
||||
&& version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
|
||||
type = buf.readByte();
|
||||
|
||||
@ -74,7 +74,7 @@ public class SessionPlayerChatPacket implements MinecraftPacket {
|
||||
} else {
|
||||
this.signature = new byte[0];
|
||||
}
|
||||
this.lastSeenMessages = new LastSeenMessages(buf);
|
||||
this.lastSeenMessages = new LastSeenMessages(buf, protocolVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -87,7 +87,7 @@ public class SessionPlayerChatPacket implements MinecraftPacket {
|
||||
if (this.signed) {
|
||||
buf.writeBytes(this.signature);
|
||||
}
|
||||
this.lastSeenMessages.encode(buf);
|
||||
this.lastSeenMessages.encode(buf, protocolVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -45,7 +45,7 @@ public class SessionPlayerCommandPacket implements MinecraftPacket {
|
||||
this.timeStamp = Instant.ofEpochMilli(buf.readLong());
|
||||
this.salt = buf.readLong();
|
||||
this.argumentSignatures = new ArgumentSignatures(buf);
|
||||
this.lastSeenMessages = new LastSeenMessages(buf);
|
||||
this.lastSeenMessages = new LastSeenMessages(buf, protocolVersion);
|
||||
|
||||
this.argumentSignatures = new ArgumentSignatures();
|
||||
}
|
||||
@ -56,7 +56,7 @@ public class SessionPlayerCommandPacket implements MinecraftPacket {
|
||||
buf.writeLong(this.timeStamp.toEpochMilli());
|
||||
buf.writeLong(this.salt);
|
||||
this.argumentSignatures.encode(buf);
|
||||
this.lastSeenMessages.encode(buf);
|
||||
this.lastSeenMessages.encode(buf, protocolVersion);
|
||||
}
|
||||
|
||||
public String getCommand() {
|
||||
|
||||
@ -68,12 +68,6 @@ public class ClientboundServerLinksPacket implements MinecraftPacket {
|
||||
|
||||
public record ServerLink(int id, ComponentHolder displayName, String url) {
|
||||
|
||||
public ServerLink(com.velocitypowered.api.util.ServerLink link, ProtocolVersion protocolVersion) {
|
||||
this(link.getBuiltInType().map(Enum::ordinal).orElse(-1),
|
||||
link.getCustomLabel().map(c -> new ComponentHolder(protocolVersion, c)).orElse(null),
|
||||
link.getUrl().toString());
|
||||
}
|
||||
|
||||
private static ServerLink read(ByteBuf buf, ProtocolVersion version) {
|
||||
if (buf.readBoolean()) {
|
||||
return new ServerLink(ProtocolUtils.readVarInt(buf), null, ProtocolUtils.readString(buf));
|
||||
|
||||
@ -43,20 +43,23 @@ public class PingSessionHandler implements MinecraftSessionHandler {
|
||||
private final MinecraftConnection connection;
|
||||
private final ProtocolVersion version;
|
||||
private boolean completed = false;
|
||||
private final String virtualHostString;
|
||||
|
||||
PingSessionHandler(CompletableFuture<ServerPing> result, RegisteredServer server,
|
||||
MinecraftConnection connection, ProtocolVersion version) {
|
||||
MinecraftConnection connection, ProtocolVersion version, String virtualHostString) {
|
||||
this.result = result;
|
||||
this.server = server;
|
||||
this.connection = connection;
|
||||
this.version = version;
|
||||
this.virtualHostString = virtualHostString;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activated() {
|
||||
HandshakePacket handshake = new HandshakePacket();
|
||||
handshake.setIntent(HandshakeIntent.STATUS);
|
||||
handshake.setServerAddress(server.getServerInfo().getAddress().getHostString());
|
||||
handshake.setServerAddress(this.virtualHostString == null || this.virtualHostString.isEmpty()
|
||||
? server.getServerInfo().getAddress().getHostString() : this.virtualHostString);
|
||||
handshake.setPort(server.getServerInfo().getAddress().getPort());
|
||||
handshake.setProtocolVersion(version);
|
||||
connection.delayedWrite(handshake);
|
||||
|
||||
@ -129,7 +129,7 @@ public class VelocityRegisteredServer implements RegisteredServer, ForwardingAud
|
||||
if (future.isSuccess()) {
|
||||
MinecraftConnection conn = future.channel().pipeline().get(MinecraftConnection.class);
|
||||
PingSessionHandler handler = new PingSessionHandler(pingFuture,
|
||||
VelocityRegisteredServer.this, conn, pingOptions.getProtocolVersion());
|
||||
VelocityRegisteredServer.this, conn, pingOptions.getProtocolVersion(), pingOptions.getVirtualHost());
|
||||
conn.setActiveSessionHandler(StateRegistry.HANDSHAKE, handler);
|
||||
} else {
|
||||
pingFuture.completeExceptionally(future.cause());
|
||||
|
||||
@ -166,7 +166,7 @@ public class KeyedVelocityTabList implements InternalTabList {
|
||||
|
||||
@Override
|
||||
public TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency,
|
||||
int gameMode, @Nullable ChatSession chatSession, boolean listed, int listOrder) {
|
||||
int gameMode, @Nullable ChatSession chatSession, boolean listed, int listOrder, boolean showHat) {
|
||||
return buildEntry(profile, displayName, latency, gameMode, chatSession, listed);
|
||||
}
|
||||
|
||||
|
||||
@ -90,7 +90,7 @@ public class VelocityTabList implements InternalTabList {
|
||||
} else {
|
||||
entry = new VelocityTabListEntry(this, entry1.getProfile(),
|
||||
entry1.getDisplayNameComponent().orElse(null),
|
||||
entry1.getLatency(), entry1.getGameMode(), entry1.getChatSession(), entry1.isListed(), entry1.getListOrder());
|
||||
entry1.getLatency(), entry1.getGameMode(), entry1.getChatSession(), entry1.isListed(), entry1.getListOrder(), entry1.isShowHat());
|
||||
}
|
||||
|
||||
EnumSet<UpsertPlayerInfoPacket.Action> actions = EnumSet
|
||||
@ -134,6 +134,11 @@ public class VelocityTabList implements InternalTabList {
|
||||
actions.add(UpsertPlayerInfoPacket.Action.UPDATE_LIST_ORDER);
|
||||
playerInfoEntry.setListOrder(entry.getListOrder());
|
||||
}
|
||||
if (!Objects.equals(previousEntry.isShowHat(), entry.isShowHat())
|
||||
&& player.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_21_4)) {
|
||||
actions.add(UpsertPlayerInfoPacket.Action.UPDATE_HAT);
|
||||
playerInfoEntry.setShowHat(entry.isShowHat());
|
||||
}
|
||||
if (!Objects.equals(previousEntry.getChatSession(), entry.getChatSession())) {
|
||||
ChatSession from = entry.getChatSession();
|
||||
if (from != null) {
|
||||
@ -173,6 +178,10 @@ public class VelocityTabList implements InternalTabList {
|
||||
actions.add(UpsertPlayerInfoPacket.Action.UPDATE_LIST_ORDER);
|
||||
playerInfoEntry.setListOrder(entry.getListOrder());
|
||||
}
|
||||
if (!entry.isShowHat() && player.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_21_4)) {
|
||||
actions.add(UpsertPlayerInfoPacket.Action.UPDATE_HAT);
|
||||
playerInfoEntry.setShowHat(entry.isShowHat());
|
||||
}
|
||||
}
|
||||
return entry;
|
||||
});
|
||||
@ -218,9 +227,9 @@ public class VelocityTabList implements InternalTabList {
|
||||
@Override
|
||||
public TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency,
|
||||
int gameMode,
|
||||
@Nullable ChatSession chatSession, boolean listed, int listOrder) {
|
||||
@Nullable ChatSession chatSession, boolean listed, int listOrder, boolean showHat) {
|
||||
return new VelocityTabListEntry(this, profile, displayName, latency, gameMode, chatSession,
|
||||
listed, listOrder);
|
||||
listed, listOrder, showHat);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -258,7 +267,8 @@ public class VelocityTabList implements InternalTabList {
|
||||
-1,
|
||||
null,
|
||||
false,
|
||||
0
|
||||
0,
|
||||
true
|
||||
)
|
||||
);
|
||||
} else {
|
||||
|
||||
@ -40,6 +40,7 @@ public class VelocityTabListEntry implements TabListEntry {
|
||||
private int gameMode;
|
||||
private boolean listed;
|
||||
private int listOrder;
|
||||
private boolean showHat;
|
||||
private @Nullable ChatSession session;
|
||||
|
||||
/**
|
||||
@ -47,7 +48,7 @@ public class VelocityTabListEntry implements TabListEntry {
|
||||
*/
|
||||
public VelocityTabListEntry(VelocityTabList tabList, GameProfile profile, Component displayName,
|
||||
int latency,
|
||||
int gameMode, @Nullable ChatSession session, boolean listed, int listOrder) {
|
||||
int gameMode, @Nullable ChatSession session, boolean listed, int listOrder, boolean showHat) {
|
||||
this.tabList = tabList;
|
||||
this.profile = profile;
|
||||
this.displayName = displayName;
|
||||
@ -56,6 +57,7 @@ public class VelocityTabListEntry implements TabListEntry {
|
||||
this.session = session;
|
||||
this.listed = listed;
|
||||
this.listOrder = listOrder;
|
||||
this.showHat = showHat;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -173,4 +175,24 @@ public class VelocityTabListEntry implements TabListEntry {
|
||||
void setListOrderWithoutUpdate(int listOrder) {
|
||||
this.listOrder = listOrder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShowHat() {
|
||||
return showHat;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VelocityTabListEntry setShowHat(boolean showHat) {
|
||||
this.showHat = showHat;
|
||||
if (tabList.getPlayer().getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_21_4)) {
|
||||
UpsertPlayerInfoPacket.Entry upsertEntry = this.tabList.createRawEntry(this);
|
||||
upsertEntry.setShowHat(showHat);
|
||||
tabList.emitActionRaw(UpsertPlayerInfoPacket.Action.UPDATE_HAT, upsertEntry);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
void setShowHatWithoutUpdate(boolean showHat) {
|
||||
this.showHat = showHat;
|
||||
}
|
||||
}
|
||||
@ -35,6 +35,8 @@ public class VelocityTabListEntryLegacy extends KeyedVelocityTabListEntry {
|
||||
@Override
|
||||
public TabListEntry setDisplayName(@Nullable Component displayName) {
|
||||
getTabList().removeEntry(getProfile().getId()); // We have to remove first if updating
|
||||
return super.setDisplayName(displayName);
|
||||
setDisplayNameInternal(displayName);
|
||||
getTabList().addEntry(this);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,6 +19,8 @@ package com.velocitypowered.proxy.tablist;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
|
||||
import com.velocitypowered.api.proxy.player.ChatSession;
|
||||
import com.velocitypowered.api.proxy.player.TabListEntry;
|
||||
import com.velocitypowered.api.util.GameProfile;
|
||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||
@ -133,9 +135,22 @@ public class VelocityTabListLegacy extends KeyedVelocityTabList {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TabListEntry buildEntry(GameProfile profile,
|
||||
net.kyori.adventure.text.@Nullable Component displayName,
|
||||
int latency, int gameMode, @Nullable IdentifiedKey key) {
|
||||
return new VelocityTabListEntryLegacy(this, profile, displayName, latency, gameMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency,
|
||||
int gameMode) {
|
||||
int gameMode, @Nullable ChatSession chatSession, boolean listed) {
|
||||
return new VelocityTabListEntryLegacy(this, profile, displayName, latency, gameMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency,
|
||||
int gameMode, @Nullable ChatSession chatSession, boolean listed, int listOrder, boolean showHat) {
|
||||
return new VelocityTabListEntryLegacy(this, profile, displayName, latency, gameMode);
|
||||
}
|
||||
}
|
||||
|
||||
@ -145,6 +145,12 @@ log-player-connections = true
|
||||
# Transfer packet (Minecraft 1.20.5) to be received.
|
||||
accepts-transfers = false
|
||||
|
||||
# Enables support for SO_REUSEPORT. This may help the proxy scale better on multicore systems
|
||||
# with a lot of incoming connections, and provide better CPU utilization than the existing
|
||||
# strategy of having a single thread accepting connections and distributing them to worker
|
||||
# threads. Disabled by default. Requires Linux or macOS.
|
||||
enable-reuse-port = false
|
||||
|
||||
[query]
|
||||
# Whether to enable responding to GameSpy 4 query responses or not.
|
||||
enabled = false
|
||||
|
||||
Reference in New Issue
Block a user