3
0
Mirror von https://github.com/PaperMC/Velocity.git synchronisiert 2024-11-17 05:20:14 +01:00

Update to 1.19.3-rc3 (#893)

Dieser Commit ist enthalten in:
Corey Shupe 2022-12-07 08:12:48 -05:00 committet von GitHub
Ursprung 15216e5b00
Commit b504e0857c
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: 4AEE18F83AFDEB23
59 geänderte Dateien mit 2907 neuen und 1272 gelöschten Zeilen

1
.gitignore vendored
Datei anzeigen

@ -5,6 +5,7 @@
*.iml *.iml
.idea_modules/ .idea_modules/
atlassian-ide-plugin.xml atlassian-ide-plugin.xml
.fleet/
### Eclipse ### ### Eclipse ###
.metadata .metadata

Datei anzeigen

@ -59,7 +59,8 @@ public enum ProtocolVersion {
MINECRAFT_1_18(757, "1.18", "1.18.1"), MINECRAFT_1_18(757, "1.18", "1.18.1"),
MINECRAFT_1_18_2(758, "1.18.2"), MINECRAFT_1_18_2(758, "1.18.2"),
MINECRAFT_1_19(759, "1.19"), MINECRAFT_1_19(759, "1.19"),
MINECRAFT_1_19_1(760, "1.19.1", "1.19.2"); MINECRAFT_1_19_1(760, "1.19.1", "1.19.2"),
MINECRAFT_1_19_3(761, 114, "1.19.3");
private static final int SNAPSHOT_BIT = 30; private static final int SNAPSHOT_BIT = 30;

Datei anzeigen

@ -0,0 +1,23 @@
/*
* Copyright (C) 2018 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.proxy.player;
import com.velocitypowered.api.proxy.crypto.KeyIdentifiable;
import java.util.UUID;
/**
* Represents a chat session held by a player.
*/
public interface ChatSession extends KeyIdentifiable {
/**
* Returns the {@link UUID} of the session.
*
* @return the session UUID
*/
UUID getSessionId();
}

Datei anzeigen

@ -68,6 +68,11 @@ public interface TabList {
*/ */
Collection<TabListEntry> getEntries(); Collection<TabListEntry> getEntries();
/**
* Clears all entries from the tab list.
*/
void clearAll();
/** /**
* Builds a tab list entry. * Builds a tab list entry.
* *
@ -96,4 +101,20 @@ public interface TabList {
@Deprecated @Deprecated
TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency, TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency,
int gameMode, @Nullable IdentifiedKey key); int gameMode, @Nullable IdentifiedKey key);
/**
* Represents an entry in a {@link Player}'s tab list.
*
* @param profile the profile
* @param displayName the display name
* @param latency the latency
* @param gameMode the game mode
* @param chatSession the chat session
* @return the entry
* @deprecated Internal usage. Use {@link TabListEntry.Builder} instead.
*/
@Deprecated
TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency,
int gameMode, @Nullable ChatSession chatSession);
} }

Datei anzeigen

@ -18,6 +18,21 @@ import org.checkerframework.checker.nullness.qual.Nullable;
* Represents a single entry in a {@link TabList}. * Represents a single entry in a {@link TabList}.
*/ */
public interface TabListEntry extends KeyIdentifiable { public interface TabListEntry extends KeyIdentifiable {
/**
* Returns the {@link ChatSession} associated with this entry.
*
* @return the chat session
*/
@Nullable ChatSession getChatSession();
@Override
default IdentifiedKey getIdentifiedKey() {
ChatSession session = getChatSession();
if (session == null) {
return null;
}
return getChatSession().getIdentifiedKey();
}
/** /**
* Returns the parent {@link TabList} of this {@code this} {@link TabListEntry}. * Returns the parent {@link TabList} of this {@code this} {@link TabListEntry}.
@ -105,6 +120,25 @@ public interface TabListEntry extends KeyIdentifiable {
*/ */
TabListEntry setGameMode(int gameMode); TabListEntry setGameMode(int gameMode);
/**
* Whether or not the entry is listed, when listed they will be visible to other players in the tab list.
*
* @return Whether this entry is listed; only changeable in 1.19.3 and above
*/
default boolean isListed() {
return true;
}
/**
* Sets whether this entry is listed.
*
* @param listed whether this entry is listed
* @return {@code this}, for chaining
*/
default TabListEntry setListed(boolean listed) {
return this;
}
/** /**
* Returns a {@link Builder} to create a {@link TabListEntry}. * Returns a {@link Builder} to create a {@link TabListEntry}.
* *
@ -127,7 +161,7 @@ public interface TabListEntry extends KeyIdentifiable {
private int latency = 0; private int latency = 0;
private int gameMode = 0; private int gameMode = 0;
private @Nullable IdentifiedKey playerKey; private @Nullable ChatSession chatSession;
private Builder() { private Builder() {
} }
@ -162,12 +196,12 @@ public interface TabListEntry extends KeyIdentifiable {
* <p>For any player currently connected to this proxy this will be filled automatically.</p> * <p>For any player currently connected to this proxy this will be filled automatically.</p>
* <p>Will ignore mismatching key revisions data.</p> * <p>Will ignore mismatching key revisions data.</p>
* *
* @param playerKey key to set * @param chatSession session to set
* @return {@code this}, for chaining * @return {@code this}, for chaining
* @see TabListEntry#getIdentifiedKey() * @see TabListEntry#getChatSession()
*/ */
public Builder playerKey(IdentifiedKey playerKey) { public Builder chatSession(ChatSession chatSession) {
this.playerKey = playerKey; this.chatSession = chatSession;
return this; return this;
} }
@ -219,7 +253,7 @@ public interface TabListEntry extends KeyIdentifiable {
if (profile == null) { if (profile == null) {
throw new IllegalStateException("The GameProfile must be set when building a TabListEntry"); throw new IllegalStateException("The GameProfile must be set when building a TabListEntry");
} }
return tabList.buildEntry(profile, displayName, latency, gameMode, playerKey); return tabList.buildEntry(profile, displayName, latency, gameMode, chatSession);
} }
} }
} }

Datei anzeigen

@ -30,10 +30,11 @@ import com.velocitypowered.proxy.protocol.packet.JoinGame;
import com.velocitypowered.proxy.protocol.packet.KeepAlive; import com.velocitypowered.proxy.protocol.packet.KeepAlive;
import com.velocitypowered.proxy.protocol.packet.LegacyHandshake; import com.velocitypowered.proxy.protocol.packet.LegacyHandshake;
import com.velocitypowered.proxy.protocol.packet.LegacyPing; import com.velocitypowered.proxy.protocol.packet.LegacyPing;
import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem;
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage; import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage;
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse; import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
import com.velocitypowered.proxy.protocol.packet.PlayerListItem;
import com.velocitypowered.proxy.protocol.packet.PluginMessage; import com.velocitypowered.proxy.protocol.packet.PluginMessage;
import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfo;
import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest; import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest;
import com.velocitypowered.proxy.protocol.packet.ResourcePackResponse; import com.velocitypowered.proxy.protocol.packet.ResourcePackResponse;
import com.velocitypowered.proxy.protocol.packet.Respawn; import com.velocitypowered.proxy.protocol.packet.Respawn;
@ -46,14 +47,14 @@ import com.velocitypowered.proxy.protocol.packet.StatusRequest;
import com.velocitypowered.proxy.protocol.packet.StatusResponse; import com.velocitypowered.proxy.protocol.packet.StatusResponse;
import com.velocitypowered.proxy.protocol.packet.TabCompleteRequest; import com.velocitypowered.proxy.protocol.packet.TabCompleteRequest;
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse; import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse;
import com.velocitypowered.proxy.protocol.packet.chat.LegacyChat; import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo;
import com.velocitypowered.proxy.protocol.packet.chat.PlayerChat;
import com.velocitypowered.proxy.protocol.packet.chat.PlayerChatCompletion; import com.velocitypowered.proxy.protocol.packet.chat.PlayerChatCompletion;
import com.velocitypowered.proxy.protocol.packet.chat.PlayerChatPreview;
import com.velocitypowered.proxy.protocol.packet.chat.PlayerCommand;
import com.velocitypowered.proxy.protocol.packet.chat.ServerChatPreview;
import com.velocitypowered.proxy.protocol.packet.chat.ServerPlayerChat;
import com.velocitypowered.proxy.protocol.packet.chat.SystemChat; import com.velocitypowered.proxy.protocol.packet.chat.SystemChat;
import com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedPlayerChat;
import com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedPlayerCommand;
import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyChat;
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerChat;
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerCommand;
import com.velocitypowered.proxy.protocol.packet.title.LegacyTitlePacket; import com.velocitypowered.proxy.protocol.packet.title.LegacyTitlePacket;
import com.velocitypowered.proxy.protocol.packet.title.TitleActionbarPacket; import com.velocitypowered.proxy.protocol.packet.title.TitleActionbarPacket;
import com.velocitypowered.proxy.protocol.packet.title.TitleClearPacket; import com.velocitypowered.proxy.protocol.packet.title.TitleClearPacket;
@ -228,7 +229,7 @@ public interface MinecraftSessionHandler {
return false; return false;
} }
default boolean handle(PlayerListItem packet) { default boolean handle(LegacyPlayerListItem packet) {
return false; return false;
} }
@ -240,7 +241,11 @@ public interface MinecraftSessionHandler {
return false; return false;
} }
default boolean handle(PlayerChat packet) { default boolean handle(KeyedPlayerChat packet) {
return false;
}
default boolean handle(SessionPlayerChat packet) {
return false; return false;
} }
@ -248,19 +253,11 @@ public interface MinecraftSessionHandler {
return false; return false;
} }
default boolean handle(ServerPlayerChat packet) { default boolean handle(KeyedPlayerCommand packet) {
return false; return false;
} }
default boolean handle(PlayerChatPreview packet) { default boolean handle(SessionPlayerCommand packet) {
return false;
}
default boolean handle(ServerChatPreview packet) {
return false;
}
default boolean handle(PlayerCommand packet) {
return false; return false;
} }
@ -271,4 +268,12 @@ public interface MinecraftSessionHandler {
default boolean handle(ServerData serverData) { default boolean handle(ServerData serverData) {
return false; return false;
} }
default boolean handle(RemovePlayerInfo packet) {
return false;
}
default boolean handle(UpsertPlayerInfo packet) {
return false;
}
} }

Datei anzeigen

@ -27,7 +27,8 @@ public class VelocityConstants {
public static final int MODERN_FORWARDING_DEFAULT = 1; public static final int MODERN_FORWARDING_DEFAULT = 1;
public static final int MODERN_FORWARDING_WITH_KEY = 2; public static final int MODERN_FORWARDING_WITH_KEY = 2;
public static final int MODERN_FORWARDING_WITH_KEY_V2 = 3; public static final int MODERN_FORWARDING_WITH_KEY_V2 = 3;
public static final int MODERN_FORWARDING_MAX_VERSION = MODERN_FORWARDING_WITH_KEY_V2; public static final int MODERN_LAZY_SESSION = 4;
public static final int MODERN_FORWARDING_MAX_VERSION = MODERN_LAZY_SESSION;
public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
} }

Datei anzeigen

@ -43,12 +43,14 @@ import com.velocitypowered.proxy.protocol.packet.AvailableCommands;
import com.velocitypowered.proxy.protocol.packet.BossBar; import com.velocitypowered.proxy.protocol.packet.BossBar;
import com.velocitypowered.proxy.protocol.packet.Disconnect; import com.velocitypowered.proxy.protocol.packet.Disconnect;
import com.velocitypowered.proxy.protocol.packet.KeepAlive; import com.velocitypowered.proxy.protocol.packet.KeepAlive;
import com.velocitypowered.proxy.protocol.packet.PlayerListItem; import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem;
import com.velocitypowered.proxy.protocol.packet.PluginMessage; import com.velocitypowered.proxy.protocol.packet.PluginMessage;
import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfo;
import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest; import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest;
import com.velocitypowered.proxy.protocol.packet.ResourcePackResponse; import com.velocitypowered.proxy.protocol.packet.ResourcePackResponse;
import com.velocitypowered.proxy.protocol.packet.ServerData; import com.velocitypowered.proxy.protocol.packet.ServerData;
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse; import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse;
import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo;
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil; import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil; import io.netty.buffer.ByteBufUtil;
@ -60,7 +62,6 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
public class BackendPlaySessionHandler implements MinecraftSessionHandler { public class BackendPlaySessionHandler implements MinecraftSessionHandler {
private static final Pattern PLAUSIBLE_SHA1_HASH = Pattern.compile("^[a-z0-9]{40}$"); private static final Pattern PLAUSIBLE_SHA1_HASH = Pattern.compile("^[a-z0-9]{40}$");
private static final Logger logger = LogManager.getLogger(BackendPlaySessionHandler.class); private static final Logger logger = LogManager.getLogger(BackendPlaySessionHandler.class);
private static final boolean BACKPRESSURE_LOG = Boolean private static final boolean BACKPRESSURE_LOG = Boolean
@ -245,8 +246,20 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
} }
@Override @Override
public boolean handle(PlayerListItem packet) { public boolean handle(LegacyPlayerListItem packet) {
serverConn.getPlayer().getTabList().processBackendPacket(packet); serverConn.getPlayer().getTabList().processLegacy(packet);
return false;
}
@Override
public boolean handle(UpsertPlayerInfo packet) {
serverConn.getPlayer().getTabList().processUpdate(packet);
return false;
}
@Override
public boolean handle(RemovePlayerInfo packet) {
serverConn.getPlayer().getTabList().processRemove(packet);
return false; return false;
} }
@ -280,7 +293,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
this.playerConnection.write( this.playerConnection.write(
new ServerData(pingEvent.getPing().getDescriptionComponent(), new ServerData(pingEvent.getPing().getDescriptionComponent(),
pingEvent.getPing().getFavicon().orElse(null), pingEvent.getPing().getFavicon().orElse(null),
packet.isPreviewsChat(), packet.isSecureChatEnforced()) packet.isSecureChatEnforced())
), playerConnection.eventLoop()); ), playerConnection.eventLoop());
return true; return true;
} }

Datei anzeigen

@ -17,12 +17,10 @@
package com.velocitypowered.proxy.connection.backend; package com.velocitypowered.proxy.connection.backend;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.event.player.ServerLoginPluginMessageEvent; import com.velocitypowered.api.event.player.ServerLoginPluginMessageEvent;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey; import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.config.PlayerInfoForwarding; import com.velocitypowered.proxy.config.PlayerInfoForwarding;
import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.config.VelocityConfiguration;
@ -51,9 +49,11 @@ import javax.crypto.Mac;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class LoginSessionHandler implements MinecraftSessionHandler { public class LoginSessionHandler implements MinecraftSessionHandler {
private static final Logger logger = LogManager.getLogger(LoginSessionHandler.class);
private static final Component MODERN_IP_FORWARDING_FAILURE = Component private static final Component MODERN_IP_FORWARDING_FAILURE = Component
.translatable("velocity.error.modern-forwarding-failed"); .translatable("velocity.error.modern-forwarding-failed");
@ -177,6 +177,10 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
// Ensure we are in range // Ensure we are in range
requested = Math.min(requested, VelocityConstants.MODERN_FORWARDING_MAX_VERSION); requested = Math.min(requested, VelocityConstants.MODERN_FORWARDING_MAX_VERSION);
if (requested > VelocityConstants.MODERN_FORWARDING_DEFAULT) { if (requested > VelocityConstants.MODERN_FORWARDING_DEFAULT) {
if (player.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19_3) >= 0) {
return requested >= VelocityConstants.MODERN_LAZY_SESSION ? VelocityConstants.MODERN_LAZY_SESSION
: VelocityConstants.MODERN_FORWARDING_DEFAULT;
}
if (player.getIdentifiedKey() != null) { if (player.getIdentifiedKey() != null) {
// No enhanced switch on java 11 // No enhanced switch on java 11
switch (player.getIdentifiedKey().getKeyRevision()) { switch (player.getIdentifiedKey().getKeyRevision()) {
@ -210,7 +214,8 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
// This serves as additional redundancy. The key normally is stored in the // This serves as additional redundancy. The key normally is stored in the
// login start to the server, but some setups require this. // login start to the server, but some setups require this.
if (actualVersion >= VelocityConstants.MODERN_FORWARDING_WITH_KEY) { if (actualVersion >= VelocityConstants.MODERN_FORWARDING_WITH_KEY
&& actualVersion < VelocityConstants.MODERN_LAZY_SESSION) {
IdentifiedKey key = player.getIdentifiedKey(); IdentifiedKey key = player.getIdentifiedKey();
assert key != null; assert key != null;
ProtocolUtils.writePlayerKey(forwarded, key); ProtocolUtils.writePlayerKey(forwarded, key);

Datei anzeigen

@ -190,7 +190,12 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
mc.setProtocolVersion(protocolVersion); mc.setProtocolVersion(protocolVersion);
mc.setState(StateRegistry.LOGIN); mc.setState(StateRegistry.LOGIN);
if (proxyPlayer.getIdentifiedKey() == null
&& proxyPlayer.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19_3) >= 0) {
mc.delayedWrite(new ServerLogin(proxyPlayer.getUsername(), proxyPlayer.getUniqueId()));
} else {
mc.delayedWrite(new ServerLogin(proxyPlayer.getUsername(), proxyPlayer.getIdentifiedKey())); mc.delayedWrite(new ServerLogin(proxyPlayer.getUsername(), proxyPlayer.getIdentifiedKey()));
}
mc.flush(); mc.flush();
} }

Datei anzeigen

@ -25,14 +25,11 @@ import static com.velocitypowered.proxy.protocol.util.PluginMessageUtil.construc
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.mojang.brigadier.suggestion.Suggestion; import com.mojang.brigadier.suggestion.Suggestion;
import com.velocitypowered.api.command.VelocityBrigadierMessage; import com.velocitypowered.api.command.VelocityBrigadierMessage;
import com.velocitypowered.api.event.command.CommandExecuteEvent.CommandResult;
import com.velocitypowered.api.event.connection.PluginMessageEvent; import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.event.player.PlayerChannelRegisterEvent; import com.velocitypowered.api.event.player.PlayerChannelRegisterEvent;
import com.velocitypowered.api.event.player.PlayerChatEvent;
import com.velocitypowered.api.event.player.PlayerClientBrandEvent; import com.velocitypowered.api.event.player.PlayerClientBrandEvent;
import com.velocitypowered.api.event.player.TabCompleteEvent; import com.velocitypowered.api.event.player.TabCompleteEvent;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier; import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier; import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
@ -43,8 +40,6 @@ import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.backend.BackendConnectionPhases; import com.velocitypowered.proxy.connection.backend.BackendConnectionPhases;
import com.velocitypowered.proxy.connection.backend.BungeeCordMessageResponder; import com.velocitypowered.proxy.connection.backend.BungeeCordMessageResponder;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.crypto.SignedChatCommand;
import com.velocitypowered.proxy.crypto.SignedChatMessage;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.BossBar; import com.velocitypowered.proxy.protocol.packet.BossBar;
@ -57,11 +52,20 @@ import com.velocitypowered.proxy.protocol.packet.Respawn;
import com.velocitypowered.proxy.protocol.packet.TabCompleteRequest; import com.velocitypowered.proxy.protocol.packet.TabCompleteRequest;
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse; import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse;
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse.Offer; import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse.Offer;
import com.velocitypowered.proxy.protocol.packet.chat.ChatBuilder; import com.velocitypowered.proxy.protocol.packet.chat.ChatHandler;
import com.velocitypowered.proxy.protocol.packet.chat.ChatQueue; import com.velocitypowered.proxy.protocol.packet.chat.ChatTimeKeeper;
import com.velocitypowered.proxy.protocol.packet.chat.LegacyChat; import com.velocitypowered.proxy.protocol.packet.chat.CommandHandler;
import com.velocitypowered.proxy.protocol.packet.chat.PlayerChat; import com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedChatHandler;
import com.velocitypowered.proxy.protocol.packet.chat.PlayerCommand; import com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedCommandHandler;
import com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedPlayerChat;
import com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedPlayerCommand;
import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyChat;
import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyChatHandler;
import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyCommandHandler;
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionChatHandler;
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionCommandHandler;
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerChat;
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerCommand;
import com.velocitypowered.proxy.protocol.packet.title.GenericTitlePacket; import com.velocitypowered.proxy.protocol.packet.title.GenericTitlePacket;
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil; import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
import com.velocitypowered.proxy.util.CharacterUtil; import com.velocitypowered.proxy.util.CharacterUtil;
@ -73,10 +77,8 @@ import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.Queue; import java.util.Queue;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
@ -98,7 +100,9 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
private final Queue<PluginMessage> loginPluginMessages = new ConcurrentLinkedQueue<>(); private final Queue<PluginMessage> loginPluginMessages = new ConcurrentLinkedQueue<>();
private final VelocityServer server; private final VelocityServer server;
private @Nullable TabCompleteRequest outstandingTabComplete; private @Nullable TabCompleteRequest outstandingTabComplete;
private @Nullable Instant lastChatMessage; // Added in 1.19 private final ChatHandler<? extends MinecraftPacket> chatHandler;
private final CommandHandler<? extends MinecraftPacket> commandHandler;
private final ChatTimeKeeper timeKeeper = new ChatTimeKeeper();
/** /**
* Constructs a client play session handler. * Constructs a client play session handler.
@ -109,127 +113,40 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
public ClientPlaySessionHandler(VelocityServer server, ConnectedPlayer player) { public ClientPlaySessionHandler(VelocityServer server, ConnectedPlayer player) {
this.player = player; this.player = player;
this.server = server; this.server = server;
if (this.player.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19_3) >= 0) {
this.chatHandler = new SessionChatHandler(this.player, this.server);
this.commandHandler = new SessionCommandHandler(this.player, this.server);
} else if (this.player.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
this.chatHandler = new KeyedChatHandler(this.server, this.player);
this.commandHandler = new KeyedCommandHandler(this.player, this.server);
} else {
this.chatHandler = new LegacyChatHandler(this.server, this.player);
this.commandHandler = new LegacyCommandHandler(this.player, this.server);
}
} }
// I will not allow hacks to bypass this; @SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean tickLastMessage(SignedChatMessage nextMessage) { private boolean updateTimeKeeper(Instant instant) {
if (lastChatMessage != null && lastChatMessage.isAfter(nextMessage.getExpiryTemporal())) { if (!this.timeKeeper.update(instant)) {
player.disconnect(Component.translatable("multiplayer.disconnect.out_of_order_chat")); player.disconnect(Component.translatable("multiplayer.disconnect.out_of_order_chat"));
return false; return false;
} }
lastChatMessage = nextMessage.getExpiryTemporal();
return true; return true;
} }
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean validateChat(String message) { private boolean validateChat(String message) {
if (CharacterUtil.containsIllegalCharacters(message)) { if (CharacterUtil.containsIllegalCharacters(message)) {
player.disconnect(Component.translatable("velocity.error.illegal-chat-characters", player.disconnect(Component.translatable("velocity.error.illegal-chat-characters", NamedTextColor.RED));
NamedTextColor.RED));
return false; return false;
} }
return true; return true;
} }
private MinecraftConnection retrieveServerConnection() {
VelocityServerConnection serverConnection = player.getConnectedServer();
if (serverConnection == null) {
return null;
}
return serverConnection.getConnection();
}
private void processCommandMessage(String message, @Nullable SignedChatCommand signedCommand,
MinecraftPacket original, Instant passedTimestamp) {
this.player.getChatQueue().queuePacket(server.getCommandManager().callCommandEvent(player, message)
.thenComposeAsync(event -> processCommandExecuteResult(message,
event.getResult(), signedCommand, passedTimestamp))
.whenComplete((ignored, throwable) -> {
if (server.getConfiguration().isLogCommandExecutions()) {
logger.info("{} -> executed command /{}", player, message);
}
})
.exceptionally(e -> {
logger.info("Exception occurred while running command for {}",
player.getUsername(), e);
player.sendMessage(Component.translatable("velocity.command.generic-error",
NamedTextColor.RED));
return null;
}), passedTimestamp);
}
private void processPlayerChat(String message, @Nullable SignedChatMessage signedMessage,
MinecraftPacket original) {
MinecraftConnection smc = retrieveServerConnection();
if (smc == null) {
return;
}
if (signedMessage == null) {
PlayerChatEvent event = new PlayerChatEvent(player, message);
callChat(original, event, null).thenAccept(smc::write);
} else {
Instant messageTimestamp = signedMessage.getExpiryTemporal();
PlayerChatEvent event = new PlayerChatEvent(player, message);
this.player.getChatQueue().queuePacket(callChat(original, event, signedMessage), messageTimestamp);
}
}
private CompletableFuture<MinecraftPacket> callChat(MinecraftPacket original, PlayerChatEvent event,
@Nullable SignedChatMessage signedMessage) {
return server.getEventManager().fire(event)
.thenApply(pme -> {
PlayerChatEvent.ChatResult chatResult = pme.getResult();
IdentifiedKey playerKey = player.getIdentifiedKey();
if (chatResult.isAllowed()) {
Optional<String> eventMsg = pme.getResult().getMessage();
if (eventMsg.isPresent()) {
String messageNew = eventMsg.get();
if (playerKey != null) {
if (signedMessage != null && !messageNew.equals(signedMessage.getMessage())) {
if (playerKey.getKeyRevision().compareTo(IdentifiedKey.Revision.LINKED_V2) >= 0) {
// Bad, very bad.
logger.fatal("A plugin tried to change a signed chat message. "
+ "This is no longer possible in 1.19.1 and newer. "
+ "Disconnecting player " + player.getUsername());
player.disconnect(Component.text("A proxy plugin caused an illegal protocol state. "
+ "Contact your network administrator."));
} else {
logger.warn("A plugin changed a signed chat message. The server may not accept it.");
return ChatBuilder.builder(player.getProtocolVersion())
.message(messageNew).toServer();
}
} else {
return original;
}
} else {
return ChatBuilder.builder(player.getProtocolVersion())
.message(messageNew).toServer();
}
} else {
return original;
}
} else {
if (playerKey != null && playerKey.getKeyRevision().compareTo(IdentifiedKey.Revision.LINKED_V2) >= 0) {
logger.fatal("A plugin tried to cancel a signed chat message."
+ " This is no longer possible in 1.19.1 and newer. "
+ "Disconnecting player " + player.getUsername());
player.disconnect(Component.text("A proxy plugin caused an illegal protocol state. "
+ "Contact your network administrator."));
}
}
return null;
})
.exceptionally((ex) -> {
logger.error("Exception while handling player chat for {}", player, ex);
return null;
});
}
@Override @Override
public void activated() { public void activated() {
Collection<String> channels = server.getChannelRegistrar().getChannelsForProtocol(player Collection<String> channels = server.getChannelRegistrar().getChannelsForProtocol(player.getProtocolVersion());
.getProtocolVersion());
if (!channels.isEmpty()) { if (!channels.isEmpty()) {
PluginMessage register = constructChannelsPacket(player.getProtocolVersion(), channels); PluginMessage register = constructChannelsPacket(player.getProtocolVersion(), channels);
player.getConnection().write(register); player.getConnection().write(register);
@ -267,48 +184,65 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
} }
@Override @Override
public boolean handle(PlayerCommand packet) { public boolean handle(SessionPlayerCommand packet) {
player.ensureAndGetCurrentServer(); player.ensureAndGetCurrentServer();
if (!updateTimeKeeper(packet.getTimeStamp())) {
return true;
}
if (!validateChat(packet.getCommand())) { if (!validateChat(packet.getCommand())) {
return true; return true;
} }
if (!packet.isUnsigned()) {
SignedChatCommand signedCommand = packet.signedContainer(player.getIdentifiedKey(), player.getUniqueId(), false);
if (signedCommand != null) {
processCommandMessage(packet.getCommand(), signedCommand, packet, packet.getTimestamp());
return true;
}
}
processCommandMessage(packet.getCommand(), null, packet, packet.getTimestamp()); return this.commandHandler.handlePlayerCommand(packet);
return true;
} }
@Override @Override
public boolean handle(PlayerChat packet) { public boolean handle(SessionPlayerChat packet) {
player.ensureAndGetCurrentServer(); player.ensureAndGetCurrentServer();
if (!updateTimeKeeper(packet.getTimestamp())) {
return true;
}
if (!validateChat(packet.getMessage())) { if (!validateChat(packet.getMessage())) {
return true; return true;
} }
if (!packet.isUnsigned()) { return this.chatHandler.handlePlayerChat(packet);
// Bad if spoofed }
SignedChatMessage signedChat = packet.signedContainer(player.getIdentifiedKey(), player.getUniqueId(), false);
if (signedChat != null) { @Override
// Server doesn't care for expiry as long as order is correct public boolean handle(KeyedPlayerCommand packet) {
if (!tickLastMessage(signedChat)) { player.ensureAndGetCurrentServer();
if (!updateTimeKeeper(packet.getTimestamp())) {
return true; return true;
} }
processPlayerChat(packet.getMessage(), signedChat, packet); if (!validateChat(packet.getCommand())) {
return true; return true;
} }
}
processPlayerChat(packet.getMessage(), null, packet); return this.commandHandler.handlePlayerCommand(packet);
}
@Override
public boolean handle(KeyedPlayerChat packet) {
player.ensureAndGetCurrentServer();
if (!updateTimeKeeper(packet.getExpiry())) {
return true; return true;
} }
if (!validateChat(packet.getMessage())) {
return true;
}
return this.chatHandler.handlePlayerChat(packet);
}
@Override @Override
public boolean handle(LegacyChat packet) { public boolean handle(LegacyChat packet) {
player.ensureAndGetCurrentServer(); player.ensureAndGetCurrentServer();
@ -318,9 +252,9 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
} }
if (msg.startsWith("/")) { if (msg.startsWith("/")) {
processCommandMessage(msg.substring(1), null, packet, Instant.now()); this.commandHandler.handlePlayerCommand(packet);
} else { } else {
processPlayerChat(msg, null, packet); this.chatHandler.handlePlayerChat(packet);
} }
return true; return true;
} }
@ -342,8 +276,9 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
MinecraftConnection backendConn = serverConn != null ? serverConn.getConnection() : null; MinecraftConnection backendConn = serverConn != null ? serverConn.getConnection() : null;
if (serverConn != null && backendConn != null) { if (serverConn != null && backendConn != null) {
if (backendConn.getState() != StateRegistry.PLAY) { if (backendConn.getState() != StateRegistry.PLAY) {
logger.warn("A plugin message was received while the backend server was not " logger.warn(
+ "ready. Channel: {}. Packet discarded.", packet.getChannel()); "A plugin message was received while the backend server was not " + "ready. Channel: {}. Packet discarded.",
packet.getChannel());
} else if (PluginMessageUtil.isRegister(packet)) { } else if (PluginMessageUtil.isRegister(packet)) {
List<String> channels = PluginMessageUtil.getChannels(packet); List<String> channels = PluginMessageUtil.getChannels(packet);
player.getKnownChannels().addAll(channels); player.getKnownChannels().addAll(channels);
@ -355,8 +290,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
channelIdentifiers.add(new LegacyChannelIdentifier(channel)); channelIdentifiers.add(new LegacyChannelIdentifier(channel));
} }
} }
server.getEventManager().fireAndForget(new PlayerChannelRegisterEvent(player, server.getEventManager()
ImmutableList.copyOf(channelIdentifiers))); .fireAndForget(new PlayerChannelRegisterEvent(player, ImmutableList.copyOf(channelIdentifiers)));
backendConn.write(packet.retain()); backendConn.write(packet.retain());
} else if (PluginMessageUtil.isUnregister(packet)) { } else if (PluginMessageUtil.isUnregister(packet)) {
player.getKnownChannels().removeAll(PluginMessageUtil.getChannels(packet)); player.getKnownChannels().removeAll(PluginMessageUtil.getChannels(packet));
@ -365,8 +300,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
String brand = PluginMessageUtil.readBrandMessage(packet.content()); String brand = PluginMessageUtil.readBrandMessage(packet.content());
server.getEventManager().fireAndForget(new PlayerClientBrandEvent(player, brand)); server.getEventManager().fireAndForget(new PlayerClientBrandEvent(player, brand));
player.setClientBrand(brand); player.setClientBrand(brand);
backendConn.write(PluginMessageUtil backendConn.write(
.rewriteMinecraftBrand(packet, server.getVersion(), player.getProtocolVersion())); PluginMessageUtil.rewriteMinecraftBrand(packet, server.getVersion(), player.getProtocolVersion()));
} else if (BungeeCordMessageResponder.isBungeeCordMessage(packet)) { } else if (BungeeCordMessageResponder.isBungeeCordMessage(packet)) {
return true; return true;
} else { } else {
@ -401,8 +336,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
PluginMessageEvent event = new PluginMessageEvent(player, serverConn, id, copy); PluginMessageEvent event = new PluginMessageEvent(player, serverConn, id, copy);
server.getEventManager().fire(event).thenAcceptAsync(pme -> { server.getEventManager().fire(event).thenAcceptAsync(pme -> {
if (pme.getResult().isAllowed()) { if (pme.getResult().isAllowed()) {
PluginMessage message = new PluginMessage(packet.getChannel(), PluginMessage message = new PluginMessage(packet.getChannel(), Unpooled.wrappedBuffer(copy));
Unpooled.wrappedBuffer(copy));
if (!player.getPhase().consideredComplete() || !serverConn.getPhase().consideredComplete()) { if (!player.getPhase().consideredComplete() || !serverConn.getPhase().consideredComplete()) {
// We're still processing the connection (see above), enqueue the packet for now. // We're still processing the connection (see above), enqueue the packet for now.
loginPluginMessages.add(message.retain()); loginPluginMessages.add(message.retain());
@ -410,10 +344,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
backendConn.write(message); backendConn.write(message);
} }
} }
}, backendConn.eventLoop()) }, backendConn.eventLoop()).exceptionally((ex) -> {
.exceptionally((ex) -> { logger.error("Exception while handling plugin message packet for {}", player, ex);
logger.error("Exception while handling plugin message packet for {}",
player, ex);
return null; return null;
}); });
} }
@ -467,8 +399,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
@Override @Override
public void exception(Throwable throwable) { public void exception(Throwable throwable) {
player.disconnect(Component.translatable("velocity.error.player-connection-error", player.disconnect(Component.translatable("velocity.error.player-connection-error", NamedTextColor.RED));
NamedTextColor.RED));
} }
@Override @Override
@ -548,8 +479,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
// Clear any title from the previous server. // Clear any title from the previous server.
if (player.getProtocolVersion().compareTo(MINECRAFT_1_8) >= 0) { if (player.getProtocolVersion().compareTo(MINECRAFT_1_8) >= 0) {
player.getConnection().delayedWrite(GenericTitlePacket.constructTitlePacket( player.getConnection().delayedWrite(
GenericTitlePacket.ActionType.RESET, player.getProtocolVersion())); GenericTitlePacket.constructTitlePacket(GenericTitlePacket.ActionType.RESET, player.getProtocolVersion()));
} }
// Flush everything // Flush everything
@ -619,8 +550,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
return false; return false;
} }
server.getCommandManager().offerBrigadierSuggestions(player, command) server.getCommandManager().offerBrigadierSuggestions(player, command).thenAcceptAsync(suggestions -> {
.thenAcceptAsync(suggestions -> {
if (suggestions.isEmpty()) { if (suggestions.isEmpty()) {
return; return;
} }
@ -629,8 +559,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
for (Suggestion suggestion : suggestions.getList()) { for (Suggestion suggestion : suggestions.getList()) {
String offer = suggestion.getText(); String offer = suggestion.getText();
Component tooltip = null; Component tooltip = null;
if (suggestion.getTooltip() != null if (suggestion.getTooltip() != null && suggestion.getTooltip() instanceof VelocityBrigadierMessage) {
&& suggestion.getTooltip() instanceof VelocityBrigadierMessage) {
tooltip = ((VelocityBrigadierMessage) suggestion.getTooltip()).asComponent(); tooltip = ((VelocityBrigadierMessage) suggestion.getTooltip()).asComponent();
} }
offers.add(new Offer(offer, tooltip)); offers.add(new Offer(offer, tooltip));
@ -644,10 +573,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
resp.getOffers().addAll(offers); resp.getOffers().addAll(offers);
player.getConnection().write(resp); player.getConnection().write(resp);
} }
}, player.getConnection().eventLoop()) }, player.getConnection().eventLoop()).exceptionally((ex) -> {
.exceptionally((ex) -> { logger.error("Exception while handling command tab completion for player {} executing {}", player, command, ex);
logger.error("Exception while handling command tab completion for player {} executing {}",
player, command, ex);
return null; return null;
}); });
return true; // Sorry, handler; we're just gonna have to lie to you here. return true; // Sorry, handler; we're just gonna have to lie to you here.
@ -683,8 +610,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
private void finishCommandTabComplete(TabCompleteRequest request, TabCompleteResponse response) { private void finishCommandTabComplete(TabCompleteRequest request, TabCompleteResponse response) {
String command = request.getCommand().substring(1); String command = request.getCommand().substring(1);
server.getCommandManager().offerBrigadierSuggestions(player, command) server.getCommandManager().offerBrigadierSuggestions(player, command).thenAcceptAsync(offers -> {
.thenAcceptAsync(offers -> {
boolean legacy = player.getProtocolVersion().compareTo(MINECRAFT_1_13) < 0; boolean legacy = player.getProtocolVersion().compareTo(MINECRAFT_1_13) < 0;
try { try {
for (Suggestion suggestion : offers.getList()) { for (Suggestion suggestion : offers.getList()) {
@ -694,8 +620,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
offer = offer.substring(command.length()); offer = offer.substring(command.length());
} }
Component tooltip = null; Component tooltip = null;
if (suggestion.getTooltip() != null if (suggestion.getTooltip() != null && suggestion.getTooltip() instanceof VelocityBrigadierMessage) {
&& suggestion.getTooltip() instanceof VelocityBrigadierMessage) {
tooltip = ((VelocityBrigadierMessage) suggestion.getTooltip()).asComponent(); tooltip = ((VelocityBrigadierMessage) suggestion.getTooltip()).asComponent();
} }
response.getOffers().add(new Offer(offer, tooltip)); response.getOffers().add(new Offer(offer, tooltip));
@ -703,15 +628,12 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
response.getOffers().sort(null); response.getOffers().sort(null);
player.getConnection().write(response); player.getConnection().write(response);
} catch (Exception e) { } catch (Exception e) {
logger.error("Unable to provide tab list completions for {} for command '{}'", logger.error("Unable to provide tab list completions for {} for command '{}'", player.getUsername(), command,
player.getUsername(), e);
command, e);
} }
}, player.getConnection().eventLoop()) }, player.getConnection().eventLoop()).exceptionally((ex) -> {
.exceptionally((ex) -> { logger.error("Exception while finishing command tab completion, with request {} and response {}", request,
logger.error( response, ex);
"Exception while finishing command tab completion, with request {} and response {}",
request, response, ex);
return null; return null;
}); });
} }
@ -721,98 +643,19 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
for (Offer offer : response.getOffers()) { for (Offer offer : response.getOffers()) {
offers.add(offer.getText()); offers.add(offer.getText());
} }
server.getEventManager().fire(new TabCompleteEvent(player, request.getCommand(), offers)) server.getEventManager().fire(new TabCompleteEvent(player, request.getCommand(), offers)).thenAcceptAsync(e -> {
.thenAcceptAsync(e -> {
response.getOffers().clear(); response.getOffers().clear();
for (String s : e.getSuggestions()) { for (String s : e.getSuggestions()) {
response.getOffers().add(new Offer(s)); response.getOffers().add(new Offer(s));
} }
player.getConnection().write(response); player.getConnection().write(response);
}, player.getConnection().eventLoop()) }, player.getConnection().eventLoop()).exceptionally((ex) -> {
.exceptionally((ex) -> { logger.error("Exception while finishing regular tab completion, with request {} and response{}", request,
logger.error( response, ex);
"Exception while finishing regular tab completion, with request {} and response{}",
request, response, ex);
return null; return null;
}); });
} }
private CompletableFuture<MinecraftPacket> processCommandExecuteResult(String originalCommand,
CommandResult result,
@Nullable SignedChatCommand signedCommand,
Instant passedTimestamp) {
IdentifiedKey playerKey = player.getIdentifiedKey();
if (result == CommandResult.denied()) {
if (playerKey != null) {
if (signedCommand != null && playerKey.getKeyRevision()
.compareTo(IdentifiedKey.Revision.LINKED_V2) >= 0) {
logger.fatal("A plugin tried to deny a command with signable component(s). "
+ "This is not supported. "
+ "Disconnecting player " + player.getUsername());
player.disconnect(Component.text("A proxy plugin caused an illegal protocol state. "
+ "Contact your network administrator."));
}
}
return CompletableFuture.completedFuture(null);
}
String commandToRun = result.getCommand().orElse(originalCommand);
if (result.isForwardToServer()) {
ChatBuilder write = ChatBuilder
.builder(player.getProtocolVersion())
.timestamp(passedTimestamp)
.asPlayer(player);
if (signedCommand != null && commandToRun.equals(signedCommand.getBaseCommand())) {
write.message(signedCommand);
} else {
if (signedCommand != null && playerKey != null && playerKey.getKeyRevision()
.compareTo(IdentifiedKey.Revision.LINKED_V2) >= 0) {
logger.fatal("A plugin tried to change a command with signed component(s). "
+ "This is not supported. "
+ "Disconnecting player " + player.getUsername());
player.disconnect(Component.text("A proxy plugin caused an illegal protocol state. "
+ "Contact your network administrator."));
return CompletableFuture.completedFuture(null);
}
write.message("/" + commandToRun);
}
return CompletableFuture.completedFuture(write.toServer());
} else {
return server.getCommandManager().executeImmediatelyAsync(player, commandToRun)
.thenApply(hasRun -> {
if (!hasRun) {
ChatBuilder write = ChatBuilder
.builder(player.getProtocolVersion())
.timestamp(passedTimestamp)
.asPlayer(player);
if (signedCommand != null && commandToRun.equals(signedCommand.getBaseCommand())) {
write.message(signedCommand);
} else {
if (signedCommand != null && playerKey != null && playerKey.getKeyRevision()
.compareTo(IdentifiedKey.Revision.LINKED_V2) >= 0) {
logger.fatal("A plugin tried to change a command with signed component(s). "
+ "This is not supported. "
+ "Disconnecting player " + player.getUsername());
player.disconnect(Component.text("A proxy plugin caused an illegal protocol state. "
+ "Contact your network administrator."));
return null;
}
write.message("/" + commandToRun);
}
return write.toServer();
}
return null;
});
}
}
private void handleCommandForward() {
}
/** /**
* Immediately send any queued messages to the server. * Immediately send any queued messages to the server.
*/ */

Datei anzeigen

@ -65,11 +65,14 @@ import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter;
import com.velocitypowered.proxy.protocol.packet.KeepAlive; import com.velocitypowered.proxy.protocol.packet.KeepAlive;
import com.velocitypowered.proxy.protocol.packet.PluginMessage; import com.velocitypowered.proxy.protocol.packet.PluginMessage;
import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest; import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest;
import com.velocitypowered.proxy.protocol.packet.chat.ChatBuilder;
import com.velocitypowered.proxy.protocol.packet.chat.ChatQueue; import com.velocitypowered.proxy.protocol.packet.chat.ChatQueue;
import com.velocitypowered.proxy.protocol.packet.chat.LegacyChat; import com.velocitypowered.proxy.protocol.packet.chat.ChatType;
import com.velocitypowered.proxy.protocol.packet.chat.builder.ChatBuilderFactory;
import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyChat;
import com.velocitypowered.proxy.protocol.packet.title.GenericTitlePacket; import com.velocitypowered.proxy.protocol.packet.title.GenericTitlePacket;
import com.velocitypowered.proxy.server.VelocityRegisteredServer; import com.velocitypowered.proxy.server.VelocityRegisteredServer;
import com.velocitypowered.proxy.tablist.InternalTabList;
import com.velocitypowered.proxy.tablist.KeyedVelocityTabList;
import com.velocitypowered.proxy.tablist.VelocityTabList; import com.velocitypowered.proxy.tablist.VelocityTabList;
import com.velocitypowered.proxy.tablist.VelocityTabListLegacy; import com.velocitypowered.proxy.tablist.VelocityTabListLegacy;
import com.velocitypowered.proxy.util.ClosestLocaleMatcher; import com.velocitypowered.proxy.util.ClosestLocaleMatcher;
@ -145,7 +148,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
private @Nullable ModInfo modInfo; private @Nullable ModInfo modInfo;
private Component playerListHeader = Component.empty(); private Component playerListHeader = Component.empty();
private Component playerListFooter = Component.empty(); private Component playerListFooter = Component.empty();
private final VelocityTabList tabList; private final InternalTabList tabList;
private final VelocityServer server; private final VelocityServer server;
private ClientConnectionPhase connectionPhase; private ClientConnectionPhase connectionPhase;
private final Collection<String> knownChannels; private final Collection<String> knownChannels;
@ -166,7 +169,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
private @Nullable String clientBrand; private @Nullable String clientBrand;
private @Nullable Locale effectiveLocale; private @Nullable Locale effectiveLocale;
private @Nullable IdentifiedKey playerKey; private @Nullable IdentifiedKey playerKey;
private ChatQueue chatQueue; private final ChatQueue chatQueue;
private final ChatBuilderFactory chatBuilderFactory;
ConnectedPlayer(VelocityServer server, GameProfile profile, MinecraftConnection connection, ConnectedPlayer(VelocityServer server, GameProfile profile, MinecraftConnection connection,
@Nullable InetSocketAddress virtualHost, boolean onlineMode, @Nullable IdentifiedKey playerKey) { @Nullable InetSocketAddress virtualHost, boolean onlineMode, @Nullable IdentifiedKey playerKey) {
@ -179,16 +183,23 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
this.knownChannels = CappedSet.create(MAX_PLUGIN_CHANNELS); this.knownChannels = CappedSet.create(MAX_PLUGIN_CHANNELS);
this.onlineMode = onlineMode; this.onlineMode = onlineMode;
if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) { if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19_3) >= 0) {
this.tabList = new VelocityTabList(this, server); this.tabList = new VelocityTabList(this);
} else if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
this.tabList = new KeyedVelocityTabList(this, server);
} else { } else {
this.tabList = new VelocityTabListLegacy(this, server); this.tabList = new VelocityTabListLegacy(this, server);
} }
this.playerKey = playerKey; this.playerKey = playerKey;
this.chatQueue = new ChatQueue(this); this.chatQueue = new ChatQueue(this);
this.chatBuilderFactory = new ChatBuilderFactory(this.getProtocolVersion());
} }
ChatQueue getChatQueue() { public ChatBuilderFactory getChatBuilderFactory() {
return chatBuilderFactory;
}
public ChatQueue getChatQueue() {
return chatQueue; return chatQueue;
} }
@ -327,7 +338,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
public void sendMessage(@NonNull Identity identity, @NonNull Component message) { public void sendMessage(@NonNull Identity identity, @NonNull Component message) {
Component translated = translateMessage(message); Component translated = translateMessage(message);
connection.write(ChatBuilder.builder(this.getProtocolVersion()) connection.write(getChatBuilderFactory().builder()
.component(translated).forIdentity(identity).toClient()); .component(translated).forIdentity(identity).toClient());
} }
@ -339,9 +350,9 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
Component translated = translateMessage(message); Component translated = translateMessage(message);
connection.write(ChatBuilder.builder(this.getProtocolVersion()) connection.write(getChatBuilderFactory().builder()
.component(translated).forIdentity(identity) .component(translated).forIdentity(identity)
.setType(type == MessageType.CHAT ? ChatBuilder.ChatType.CHAT : ChatBuilder.ChatType.SYSTEM) .setType(type == MessageType.CHAT ? ChatType.CHAT : ChatType.SYSTEM)
.toClient()); .toClient());
} }
@ -525,7 +536,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
} }
@Override @Override
public VelocityTabList getTabList() { public InternalTabList getTabList() {
return tabList; return tabList;
} }
@ -913,13 +924,13 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
"input cannot be greater than " + LegacyChat.MAX_SERVERBOUND_MESSAGE_LENGTH "input cannot be greater than " + LegacyChat.MAX_SERVERBOUND_MESSAGE_LENGTH
+ " characters in length"); + " characters in length");
if (getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) { if (getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
this.chatQueue.hijack(ChatBuilder.builder(getProtocolVersion()).asPlayer(this).message(input), this.chatQueue.hijack(getChatBuilderFactory().builder().asPlayer(this).message(input),
(instant, item) -> { (instant, item) -> {
item.timestamp(instant); item.setTimestamp(instant);
return item.toServer(); return item.toServer();
}); });
} else { } else {
ensureBackendConnection().write(ChatBuilder.builder(getProtocolVersion()) ensureBackendConnection().write(getChatBuilderFactory().builder()
.asPlayer(this).message(input).toServer()); .asPlayer(this).message(input).toServer());
} }
} }

Datei anzeigen

@ -25,15 +25,11 @@ import static com.velocitypowered.proxy.crypto.EncryptionUtils.generateServerId;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.primitives.Longs; import com.google.common.primitives.Longs;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.stream.JsonReader;
import com.velocitypowered.api.event.connection.PreLoginEvent; import com.velocitypowered.api.event.connection.PreLoginEvent;
import com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult; import com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey; import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.api.util.UuidUtils;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
@ -50,7 +46,6 @@ import java.security.KeyPair;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.util.Arrays; import java.util.Arrays;
import java.util.Optional; import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
@ -60,7 +55,6 @@ import org.apache.logging.log4j.Logger;
import org.asynchttpclient.ListenableFuture; import org.asynchttpclient.ListenableFuture;
import org.asynchttpclient.Response; import org.asynchttpclient.Response;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
public class InitialLoginSessionHandler implements MinecraftSessionHandler { public class InitialLoginSessionHandler implements MinecraftSessionHandler {
@ -111,7 +105,8 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
return true; return true;
} }
} else if (mcConnection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0 } else if (mcConnection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0
&& forceKeyAuthentication) { && forceKeyAuthentication
&& mcConnection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19_3) < 0) {
inbound.disconnect(Component.translatable("multiplayer.disconnect.missing_public_key")); inbound.disconnect(Component.translatable("multiplayer.disconnect.missing_public_key"));
return true; return true;
} }

Datei anzeigen

@ -1,119 +0,0 @@
/*
* Copyright (C) 2018 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.crypto;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.crypto.SignedMessage;
import com.velocitypowered.proxy.util.except.QuietDecoderException;
import java.security.PublicKey;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.TemporalAmount;
import java.util.UUID;
import org.checkerframework.checker.nullness.qual.Nullable;
public class SignedChatMessage implements SignedMessage {
private static final QuietDecoderException INVALID_SIGNED_CHAT =
new QuietDecoderException("Couldn't parse chat message");
public static final TemporalAmount EXPIRY_TIME = Duration.ofMinutes(2L);
private final String message;
private final PublicKey signer;
private final byte[] signature;
private final Instant expiry;
private final byte[] salt;
private final UUID sender;
//private final boolean isValid;
private final SignaturePair[] previousSignatures;
private final @Nullable SignaturePair previousSignature;
private final boolean isPreviewSigned;
/**
* Create a signed message from data.
*/
public SignedChatMessage(String message, PublicKey signer, UUID sender,
Instant expiry, byte[] signature, byte[] salt,
boolean isPreviewSigned, @Nullable SignaturePair[] previousSignatures,
@Nullable SignaturePair previousSignature) {
this.message = Preconditions.checkNotNull(message);
this.signer = Preconditions.checkNotNull(signer);
this.sender = Preconditions.checkNotNull(sender);
this.signature = Preconditions.checkNotNull(signature);
this.expiry = Preconditions.checkNotNull(expiry);
this.salt = Preconditions.checkNotNull(salt);
this.isPreviewSigned = isPreviewSigned;
this.previousSignatures = previousSignatures;
this.previousSignature = previousSignature;
//this.isValid = EncryptionUtils.verifySignature(EncryptionUtils.SHA1_WITH_RSA, signer,
// signature, salt, EncryptionUtils.longToBigEndianByteArray(
// sender.getMostSignificantBits(), sender.getLeastSignificantBits()
// ), Longs.toByteArray(expiry.getEpochSecond()), message.getBytes(StandardCharsets.UTF_8));
}
@Override
public PublicKey getSigner() {
return signer;
}
@Override
public Instant getExpiryTemporal() {
return expiry;
}
@Override
public @Nullable byte[] getSignature() {
return signature;
}
public SignaturePair[] getPreviousSignatures() {
return previousSignatures;
}
public SignaturePair getPreviousSignature() {
return previousSignature;
}
//@Override
//public boolean isSignatureValid() {
// return isValid;
//}
@Override
public String getMessage() {
return message;
}
@Override
public UUID getSignerUuid() {
return sender;
}
@Override
public boolean isPreviewSigned() {
return isPreviewSigned;
}
@Override
public byte[] getSalt() {
return salt;
}
}

Datei anzeigen

@ -31,6 +31,7 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_18;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_18_2; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_18_2;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_1; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_1;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_3;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_7_2; 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_8;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9;
@ -50,10 +51,11 @@ import com.velocitypowered.proxy.protocol.packet.Handshake;
import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter; import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter;
import com.velocitypowered.proxy.protocol.packet.JoinGame; import com.velocitypowered.proxy.protocol.packet.JoinGame;
import com.velocitypowered.proxy.protocol.packet.KeepAlive; import com.velocitypowered.proxy.protocol.packet.KeepAlive;
import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem;
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage; import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage;
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse; import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
import com.velocitypowered.proxy.protocol.packet.PlayerListItem;
import com.velocitypowered.proxy.protocol.packet.PluginMessage; import com.velocitypowered.proxy.protocol.packet.PluginMessage;
import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfo;
import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest; import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest;
import com.velocitypowered.proxy.protocol.packet.ResourcePackResponse; import com.velocitypowered.proxy.protocol.packet.ResourcePackResponse;
import com.velocitypowered.proxy.protocol.packet.Respawn; import com.velocitypowered.proxy.protocol.packet.Respawn;
@ -66,11 +68,14 @@ import com.velocitypowered.proxy.protocol.packet.StatusRequest;
import com.velocitypowered.proxy.protocol.packet.StatusResponse; import com.velocitypowered.proxy.protocol.packet.StatusResponse;
import com.velocitypowered.proxy.protocol.packet.TabCompleteRequest; import com.velocitypowered.proxy.protocol.packet.TabCompleteRequest;
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse; import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse;
import com.velocitypowered.proxy.protocol.packet.chat.LegacyChat; import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo;
import com.velocitypowered.proxy.protocol.packet.chat.PlayerChat;
import com.velocitypowered.proxy.protocol.packet.chat.PlayerChatCompletion; import com.velocitypowered.proxy.protocol.packet.chat.PlayerChatCompletion;
import com.velocitypowered.proxy.protocol.packet.chat.PlayerCommand;
import com.velocitypowered.proxy.protocol.packet.chat.SystemChat; import com.velocitypowered.proxy.protocol.packet.chat.SystemChat;
import com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedPlayerChat;
import com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedPlayerCommand;
import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyChat;
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerChat;
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerCommand;
import com.velocitypowered.proxy.protocol.packet.title.LegacyTitlePacket; import com.velocitypowered.proxy.protocol.packet.title.LegacyTitlePacket;
import com.velocitypowered.proxy.protocol.packet.title.TitleActionbarPacket; import com.velocitypowered.proxy.protocol.packet.title.TitleActionbarPacket;
import com.velocitypowered.proxy.protocol.packet.title.TitleClearPacket; import com.velocitypowered.proxy.protocol.packet.title.TitleClearPacket;
@ -124,19 +129,24 @@ public enum StateRegistry {
map(0x05, MINECRAFT_1_13, false), map(0x05, MINECRAFT_1_13, false),
map(0x06, MINECRAFT_1_14, false), map(0x06, MINECRAFT_1_14, false),
map(0x08, MINECRAFT_1_19, false), map(0x08, MINECRAFT_1_19, false),
map(0x09, MINECRAFT_1_19_1, false)); map(0x09, MINECRAFT_1_19_1, false),
map(0x08, MINECRAFT_1_19_3, false));
serverbound.register(LegacyChat.class, LegacyChat::new, serverbound.register(LegacyChat.class, LegacyChat::new,
map(0x01, MINECRAFT_1_7_2, false), map(0x01, MINECRAFT_1_7_2, false),
map(0x02, MINECRAFT_1_9, false), map(0x02, MINECRAFT_1_9, false),
map(0x03, MINECRAFT_1_12, false), map(0x03, MINECRAFT_1_12, false),
map(0x02, MINECRAFT_1_12_1, false), map(0x02, MINECRAFT_1_12_1, false),
map(0x03, MINECRAFT_1_14, MINECRAFT_1_18_2, false)); map(0x03, MINECRAFT_1_14, MINECRAFT_1_18_2, false));
serverbound.register(PlayerCommand.class, PlayerCommand::new, serverbound.register(KeyedPlayerCommand.class, KeyedPlayerCommand::new,
map(0x03, MINECRAFT_1_19, false), map(0x03, MINECRAFT_1_19, false),
map(0x04, MINECRAFT_1_19_1, false)); map(0x04, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false));
serverbound.register(PlayerChat.class, PlayerChat::new, serverbound.register(KeyedPlayerChat.class, KeyedPlayerChat::new,
map(0x04, MINECRAFT_1_19, false), map(0x04, MINECRAFT_1_19, false),
map(0x05, MINECRAFT_1_19_1, false)); map(0x05, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false));
serverbound.register(SessionPlayerCommand.class, SessionPlayerCommand::new,
map(0x04, MINECRAFT_1_19_3, false));
serverbound.register(SessionPlayerChat.class, SessionPlayerChat::new,
map(0x05, MINECRAFT_1_19_3, false));
serverbound.register(ClientSettings.class, ClientSettings::new, serverbound.register(ClientSettings.class, ClientSettings::new,
map(0x15, MINECRAFT_1_7_2, false), map(0x15, MINECRAFT_1_7_2, false),
map(0x04, MINECRAFT_1_9, false), map(0x04, MINECRAFT_1_9, false),
@ -144,7 +154,8 @@ public enum StateRegistry {
map(0x04, MINECRAFT_1_12_1, false), map(0x04, MINECRAFT_1_12_1, false),
map(0x05, MINECRAFT_1_14, false), map(0x05, MINECRAFT_1_14, false),
map(0x07, MINECRAFT_1_19, false), map(0x07, MINECRAFT_1_19, false),
map(0x08, MINECRAFT_1_19_1, false)); map(0x08, MINECRAFT_1_19_1, false),
map(0x07, MINECRAFT_1_19_3, false));
serverbound.register(PluginMessage.class, PluginMessage::new, serverbound.register(PluginMessage.class, PluginMessage::new,
map(0x17, MINECRAFT_1_7_2, false), map(0x17, MINECRAFT_1_7_2, false),
map(0x09, MINECRAFT_1_9, false), map(0x09, MINECRAFT_1_9, false),
@ -154,7 +165,8 @@ public enum StateRegistry {
map(0x0B, MINECRAFT_1_14, false), map(0x0B, MINECRAFT_1_14, false),
map(0x0A, MINECRAFT_1_17, false), map(0x0A, MINECRAFT_1_17, false),
map(0x0C, MINECRAFT_1_19, false), map(0x0C, MINECRAFT_1_19, false),
map(0x0D, MINECRAFT_1_19_1, false)); map(0x0D, MINECRAFT_1_19_1, false),
map(0x0C, MINECRAFT_1_19_3, false));
serverbound.register(KeepAlive.class, KeepAlive::new, serverbound.register(KeepAlive.class, KeepAlive::new,
map(0x00, MINECRAFT_1_7_2, false), map(0x00, MINECRAFT_1_7_2, false),
map(0x0B, MINECRAFT_1_9, false), map(0x0B, MINECRAFT_1_9, false),
@ -165,7 +177,8 @@ public enum StateRegistry {
map(0x10, MINECRAFT_1_16, false), map(0x10, MINECRAFT_1_16, false),
map(0x0F, MINECRAFT_1_17, false), map(0x0F, MINECRAFT_1_17, false),
map(0x11, MINECRAFT_1_19, false), map(0x11, MINECRAFT_1_19, false),
map(0x12, MINECRAFT_1_19_1, false)); map(0x12, MINECRAFT_1_19_1, false),
map(0x11, MINECRAFT_1_19_3, false));
serverbound.register(ResourcePackResponse.class, ResourcePackResponse::new, serverbound.register(ResourcePackResponse.class, ResourcePackResponse::new,
map(0x19, MINECRAFT_1_8, false), map(0x19, MINECRAFT_1_8, false),
map(0x16, MINECRAFT_1_9, false), map(0x16, MINECRAFT_1_9, false),
@ -198,14 +211,16 @@ public enum StateRegistry {
map(0x10, MINECRAFT_1_16, false), map(0x10, MINECRAFT_1_16, false),
map(0x0F, MINECRAFT_1_16_2, false), map(0x0F, MINECRAFT_1_16_2, false),
map(0x11, MINECRAFT_1_17, false), map(0x11, MINECRAFT_1_17, false),
map(0x0E, MINECRAFT_1_19, false)); map(0x0E, MINECRAFT_1_19, false),
map(0x0D, MINECRAFT_1_19_3, false));
clientbound.register(AvailableCommands.class, AvailableCommands::new, clientbound.register(AvailableCommands.class, AvailableCommands::new,
map(0x11, MINECRAFT_1_13, false), map(0x11, MINECRAFT_1_13, false),
map(0x12, MINECRAFT_1_15, false), map(0x12, MINECRAFT_1_15, false),
map(0x11, MINECRAFT_1_16, false), map(0x11, MINECRAFT_1_16, false),
map(0x10, MINECRAFT_1_16_2, false), map(0x10, MINECRAFT_1_16_2, false),
map(0x12, MINECRAFT_1_17, false), map(0x12, MINECRAFT_1_17, false),
map(0x0F, MINECRAFT_1_19, false)); map(0x0F, MINECRAFT_1_19, false),
map(0x0E, MINECRAFT_1_19_3, false));
clientbound.register(PluginMessage.class, PluginMessage::new, clientbound.register(PluginMessage.class, PluginMessage::new,
map(0x3F, MINECRAFT_1_7_2, false), map(0x3F, MINECRAFT_1_7_2, false),
map(0x18, MINECRAFT_1_9, false), map(0x18, MINECRAFT_1_9, false),
@ -216,7 +231,8 @@ public enum StateRegistry {
map(0x17, MINECRAFT_1_16_2, false), map(0x17, MINECRAFT_1_16_2, false),
map(0x18, MINECRAFT_1_17, false), map(0x18, MINECRAFT_1_17, false),
map(0x15, MINECRAFT_1_19, false), map(0x15, MINECRAFT_1_19, false),
map(0x16, MINECRAFT_1_19_1, false)); map(0x16, MINECRAFT_1_19_1, false),
map(0x15, MINECRAFT_1_19_3, false));
clientbound.register(Disconnect.class, Disconnect::new, clientbound.register(Disconnect.class, Disconnect::new,
map(0x40, MINECRAFT_1_7_2, false), map(0x40, MINECRAFT_1_7_2, false),
map(0x1A, MINECRAFT_1_9, false), map(0x1A, MINECRAFT_1_9, false),
@ -227,7 +243,8 @@ public enum StateRegistry {
map(0x19, MINECRAFT_1_16_2, false), map(0x19, MINECRAFT_1_16_2, false),
map(0x1A, MINECRAFT_1_17, false), map(0x1A, MINECRAFT_1_17, false),
map(0x17, MINECRAFT_1_19, false), map(0x17, MINECRAFT_1_19, false),
map(0x19, MINECRAFT_1_19_1, false)); map(0x19, MINECRAFT_1_19_1, false),
map(0x17, MINECRAFT_1_19_3, false));
clientbound.register(KeepAlive.class, KeepAlive::new, clientbound.register(KeepAlive.class, KeepAlive::new,
map(0x00, MINECRAFT_1_7_2, false), map(0x00, MINECRAFT_1_7_2, false),
map(0x1F, MINECRAFT_1_9, false), map(0x1F, MINECRAFT_1_9, false),
@ -238,7 +255,8 @@ public enum StateRegistry {
map(0x1F, MINECRAFT_1_16_2, false), map(0x1F, MINECRAFT_1_16_2, false),
map(0x21, MINECRAFT_1_17, false), map(0x21, MINECRAFT_1_17, false),
map(0x1E, MINECRAFT_1_19, false), map(0x1E, MINECRAFT_1_19, false),
map(0x20, MINECRAFT_1_19_1, false)); map(0x20, MINECRAFT_1_19_1, false),
map(0x1F, MINECRAFT_1_19_3, false));
clientbound.register(JoinGame.class, JoinGame::new, clientbound.register(JoinGame.class, JoinGame::new,
map(0x01, MINECRAFT_1_7_2, false), map(0x01, MINECRAFT_1_7_2, false),
map(0x23, MINECRAFT_1_9, false), map(0x23, MINECRAFT_1_9, false),
@ -249,7 +267,8 @@ public enum StateRegistry {
map(0x24, MINECRAFT_1_16_2, false), map(0x24, MINECRAFT_1_16_2, false),
map(0x26, MINECRAFT_1_17, false), map(0x26, MINECRAFT_1_17, false),
map(0x23, MINECRAFT_1_19, false), map(0x23, MINECRAFT_1_19, false),
map(0x25, MINECRAFT_1_19_1, false)); map(0x25, MINECRAFT_1_19_1, false),
map(0x24, MINECRAFT_1_19_3, false));
clientbound.register(Respawn.class, Respawn::new, clientbound.register(Respawn.class, Respawn::new,
map(0x07, MINECRAFT_1_7_2, true), map(0x07, MINECRAFT_1_7_2, true),
map(0x33, MINECRAFT_1_9, true), map(0x33, MINECRAFT_1_9, true),
@ -262,7 +281,8 @@ public enum StateRegistry {
map(0x39, MINECRAFT_1_16_2, true), map(0x39, MINECRAFT_1_16_2, true),
map(0x3D, MINECRAFT_1_17, true), map(0x3D, MINECRAFT_1_17, true),
map(0x3B, MINECRAFT_1_19, true), map(0x3B, MINECRAFT_1_19, true),
map(0x3E, MINECRAFT_1_19_1, true)); map(0x3E, MINECRAFT_1_19_1, true),
map(0x3D, MINECRAFT_1_19_3, true));
clientbound.register(ResourcePackRequest.class, ResourcePackRequest::new, clientbound.register(ResourcePackRequest.class, ResourcePackRequest::new,
map(0x48, MINECRAFT_1_8, false), map(0x48, MINECRAFT_1_8, false),
map(0x32, MINECRAFT_1_9, false), map(0x32, MINECRAFT_1_9, false),
@ -275,7 +295,8 @@ public enum StateRegistry {
map(0x38, MINECRAFT_1_16_2, false), map(0x38, MINECRAFT_1_16_2, false),
map(0x3C, MINECRAFT_1_17, false), map(0x3C, MINECRAFT_1_17, false),
map(0x3A, MINECRAFT_1_19, false), map(0x3A, MINECRAFT_1_19, false),
map(0x3D, MINECRAFT_1_19_1, false)); map(0x3D, MINECRAFT_1_19_1, false),
map(0x3C, MINECRAFT_1_19_3, false));
clientbound.register(HeaderAndFooter.class, HeaderAndFooter::new, clientbound.register(HeaderAndFooter.class, HeaderAndFooter::new,
map(0x47, MINECRAFT_1_8, true), map(0x47, MINECRAFT_1_8, true),
map(0x48, MINECRAFT_1_9, true), map(0x48, MINECRAFT_1_9, true),
@ -289,7 +310,8 @@ public enum StateRegistry {
map(0x5E, MINECRAFT_1_17, true), map(0x5E, MINECRAFT_1_17, true),
map(0x5F, MINECRAFT_1_18, true), map(0x5F, MINECRAFT_1_18, true),
map(0x60, MINECRAFT_1_19, true), map(0x60, MINECRAFT_1_19, true),
map(0x63, MINECRAFT_1_19_1, true)); map(0x63, MINECRAFT_1_19_1, true),
map(0x61, MINECRAFT_1_19_3, true));
clientbound.register(LegacyTitlePacket.class, LegacyTitlePacket::new, clientbound.register(LegacyTitlePacket.class, LegacyTitlePacket::new,
map(0x45, MINECRAFT_1_8, true), map(0x45, MINECRAFT_1_8, true),
map(0x45, MINECRAFT_1_9, true), map(0x45, MINECRAFT_1_9, true),
@ -302,23 +324,28 @@ public enum StateRegistry {
clientbound.register(TitleSubtitlePacket.class, TitleSubtitlePacket::new, clientbound.register(TitleSubtitlePacket.class, TitleSubtitlePacket::new,
map(0x57, MINECRAFT_1_17, true), map(0x57, MINECRAFT_1_17, true),
map(0x58, MINECRAFT_1_18, true), map(0x58, MINECRAFT_1_18, true),
map(0x5B, MINECRAFT_1_19_1, true)); map(0x5B, MINECRAFT_1_19_1, true),
map(0x59, MINECRAFT_1_19_3, true));
clientbound.register(TitleTextPacket.class, TitleTextPacket::new, clientbound.register(TitleTextPacket.class, TitleTextPacket::new,
map(0x59, MINECRAFT_1_17, true), map(0x59, MINECRAFT_1_17, true),
map(0x5A, MINECRAFT_1_18, true), map(0x5A, MINECRAFT_1_18, true),
map(0x5D, MINECRAFT_1_19_1, true)); map(0x5D, MINECRAFT_1_19_1, true),
map(0x5B, MINECRAFT_1_19_3, true));
clientbound.register(TitleActionbarPacket.class, TitleActionbarPacket::new, clientbound.register(TitleActionbarPacket.class, TitleActionbarPacket::new,
map(0x41, MINECRAFT_1_17, true), map(0x41, MINECRAFT_1_17, true),
map(0x40, MINECRAFT_1_19, true), map(0x40, MINECRAFT_1_19, true),
map(0x43, MINECRAFT_1_19_1, true)); map(0x43, MINECRAFT_1_19_1, true),
map(0x42, MINECRAFT_1_19_3, true));
clientbound.register(TitleTimesPacket.class, TitleTimesPacket::new, clientbound.register(TitleTimesPacket.class, TitleTimesPacket::new,
map(0x5A, MINECRAFT_1_17, true), map(0x5A, MINECRAFT_1_17, true),
map(0x5B, MINECRAFT_1_18, true), map(0x5B, MINECRAFT_1_18, true),
map(0x5E, MINECRAFT_1_19_1, true)); map(0x5E, MINECRAFT_1_19_1, true),
map(0x5A, MINECRAFT_1_19_3, true));
clientbound.register(TitleClearPacket.class, TitleClearPacket::new, clientbound.register(TitleClearPacket.class, TitleClearPacket::new,
map(0x10, MINECRAFT_1_17, true), map(0x10, MINECRAFT_1_17, true),
map(0x0D, MINECRAFT_1_19, true)); map(0x0D, MINECRAFT_1_19, true),
clientbound.register(PlayerListItem.class, PlayerListItem::new, map(0x0C, MINECRAFT_1_19_3, true));
clientbound.register(LegacyPlayerListItem.class, LegacyPlayerListItem::new,
map(0x38, MINECRAFT_1_7_2, false), map(0x38, MINECRAFT_1_7_2, false),
map(0x2D, MINECRAFT_1_9, false), map(0x2D, MINECRAFT_1_9, false),
map(0x2E, MINECRAFT_1_12_1, false), map(0x2E, MINECRAFT_1_12_1, false),
@ -329,15 +356,22 @@ public enum StateRegistry {
map(0x32, MINECRAFT_1_16_2, false), map(0x32, MINECRAFT_1_16_2, false),
map(0x36, MINECRAFT_1_17, false), map(0x36, MINECRAFT_1_17, false),
map(0x34, MINECRAFT_1_19, false), map(0x34, MINECRAFT_1_19, false),
map(0x37, MINECRAFT_1_19_1, false)); map(0x37, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false));
clientbound.register(RemovePlayerInfo.class, RemovePlayerInfo::new,
map(0x35, MINECRAFT_1_19_3, false));
clientbound.register(UpsertPlayerInfo.class, UpsertPlayerInfo::new,
map(0x36, MINECRAFT_1_19_3, false));
clientbound.register(SystemChat.class, SystemChat::new, clientbound.register(SystemChat.class, SystemChat::new,
map(0x5F, MINECRAFT_1_19, true), map(0x5F, MINECRAFT_1_19, true),
map(0x62, MINECRAFT_1_19_1, true)); map(0x62, MINECRAFT_1_19_1, true),
map(0x60, MINECRAFT_1_19_3, true));
clientbound.register(PlayerChatCompletion.class, PlayerChatCompletion::new, clientbound.register(PlayerChatCompletion.class, PlayerChatCompletion::new,
StateRegistry.map(0x15, MINECRAFT_1_19_1, true)); map(0x15, MINECRAFT_1_19_1, true),
map(0x14, MINECRAFT_1_19_3, true));
clientbound.register(ServerData.class, ServerData::new, clientbound.register(ServerData.class, ServerData::new,
map(0x3F, MINECRAFT_1_19, false), map(0x3F, MINECRAFT_1_19, false),
map(0x42, MINECRAFT_1_19_1, false)); map(0x42, MINECRAFT_1_19_1, false),
map(0x41, MINECRAFT_1_19_3, false));
} }
}, },
LOGIN { LOGIN {

Datei anzeigen

@ -67,6 +67,7 @@ public class EncryptionResponse implements MinecraftPacket {
this.sharedSecret = ProtocolUtils.readByteArray(buf, 128); this.sharedSecret = ProtocolUtils.readByteArray(buf, 128);
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0 if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0
&& version.compareTo(ProtocolVersion.MINECRAFT_1_19_3) < 0
&& !buf.readBoolean()) { && !buf.readBoolean()) {
salt = buf.readLong(); salt = buf.readLong();
} }
@ -83,7 +84,8 @@ public class EncryptionResponse implements MinecraftPacket {
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
ProtocolUtils.writeByteArray(buf, sharedSecret); ProtocolUtils.writeByteArray(buf, sharedSecret);
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0
&& version.compareTo(ProtocolVersion.MINECRAFT_1_19_3) < 0) {
if (salt != null) { if (salt != null) {
buf.writeBoolean(false); buf.writeBoolean(false);
buf.writeLong(salt); buf.writeLong(salt);
@ -108,6 +110,9 @@ public class EncryptionResponse implements MinecraftPacket {
// It turns out these come out to the same length, whether we're talking >=1.8 or not. // It turns out these come out to the same length, whether we're talking >=1.8 or not.
// The length prefix always winds up being 2 bytes. // The length prefix always winds up being 2 bytes.
int base = 256 + 2 + 2; int base = 256 + 2 + 2;
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_3) >= 0) {
return base + 128;
}
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
// Verify token is twice as long on 1.19+ // Verify token is twice as long on 1.19+
// Additional 1 byte for left <> right and 8 bytes for salt // Additional 1 byte for left <> right and 8 bytes for salt

Datei anzeigen

@ -33,7 +33,7 @@ import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
public class PlayerListItem implements MinecraftPacket { public class LegacyPlayerListItem implements MinecraftPacket {
public static final int ADD_PLAYER = 0; public static final int ADD_PLAYER = 0;
public static final int UPDATE_GAMEMODE = 1; public static final int UPDATE_GAMEMODE = 1;
@ -43,12 +43,12 @@ public class PlayerListItem implements MinecraftPacket {
private int action; private int action;
private final List<Item> items = new ArrayList<>(); private final List<Item> items = new ArrayList<>();
public PlayerListItem(int action, List<Item> items) { public LegacyPlayerListItem(int action, List<Item> items) {
this.action = action; this.action = action;
this.items.addAll(items); this.items.addAll(items);
} }
public PlayerListItem() { public LegacyPlayerListItem() {
} }
public int getAction() { public int getAction() {

Datei anzeigen

@ -15,46 +15,52 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.velocitypowered.proxy.protocol.packet.chat; package com.velocitypowered.proxy.protocol.packet;
import com.google.common.collect.Lists;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import net.kyori.adventure.text.Component; import java.util.ArrayList;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import java.util.Collection;
import org.checkerframework.checker.nullness.qual.Nullable; import java.util.UUID;
public class ServerChatPreview implements MinecraftPacket { public class RemovePlayerInfo implements MinecraftPacket {
private Collection<UUID> profilesToRemove;
private int id; public RemovePlayerInfo() {
private @Nullable Component preview; this.profilesToRemove = new ArrayList<>();
public Component getPreview() {
return preview;
} }
public int getId() { public RemovePlayerInfo(Collection<UUID> profilesToRemove) {
return id; this.profilesToRemove = profilesToRemove;
}
public Collection<UUID> getProfilesToRemove() {
return profilesToRemove;
}
public void setProfilesToRemove(Collection<UUID> profilesToRemove) {
this.profilesToRemove = profilesToRemove;
} }
@Override @Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
id = buf.readInt(); int length = ProtocolUtils.readVarInt(buf);
if (buf.readBoolean()) { Collection<UUID> profilesToRemove = Lists.newArrayListWithCapacity(length);
preview = GsonComponentSerializer.gson().deserialize(ProtocolUtils.readString(buf)); for (int idx = 0; idx < length; idx++) {
profilesToRemove.add(ProtocolUtils.readUuid(buf));
} }
this.profilesToRemove = profilesToRemove;
} }
@Override @Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
buf.writeInt(id); ProtocolUtils.writeVarInt(buf, this.profilesToRemove.size());
if (preview != null) { for (UUID uuid : this.profilesToRemove) {
buf.writeBoolean(true); ProtocolUtils.writeUuid(buf, uuid);
ProtocolUtils.writeString(buf, GsonComponentSerializer.gson().serialize(preview));
} else {
buf.writeBoolean(false);
} }
} }

Datei anzeigen

@ -36,7 +36,7 @@ public class Respawn implements MinecraftPacket {
private short difficulty; private short difficulty;
private short gamemode; private short gamemode;
private String levelType = ""; private String levelType = "";
private boolean shouldKeepPlayerData; // 1.16+ private byte dataToKeep; // 1.16+
private DimensionInfo dimensionInfo; // 1.16-1.16.1 private DimensionInfo dimensionInfo; // 1.16-1.16.1
private short previousGamemode; // 1.16+ private short previousGamemode; // 1.16+
private DimensionData currentDimensionData; // 1.16.2+ private DimensionData currentDimensionData; // 1.16.2+
@ -46,14 +46,15 @@ public class Respawn implements MinecraftPacket {
} }
public Respawn(int dimension, long partialHashedSeed, short difficulty, short gamemode, public Respawn(int dimension, long partialHashedSeed, short difficulty, short gamemode,
String levelType, boolean shouldKeepPlayerData, DimensionInfo dimensionInfo, String levelType, byte dataToKeep, DimensionInfo dimensionInfo,
short previousGamemode, DimensionData currentDimensionData, @Nullable Pair<String, Long> lastDeathPosition) { short previousGamemode, DimensionData currentDimensionData,
@Nullable Pair<String, Long> lastDeathPosition) {
this.dimension = dimension; this.dimension = dimension;
this.partialHashedSeed = partialHashedSeed; this.partialHashedSeed = partialHashedSeed;
this.difficulty = difficulty; this.difficulty = difficulty;
this.gamemode = gamemode; this.gamemode = gamemode;
this.levelType = levelType; this.levelType = levelType;
this.shouldKeepPlayerData = shouldKeepPlayerData; this.dataToKeep = dataToKeep;
this.dimensionInfo = dimensionInfo; this.dimensionInfo = dimensionInfo;
this.previousGamemode = previousGamemode; this.previousGamemode = previousGamemode;
this.currentDimensionData = currentDimensionData; this.currentDimensionData = currentDimensionData;
@ -63,7 +64,7 @@ public class Respawn implements MinecraftPacket {
public static Respawn fromJoinGame(JoinGame joinGame) { public static Respawn fromJoinGame(JoinGame joinGame) {
return new Respawn(joinGame.getDimension(), joinGame.getPartialHashedSeed(), return new Respawn(joinGame.getDimension(), joinGame.getPartialHashedSeed(),
joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(), joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(),
false, joinGame.getDimensionInfo(), joinGame.getPreviousGamemode(), (byte) 0, joinGame.getDimensionInfo(), joinGame.getPreviousGamemode(),
joinGame.getCurrentDimensionData(), joinGame.getLastDeathPosition()); joinGame.getCurrentDimensionData(), joinGame.getLastDeathPosition());
} }
@ -107,12 +108,12 @@ public class Respawn implements MinecraftPacket {
this.levelType = levelType; this.levelType = levelType;
} }
public boolean getShouldKeepPlayerData() { public byte getDataToKeep() {
return shouldKeepPlayerData; return dataToKeep;
} }
public void setShouldKeepPlayerData(boolean shouldKeepPlayerData) { public void setDataToKeep(byte dataToKeep) {
this.shouldKeepPlayerData = shouldKeepPlayerData; this.dataToKeep = dataToKeep;
} }
public short getPreviousGamemode() { public short getPreviousGamemode() {
@ -139,7 +140,7 @@ public class Respawn implements MinecraftPacket {
+ ", difficulty=" + difficulty + ", difficulty=" + difficulty
+ ", gamemode=" + gamemode + ", gamemode=" + gamemode
+ ", levelType='" + levelType + '\'' + ", levelType='" + levelType + '\''
+ ", shouldKeepPlayerData=" + shouldKeepPlayerData + ", dataToKeep=" + dataToKeep
+ ", dimensionRegistryName='" + dimensionInfo.toString() + '\'' + ", dimensionRegistryName='" + dimensionInfo.toString() + '\''
+ ", dimensionInfo=" + dimensionInfo + ", dimensionInfo=" + dimensionInfo
+ ", previousGamemode=" + previousGamemode + ", previousGamemode=" + previousGamemode
@ -177,7 +178,13 @@ public class Respawn implements MinecraftPacket {
boolean isDebug = buf.readBoolean(); boolean isDebug = buf.readBoolean();
boolean isFlat = buf.readBoolean(); boolean isFlat = buf.readBoolean();
this.dimensionInfo = new DimensionInfo(dimensionIdentifier, levelName, isFlat, isDebug); this.dimensionInfo = new DimensionInfo(dimensionIdentifier, levelName, isFlat, isDebug);
this.shouldKeepPlayerData = buf.readBoolean(); if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_3) >= 0) {
this.dataToKeep = buf.readByte();
} else if (buf.readBoolean()) {
this.dataToKeep = 1;
} else {
this.dataToKeep = 0;
}
} else { } else {
this.levelType = ProtocolUtils.readString(buf, 16); this.levelType = ProtocolUtils.readString(buf, 16);
} }
@ -211,7 +218,11 @@ public class Respawn implements MinecraftPacket {
buf.writeByte(previousGamemode); buf.writeByte(previousGamemode);
buf.writeBoolean(dimensionInfo.isDebugType()); buf.writeBoolean(dimensionInfo.isDebugType());
buf.writeBoolean(dimensionInfo.isFlat()); buf.writeBoolean(dimensionInfo.isFlat());
buf.writeBoolean(shouldKeepPlayerData); if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_3) >= 0) {
buf.writeByte(dataToKeep);
} else {
buf.writeBoolean(dataToKeep != 0);
}
} else { } else {
ProtocolUtils.writeString(buf, levelType); ProtocolUtils.writeString(buf, levelType);
} }

Datei anzeigen

@ -30,17 +30,15 @@ public class ServerData implements MinecraftPacket {
private @Nullable Component description; private @Nullable Component description;
private @Nullable Favicon favicon; private @Nullable Favicon favicon;
private boolean previewsChat;
private boolean secureChatEnforced; // Added in 1.19.1 private boolean secureChatEnforced; // Added in 1.19.1
public ServerData() { public ServerData() {
} }
public ServerData(@Nullable Component description, @Nullable Favicon favicon, public ServerData(@Nullable Component description, @Nullable Favicon favicon,
boolean previewsChat, boolean secureChatEnforced) { boolean secureChatEnforced) {
this.description = description; this.description = description;
this.favicon = favicon; this.favicon = favicon;
this.previewsChat = previewsChat;
this.secureChatEnforced = secureChatEnforced; this.secureChatEnforced = secureChatEnforced;
} }
@ -54,7 +52,9 @@ public class ServerData implements MinecraftPacket {
if (buf.readBoolean()) { if (buf.readBoolean()) {
this.favicon = new Favicon(ProtocolUtils.readString(buf)); this.favicon = new Favicon(ProtocolUtils.readString(buf));
} }
this.previewsChat = buf.readBoolean(); if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_3) < 0) {
buf.readBoolean();
}
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) { if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
this.secureChatEnforced = buf.readBoolean(); this.secureChatEnforced = buf.readBoolean();
} }
@ -77,7 +77,9 @@ public class ServerData implements MinecraftPacket {
ProtocolUtils.writeString(buf, favicon.getBase64Url()); ProtocolUtils.writeString(buf, favicon.getBase64Url());
} }
buf.writeBoolean(this.previewsChat); if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_3) < 0) {
buf.writeBoolean(false);
}
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) { if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
buf.writeBoolean(this.secureChatEnforced); buf.writeBoolean(this.secureChatEnforced);
} }
@ -88,18 +90,14 @@ public class ServerData implements MinecraftPacket {
return handler.handle(this); return handler.handle(this);
} }
public Component getDescription() { public @Nullable Component getDescription() {
return description; return description;
} }
public Favicon getFavicon() { public @Nullable Favicon getFavicon() {
return favicon; return favicon;
} }
public boolean isPreviewsChat() {
return previewsChat;
}
public boolean isSecureChatEnforced() { public boolean isSecureChatEnforced() {
return secureChatEnforced; return secureChatEnforced;
} }

Datei anzeigen

@ -35,7 +35,7 @@ public class ServerLogin implements MinecraftPacket {
private static final QuietDecoderException EMPTY_USERNAME = new QuietDecoderException("Empty username!"); private static final QuietDecoderException EMPTY_USERNAME = new QuietDecoderException("Empty username!");
private @Nullable String username; private @Nullable String username;
private @Nullable IdentifiedKey playerKey; // Introduced in 1.19 private @Nullable IdentifiedKey playerKey; // Introduced in 1.19.3
private @Nullable UUID holderUuid; // Used for key revision 2 private @Nullable UUID holderUuid; // Used for key revision 2
public ServerLogin() { public ServerLogin() {
@ -46,6 +46,12 @@ public class ServerLogin implements MinecraftPacket {
this.playerKey = playerKey; this.playerKey = playerKey;
} }
public ServerLogin(String username, @Nullable UUID holderUuid) {
this.username = Preconditions.checkNotNull(username, "username");
this.holderUuid = holderUuid;
this.playerKey = null;
}
public String getUsername() { public String getUsername() {
if (username == null) { if (username == null) {
throw new IllegalStateException("No username found!"); throw new IllegalStateException("No username found!");
@ -53,15 +59,15 @@ public class ServerLogin implements MinecraftPacket {
return username; return username;
} }
public IdentifiedKey getPlayerKey() { public @Nullable IdentifiedKey getPlayerKey() {
return playerKey; return this.playerKey;
} }
public void setPlayerKey(IdentifiedKey playerKey) { public void setPlayerKey(IdentifiedKey playerKey) {
this.playerKey = playerKey; this.playerKey = playerKey;
} }
public UUID getHolderUuid() { public @Nullable UUID getHolderUuid() {
return holderUuid; return holderUuid;
} }
@ -74,15 +80,21 @@ public class ServerLogin implements MinecraftPacket {
} }
@Override @Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { public void decode(ByteBuf buf, Direction direction, ProtocolVersion version) {
username = ProtocolUtils.readString(buf, 16); username = ProtocolUtils.readString(buf, 16);
if (username.isEmpty()) { if (username.isEmpty()) {
throw EMPTY_USERNAME; throw EMPTY_USERNAME;
} }
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_3) >= 0) {
playerKey = null;
} else {
if (buf.readBoolean()) { if (buf.readBoolean()) {
playerKey = ProtocolUtils.readPlayerKey(version, buf); playerKey = ProtocolUtils.readPlayerKey(version, buf);
} else {
playerKey = null;
}
} }
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
@ -90,6 +102,8 @@ public class ServerLogin implements MinecraftPacket {
holderUuid = ProtocolUtils.readUuid(buf); holderUuid = ProtocolUtils.readUuid(buf);
} }
} }
} else {
playerKey = null;
} }
} }
@ -101,17 +115,22 @@ public class ServerLogin implements MinecraftPacket {
ProtocolUtils.writeString(buf, username); ProtocolUtils.writeString(buf, username);
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_3) < 0) {
if (playerKey != null) { if (playerKey != null) {
buf.writeBoolean(true); buf.writeBoolean(true);
ProtocolUtils.writePlayerKey(buf, playerKey); ProtocolUtils.writePlayerKey(buf, playerKey);
} else { } else {
buf.writeBoolean(false); buf.writeBoolean(false);
} }
}
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
if (playerKey != null && playerKey.getSignatureHolder() != null) { if (playerKey != null && playerKey.getSignatureHolder() != null) {
buf.writeBoolean(true); buf.writeBoolean(true);
ProtocolUtils.writeUuid(buf, playerKey.getSignatureHolder()); ProtocolUtils.writeUuid(buf, playerKey.getSignatureHolder());
} else if (this.holderUuid != null) {
buf.writeBoolean(true);
ProtocolUtils.writeUuid(buf, this.holderUuid);
} else { } else {
buf.writeBoolean(false); buf.writeBoolean(false);
} }
@ -126,6 +145,7 @@ public class ServerLogin implements MinecraftPacket {
int base = 1 + (16 * 4); int base = 1 + (16 * 4);
// Adjustments for Key-authentication // Adjustments for Key-authentication
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_3) < 0) {
// + 1 for the boolean present/ not present // + 1 for the boolean present/ not present
// + 8 for the long expiry // + 8 for the long expiry
// + 2 len for varint key size // + 2 len for varint key size
@ -133,6 +153,7 @@ public class ServerLogin implements MinecraftPacket {
// + 2 len for varint signature size // + 2 len for varint signature size
// + 512 for signature // + 512 for signature
base += 1 + 8 + 2 + 294 + 2 + 512; base += 1 + 8 + 2 + 294 + 2 + 512;
}
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
// +1 boolean uuid optional // +1 boolean uuid optional
// + 2 * 8 for the long msb/lsb // + 2 * 8 for the long msb/lsb

Datei anzeigen

@ -0,0 +1,291 @@
/*
* Copyright (C) 2018 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.api.util.GameProfile;
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.RemoteChatSession;
import io.netty.buffer.ByteBuf;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
import java.util.UUID;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.Nullable;
public class UpsertPlayerInfo implements MinecraftPacket {
private final EnumSet<Action> actions;
private final List<Entry> entries;
public UpsertPlayerInfo() {
this.actions = EnumSet.noneOf(Action.class);
this.entries = new ArrayList<>();
}
public UpsertPlayerInfo(Action action) {
this.actions = EnumSet.of(action);
this.entries = new ArrayList<>();
}
public UpsertPlayerInfo(EnumSet<Action> actions, List<Entry> entries) {
this.actions = actions;
this.entries = entries;
}
public List<Entry> getEntries() {
return entries;
}
public EnumSet<Action> getActions() {
return actions;
}
public boolean containsAction(Action action) {
return this.actions.contains(action);
}
public void addAction(Action action) {
this.actions.add(action);
}
public void addAllActions(Collection<? extends Action> actions) {
this.actions.addAll(actions);
}
public void addEntry(Entry entry) {
this.entries.add(entry);
}
public void addAllEntries(Collection<? extends Entry> entries) {
this.entries.addAll(entries);
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
Action[] actions = Action.class.getEnumConstants();
byte[] bytes = new byte[-Math.floorDiv(-actions.length, 8)];
buf.readBytes(bytes);
BitSet actionSet = BitSet.valueOf(bytes);
for (int idx = 0; idx < actions.length; idx++) {
if (actionSet.get(idx)) {
addAction(actions[idx]);
}
}
int length = ProtocolUtils.readVarInt(buf);
for (int idx = 0; idx < length; idx++) {
Entry entry = new Entry(ProtocolUtils.readUuid(buf));
for (Action action : this.actions) {
action.read.read(protocolVersion, buf, entry);
}
addEntry(entry);
}
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
Action[] actions = Action.class.getEnumConstants();
BitSet set = new BitSet(actions.length);
for (int idx = 0; idx < actions.length; idx++) {
set.set(idx, this.actions.contains(actions[idx]));
}
byte[] bytes = set.toByteArray();
buf.writeBytes(Arrays.copyOf(bytes, -Math.floorDiv(-actions.length, 8)));
ProtocolUtils.writeVarInt(buf, this.entries.size());
for (Entry entry : this.entries) {
ProtocolUtils.writeUuid(buf, entry.profileId);
for (Action action : this.actions) {
action.write.write(protocolVersion, buf, entry);
}
}
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public BitSet readFixedBitSet(ByteBuf buf, int param0) {
byte[] var0 = new byte[-Math.floorDiv(-param0, 8)];
buf.readBytes(var0);
return BitSet.valueOf(var0);
}
public enum Action {
ADD_PLAYER((ignored, buf, info) -> { // read
info.profile = new GameProfile(
info.profileId,
ProtocolUtils.readString(buf, 16),
ProtocolUtils.readProperties(buf)
);
}, (ignored, buf, info) -> { // write
ProtocolUtils.writeString(buf, info.profile.getName());
ProtocolUtils.writeProperties(buf, info.profile.getProperties());
}),
INITIALIZE_CHAT((version, buf, info) -> { // read
if (buf.readBoolean()) {
info.chatSession = new RemoteChatSession(version, buf);
} else {
info.chatSession = null;
}
}, (ignored, buf, info) -> { // write
buf.writeBoolean(info.chatSession != null);
if (info.chatSession != null) {
info.chatSession.write(buf);
}
}),
UPDATE_GAME_MODE((ignored, buf, info) -> { // read
info.gameMode = ProtocolUtils.readVarInt(buf);
}, (ignored, buf, info) -> { // write
ProtocolUtils.writeVarInt(buf, info.gameMode);
}),
UPDATE_LISTED((ignored, buf, info) -> { // read
info.listed = buf.readBoolean();
}, (ignored, buf, info) -> { // write
buf.writeBoolean(info.listed);
}),
UPDATE_LATENCY((ignored, buf, info) -> { // read
info.latency = ProtocolUtils.readVarInt(buf);
}, (ignored, buf, info) -> { // write
ProtocolUtils.writeVarInt(buf, info.latency);
}),
UPDATE_DISPLAY_NAME((version, buf, info) -> { // read
if (buf.readBoolean()) {
info.displayName = ProtocolUtils.getJsonChatSerializer(version)
.deserialize(ProtocolUtils.readString(buf));
} else {
info.displayName = null;
}
}, (version, buf, info) -> { // write
buf.writeBoolean(info.displayName != null);
if (info.displayName != null) {
ProtocolUtils.writeString(buf, ProtocolUtils.getJsonChatSerializer(version)
.serialize(info.displayName));
}
});
private final Read read;
private final Write write;
Action(Read read, Write write) {
this.read = read;
this.write = write;
}
private interface Read {
void read(ProtocolVersion version, ByteBuf buf, Entry info);
}
private interface Write {
void write(ProtocolVersion version, ByteBuf buf, Entry info);
}
}
public static class Entry {
private final UUID profileId;
private GameProfile profile;
private boolean listed;
private int latency;
private int gameMode;
@Nullable
private Component displayName;
@Nullable
private RemoteChatSession chatSession;
public Entry(UUID uuid) {
this.profileId = uuid;
}
public UUID getProfileId() {
return profileId;
}
public GameProfile getProfile() {
return profile;
}
public boolean isListed() {
return listed;
}
public int getLatency() {
return latency;
}
public int getGameMode() {
return gameMode;
}
@Nullable
public Component getDisplayName() {
return displayName;
}
@Nullable
public RemoteChatSession getChatSession() {
return chatSession;
}
public void setProfile(GameProfile profile) {
this.profile = profile;
}
public void setListed(boolean listed) {
this.listed = listed;
}
public void setLatency(int latency) {
this.latency = latency;
}
public void setGameMode(int gameMode) {
this.gameMode = gameMode;
}
public void setDisplayName(@Nullable Component displayName) {
this.displayName = displayName;
}
public void setChatSession(@Nullable RemoteChatSession chatSession) {
this.chatSession = chatSession;
}
@Override
public String toString() {
return "Entry{" +
"profileId=" + profileId +
", profile=" + profile +
", listed=" + listed +
", latency=" + latency +
", gameMode=" + gameMode +
", displayName=" + displayName +
", chatSession=" + chatSession +
'}';
}
}
}

Datei anzeigen

@ -47,7 +47,7 @@ public class ArgumentIdentifier {
for (ProtocolVersion v : ProtocolVersion.values()) { for (ProtocolVersion v : ProtocolVersion.values()) {
if (v.compareTo(current.getVersion()) >= 0) { if (v.compareTo(current.getVersion()) >= 0) {
temp.put(v, current.getId()); temp.putIfAbsent(v, current.getId());
} }
} }
previous = current.getVersion(); previous = current.getVersion();

Datei anzeigen

@ -18,6 +18,7 @@
package com.velocitypowered.proxy.protocol.packet.brigadier; package com.velocitypowered.proxy.protocol.packet.brigadier;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_3;
import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.id; 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.ArgumentIdentifier.mapSet;
import static com.velocitypowered.proxy.protocol.packet.brigadier.DoubleArgumentPropertySerializer.DOUBLE; import static com.velocitypowered.proxy.protocol.packet.brigadier.DoubleArgumentPropertySerializer.DOUBLE;
@ -72,6 +73,7 @@ public class ArgumentPropertyRegistry {
/** /**
* Deserializes the {@link ArgumentType}. * Deserializes the {@link ArgumentType}.
*
* @param buf the buffer to deserialize * @param buf the buffer to deserialize
* @return the deserialized {@link ArgumentType} * @return the deserialized {@link ArgumentType}
*/ */
@ -93,6 +95,7 @@ public class ArgumentPropertyRegistry {
/** /**
* Serializes the {@code type} into the provided {@code buf}. * Serializes the {@code type} into the provided {@code buf}.
*
* @param buf the buffer to serialize into * @param buf the buffer to serialize into
* @param type the type to serialize * @param type the type to serialize
*/ */
@ -122,6 +125,7 @@ public class ArgumentPropertyRegistry {
/** /**
* Writes the {@link ArgumentIdentifier} to a version-specific buffer. * Writes the {@link ArgumentIdentifier} to a version-specific buffer.
*
* @param buf the buffer to write to * @param buf the buffer to write to
* @param identifier the identifier to write * @param identifier the identifier to write
* @param protocolVersion the protocol version to use * @param protocolVersion the protocol version to use
@ -141,6 +145,7 @@ public class ArgumentPropertyRegistry {
/** /**
* Reads the {@link ArgumentIdentifier} from a version-specific buffer. * Reads the {@link ArgumentIdentifier} from a version-specific buffer.
*
* @param buf the buffer to write to * @param buf the buffer to write to
* @param protocolVersion the protocol version to use * @param protocolVersion the protocol version to use
* @return the identifier read from the buffer * @return the identifier read from the buffer
@ -214,27 +219,31 @@ public class ArgumentPropertyRegistry {
empty(id("minecraft:team", mapSet(MINECRAFT_1_19, 31))); empty(id("minecraft:team", mapSet(MINECRAFT_1_19, 31)));
empty(id("minecraft:item_slot", mapSet(MINECRAFT_1_19, 32))); empty(id("minecraft:item_slot", mapSet(MINECRAFT_1_19, 32)));
empty(id("minecraft:resource_location", mapSet(MINECRAFT_1_19, 33))); empty(id("minecraft:resource_location", mapSet(MINECRAFT_1_19, 33)));
empty(id("minecraft:mob_effect", mapSet(MINECRAFT_1_19, 34))); empty(id("minecraft:mob_effect", mapSet(MINECRAFT_1_19_3, -1), mapSet(MINECRAFT_1_19, 34)));
empty(id("minecraft:function", mapSet(MINECRAFT_1_19, 35))); empty(id("minecraft:function", mapSet(MINECRAFT_1_19_3, 34), mapSet(MINECRAFT_1_19, 35)));
empty(id("minecraft:entity_anchor", mapSet(MINECRAFT_1_19, 36))); empty(id("minecraft:entity_anchor", mapSet(MINECRAFT_1_19_3, 35), mapSet(MINECRAFT_1_19, 36)));
empty(id("minecraft:int_range", mapSet(MINECRAFT_1_19, 37))); empty(id("minecraft:int_range", mapSet(MINECRAFT_1_19_3, 36), mapSet(MINECRAFT_1_19, 37)));
empty(id("minecraft:float_range", mapSet(MINECRAFT_1_19, 38))); empty(id("minecraft:float_range", mapSet(MINECRAFT_1_19_3, 37), mapSet(MINECRAFT_1_19, 38)));
empty(id("minecraft:item_enchantment", mapSet(MINECRAFT_1_19, 39))); empty(id("minecraft:item_enchantment", mapSet(MINECRAFT_1_19_3, -1), mapSet(MINECRAFT_1_19, 39)));
empty(id("minecraft:entity_summon", mapSet(MINECRAFT_1_19, 40))); empty(id("minecraft:entity_summon", mapSet(MINECRAFT_1_19_3, -1), mapSet(MINECRAFT_1_19, 40)));
empty(id("minecraft:dimension", mapSet(MINECRAFT_1_19, 41))); empty(id("minecraft:dimension", mapSet(MINECRAFT_1_19_3, 38), mapSet(MINECRAFT_1_19, 41)));
empty(id("minecraft:time", mapSet(MINECRAFT_1_19, 42))); // added in 1.14 empty(id("minecraft:gamemode", mapSet(MINECRAFT_1_19_3, 39))); // 1.19.3
empty(id("minecraft:time", mapSet(MINECRAFT_1_19_3, 40), mapSet(MINECRAFT_1_19, 42))); // added in 1.14
register(id("minecraft:resource_or_tag", mapSet(MINECRAFT_1_19, 43)), register(id("minecraft:resource_or_tag", mapSet(MINECRAFT_1_19_3, 41), mapSet(MINECRAFT_1_19, 43)),
RegistryKeyArgument.class, RegistryKeyArgumentSerializer.REGISTRY); RegistryKeyArgument.class, RegistryKeyArgumentSerializer.REGISTRY);
register(id("minecraft:resource", mapSet(MINECRAFT_1_19, 44)), register(id("minecraft:resource_or_tag_key", mapSet(MINECRAFT_1_19_3, 42)),
RegistryKeyArgumentList.ResourceOrTagKey.class, RegistryKeyArgumentList.ResourceOrTagKey.Serializer.REGISTRY);
register(id("minecraft:resource", mapSet(MINECRAFT_1_19_3, 43), mapSet(MINECRAFT_1_19, 44)),
RegistryKeyArgument.class, RegistryKeyArgumentSerializer.REGISTRY); RegistryKeyArgument.class, RegistryKeyArgumentSerializer.REGISTRY);
register(id("minecraft:resource_key", mapSet(MINECRAFT_1_19_3, 44)),
RegistryKeyArgumentList.ResourceKey.class, RegistryKeyArgumentList.ResourceKey.Serializer.REGISTRY);
empty(id("minecraft:template_mirror", mapSet(MINECRAFT_1_19, 45))); // 1.19 empty(id("minecraft:template_mirror", mapSet(MINECRAFT_1_19, 45))); // 1.19
empty(id("minecraft:template_rotation", mapSet(MINECRAFT_1_19, 46))); // 1.19 empty(id("minecraft:template_rotation", mapSet(MINECRAFT_1_19, 46))); // 1.19
empty(id("minecraft:uuid", mapSet(MINECRAFT_1_19, 47))); // added in 1.16 empty(id("minecraft:uuid", mapSet(MINECRAFT_1_19, 47))); // added in 1.16
// Crossstitch support // Crossstitch support
register(id("crossstitch:mod_argument", mapSet(MINECRAFT_1_19, -256)), ModArgumentProperty.class, MOD); register(id("crossstitch:mod_argument", mapSet(MINECRAFT_1_19, -256)), ModArgumentProperty.class, MOD);

Datei anzeigen

@ -0,0 +1,112 @@
/*
* Copyright (C) 2018 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.brigadier;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public final class RegistryKeyArgumentList {
public static class ResourceOrTag extends RegistryKeyArgument {
public ResourceOrTag(String identifier) {
super(identifier);
}
public static class Serializer implements ArgumentPropertySerializer<ResourceOrTag> {
static final ResourceOrTag.Serializer REGISTRY = new ResourceOrTag.Serializer();
@Override
public ResourceOrTag deserialize(ByteBuf buf, ProtocolVersion protocolVersion) {
return new ResourceOrTag(ProtocolUtils.readString(buf));
}
@Override
public void serialize(ResourceOrTag object, ByteBuf buf, ProtocolVersion protocolVersion) {
ProtocolUtils.writeString(buf, object.getIdentifier());
}
}
}
public static class ResourceOrTagKey extends RegistryKeyArgument {
public ResourceOrTagKey(String identifier) {
super(identifier);
}
public static class Serializer implements ArgumentPropertySerializer<ResourceOrTagKey> {
static final ResourceOrTagKey.Serializer REGISTRY = new ResourceOrTagKey.Serializer();
@Override
public ResourceOrTagKey deserialize(ByteBuf buf, ProtocolVersion protocolVersion) {
return new ResourceOrTagKey(ProtocolUtils.readString(buf));
}
@Override
public void serialize(ResourceOrTagKey object, ByteBuf buf, ProtocolVersion protocolVersion) {
ProtocolUtils.writeString(buf, object.getIdentifier());
}
}
}
public static class Resource extends RegistryKeyArgument {
public Resource(String identifier) {
super(identifier);
}
public static class Serializer implements ArgumentPropertySerializer<Resource> {
static final Resource.Serializer REGISTRY = new Resource.Serializer();
@Override
public Resource deserialize(ByteBuf buf, ProtocolVersion protocolVersion) {
return new Resource(ProtocolUtils.readString(buf));
}
@Override
public void serialize(Resource object, ByteBuf buf, ProtocolVersion protocolVersion) {
ProtocolUtils.writeString(buf, object.getIdentifier());
}
}
}
public static class ResourceKey extends RegistryKeyArgument {
public ResourceKey(String identifier) {
super(identifier);
}
public static class Serializer implements ArgumentPropertySerializer<ResourceKey> {
static final ResourceKey.Serializer REGISTRY = new ResourceKey.Serializer();
@Override
public ResourceKey deserialize(ByteBuf buf, ProtocolVersion protocolVersion) {
return new ResourceKey(ProtocolUtils.readString(buf));
}
@Override
public void serialize(ResourceKey object, ByteBuf buf, ProtocolVersion protocolVersion) {
ProtocolUtils.writeString(buf, object.getIdentifier());
}
}
}
RegistryKeyArgumentList() {
}
}

Datei anzeigen

@ -1,191 +0,0 @@
/*
* Copyright (C) 2018 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.chat;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.proxy.crypto.SignedChatCommand;
import com.velocitypowered.proxy.crypto.SignedChatMessage;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import java.time.Instant;
import java.util.UUID;
import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.jetbrains.annotations.NotNull;
public class ChatBuilder {
private final ProtocolVersion version;
private @MonotonicNonNull Component component;
private @MonotonicNonNull String message;
private @MonotonicNonNull SignedChatMessage signedChatMessage;
private @MonotonicNonNull SignedChatCommand signedCommand;
private @Nullable Player sender;
private @Nullable Identity senderIdentity;
private @NotNull Instant timestamp;
private ChatType type = ChatType.CHAT;
private ChatBuilder(ProtocolVersion version) {
this.version = version;
this.timestamp = Instant.now();
}
public static ChatBuilder builder(ProtocolVersion version) {
return new ChatBuilder(Preconditions.checkNotNull(version));
}
public ChatBuilder component(Component message) {
this.component = Preconditions.checkNotNull(message);
return this;
}
/**
* Sets the message to the provided message.
*
* @param message The message for the chat.
* @return {@code this}
*/
public ChatBuilder message(String message) {
Preconditions.checkArgument(this.message == null);
this.message = Preconditions.checkNotNull(message);
return this;
}
/**
* Sets the signed message to the provided message.
*
* @param message The signed message for the chat.
* @return {@code this}
*/
public ChatBuilder message(SignedChatMessage message) {
Preconditions.checkNotNull(message);
Preconditions.checkArgument(this.message == null);
this.message = message.getMessage();
this.signedChatMessage = message;
return this;
}
/**
* Sets the signed command to the provided command.
*
* @param command The signed command for the chat.
* @return {@code this}
*/
public ChatBuilder message(SignedChatCommand command) {
Preconditions.checkNotNull(command);
Preconditions.checkArgument(this.message == null);
this.message = command.getBaseCommand();
this.signedCommand = command;
return this;
}
public ChatBuilder setType(ChatType type) {
this.type = type;
return this;
}
public ChatBuilder asPlayer(@Nullable Player player) {
this.sender = player;
return this;
}
public ChatBuilder forIdentity(@Nullable Identity identity) {
this.senderIdentity = identity;
return this;
}
public ChatBuilder timestamp(Instant timestamp) {
this.timestamp = timestamp;
return this;
}
public ChatBuilder asServer() {
this.sender = null;
return this;
}
/**
* Creates a {@link MinecraftPacket} which can be sent to the client; using the provided information in the builder.
*
* @return The {@link MinecraftPacket} to send to the client.
*/
public MinecraftPacket toClient() {
// This is temporary
UUID identity = sender == null ? (senderIdentity == null ? Identity.nil().uuid()
: senderIdentity.uuid()) : sender.getUniqueId();
Component msg = component == null ? Component.text(message) : component;
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
// hard override chat > system for now
return new SystemChat(msg, type == ChatType.CHAT ? ChatType.SYSTEM : type);
} else {
return new LegacyChat(ProtocolUtils.getJsonChatSerializer(version).serialize(msg), type.getId(), identity);
}
}
/**
* Creates a {@link MinecraftPacket} which can be sent to the server; using the provided information in the builder.
*
* @return The {@link MinecraftPacket} to send to the server.
*/
public MinecraftPacket toServer() {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
if (signedChatMessage != null) {
return new PlayerChat(signedChatMessage);
} else if (signedCommand != null) {
return new PlayerCommand(signedCommand);
} else {
// Well crap
if (message.startsWith("/")) {
return new PlayerCommand(message.substring(1), ImmutableList.of(), timestamp);
} else {
// This will produce an error on the server, but needs to be here.
return new PlayerChat(message);
}
}
}
LegacyChat chat = new LegacyChat();
chat.setMessage(message);
return chat;
}
public static enum ChatType {
CHAT((byte) 0),
SYSTEM((byte) 1),
GAME_INFO((byte) 2);
private final byte raw;
ChatType(byte raw) {
this.raw = raw;
}
public byte getId() {
return raw;
}
}
}

Datei anzeigen

@ -0,0 +1,34 @@
/*
* Copyright (C) 2018 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.chat;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
public interface ChatHandler<T extends MinecraftPacket> {
Class<T> packetClass();
void handlePlayerChatInternal(T packet);
default boolean handlePlayerChat(MinecraftPacket packet) {
if (packetClass().isInstance(packet)) {
handlePlayerChatInternal(packetClass().cast(packet));
return true;
}
return false;
}
}

Datei anzeigen

@ -23,6 +23,7 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
import java.time.Instant; import java.time.Instant;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import org.apache.logging.log4j.LogManager;
/** /**
* A precisely ordered queue which allows for outside entries into the ordered queue through piggybacking timestamps. * A precisely ordered queue which allows for outside entries into the ordered queue through piggybacking timestamps.

Datei anzeigen

@ -0,0 +1,37 @@
/*
* Copyright (C) 2018 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.chat;
import java.time.Instant;
public class ChatTimeKeeper {
private Instant lastTimestamp;
public ChatTimeKeeper() {
this.lastTimestamp = Instant.MIN;
}
public boolean update(Instant instant) {
if (instant.isBefore(this.lastTimestamp)) {
this.lastTimestamp = instant;
return false;
}
this.lastTimestamp = instant;
return true;
}
}

Datei anzeigen

@ -0,0 +1,34 @@
/*
* Copyright (C) 2018 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.chat;
public enum ChatType {
CHAT((byte) 0),
SYSTEM((byte) 1),
GAME_INFO((byte) 2);
private final byte raw;
ChatType(byte raw) {
this.raw = raw;
}
public byte getId() {
return raw;
}
}

Datei anzeigen

@ -0,0 +1,68 @@
/*
* Copyright (C) 2018 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.chat;
import com.velocitypowered.api.event.command.CommandExecuteEvent;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import java.time.Instant;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public interface CommandHandler<T extends MinecraftPacket> {
Logger logger = LogManager.getLogger(CommandHandler.class);
Class<T> packetClass();
void handlePlayerCommandInternal(T packet);
default boolean handlePlayerCommand(MinecraftPacket packet) {
if (packetClass().isInstance(packet)) {
handlePlayerCommandInternal(packetClass().cast(packet));
return true;
}
return false;
}
default CompletableFuture<MinecraftPacket> runCommand(VelocityServer server, ConnectedPlayer player, String command,
Function<Boolean, MinecraftPacket> hasRunPacketFunction) {
return server.getCommandManager().executeImmediatelyAsync(player, command).thenApply(hasRunPacketFunction);
}
default void queueCommandResult(VelocityServer server, ConnectedPlayer player,
Function<CommandExecuteEvent, CompletableFuture<MinecraftPacket>> futurePacketCreator,
String message, Instant timestamp) {
player.getChatQueue().queuePacket(
server.getCommandManager().callCommandEvent(player, message).thenComposeAsync(futurePacketCreator)
.thenApply(pkt -> {
if (server.getConfiguration().isLogCommandExecutions()) {
logger.info("{} -> executed command /{}", player, message);
}
return pkt;
}).exceptionally(e -> {
logger.info("Exception occurred while running command for {}", player.getUsername(), e);
player.sendMessage(Component.translatable("velocity.command.generic-error", NamedTextColor.RED));
return null;
}), timestamp);
}
}

Datei anzeigen

@ -0,0 +1,47 @@
/*
* Copyright (C) 2018 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.chat;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import java.util.Arrays;
import java.util.BitSet;
public class LastSeenMessages {
private static final int DIV_FLOOR = -Math.floorDiv(-20, 8);
private int offset;
private BitSet acknowledged;
public LastSeenMessages() {
this.offset = 0;
this.acknowledged = new BitSet();
}
public LastSeenMessages(ByteBuf buf) {
this.offset = ProtocolUtils.readVarInt(buf);
byte[] bytes = new byte[DIV_FLOOR];
buf.readBytes(bytes);
this.acknowledged = BitSet.valueOf(bytes);
}
public void encode(ByteBuf buf) {
ProtocolUtils.writeVarInt(buf, offset);
buf.writeBytes(Arrays.copyOf(acknowledged.toByteArray(), DIV_FLOOR));
}
}

Datei anzeigen

@ -0,0 +1,55 @@
/*
* Copyright (C) 2018 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.chat;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
import com.velocitypowered.api.proxy.player.ChatSession;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import java.util.Objects;
import java.util.UUID;
import org.checkerframework.checker.nullness.qual.Nullable;
public class RemoteChatSession implements ChatSession {
private final @Nullable UUID sessionId;
private final IdentifiedKey identifiedKey;
public RemoteChatSession(ProtocolVersion version, ByteBuf buf) {
this.sessionId = ProtocolUtils.readUuid(buf);
this.identifiedKey = ProtocolUtils.readPlayerKey(version, buf);
}
public RemoteChatSession(@Nullable UUID sessionId, IdentifiedKey identifiedKey) {
this.sessionId = sessionId;
this.identifiedKey = identifiedKey;
}
public IdentifiedKey getIdentifiedKey() {
return identifiedKey;
}
public @Nullable UUID getSessionId() {
return sessionId;
}
public void write(ByteBuf buf) {
ProtocolUtils.writeUuid(buf, Objects.requireNonNull(this.sessionId));
ProtocolUtils.writePlayerKey(buf, this.identifiedKey);
}
}

Datei anzeigen

@ -1,89 +0,0 @@
/*
* Copyright (C) 2018 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.chat;
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.time.Instant;
import java.util.UUID;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable;
public class ServerPlayerChat implements MinecraftPacket {
private Component component;
private @Nullable Component unsignedComponent;
private int type;
private UUID sender;
private Component senderName;
private @Nullable Component teamName;
private Instant expiry;
public void setType(int type) {
this.type = type;
}
public void setComponent(Component component) {
this.component = component;
}
public int getType() {
return type;
}
public Component getComponent() {
return component;
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
component = ProtocolUtils.getJsonChatSerializer(protocolVersion).deserialize(ProtocolUtils.readString(buf));
if (buf.readBoolean()) {
unsignedComponent = component = ProtocolUtils.getJsonChatSerializer(protocolVersion)
.deserialize(ProtocolUtils.readString(buf));
}
type = ProtocolUtils.readVarInt(buf);
sender = ProtocolUtils.readUuid(buf);
senderName = ProtocolUtils.getJsonChatSerializer(protocolVersion).deserialize(ProtocolUtils.readString(buf));
if (buf.readBoolean()) {
teamName = ProtocolUtils.getJsonChatSerializer(protocolVersion).deserialize(ProtocolUtils.readString(buf));
}
expiry = Instant.ofEpochMilli(buf.readLong());
long salt = buf.readLong();
byte[] signature = ProtocolUtils.readByteArray(buf);
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
// TBD
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
}

Datei anzeigen

@ -28,15 +28,15 @@ public class SystemChat implements MinecraftPacket {
public SystemChat() {} public SystemChat() {}
public SystemChat(Component component, ChatBuilder.ChatType type) { public SystemChat(Component component, ChatType type) {
this.component = component; this.component = component;
this.type = type; this.type = type;
} }
private Component component; private Component component;
private ChatBuilder.ChatType type; private ChatType type;
public ChatBuilder.ChatType getType() { public ChatType getType() {
return type; return type;
} }
@ -48,7 +48,7 @@ public class SystemChat implements MinecraftPacket {
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
component = ProtocolUtils.getJsonChatSerializer(protocolVersion).deserialize(ProtocolUtils.readString(buf)); component = ProtocolUtils.getJsonChatSerializer(protocolVersion).deserialize(ProtocolUtils.readString(buf));
// System chat is never decoded so this doesn't matter for now // System chat is never decoded so this doesn't matter for now
type = ChatBuilder.ChatType.values()[ProtocolUtils.readVarInt(buf)]; type = ChatType.values()[ProtocolUtils.readVarInt(buf)];
} }
@Override @Override

Datei anzeigen

@ -0,0 +1,44 @@
/*
* Copyright (C) 2018 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.chat.builder;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedChatBuilder;
import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyChatBuilder;
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionChatBuilder;
import java.util.function.Function;
public class ChatBuilderFactory {
private final ProtocolVersion version;
private final Function<ProtocolVersion, ChatBuilderV2> builderFunction;
public ChatBuilderFactory(ProtocolVersion version) {
this.version = version;
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_3) >= 0) {
this.builderFunction = SessionChatBuilder::new;
} else if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
this.builderFunction = KeyedChatBuilder::new;
} else {
this.builderFunction = LegacyChatBuilder::new;
}
}
public ChatBuilderV2 builder() {
return this.builderFunction.apply(this.version);
}
}

Datei anzeigen

@ -0,0 +1,82 @@
/*
* Copyright (C) 2018 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.chat.builder;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.packet.chat.ChatType;
import java.time.Instant;
import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
public abstract class ChatBuilderV2 {
protected final ProtocolVersion version;
protected @MonotonicNonNull Component component;
protected @MonotonicNonNull String message;
protected @Nullable Player sender;
protected @Nullable Identity senderIdentity;
protected Instant timestamp;
protected ChatType type = ChatType.CHAT;
protected ChatBuilderV2(ProtocolVersion version) {
this.version = version;
this.timestamp = Instant.now();
}
public ChatBuilderV2 component(Component component) {
this.component = component;
return this;
}
public ChatBuilderV2 message(String message) {
this.message = message;
return this;
}
public ChatBuilderV2 setType(ChatType chatType) {
this.type = chatType;
return this;
}
public ChatBuilderV2 setTimestamp(Instant timestamp) {
this.timestamp = timestamp;
return this;
}
public ChatBuilderV2 forIdentity(Identity identity) {
this.senderIdentity = identity;
return this;
}
public ChatBuilderV2 asPlayer(@Nullable Player player) {
this.sender = player;
return this;
}
public ChatBuilderV2 asServer() {
this.senderIdentity = null;
return this;
}
public abstract MinecraftPacket toClient();
public abstract MinecraftPacket toServer();
}

Datei anzeigen

@ -0,0 +1,51 @@
/*
* Copyright (C) 2018 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.chat.keyed;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.packet.chat.ChatType;
import com.velocitypowered.proxy.protocol.packet.chat.SystemChat;
import com.velocitypowered.proxy.protocol.packet.chat.builder.ChatBuilderV2;
import net.kyori.adventure.text.Component;
public class KeyedChatBuilder extends ChatBuilderV2 {
public KeyedChatBuilder(ProtocolVersion version) {
super(version);
}
@Override
public MinecraftPacket toClient() {
// This is temporary
Component msg = component == null ? Component.text(message) : component;
return new SystemChat(msg, type == ChatType.CHAT ? ChatType.SYSTEM : type);
}
@Override
public MinecraftPacket toServer() {
if (message.startsWith("/")) {
return new KeyedPlayerCommand(message.substring(1), ImmutableList.of(), timestamp);
} else {
// This will produce an error on the server, but needs to be here.
KeyedPlayerChat v1Chat = new KeyedPlayerChat(message);
v1Chat.setExpiry(this.timestamp);
return v1Chat;
}
}
}

Datei anzeigen

@ -0,0 +1,124 @@
/*
* Copyright (C) 2018 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.chat.keyed;
import com.velocitypowered.api.event.EventManager;
import com.velocitypowered.api.event.player.PlayerChatEvent;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.packet.chat.ChatQueue;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import net.kyori.adventure.text.Component;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class KeyedChatHandler implements com.velocitypowered.proxy.protocol.packet.chat.ChatHandler<KeyedPlayerChat> {
private static final Logger logger = LogManager.getLogger(KeyedChatHandler.class);
private final VelocityServer server;
private final ConnectedPlayer player;
public KeyedChatHandler(VelocityServer server, ConnectedPlayer player) {
this.server = server;
this.player = player;
}
@Override
public Class<KeyedPlayerChat> packetClass() {
return KeyedPlayerChat.class;
}
public static void invalidCancel(Logger logger, ConnectedPlayer player) {
logger.fatal("A plugin tried to cancel a signed chat message."
+ " This is no longer possible in 1.19.1 and newer. "
+ "Disconnecting player " + player.getUsername());
player.disconnect(Component.text("A proxy plugin caused an illegal protocol state. "
+ "Contact your network administrator."));
}
public static void invalidChange(Logger logger, ConnectedPlayer player) {
logger.fatal("A plugin tried to change a signed chat message. "
+ "This is no longer possible in 1.19.1 and newer. "
+ "Disconnecting player " + player.getUsername());
player.disconnect(Component.text("A proxy plugin caused an illegal protocol state. "
+ "Contact your network administrator."));
}
@Override
public void handlePlayerChatInternal(KeyedPlayerChat packet) {
ChatQueue chatQueue = this.player.getChatQueue();
EventManager eventManager = this.server.getEventManager();
PlayerChatEvent toSend = new PlayerChatEvent(player, packet.getMessage());
CompletableFuture<PlayerChatEvent> future = eventManager.fire(toSend);
CompletableFuture<MinecraftPacket> chatFuture;
IdentifiedKey playerKey = this.player.getIdentifiedKey();
if (playerKey != null && !packet.isUnsigned()) {
// 1.19->1.19.2 signed version
chatFuture = future.thenApply(handleOldSignedChat(packet));
} else {
// 1.19->1.19.2 unsigned version
chatFuture = future.thenApply(pme -> {
PlayerChatEvent.ChatResult chatResult = pme.getResult();
if (!chatResult.isAllowed()) {
return null;
}
return player.getChatBuilderFactory().builder()
.message(chatResult.getMessage().orElse(packet.getMessage())).setTimestamp(packet.getExpiry()).toServer();
});
}
chatQueue.queuePacket(
chatFuture.exceptionally((ex) -> {
logger.error("Exception while handling player chat for {}", player, ex);
return null;
}),
packet.getExpiry()
);
}
private Function<PlayerChatEvent, MinecraftPacket> handleOldSignedChat(KeyedPlayerChat packet) {
IdentifiedKey playerKey = this.player.getIdentifiedKey();
assert playerKey != null;
return pme -> {
PlayerChatEvent.ChatResult chatResult = pme.getResult();
if (!chatResult.isAllowed() && playerKey.getKeyRevision().compareTo(IdentifiedKey.Revision.LINKED_V2) >= 0) {
invalidCancel(logger, player);
return null;
}
if (chatResult.getMessage().map(str -> !str.equals(packet.getMessage())).orElse(false)) {
if (playerKey.getKeyRevision().compareTo(IdentifiedKey.Revision.LINKED_V2) >= 0) {
// Bad, very bad.
invalidChange(logger, player);
} else {
logger.warn("A plugin changed a signed chat message. The server may not accept it.");
return player.getChatBuilderFactory().builder()
.message(chatResult.getMessage().get() /* always present at this point */)
.setTimestamp(packet.getExpiry())
.toServer();
}
}
return packet;
};
}
}

Datei anzeigen

@ -0,0 +1,108 @@
/*
* Copyright (C) 2018 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.chat.keyed;
import com.velocitypowered.api.event.command.CommandExecuteEvent;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.packet.chat.CommandHandler;
import com.velocitypowered.proxy.protocol.packet.chat.builder.ChatBuilderV2;
import java.util.concurrent.CompletableFuture;
import net.kyori.adventure.text.Component;
public class KeyedCommandHandler implements CommandHandler<KeyedPlayerCommand> {
private final ConnectedPlayer player;
private final VelocityServer server;
public KeyedCommandHandler(ConnectedPlayer player, VelocityServer server) {
this.player = player;
this.server = server;
}
@Override
public Class<KeyedPlayerCommand> packetClass() {
return KeyedPlayerCommand.class;
}
@Override
public void handlePlayerCommandInternal(KeyedPlayerCommand packet) {
queueCommandResult(this.server, this.player, event -> {
CommandExecuteEvent.CommandResult result = event.getResult();
IdentifiedKey playerKey = player.getIdentifiedKey();
if (result == CommandExecuteEvent.CommandResult.denied()) {
if (playerKey != null) {
if (!packet.isUnsigned() && playerKey.getKeyRevision().compareTo(IdentifiedKey.Revision.LINKED_V2) >= 0) {
logger.fatal("A plugin tried to deny a command with signable component(s). " + "This is not supported. "
+ "Disconnecting player " + player.getUsername());
player.disconnect(Component.text(
"A proxy plugin caused an illegal protocol state. " + "Contact your network administrator."));
}
}
return CompletableFuture.completedFuture(null);
}
String commandToRun = result.getCommand().orElse(packet.getCommand());
if (result.isForwardToServer()) {
ChatBuilderV2 write = this.player.getChatBuilderFactory()
.builder()
.setTimestamp(packet.getTimestamp())
.asPlayer(this.player);
if (!packet.isUnsigned() && commandToRun.equals(packet.getCommand())) {
return CompletableFuture.completedFuture(packet);
} else {
if (!packet.isUnsigned() && playerKey != null
&& playerKey.getKeyRevision().compareTo(IdentifiedKey.Revision.LINKED_V2) >= 0) {
logger.fatal("A plugin tried to change a command with signed component(s). " + "This is not supported. "
+ "Disconnecting player " + player.getUsername());
player.disconnect(Component.text(
"A proxy plugin caused an illegal protocol state. " + "Contact your network administrator."));
return CompletableFuture.completedFuture(null);
}
write.message("/" + commandToRun);
}
return CompletableFuture.completedFuture(write.toServer());
}
return runCommand(this.server, this.player, commandToRun, hasRun -> {
if (!hasRun) {
if (commandToRun.equals(packet.getCommand())) {
return packet;
}
if (!packet.isUnsigned() && playerKey != null
&& playerKey.getKeyRevision().compareTo(IdentifiedKey.Revision.LINKED_V2) >= 0) {
logger.fatal("A plugin tried to change a command with signed component(s). " + "This is not supported. "
+ "Disconnecting player " + player.getUsername());
player.disconnect(Component.text(
"A proxy plugin caused an illegal protocol state. " + "Contact your network administrator."));
return null;
}
return this.player.getChatBuilderFactory()
.builder()
.setTimestamp(packet.getTimestamp())
.asPlayer(this.player)
.message("/" + commandToRun)
.toServer();
}
return null;
});
}, packet.getCommand(), packet.getTimestamp());
}
}

Datei anzeigen

@ -15,24 +15,21 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.velocitypowered.proxy.protocol.packet.chat; package com.velocitypowered.proxy.protocol.packet.chat.keyed;
import com.google.common.primitives.Longs; import com.google.common.primitives.Longs;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.crypto.EncryptionUtils; import com.velocitypowered.proxy.crypto.EncryptionUtils;
import com.velocitypowered.proxy.crypto.SignaturePair; import com.velocitypowered.proxy.crypto.SignaturePair;
import com.velocitypowered.proxy.crypto.SignedChatMessage;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.util.except.QuietDecoderException; import com.velocitypowered.proxy.util.except.QuietDecoderException;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import java.time.Instant; import java.time.Instant;
import java.util.UUID;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
public class PlayerChat implements MinecraftPacket { public class KeyedPlayerChat implements MinecraftPacket {
private String message; private String message;
private boolean signedPreview; private boolean signedPreview;
@ -48,27 +45,16 @@ public class PlayerChat implements MinecraftPacket {
public static final QuietDecoderException INVALID_PREVIOUS_MESSAGES = public static final QuietDecoderException INVALID_PREVIOUS_MESSAGES =
new QuietDecoderException("Invalid previous messages"); new QuietDecoderException("Invalid previous messages");
public PlayerChat() { public KeyedPlayerChat() {
} }
public PlayerChat(String message) { public KeyedPlayerChat(String message) {
this.message = message; this.message = message;
this.unsigned = true; this.unsigned = true;
} }
/** public void setExpiry(@Nullable Instant expiry) {
* Create new {@link PlayerChat} based on a previously {@link SignedChatMessage}. this.expiry = expiry;
*
* @param message The {@link SignedChatMessage} to turn into {@link PlayerChat}.
*/
public PlayerChat(SignedChatMessage message) {
this.message = message.getMessage();
this.expiry = message.getExpiryTemporal();
this.salt = message.getSalt();
this.signature = message.getSignature();
this.signedPreview = message.isPreviewSigned();
this.lastMessage = message.getPreviousSignature();
this.previousMessages = message.getPreviousSignatures();
} }
public Instant getExpiry() { public Instant getExpiry() {
@ -135,11 +121,11 @@ public class PlayerChat implements MinecraftPacket {
buf.writeLong(unsigned ? Instant.now().toEpochMilli() : expiry.toEpochMilli()); buf.writeLong(unsigned ? Instant.now().toEpochMilli() : expiry.toEpochMilli());
buf.writeLong(unsigned ? 0L : Longs.fromByteArray(salt)); buf.writeLong(unsigned ? 0L : Longs.fromByteArray(salt));
ProtocolUtils.writeByteArray(buf, unsigned ? EncryptionUtils.EMPTY : signature); ProtocolUtils.writeByteArray(buf, unsigned ? EncryptionUtils.EMPTY : signature);
buf.writeBoolean(signedPreview); buf.writeBoolean(signedPreview);
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) { if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
ProtocolUtils.writeVarInt(buf, previousMessages.length); ProtocolUtils.writeVarInt(buf, previousMessages.length);
for (SignaturePair previousMessage : previousMessages) { for (SignaturePair previousMessage : previousMessages) {
@ -157,28 +143,6 @@ public class PlayerChat implements MinecraftPacket {
} }
} }
/**
* Validates a signature and creates a {@link SignedChatMessage} from the given signature.
*
* @param signer the signer's information
* @param sender the sender of the message
* @param mustSign instructs the function to throw if the signature is invalid.
* @return The {@link SignedChatMessage} or null if the signature couldn't be verified.
* @throws com.velocitypowered.proxy.util.except.QuietDecoderException when mustSign is {@code true} and the signature
* is invalid.
*/
public SignedChatMessage signedContainer(IdentifiedKey signer, UUID sender, boolean mustSign) {
if (unsigned) {
if (mustSign) {
throw EncryptionUtils.INVALID_SIGNATURE;
}
return null;
}
return new SignedChatMessage(message, signer.getSignedPublicKey(), sender, expiry, signature,
salt, signedPreview, previousMessages, lastMessage);
}
@Override @Override
public boolean handle(MinecraftSessionHandler handler) { public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this); return handler.handle(this);

Datei anzeigen

@ -15,20 +15,16 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.velocitypowered.proxy.protocol.packet.chat; package com.velocitypowered.proxy.protocol.packet.chat.keyed;
import static com.velocitypowered.proxy.protocol.packet.chat.PlayerChat.INVALID_PREVIOUS_MESSAGES; import static com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedPlayerChat.INVALID_PREVIOUS_MESSAGES;
import static com.velocitypowered.proxy.protocol.packet.chat.PlayerChat.MAXIMUM_PREVIOUS_MESSAGE_COUNT; import static com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedPlayerChat.MAXIMUM_PREVIOUS_MESSAGE_COUNT;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.primitives.Longs;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.crypto.EncryptionUtils; import com.velocitypowered.proxy.crypto.EncryptionUtils;
import com.velocitypowered.proxy.crypto.SignaturePair; import com.velocitypowered.proxy.crypto.SignaturePair;
import com.velocitypowered.proxy.crypto.SignedChatCommand;
import com.velocitypowered.proxy.crypto.SignedChatMessage;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.util.except.QuietDecoderException; import com.velocitypowered.proxy.util.except.QuietDecoderException;
@ -37,10 +33,9 @@ import java.time.Instant;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
public class PlayerCommand implements MinecraftPacket { public class KeyedPlayerCommand implements MinecraftPacket {
private static final int MAX_NUM_ARGUMENTS = 8; private static final int MAX_NUM_ARGUMENTS = 8;
private static final int MAX_LENGTH_ARGUMENTS = 16; private static final int MAX_LENGTH_ARGUMENTS = 16;
@ -51,15 +46,11 @@ public class PlayerCommand implements MinecraftPacket {
private String command; private String command;
private Instant timestamp; private Instant timestamp;
private long salt; private long salt;
private boolean signedPreview; // Good god. Please no. private boolean signedPreview; // purely for pass through for 1.19 -> 1.19.2 - this will never be implemented
private SignaturePair[] previousMessages = new SignaturePair[0]; private SignaturePair[] previousMessages = new SignaturePair[0];
private @Nullable SignaturePair lastMessage; private @Nullable SignaturePair lastMessage;
private Map<String, byte[]> arguments = ImmutableMap.of(); private Map<String, byte[]> arguments = ImmutableMap.of();
public boolean isSignedPreview() {
return signedPreview;
}
public Instant getTimestamp() { public Instant getTimestamp() {
return timestamp; return timestamp;
} }
@ -72,17 +63,17 @@ public class PlayerCommand implements MinecraftPacket {
return command; return command;
} }
public PlayerCommand() { public KeyedPlayerCommand() {
} }
/** /**
* Creates an {@link PlayerCommand} packet based on a command and list of arguments. * Creates an {@link KeyedPlayerCommand} packet based on a command and list of arguments.
* *
* @param command the command to run * @param command the command to run
* @param arguments the arguments of the command * @param arguments the arguments of the command
* @param timestamp the timestamp of the command execution * @param timestamp the timestamp of the command execution
*/ */
public PlayerCommand(String command, List<String> arguments, Instant timestamp) { public KeyedPlayerCommand(String command, List<String> arguments, Instant timestamp) {
this.unsigned = true; this.unsigned = true;
ImmutableMap.Builder<String, byte[]> builder = ImmutableMap.builder(); ImmutableMap.Builder<String, byte[]> builder = ImmutableMap.builder();
arguments.forEach(entry -> builder.put(entry, EncryptionUtils.EMPTY)); arguments.forEach(entry -> builder.put(entry, EncryptionUtils.EMPTY));
@ -93,21 +84,6 @@ public class PlayerCommand implements MinecraftPacket {
this.salt = 0L; this.salt = 0L;
} }
/**
* Create new {@link PlayerCommand} based on a previously {@link SignedChatCommand}.
*
* @param signedCommand The {@link SignedChatCommand} to turn into {@link PlayerCommand}.
*/
public PlayerCommand(SignedChatCommand signedCommand) {
this.command = signedCommand.getBaseCommand();
this.arguments = ImmutableMap.copyOf(signedCommand.getSignatures());
this.timestamp = signedCommand.getExpiryTemporal();
this.salt = Longs.fromByteArray(signedCommand.getSalt());
this.signedPreview = signedCommand.isPreviewSigned();
this.lastMessage = signedCommand.getLastSignature();
this.previousMessages = signedCommand.getPreviousSignatures();
}
@Override @Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
command = ProtocolUtils.readString(buf, 256); command = ProtocolUtils.readString(buf, 256);
@ -127,7 +103,7 @@ public class PlayerCommand implements MinecraftPacket {
} }
arguments = entries.build(); arguments = entries.build();
signedPreview = buf.readBoolean(); this.signedPreview = buf.readBoolean();
if (unsigned && signedPreview) { if (unsigned && signedPreview) {
throw EncryptionUtils.PREVIEW_SIGNATURE_MISSING; throw EncryptionUtils.PREVIEW_SIGNATURE_MISSING;
} }
@ -193,32 +169,6 @@ public class PlayerCommand implements MinecraftPacket {
} }
/**
* Validates a signature and creates a {@link SignedChatCommand} from the given signature.
*
* @param signer the signer's information
* @param sender the sender of the message
* @param mustSign instructs the function to throw if the signature is invalid.
* @return The {@link SignedChatCommand} or null if the signature couldn't be verified.
* @throws com.velocitypowered.proxy.util.except.QuietDecoderException when mustSign is {@code true} and the signature
* is invalid.
*/
public SignedChatCommand signedContainer(
@Nullable IdentifiedKey signer, UUID sender, boolean mustSign) {
// There's a certain mod that is very broken that still signs messages but
// doesn't provide the player key. This is broken and wrong, but we need to
// work around that.
if (unsigned || signer == null) {
if (mustSign) {
throw EncryptionUtils.INVALID_SIGNATURE;
}
return null;
}
return new SignedChatCommand(command, signer.getSignedPublicKey(), sender, timestamp,
arguments, Longs.toByteArray(salt), signedPreview, previousMessages, lastMessage);
}
@Override @Override
public String toString() { public String toString() {
return "PlayerCommand{" return "PlayerCommand{"

Datei anzeigen

@ -15,16 +15,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.velocitypowered.proxy.protocol.packet.chat; package com.velocitypowered.proxy.protocol.packet.chat.legacy;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import java.util.UUID; import java.util.UUID;
import net.kyori.adventure.identity.Identity;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
public class LegacyChat implements MinecraftPacket { public class LegacyChat implements MinecraftPacket {

Datei anzeigen

@ -15,41 +15,35 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.velocitypowered.proxy.protocol.packet.chat; package com.velocitypowered.proxy.protocol.packet.chat.legacy;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf; import com.velocitypowered.proxy.protocol.packet.chat.builder.ChatBuilderV2;
import java.util.UUID;
import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.text.Component;
public class PlayerChatPreview implements MinecraftPacket { public class LegacyChatBuilder extends ChatBuilderV2 {
public LegacyChatBuilder(ProtocolVersion version) {
private int id; super(version);
private String query;
public int getId() {
return id;
}
public String getQuery() {
return query;
} }
@Override @Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { public MinecraftPacket toClient() {
id = buf.readInt(); // This is temporary
query = ProtocolUtils.readString(buf, 256); UUID identity = sender == null ? (senderIdentity == null ? Identity.nil().uuid()
: senderIdentity.uuid()) : sender.getUniqueId();
Component msg = component == null ? Component.text(message) : component;
return new LegacyChat(ProtocolUtils.getJsonChatSerializer(version).serialize(msg), type.getId(), identity);
} }
@Override @Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { public MinecraftPacket toServer() {
buf.writeInt(id); LegacyChat chat = new LegacyChat();
ProtocolUtils.writeString(buf, query); chat.setMessage(message);
} return chat;
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
} }
} }

Datei anzeigen

@ -0,0 +1,56 @@
/*
* Copyright (C) 2018 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.chat.legacy;
import com.velocitypowered.api.event.player.PlayerChatEvent;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.packet.chat.ChatHandler;
public class LegacyChatHandler implements ChatHandler<LegacyChat> {
private final VelocityServer server;
private final ConnectedPlayer player;
public LegacyChatHandler(VelocityServer server, ConnectedPlayer player) {
this.server = server;
this.player = player;
}
@Override
public Class<LegacyChat> packetClass() {
return LegacyChat.class;
}
@Override
public void handlePlayerChatInternal(LegacyChat packet) {
MinecraftConnection serverConnection = player.ensureAndGetCurrentServer().ensureConnected();
if (serverConnection == null) {
return;
}
this.server.getEventManager().fire(new PlayerChatEvent(this.player, packet.getMessage()))
.whenComplete((chatEvent, throwable) -> {
if (chatEvent.getResult().isAllowed()) {
return;
}
serverConnection.write(this.player.getChatBuilderFactory().builder()
.message(chatEvent.getResult().getMessage().orElse(packet.getMessage())).toServer());
});
}
}

Datei anzeigen

@ -0,0 +1,66 @@
/*
* Copyright (C) 2018 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.chat.legacy;
import com.velocitypowered.api.event.command.CommandExecuteEvent;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.packet.chat.CommandHandler;
import java.time.Instant;
import java.util.concurrent.CompletableFuture;
public class LegacyCommandHandler implements CommandHandler<LegacyChat> {
private final ConnectedPlayer player;
private final VelocityServer server;
public LegacyCommandHandler(ConnectedPlayer player, VelocityServer server) {
this.player = player;
this.server = server;
}
@Override
public Class<LegacyChat> packetClass() {
return LegacyChat.class;
}
@Override
public void handlePlayerCommandInternal(LegacyChat packet) {
String command = packet.getMessage().substring(1);
queueCommandResult(this.server, this.player, event -> {
CommandExecuteEvent.CommandResult result = event.getResult();
if (!result.isAllowed()) {
return CompletableFuture.completedFuture(null);
}
String commandToRun = result.getCommand().orElse(command);
if (result.isForwardToServer()) {
return CompletableFuture.completedFuture(this.player.getChatBuilderFactory().builder()
.message("/" + commandToRun)
.toServer());
}
return runCommand(this.server, this.player, commandToRun, hasRun -> {
if (!hasRun) {
return this.player.getChatBuilderFactory().builder()
.message(packet.getMessage())
.asPlayer(this.player)
.toServer();
}
return null;
});
}, command, Instant.now());
}
}

Datei anzeigen

@ -0,0 +1,60 @@
/*
* Copyright (C) 2018 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.chat.session;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.packet.chat.ChatType;
import com.velocitypowered.proxy.protocol.packet.chat.LastSeenMessages;
import com.velocitypowered.proxy.protocol.packet.chat.SystemChat;
import com.velocitypowered.proxy.protocol.packet.chat.builder.ChatBuilderV2;
import net.kyori.adventure.text.Component;
public class SessionChatBuilder extends ChatBuilderV2 {
public SessionChatBuilder(ProtocolVersion version) {
super(version);
}
@Override
public MinecraftPacket toClient() {
// This is temporary
Component msg = component == null ? Component.text(message) : component;
return new SystemChat(msg, type == ChatType.CHAT ? ChatType.SYSTEM : type);
}
@Override
public MinecraftPacket toServer() {
if (message.startsWith("/")) {
SessionPlayerCommand command = new SessionPlayerCommand();
command.command = message.substring(1);
command.salt = 0L;
command.timeStamp = timestamp;
command.argumentSignatures = new SessionPlayerCommand.ArgumentSignatures();
command.lastSeenMessages = new LastSeenMessages();
return command;
} else {
SessionPlayerChat chat = new SessionPlayerChat();
chat.message = message;
chat.signed = false;
chat.signature = new byte[0];
chat.timestamp = timestamp;
chat.salt = 0L;
return chat;
}
}
}

Datei anzeigen

@ -0,0 +1,82 @@
/*
* Copyright (C) 2018 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.chat.session;
import static com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedChatHandler.invalidCancel;
import static com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedChatHandler.invalidChange;
import com.velocitypowered.api.event.EventManager;
import com.velocitypowered.api.event.player.PlayerChatEvent;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.packet.chat.ChatHandler;
import com.velocitypowered.proxy.protocol.packet.chat.ChatQueue;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class SessionChatHandler implements ChatHandler<SessionPlayerChat> {
private static final Logger logger = LogManager.getLogger(SessionChatHandler.class);
private final ConnectedPlayer player;
private final VelocityServer server;
public SessionChatHandler(ConnectedPlayer player, VelocityServer server) {
this.player = player;
this.server = server;
}
@Override
public Class<SessionPlayerChat> packetClass() {
return SessionPlayerChat.class;
}
@Override
public void handlePlayerChatInternal(SessionPlayerChat packet) {
ChatQueue chatQueue = this.player.getChatQueue();
EventManager eventManager = this.server.getEventManager();
PlayerChatEvent toSend = new PlayerChatEvent(player, packet.getMessage());
chatQueue.queuePacket(
eventManager.fire(toSend)
.thenApply(pme -> {
PlayerChatEvent.ChatResult chatResult = pme.getResult();
if (!chatResult.isAllowed()) {
if (packet.isSigned()) {
invalidCancel(logger, player);
}
return null;
}
if (chatResult.getMessage().map(str -> !str.equals(packet.getMessage())).orElse(false)) {
if (packet.isSigned()) {
invalidChange(logger, player);
return null;
}
return this.player.getChatBuilderFactory().builder().message(packet.message)
.setTimestamp(packet.timestamp)
.toServer();
}
return packet;
})
.exceptionally((ex) -> {
logger.error("Exception while handling player chat for {}", player, ex);
return null;
}),
packet.getTimestamp()
);
}
}

Datei anzeigen

@ -0,0 +1,102 @@
/*
* Copyright (C) 2018 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.chat.session;
import com.velocitypowered.api.event.command.CommandExecuteEvent;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.packet.chat.CommandHandler;
import java.util.concurrent.CompletableFuture;
import net.kyori.adventure.text.Component;
public class SessionCommandHandler implements CommandHandler<SessionPlayerCommand> {
private final ConnectedPlayer player;
private final VelocityServer server;
public SessionCommandHandler(ConnectedPlayer player, VelocityServer server) {
this.player = player;
this.server = server;
}
@Override
public Class<SessionPlayerCommand> packetClass() {
return SessionPlayerCommand.class;
}
@Override
public void handlePlayerCommandInternal(SessionPlayerCommand packet) {
queueCommandResult(this.server, this.player, event -> {
CommandExecuteEvent.CommandResult result = event.getResult();
if (!result.isAllowed()) {
if (!packet.argumentSignatures.isEmpty()) {
logger.fatal("A plugin tried to deny a command with signable component(s). " + "This is not supported. "
+ "Disconnecting player " + player.getUsername());
player.disconnect(Component.text(
"A proxy plugin caused an illegal protocol state. " + "Contact your network administrator."));
}
return CompletableFuture.completedFuture(null);
}
String commandToRun = result.getCommand().orElse(packet.command);
if (result.isForwardToServer()) {
if (!packet.argumentSignatures.isEmpty() && commandToRun.equals(packet.command)) {
return CompletableFuture.completedFuture(packet);
} else {
if (!packet.argumentSignatures.isEmpty()) {
logger.fatal("A plugin tried to change a command with signed component(s). " + "This is not supported. "
+ "Disconnecting player " + player.getUsername());
player.disconnect(Component.text(
"A proxy plugin caused an illegal protocol state. " + "Contact your network administrator."));
return CompletableFuture.completedFuture(null);
}
return CompletableFuture.completedFuture(this.player.getChatBuilderFactory()
.builder()
.setTimestamp(packet.timeStamp)
.asPlayer(this.player)
.message("/" + commandToRun)
.toServer());
}
}
return runCommand(this.server, this.player, commandToRun, hasRun -> {
if (!hasRun) {
if (!packet.argumentSignatures.isEmpty() && commandToRun.equals(packet.command)) {
return packet;
} else {
if (!packet.argumentSignatures.isEmpty()) {
logger.fatal("A plugin tried to change a command with signed component(s). " + "This is not supported. "
+ "Disconnecting player " + player.getUsername());
player.disconnect(Component.text(
"A proxy plugin caused an illegal protocol state. " + "Contact your network administrator."));
return null;
}
return this.player.getChatBuilderFactory()
.builder()
.setTimestamp(packet.timeStamp)
.asPlayer(this.player)
.message("/" + commandToRun)
.toServer();
}
}
return null;
});
}, packet.command, packet.timeStamp);
}
}

Datei anzeigen

@ -0,0 +1,99 @@
/*
* Copyright (C) 2018 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.chat.session;
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.LastSeenMessages;
import io.netty.buffer.ByteBuf;
import java.time.Instant;
public class SessionPlayerChat implements MinecraftPacket {
protected String message;
protected Instant timestamp;
protected long salt;
protected boolean signed;
protected byte[] signature;
protected LastSeenMessages lastSeenMessages;
public SessionPlayerChat() {
}
public String getMessage() {
return message;
}
public Instant getTimestamp() {
return timestamp;
}
public long getSalt() {
return salt;
}
public boolean isSigned() {
return signed;
}
public byte[] getSignature() {
return signature;
}
public LastSeenMessages getLastSeenMessages() {
return lastSeenMessages;
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
this.message = ProtocolUtils.readString(buf, 256);
this.timestamp = Instant.ofEpochMilli(buf.readLong());
this.salt = buf.readLong();
this.signed = buf.readBoolean();
if (this.signed) {
this.signature = readMessageSignature(buf);
} else {
this.signature = new byte[0];
}
this.lastSeenMessages = new LastSeenMessages(buf);
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeString(buf, this.message);
buf.writeLong(this.timestamp.toEpochMilli());
buf.writeLong(this.salt);
buf.writeBoolean(this.signed);
if (this.signed) {
buf.writeBytes(this.signature);
}
this.lastSeenMessages.encode(buf);
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
protected static byte[] readMessageSignature(ByteBuf buf) {
byte[] signature = new byte[256];
buf.readBytes(signature);
return signature;
}
}

Datei anzeigen

@ -0,0 +1,114 @@
/*
* Copyright (C) 2018 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.chat.session;
import com.google.common.collect.Lists;
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.LastSeenMessages;
import com.velocitypowered.proxy.util.except.QuietDecoderException;
import io.netty.buffer.ByteBuf;
import java.time.Instant;
import java.util.List;
public class SessionPlayerCommand implements MinecraftPacket {
protected String command;
protected Instant timeStamp;
protected long salt;
protected ArgumentSignatures argumentSignatures;
protected LastSeenMessages lastSeenMessages;
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
this.command = ProtocolUtils.readString(buf, 256);
this.timeStamp = Instant.ofEpochMilli(buf.readLong());
this.salt = buf.readLong();
this.argumentSignatures = new ArgumentSignatures(buf);
this.lastSeenMessages = new LastSeenMessages(buf);
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeString(buf, this.command);
buf.writeLong(this.timeStamp.toEpochMilli());
buf.writeLong(this.salt);
this.argumentSignatures.encode(buf);
this.lastSeenMessages.encode(buf);
}
public String getCommand() {
return command;
}
public Instant getTimeStamp() {
return timeStamp;
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class ArgumentSignatures {
private final List<ArgumentSignature> entries;
public ArgumentSignatures() {
this.entries = List.of();
}
public ArgumentSignatures(ByteBuf buf) {
int size = ProtocolUtils.readVarInt(buf);
if (size > 8) {
throw new QuietDecoderException(String.format("Too many argument signatures, %d is above limit %d", size, 8));
}
this.entries = Lists.newArrayListWithCapacity(size);
for (int i = 0; i < size; i++) {
this.entries.add(new ArgumentSignature(buf));
}
}
public boolean isEmpty() {
return this.entries.isEmpty();
}
public void encode(ByteBuf buf) {
ProtocolUtils.writeVarInt(buf, entries.size());
for (ArgumentSignature entry : entries) {
entry.encode(buf);
}
}
}
public static class ArgumentSignature {
private final String name;
private final byte[] signature;
public ArgumentSignature(ByteBuf buf) {
name = ProtocolUtils.readString(buf, 16);
signature = SessionPlayerChat.readMessageSignature(buf);
}
public void encode(ByteBuf buf) {
ProtocolUtils.writeString(buf, name);
buf.writeBytes(signature);
}
}
}

Datei anzeigen

@ -0,0 +1,34 @@
/*
* Copyright (C) 2018 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.tablist;
import com.velocitypowered.api.proxy.player.TabList;
import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem;
import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfo;
import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo;
public interface InternalTabList extends TabList {
default void processLegacy(LegacyPlayerListItem packet) {
}
default void processUpdate(UpsertPlayerInfo infoPacket) {
}
default void processRemove(RemovePlayerInfo infoPacket) {
}
}

Datei anzeigen

@ -0,0 +1,254 @@
/*
* Copyright (C) 2018 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.tablist;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.Player;
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.TabList;
import com.velocitypowered.api.proxy.player.TabListEntry;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter;
import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem;
import com.velocitypowered.proxy.protocol.packet.chat.RemoteChatSession;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable;
public class KeyedVelocityTabList implements InternalTabList {
protected final ConnectedPlayer player;
protected final MinecraftConnection connection;
protected final ProxyServer proxyServer;
protected final Map<UUID, KeyedVelocityTabListEntry> entries = new ConcurrentHashMap<>();
/**
* Creates a new VelocityTabList.
*/
public KeyedVelocityTabList(final ConnectedPlayer player, final ProxyServer proxyServer) {
this.player = player;
this.proxyServer = proxyServer;
this.connection = player.getConnection();
}
@Deprecated
@Override
public void setHeaderAndFooter(Component header, Component footer) {
Preconditions.checkNotNull(header, "header");
Preconditions.checkNotNull(footer, "footer");
this.player.sendPlayerListHeaderAndFooter(header, footer);
}
@Override
public void clearHeaderAndFooter() {
connection.write(HeaderAndFooter.reset());
}
@Override
public void addEntry(TabListEntry entry) {
Preconditions.checkNotNull(entry, "entry");
Preconditions.checkArgument(entry.getTabList().equals(this),
"The provided entry was not created by this tab list");
Preconditions.checkArgument(!entries.containsKey(entry.getProfile().getId()),
"this TabList already contains an entry with the same uuid");
Preconditions.checkArgument(entry instanceof KeyedVelocityTabListEntry,
"Not a Velocity tab list entry");
LegacyPlayerListItem.Item packetItem = LegacyPlayerListItem.Item.from(entry);
connection.write(
new LegacyPlayerListItem(LegacyPlayerListItem.ADD_PLAYER, Collections.singletonList(packetItem)));
entries.put(entry.getProfile().getId(), (KeyedVelocityTabListEntry) entry);
}
@Override
public Optional<TabListEntry> removeEntry(UUID uuid) {
Preconditions.checkNotNull(uuid, "uuid");
TabListEntry entry = entries.remove(uuid);
if (entry != null) {
LegacyPlayerListItem.Item packetItem = LegacyPlayerListItem.Item.from(entry);
connection.write(
new LegacyPlayerListItem(LegacyPlayerListItem.REMOVE_PLAYER, Collections.singletonList(packetItem)));
}
return Optional.ofNullable(entry);
}
@Override
public boolean containsEntry(UUID uuid) {
Preconditions.checkNotNull(uuid, "uuid");
return entries.containsKey(uuid);
}
/**
* Clears all entries from the tab list. Note that the entries are written with {@link
* MinecraftConnection#delayedWrite(Object)}, so make sure to do an explicit {@link
* MinecraftConnection#flush()}.
*/
@Override
public void clearAll() {
Collection<KeyedVelocityTabListEntry> listEntries = entries.values();
if (listEntries.isEmpty()) {
return;
}
List<LegacyPlayerListItem.Item> items = new ArrayList<>(listEntries.size());
for (TabListEntry value : listEntries) {
items.add(LegacyPlayerListItem.Item.from(value));
}
entries.clear();
connection.delayedWrite(new LegacyPlayerListItem(LegacyPlayerListItem.REMOVE_PLAYER, items));
}
@Override
public Collection<TabListEntry> getEntries() {
return Collections.unmodifiableCollection(this.entries.values());
}
@Override
public TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency, int gameMode) {
return buildEntry(profile, displayName, latency, gameMode, (ChatSession) null);
}
@Override
public TabListEntry buildEntry(GameProfile profile,
net.kyori.adventure.text.@Nullable Component displayName,
int latency, int gameMode, @Nullable IdentifiedKey key) {
return new KeyedVelocityTabListEntry(this, profile, displayName, latency, gameMode, key);
}
@Override
public TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency, int gameMode,
@Nullable ChatSession chatSession) {
return new KeyedVelocityTabListEntry(this, profile, displayName, latency, gameMode,
chatSession == null ? null : chatSession.getIdentifiedKey());
}
@Override
public void processLegacy(LegacyPlayerListItem packet) {
// Packets are already forwarded on, so no need to do that here
for (LegacyPlayerListItem.Item item : packet.getItems()) {
UUID uuid = item.getUuid();
assert uuid != null : "1.7 tab list entry given to modern tab list handler!";
if (packet.getAction() != LegacyPlayerListItem.ADD_PLAYER && !entries.containsKey(uuid)) {
// Sometimes UPDATE_GAMEMODE is sent before ADD_PLAYER so don't want to warn here
continue;
}
switch (packet.getAction()) {
case LegacyPlayerListItem.ADD_PLAYER: {
// ensure that name and properties are available
String name = item.getName();
List<GameProfile.Property> properties = item.getProperties();
if (name == null || properties == null) {
throw new IllegalStateException("Got null game profile for ADD_PLAYER");
}
/* why are we verifying the key here - multi-proxy setups break this
// Verify key
IdentifiedKey providedKey = item.getPlayerKey();
Optional<Player> connected = proxyServer.getPlayer(uuid);
if (connected.isPresent()) {
IdentifiedKey expectedKey = connected.get().getIdentifiedKey();
if (providedKey != null) {
if (!Objects.equals(expectedKey, providedKey)) {
throw new IllegalStateException("Server provided incorrect player key in playerlist for "
+ name + " UUID: " + uuid);
}
} else {
// Substitute the key
// It shouldn't be propagated to remove the signature.
providedKey = expectedKey;
}
}
*/
entries.putIfAbsent(item.getUuid(), (KeyedVelocityTabListEntry) TabListEntry.builder()
.tabList(this)
.profile(new GameProfile(uuid, name, properties))
.displayName(item.getDisplayName())
.latency(item.getLatency())
.chatSession(new RemoteChatSession(null, item.getPlayerKey()))
.gameMode(item.getGameMode())
.build());
break;
}
case LegacyPlayerListItem.REMOVE_PLAYER:
entries.remove(uuid);
break;
case LegacyPlayerListItem.UPDATE_DISPLAY_NAME: {
KeyedVelocityTabListEntry entry = entries.get(uuid);
if (entry != null) {
entry.setDisplayNameInternal(item.getDisplayName());
}
break;
}
case LegacyPlayerListItem.UPDATE_LATENCY: {
KeyedVelocityTabListEntry entry = entries.get(uuid);
if (entry != null) {
entry.setLatencyInternal(item.getLatency());
}
break;
}
case LegacyPlayerListItem.UPDATE_GAMEMODE: {
KeyedVelocityTabListEntry entry = entries.get(uuid);
if (entry != null) {
entry.setGameModeInternal(item.getGameMode());
}
break;
}
default:
// Nothing we can do here
break;
}
}
}
void updateEntry(int action, TabListEntry entry) {
if (entries.containsKey(entry.getProfile().getId())) {
LegacyPlayerListItem.Item packetItem = LegacyPlayerListItem.Item.from(entry);
IdentifiedKey selectedKey = packetItem.getPlayerKey();
Optional<Player> existing = proxyServer.getPlayer(entry.getProfile().getId());
if (existing.isPresent()) {
selectedKey = existing.get().getIdentifiedKey();
}
if (selectedKey != null
&& selectedKey.getKeyRevision().getApplicableTo().contains(connection.getProtocolVersion())
&& Objects.equals(selectedKey.getSignatureHolder(), entry.getProfile().getId())) {
packetItem.setPlayerKey(selectedKey);
} else {
packetItem.setPlayerKey(null);
}
connection.write(new LegacyPlayerListItem(action, Collections.singletonList(packetItem)));
}
}
}

Datei anzeigen

@ -0,0 +1,121 @@
/*
* Copyright (C) 2018 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.tablist;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
import com.velocitypowered.api.proxy.player.ChatSession;
import com.velocitypowered.api.proxy.player.TabList;
import com.velocitypowered.api.proxy.player.TabListEntry;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem;
import com.velocitypowered.proxy.protocol.packet.chat.RemoteChatSession;
import java.util.Optional;
import org.checkerframework.checker.nullness.qual.Nullable;
public class KeyedVelocityTabListEntry implements TabListEntry {
private final KeyedVelocityTabList tabList;
private final GameProfile profile;
private net.kyori.adventure.text.Component displayName;
private int latency;
private int gameMode;
private @Nullable IdentifiedKey playerKey;
KeyedVelocityTabListEntry(KeyedVelocityTabList tabList, GameProfile profile,
net.kyori.adventure.text.@Nullable Component displayName, int latency, int gameMode,
@Nullable IdentifiedKey playerKey) {
this.tabList = tabList;
this.profile = profile;
this.displayName = displayName;
this.latency = latency;
this.gameMode = gameMode;
this.playerKey = playerKey;
}
@Override
public TabList getTabList() {
return tabList;
}
@Override
public GameProfile getProfile() {
return profile;
}
@Override
public Optional<net.kyori.adventure.text.Component> getDisplayNameComponent() {
return Optional.ofNullable(displayName);
}
@Override
public TabListEntry setDisplayName(net.kyori.adventure.text.@Nullable Component displayName) {
this.displayName = displayName;
tabList.updateEntry(LegacyPlayerListItem.UPDATE_DISPLAY_NAME, this);
return this;
}
void setDisplayNameInternal(net.kyori.adventure.text.@Nullable Component displayName) {
this.displayName = displayName;
}
@Override
public int getLatency() {
return latency;
}
@Override
public TabListEntry setLatency(int latency) {
this.latency = latency;
tabList.updateEntry(LegacyPlayerListItem.UPDATE_LATENCY, this);
return this;
}
void setLatencyInternal(int latency) {
this.latency = latency;
}
@Override
public int getGameMode() {
return gameMode;
}
@Override
public TabListEntry setGameMode(int gameMode) {
this.gameMode = gameMode;
tabList.updateEntry(LegacyPlayerListItem.UPDATE_GAMEMODE, this);
return this;
}
void setGameModeInternal(int gameMode) {
this.gameMode = gameMode;
}
@Override
public @Nullable ChatSession getChatSession() {
return new RemoteChatSession(null, this.playerKey);
}
@Override
public IdentifiedKey getIdentifiedKey() {
return playerKey;
}
void setPlayerKeyInternal(IdentifiedKey playerKey) {
this.playerKey = playerKey;
}
}

Datei anzeigen

@ -18,46 +18,45 @@
package com.velocitypowered.proxy.tablist; package com.velocitypowered.proxy.tablist;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.velocitypowered.api.network.ProtocolVersion; import com.google.common.collect.Maps;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey; import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
import com.velocitypowered.api.proxy.player.TabList; import com.velocitypowered.api.proxy.player.ChatSession;
import com.velocitypowered.api.proxy.player.TabListEntry; import com.velocitypowered.api.proxy.player.TabListEntry;
import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.console.VelocityConsole;
import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter; import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter;
import com.velocitypowered.proxy.protocol.packet.PlayerListItem; import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfo;
import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo;
import com.velocitypowered.proxy.protocol.packet.chat.RemoteChatSession;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
public class VelocityTabList implements TabList { public class VelocityTabList implements InternalTabList {
private static final Logger logger = LogManager.getLogger(VelocityConsole.class);
private final ConnectedPlayer player;
private final MinecraftConnection connection;
private final Map<UUID, VelocityTabListEntry> entries;
protected final ConnectedPlayer player; public VelocityTabList(ConnectedPlayer player) {
protected final MinecraftConnection connection;
protected final ProxyServer proxyServer;
protected final Map<UUID, VelocityTabListEntry> entries = new ConcurrentHashMap<>();
/**
* Creates a new VelocityTabList.
*/
public VelocityTabList(final ConnectedPlayer player, final ProxyServer proxyServer) {
this.player = player; this.player = player;
this.proxyServer = proxyServer;
this.connection = player.getConnection(); this.connection = player.getConnection();
this.entries = Maps.newHashMap();
} }
@Deprecated
@Override @Override
public void setHeaderAndFooter(Component header, Component footer) { public void setHeaderAndFooter(Component header, Component footer) {
Preconditions.checkNotNull(header, "header"); Preconditions.checkNotNull(header, "header");
@ -71,177 +70,178 @@ public class VelocityTabList implements TabList {
} }
@Override @Override
public void addEntry(TabListEntry entry) { public void addEntry(TabListEntry entry1) {
Preconditions.checkNotNull(entry, "entry"); VelocityTabListEntry entry;
Preconditions.checkArgument(entry.getTabList().equals(this), if (entry1 instanceof VelocityTabListEntry) {
"The provided entry was not created by this tab list"); entry = (VelocityTabListEntry) entry1;
Preconditions.checkArgument(!entries.containsKey(entry.getProfile().getId()), } else {
"this TabList already contains an entry with the same uuid"); entry = new VelocityTabListEntry(this, entry1.getProfile(), entry1.getDisplayNameComponent().orElse(null),
Preconditions.checkArgument(entry instanceof VelocityTabListEntry, entry1.getLatency(), entry1.getGameMode(), entry1.getChatSession(), entry1.isListed());
"Not a Velocity tab list entry"); }
PlayerListItem.Item packetItem = PlayerListItem.Item.from(entry); EnumSet<UpsertPlayerInfo.Action> actions = EnumSet.noneOf(UpsertPlayerInfo.Action.class);
connection.write( UpsertPlayerInfo.Entry playerInfoEntry = new UpsertPlayerInfo.Entry(entry.getProfile().getId());
new PlayerListItem(PlayerListItem.ADD_PLAYER, Collections.singletonList(packetItem)));
entries.put(entry.getProfile().getId(), (VelocityTabListEntry) entry); Preconditions.checkNotNull(entry.getProfile(), "Profile cannot be null");
Preconditions.checkNotNull(entry.getProfile().getId(), "Profile ID cannot be null");
TabListEntry previousEntry = this.entries.put(entry.getProfile().getId(), entry);
if (previousEntry != null) {
// we should merge entries here
if (previousEntry.equals(entry)) {
return; // nothing else to do, this entry is perfect
}
if (!Objects.equals(previousEntry.getDisplayNameComponent().orElse(null),
entry.getDisplayNameComponent().orElse(null))) {
actions.add(UpsertPlayerInfo.Action.UPDATE_DISPLAY_NAME);
playerInfoEntry.setDisplayName(entry.getDisplayNameComponent().get());
}
if (!Objects.equals(previousEntry.getLatency(), entry.getLatency())) {
actions.add(UpsertPlayerInfo.Action.UPDATE_LATENCY);
playerInfoEntry.setLatency(entry.getLatency());
}
if (!Objects.equals(previousEntry.getGameMode(), entry.getGameMode())) {
actions.add(UpsertPlayerInfo.Action.UPDATE_GAME_MODE);
playerInfoEntry.setGameMode(entry.getGameMode());
}
if (!Objects.equals(previousEntry.isListed(), entry.isListed())) {
actions.add(UpsertPlayerInfo.Action.UPDATE_LISTED);
playerInfoEntry.setListed(entry.isListed());
}
if (!Objects.equals(previousEntry.getChatSession(), entry.getChatSession())) {
ChatSession from = entry.getChatSession();
if (from != null) {
actions.add(UpsertPlayerInfo.Action.INITIALIZE_CHAT);
playerInfoEntry.setChatSession(new RemoteChatSession(from.getSessionId(), from.getIdentifiedKey()));
}
}
} else {
actions.addAll(EnumSet.of(UpsertPlayerInfo.Action.ADD_PLAYER,
UpsertPlayerInfo.Action.UPDATE_LATENCY,
UpsertPlayerInfo.Action.UPDATE_LISTED));
if (entry.getDisplayNameComponent().isPresent()) {
actions.add(UpsertPlayerInfo.Action.UPDATE_DISPLAY_NAME);
playerInfoEntry.setDisplayName(entry.getDisplayNameComponent().get());
}
if (entry.getChatSession() != null) {
actions.add(UpsertPlayerInfo.Action.INITIALIZE_CHAT);
ChatSession from = entry.getChatSession();
playerInfoEntry.setChatSession(new RemoteChatSession(from.getSessionId(), from.getIdentifiedKey()));
}
if (entry.getGameMode() != -1 && entry.getGameMode() != 256) {
actions.add(UpsertPlayerInfo.Action.UPDATE_GAME_MODE);
playerInfoEntry.setGameMode(entry.getGameMode());
}
playerInfoEntry.setLatency(entry.getLatency());
playerInfoEntry.setListed(entry.isListed());
}
this.connection.write(new UpsertPlayerInfo(actions, List.of(playerInfoEntry)));
} }
@Override @Override
public Optional<TabListEntry> removeEntry(UUID uuid) { public Optional<TabListEntry> removeEntry(UUID uuid) {
Preconditions.checkNotNull(uuid, "uuid"); this.connection.write(new RemovePlayerInfo(List.of(uuid)));
return Optional.ofNullable(this.entries.remove(uuid));
TabListEntry entry = entries.remove(uuid);
if (entry != null) {
PlayerListItem.Item packetItem = PlayerListItem.Item.from(entry);
connection.write(
new PlayerListItem(PlayerListItem.REMOVE_PLAYER, Collections.singletonList(packetItem)));
}
return Optional.ofNullable(entry);
} }
@Override @Override
public boolean containsEntry(UUID uuid) { public boolean containsEntry(UUID uuid) {
Preconditions.checkNotNull(uuid, "uuid"); return this.entries.containsKey(uuid);
return entries.containsKey(uuid);
}
/**
* Clears all entries from the tab list. Note that the entries are written with {@link
* MinecraftConnection#delayedWrite(Object)}, so make sure to do an explicit {@link
* MinecraftConnection#flush()}.
*/
public void clearAll() {
Collection<VelocityTabListEntry> listEntries = entries.values();
if (listEntries.isEmpty()) {
return;
}
List<PlayerListItem.Item> items = new ArrayList<>(listEntries.size());
for (TabListEntry value : listEntries) {
items.add(PlayerListItem.Item.from(value));
}
entries.clear();
connection.delayedWrite(new PlayerListItem(PlayerListItem.REMOVE_PLAYER, items));
} }
@Override @Override
public Collection<TabListEntry> getEntries() { public Collection<TabListEntry> getEntries() {
return Collections.unmodifiableCollection(this.entries.values()); return this.entries.values().stream().map(e -> (TabListEntry) e).collect(Collectors.toList());
}
@Override
public void clearAll() {
this.connection.delayedWrite(new RemovePlayerInfo(new ArrayList<>(this.entries.keySet())));
this.entries.clear();
} }
@Override @Override
public TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency, int gameMode) { public TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency, int gameMode) {
return buildEntry(profile, displayName, latency, gameMode, null); return new VelocityTabListEntry(this, profile, displayName, latency, gameMode, null, true);
} }
@Override @Override
public TabListEntry buildEntry(GameProfile profile, public TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency, int gameMode,
net.kyori.adventure.text.@Nullable Component displayName, @Nullable IdentifiedKey key) {
int latency, int gameMode, @Nullable IdentifiedKey key) { return new VelocityTabListEntry(this, profile, displayName, latency, gameMode, null, true);
return new VelocityTabListEntry(this, profile, displayName, latency, gameMode, key);
} }
/** @Override
* Processes a tab list entry packet from the backend. public TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency, int gameMode,
* @Nullable ChatSession chatSession) {
* @param packet the packet to process return new VelocityTabListEntry(this, profile, displayName, latency, gameMode, chatSession, true);
*/
public void processBackendPacket(PlayerListItem packet) {
// Packets are already forwarded on, so no need to do that here
for (PlayerListItem.Item item : packet.getItems()) {
UUID uuid = item.getUuid();
assert uuid != null : "1.7 tab list entry given to modern tab list handler!";
if (packet.getAction() != PlayerListItem.ADD_PLAYER && !entries.containsKey(uuid)) {
// Sometimes UPDATE_GAMEMODE is sent before ADD_PLAYER so don't want to warn here
continue;
} }
switch (packet.getAction()) { @Override
case PlayerListItem.ADD_PLAYER: { public void processUpdate(UpsertPlayerInfo infoPacket) {
// ensure that name and properties are available for (UpsertPlayerInfo.Entry entry : infoPacket.getEntries()) {
String name = item.getName(); processUpsert(infoPacket.getActions(), entry);
List<GameProfile.Property> properties = item.getProperties();
if (name == null || properties == null) {
throw new IllegalStateException("Got null game profile for ADD_PLAYER");
} }
// Verify key
IdentifiedKey providedKey = item.getPlayerKey();
Optional<Player> connected = proxyServer.getPlayer(uuid);
if (connected.isPresent()) {
IdentifiedKey expectedKey = connected.get().getIdentifiedKey();
if (providedKey != null) {
if (!Objects.equals(expectedKey, providedKey)) {
throw new IllegalStateException("Server provided incorrect player key in playerlist for "
+ name + " UUID: " + uuid);
} }
protected UpsertPlayerInfo.Entry createRawEntry(VelocityTabListEntry entry) {
Preconditions.checkNotNull(entry, "entry");
Preconditions.checkNotNull(entry.getProfile(), "Profile cannot be null");
Preconditions.checkNotNull(entry.getProfile().getId(), "Profile ID cannot be null");
return new UpsertPlayerInfo.Entry(entry.getProfile().getId());
}
protected void emitActionRaw(UpsertPlayerInfo.Action action, UpsertPlayerInfo.Entry entry) {
this.connection.write(new UpsertPlayerInfo(EnumSet.of(action), Collections.singletonList(entry)));
}
private void processUpsert(EnumSet<UpsertPlayerInfo.Action> actions, UpsertPlayerInfo.Entry entry) {
Preconditions.checkNotNull(entry.getProfileId(), "Profile ID cannot be null");
UUID profileId = entry.getProfileId();
VelocityTabListEntry currentEntry = this.entries.get(profileId);
if (actions.contains(UpsertPlayerInfo.Action.ADD_PLAYER)) {
if (currentEntry == null) {
this.entries.put(profileId,
currentEntry = new VelocityTabListEntry(
this,
entry.getProfile(),
null,
0,
-1,
null,
true
)
);
} else { } else {
// Substitute the key logger.debug("Received an add player packet for an existing entry; this does nothing.");
// It shouldn't be propagated to remove the signature. }
providedKey = expectedKey; } else if (currentEntry == null) {
logger.debug(
"Received a partial player before an ADD_PLAYER action; profile could not be built. {}", entry);
return;
}
if (actions.contains(UpsertPlayerInfo.Action.UPDATE_GAME_MODE)) {
currentEntry.setGameMode(entry.getGameMode());
}
if (actions.contains(UpsertPlayerInfo.Action.UPDATE_LATENCY)) {
currentEntry.setLatency(entry.getLatency());
}
if (actions.contains(UpsertPlayerInfo.Action.UPDATE_DISPLAY_NAME)) {
currentEntry.setDisplayName(entry.getDisplayName());
}
if (actions.contains(UpsertPlayerInfo.Action.INITIALIZE_CHAT)) {
currentEntry.setChatSession(entry.getChatSession());
}
if (actions.contains(UpsertPlayerInfo.Action.UPDATE_LISTED)) {
currentEntry.setListed(entry.isListed());
} }
} }
entries.putIfAbsent(item.getUuid(), (VelocityTabListEntry) TabListEntry.builder() @Override
.tabList(this) public void processRemove(RemovePlayerInfo infoPacket) {
.profile(new GameProfile(uuid, name, properties)) for (UUID uuid : infoPacket.getProfilesToRemove()) {
.displayName(item.getDisplayName()) this.entries.remove(uuid);
.latency(item.getLatency())
.playerKey(providedKey)
.gameMode(item.getGameMode())
.build());
break;
}
case PlayerListItem.REMOVE_PLAYER:
entries.remove(uuid);
break;
case PlayerListItem.UPDATE_DISPLAY_NAME: {
VelocityTabListEntry entry = entries.get(uuid);
if (entry != null) {
entry.setDisplayNameInternal(item.getDisplayName());
}
break;
}
case PlayerListItem.UPDATE_LATENCY: {
VelocityTabListEntry entry = entries.get(uuid);
if (entry != null) {
entry.setLatencyInternal(item.getLatency());
}
break;
}
case PlayerListItem.UPDATE_GAMEMODE: {
VelocityTabListEntry entry = entries.get(uuid);
if (entry != null) {
entry.setGameModeInternal(item.getGameMode());
}
break;
}
default:
// Nothing we can do here
break;
}
}
}
void updateEntry(int action, TabListEntry entry) {
if (entries.containsKey(entry.getProfile().getId())) {
PlayerListItem.Item packetItem = PlayerListItem.Item.from(entry);
IdentifiedKey selectedKey = packetItem.getPlayerKey();
Optional<Player> existing = proxyServer.getPlayer(entry.getProfile().getId());
if (existing.isPresent()) {
selectedKey = existing.get().getIdentifiedKey();
}
if (selectedKey != null
&& selectedKey.getKeyRevision().getApplicableTo().contains(connection.getProtocolVersion())
&& Objects.equals(selectedKey.getSignatureHolder(), entry.getProfile().getId())) {
packetItem.setPlayerKey(selectedKey);
} else {
packetItem.setPlayerKey(null);
}
connection.write(new PlayerListItem(action, Collections.singletonList(packetItem)));
} }
} }
} }

Datei anzeigen

@ -17,98 +17,108 @@
package com.velocitypowered.proxy.tablist; package com.velocitypowered.proxy.tablist;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey; import com.velocitypowered.api.proxy.player.ChatSession;
import com.velocitypowered.api.proxy.player.TabList; import com.velocitypowered.api.proxy.player.TabList;
import com.velocitypowered.api.proxy.player.TabListEntry; import com.velocitypowered.api.proxy.player.TabListEntry;
import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.protocol.packet.PlayerListItem; import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo;
import com.velocitypowered.proxy.protocol.packet.chat.RemoteChatSession;
import java.util.Optional; import java.util.Optional;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
public class VelocityTabListEntry implements TabListEntry { public class VelocityTabListEntry implements TabListEntry {
private final VelocityTabList tabList; private final VelocityTabList tabList;
private final GameProfile profile; private final GameProfile profile;
private net.kyori.adventure.text.Component displayName; private Component displayName;
private int latency; private int latency;
private int gameMode; private int gameMode;
private @Nullable IdentifiedKey playerKey; private boolean listed;
private @Nullable ChatSession session;
VelocityTabListEntry(VelocityTabList tabList, GameProfile profile, public VelocityTabListEntry(VelocityTabList tabList, GameProfile profile, Component displayName, int latency,
net.kyori.adventure.text.@Nullable Component displayName, int latency, int gameMode, int gameMode, @Nullable ChatSession session, boolean listed) {
@Nullable IdentifiedKey playerKey) {
this.tabList = tabList; this.tabList = tabList;
this.profile = profile; this.profile = profile;
this.displayName = displayName; this.displayName = displayName;
this.latency = latency; this.latency = latency;
this.gameMode = gameMode; this.gameMode = gameMode;
this.playerKey = playerKey; this.session = session;
this.listed = listed;
}
@Override
public @Nullable ChatSession getChatSession() {
return this.session;
} }
@Override @Override
public TabList getTabList() { public TabList getTabList() {
return tabList; return this.tabList;
} }
@Override @Override
public GameProfile getProfile() { public GameProfile getProfile() {
return profile; return this.profile;
} }
@Override @Override
public Optional<net.kyori.adventure.text.Component> getDisplayNameComponent() { public Optional<Component> getDisplayNameComponent() {
return Optional.ofNullable(displayName); return Optional.ofNullable(displayName);
} }
@Override @Override
public TabListEntry setDisplayName(net.kyori.adventure.text.@Nullable Component displayName) { public TabListEntry setDisplayName(@Nullable Component displayName) {
this.displayName = displayName; this.displayName = displayName;
tabList.updateEntry(PlayerListItem.UPDATE_DISPLAY_NAME, this); UpsertPlayerInfo.Entry upsertEntry = this.tabList.createRawEntry(this);
upsertEntry.setDisplayName(displayName);
this.tabList.emitActionRaw(UpsertPlayerInfo.Action.UPDATE_DISPLAY_NAME, upsertEntry);
return this; return this;
} }
void setDisplayNameInternal(net.kyori.adventure.text.@Nullable Component displayName) {
this.displayName = displayName;
}
@Override @Override
public int getLatency() { public int getLatency() {
return latency; return this.latency;
} }
@Override @Override
public TabListEntry setLatency(int latency) { public TabListEntry setLatency(int latency) {
this.latency = latency; this.latency = latency;
tabList.updateEntry(PlayerListItem.UPDATE_LATENCY, this); UpsertPlayerInfo.Entry upsertEntry = this.tabList.createRawEntry(this);
upsertEntry.setLatency(latency);
this.tabList.emitActionRaw(UpsertPlayerInfo.Action.UPDATE_LATENCY, upsertEntry);
return this; return this;
} }
void setLatencyInternal(int latency) {
this.latency = latency;
}
@Override @Override
public int getGameMode() { public int getGameMode() {
return gameMode; return this.gameMode;
} }
@Override @Override
public TabListEntry setGameMode(int gameMode) { public TabListEntry setGameMode(int gameMode) {
this.gameMode = gameMode; this.gameMode = gameMode;
tabList.updateEntry(PlayerListItem.UPDATE_GAMEMODE, this); UpsertPlayerInfo.Entry upsertEntry = this.tabList.createRawEntry(this);
upsertEntry.setGameMode(gameMode);
this.tabList.emitActionRaw(UpsertPlayerInfo.Action.UPDATE_GAME_MODE, upsertEntry);
return this; return this;
} }
void setGameModeInternal(int gameMode) { protected void setChatSession(@Nullable ChatSession session) {
this.gameMode = gameMode; this.session = session;
} }
@Override @Override
public IdentifiedKey getIdentifiedKey() { public boolean isListed() {
return playerKey; return listed;
} }
void setPlayerKeyInternal(IdentifiedKey playerKey) { @Override
this.playerKey = playerKey; public VelocityTabListEntry setListed(boolean listed) {
this.listed = listed;
UpsertPlayerInfo.Entry upsertEntry = this.tabList.createRawEntry(this);
upsertEntry.setListed(listed);
this.tabList.emitActionRaw(UpsertPlayerInfo.Action.UPDATE_LISTED, upsertEntry);
return this;
} }
} }

Datei anzeigen

@ -22,7 +22,7 @@ import com.velocitypowered.api.util.GameProfile;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
public class VelocityTabListEntryLegacy extends VelocityTabListEntry { public class VelocityTabListEntryLegacy extends KeyedVelocityTabListEntry {
VelocityTabListEntryLegacy(VelocityTabListLegacy tabList, GameProfile profile, VelocityTabListEntryLegacy(VelocityTabListLegacy tabList, GameProfile profile,
@Nullable Component displayName, int latency, int gameMode) { @Nullable Component displayName, int latency, int gameMode) {

Datei anzeigen

@ -22,8 +22,8 @@ import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.player.TabListEntry; import com.velocitypowered.api.proxy.player.TabListEntry;
import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.packet.PlayerListItem; import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem;
import com.velocitypowered.proxy.protocol.packet.PlayerListItem.Item; import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem.Item;
import java.util.Collections; import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@ -32,7 +32,7 @@ import java.util.concurrent.ConcurrentHashMap;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
public class VelocityTabListLegacy extends VelocityTabList { public class VelocityTabListLegacy extends KeyedVelocityTabList {
private final Map<String, UUID> nameMapping = new ConcurrentHashMap<>(); private final Map<String, UUID> nameMapping = new ConcurrentHashMap<>();
@ -65,35 +65,35 @@ public class VelocityTabListLegacy extends VelocityTabList {
@Override @Override
public void clearAll() { public void clearAll() {
for (TabListEntry value : entries.values()) { for (TabListEntry value : entries.values()) {
connection.delayedWrite(new PlayerListItem(PlayerListItem.REMOVE_PLAYER, connection.delayedWrite(new LegacyPlayerListItem(LegacyPlayerListItem.REMOVE_PLAYER,
Collections.singletonList(PlayerListItem.Item.from(value)))); Collections.singletonList(LegacyPlayerListItem.Item.from(value))));
} }
entries.clear(); entries.clear();
nameMapping.clear(); nameMapping.clear();
} }
@Override @Override
public void processBackendPacket(PlayerListItem packet) { public void processLegacy(LegacyPlayerListItem packet) {
Item item = packet.getItems().get(0); // Only one item per packet in 1.7 Item item = packet.getItems().get(0); // Only one item per packet in 1.7
switch (packet.getAction()) { switch (packet.getAction()) {
case PlayerListItem.ADD_PLAYER: case LegacyPlayerListItem.ADD_PLAYER:
if (nameMapping.containsKey(item.getName())) { // ADD_PLAYER also used for updating ping if (nameMapping.containsKey(item.getName())) { // ADD_PLAYER also used for updating ping
VelocityTabListEntry entry = entries.get(nameMapping.get(item.getName())); KeyedVelocityTabListEntry entry = entries.get(nameMapping.get(item.getName()));
if (entry != null) { if (entry != null) {
entry.setLatencyInternal(item.getLatency()); entry.setLatencyInternal(item.getLatency());
} }
} else { } else {
UUID uuid = UUID.randomUUID(); // Use a fake uuid to preserve function of custom entries UUID uuid = UUID.randomUUID(); // Use a fake uuid to preserve function of custom entries
nameMapping.put(item.getName(), uuid); nameMapping.put(item.getName(), uuid);
entries.put(uuid, (VelocityTabListEntry) TabListEntry.builder() entries.put(uuid, (KeyedVelocityTabListEntry) TabListEntry.builder()
.tabList(this) .tabList(this)
.profile(new GameProfile(uuid, item.getName(), ImmutableList.of())) .profile(new GameProfile(uuid, item.getName(), ImmutableList.of()))
.latency(item.getLatency()) .latency(item.getLatency())
.build()); .build());
} }
break; break;
case PlayerListItem.REMOVE_PLAYER: case LegacyPlayerListItem.REMOVE_PLAYER:
UUID removedUuid = nameMapping.remove(item.getName()); UUID removedUuid = nameMapping.remove(item.getName());
if (removedUuid != null) { if (removedUuid != null) {
entries.remove(removedUuid); entries.remove(removedUuid);
@ -109,11 +109,11 @@ public class VelocityTabListLegacy extends VelocityTabList {
void updateEntry(int action, TabListEntry entry) { void updateEntry(int action, TabListEntry entry) {
if (entries.containsKey(entry.getProfile().getId())) { if (entries.containsKey(entry.getProfile().getId())) {
switch (action) { switch (action) {
case PlayerListItem.UPDATE_LATENCY: case LegacyPlayerListItem.UPDATE_LATENCY:
case PlayerListItem.UPDATE_DISPLAY_NAME: // Add here because we removed beforehand case LegacyPlayerListItem.UPDATE_DISPLAY_NAME: // Add here because we removed beforehand
connection connection
.write(new PlayerListItem(PlayerListItem.ADD_PLAYER, // ADD_PLAYER also updates ping .write(new LegacyPlayerListItem(LegacyPlayerListItem.ADD_PLAYER, // ADD_PLAYER also updates ping
Collections.singletonList(PlayerListItem.Item.from(entry)))); Collections.singletonList(LegacyPlayerListItem.Item.from(entry))));
break; break;
default: default:
// Can't do anything else // Can't do anything else