3
0
Mirror von https://github.com/PaperMC/Velocity.git synchronisiert 2024-12-24 23:30:26 +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
.idea_modules/
atlassian-ide-plugin.xml
.fleet/
### Eclipse ###
.metadata

Datei anzeigen

@ -23,11 +23,11 @@ public enum ProtocolVersion {
UNKNOWN(-1, "Unknown"),
LEGACY(-2, "Legacy"),
MINECRAFT_1_7_2(4,
"1.7.2", "1.7.3", "1.7.4", "1.7.5"),
"1.7.2", "1.7.3", "1.7.4", "1.7.5"),
MINECRAFT_1_7_6(5,
"1.7.6", "1.7.7", "1.7.8", "1.7.9", "1.7.10"),
"1.7.6", "1.7.7", "1.7.8", "1.7.9", "1.7.10"),
MINECRAFT_1_8(47,
"1.8", "1.8.1", "1.8.2", "1.8.3", "1.8.4", "1.8.5", "1.8.6", "1.8.7", "1.8.8", "1.8.9"),
"1.8", "1.8.1", "1.8.2", "1.8.3", "1.8.4", "1.8.5", "1.8.6", "1.8.7", "1.8.8", "1.8.9"),
MINECRAFT_1_9(107, "1.9"),
MINECRAFT_1_9_1(108, "1.9.1"),
MINECRAFT_1_9_2(109, "1.9.2"),
@ -59,7 +59,8 @@ public enum ProtocolVersion {
MINECRAFT_1_18(757, "1.18", "1.18.1"),
MINECRAFT_1_18_2(758, "1.18.2"),
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;
@ -80,8 +81,8 @@ public enum ProtocolVersion {
* The user-friendly representation of the lowest and highest supported versions.
*/
public static final String SUPPORTED_VERSION_STRING = String
.format("%s-%s", MINIMUM_VERSION.getVersionIntroducedIn(),
MAXIMUM_VERSION.getMostRecentSupportedVersion());
.format("%s-%s", MINIMUM_VERSION.getVersionIntroducedIn(),
MAXIMUM_VERSION.getMostRecentSupportedVersion());
/**
* A map linking the protocol version number to its {@link ProtocolVersion} representation.

Datei anzeigen

@ -59,7 +59,7 @@ public interface IdentifiedKey extends KeySigned {
final Set<Revision> backwardsCompatibleTo;
final Set<ProtocolVersion> applicableTo;
Revision(Set<Revision> backwardsCompatibleTo, Set<ProtocolVersion> applicableTo) {
this.backwardsCompatibleTo = backwardsCompatibleTo;
this.applicableTo = applicableTo;

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,32 +68,53 @@ public interface TabList {
*/
Collection<TabListEntry> getEntries();
/**
* Clears all entries from the tab list.
*/
void clearAll();
/**
* Builds a tab list entry.
*
* @param profile profile
* @param profile profile
* @param displayName display name
* @param latency latency
* @param gameMode game mode
* @param latency latency
* @param gameMode game mode
* @return entry
* @deprecated Internal usage. Use {@link TabListEntry.Builder} instead.
*/
@Deprecated
TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency,
int gameMode);
int gameMode);
/**
* Builds a tab list entry.
*
* @param profile profile
* @param profile profile
* @param displayName display name
* @param latency latency
* @param gameMode game mode
* @param key the player key
* @param latency latency
* @param gameMode game mode
* @param key the player key
* @return entry
* @deprecated Internal usage. Use {@link TabListEntry.Builder} instead.
* @deprecated Internal usage. Use {@link TabListEntry.Builder} instead.
*/
@Deprecated
TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency,
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}.
*/
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}.
@ -41,7 +56,7 @@ public interface TabListEntry extends KeyIdentifiable {
* {@link GameProfile#getName()} is shown.
*
* @return {@link Optional} text {@link net.kyori.adventure.text.Component} of name displayed in
* the tab list
* the tab list
*/
Optional<Component> getDisplayNameComponent();
@ -105,6 +120,25 @@ public interface TabListEntry extends KeyIdentifiable {
*/
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}.
*
@ -127,7 +161,7 @@ public interface TabListEntry extends KeyIdentifiable {
private int latency = 0;
private int gameMode = 0;
private @Nullable IdentifiedKey playerKey;
private @Nullable ChatSession chatSession;
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>Will ignore mismatching key revisions data.</p>
*
* @param playerKey key to set
* @param chatSession session to set
* @return {@code this}, for chaining
* @see TabListEntry#getIdentifiedKey()
* @see TabListEntry#getChatSession()
*/
public Builder playerKey(IdentifiedKey playerKey) {
this.playerKey = playerKey;
public Builder chatSession(ChatSession chatSession) {
this.chatSession = chatSession;
return this;
}
@ -219,7 +253,7 @@ public interface TabListEntry extends KeyIdentifiable {
if (profile == null) {
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.LegacyHandshake;
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.LoginPluginResponse;
import com.velocitypowered.proxy.protocol.packet.PlayerListItem;
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.ResourcePackResponse;
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.TabCompleteRequest;
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse;
import com.velocitypowered.proxy.protocol.packet.chat.LegacyChat;
import com.velocitypowered.proxy.protocol.packet.chat.PlayerChat;
import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo;
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.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.TitleActionbarPacket;
import com.velocitypowered.proxy.protocol.packet.title.TitleClearPacket;
@ -228,7 +229,7 @@ public interface MinecraftSessionHandler {
return false;
}
default boolean handle(PlayerListItem packet) {
default boolean handle(LegacyPlayerListItem packet) {
return false;
}
@ -240,7 +241,11 @@ public interface MinecraftSessionHandler {
return false;
}
default boolean handle(PlayerChat packet) {
default boolean handle(KeyedPlayerChat packet) {
return false;
}
default boolean handle(SessionPlayerChat packet) {
return false;
}
@ -248,27 +253,27 @@ public interface MinecraftSessionHandler {
return false;
}
default boolean handle(ServerPlayerChat packet) {
default boolean handle(KeyedPlayerCommand packet) {
return false;
}
default boolean handle(PlayerChatPreview packet) {
return false;
}
default boolean handle(ServerChatPreview packet) {
return false;
}
default boolean handle(PlayerCommand packet) {
default boolean handle(SessionPlayerCommand packet) {
return false;
}
default boolean handle(PlayerChatCompletion packet) {
return false;
}
default boolean handle(ServerData serverData) {
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_WITH_KEY = 2;
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];
}

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.Disconnect;
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.RemovePlayerInfo;
import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest;
import com.velocitypowered.proxy.protocol.packet.ResourcePackResponse;
import com.velocitypowered.proxy.protocol.packet.ServerData;
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse;
import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo;
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
@ -60,7 +62,6 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class BackendPlaySessionHandler implements MinecraftSessionHandler {
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 boolean BACKPRESSURE_LOG = Boolean
@ -140,10 +141,10 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
@Override
public boolean handle(ResourcePackRequest packet) {
ResourcePackInfo.Builder builder = new VelocityResourcePackInfo.BuilderImpl(
Preconditions.checkNotNull(packet.getUrl()))
.setPrompt(packet.getPrompt())
.setShouldForce(packet.isRequired())
.setOrigin(ResourcePackInfo.Origin.DOWNSTREAM_SERVER);
Preconditions.checkNotNull(packet.getUrl()))
.setPrompt(packet.getPrompt())
.setShouldForce(packet.isRequired())
.setOrigin(ResourcePackInfo.Origin.DOWNSTREAM_SERVER);
String hash = packet.getHash();
if (hash != null && !hash.isEmpty()) {
@ -153,7 +154,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
}
ServerResourcePackSendEvent event = new ServerResourcePackSendEvent(
builder.build(), this.serverConn);
builder.build(), this.serverConn);
server.getEventManager().fire(event).thenAcceptAsync(serverResourcePackSendEvent -> {
if (playerConnection.isClosed()) {
@ -163,7 +164,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
ResourcePackInfo toSend = serverResourcePackSendEvent.getProvidedResourcePack();
if (toSend != serverResourcePackSendEvent.getReceivedResourcePack()) {
((VelocityResourcePackInfo) toSend)
.setOriginalOrigin(ResourcePackInfo.Origin.DOWNSTREAM_SERVER);
.setOriginalOrigin(ResourcePackInfo.Origin.DOWNSTREAM_SERVER);
}
serverConn.getPlayer().queueResourcePack(toSend);
@ -176,8 +177,8 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
}, playerConnection.eventLoop()).exceptionally((ex) -> {
if (serverConn.getConnection() != null) {
serverConn.getConnection().write(new ResourcePackResponse(
packet.getHash(),
PlayerResourcePackStatusEvent.Status.DECLINED
packet.getHash(),
PlayerResourcePackStatusEvent.Status.DECLINED
));
}
logger.error("Exception while handling resource pack send for {}", playerConnection, ex);
@ -245,8 +246,20 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
}
@Override
public boolean handle(PlayerListItem packet) {
serverConn.getPlayer().getTabList().processBackendPacket(packet);
public boolean handle(LegacyPlayerListItem 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;
}
@ -260,7 +273,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
}
server.getEventManager().fire(
new PlayerAvailableCommandsEvent(serverConn.getPlayer(), rootNode))
new PlayerAvailableCommandsEvent(serverConn.getPlayer(), rootNode))
.thenAcceptAsync(event -> playerConnection.write(commands), playerConnection.eventLoop())
.exceptionally((ex) -> {
logger.error("Exception while handling available commands for {}", playerConnection, ex);
@ -280,7 +293,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
this.playerConnection.write(
new ServerData(pingEvent.getPing().getDescriptionComponent(),
pingEvent.getPing().getFavicon().orElse(null),
packet.isPreviewsChat(), packet.isSecureChatEnforced())
packet.isSecureChatEnforced())
), playerConnection.eventLoop());
return true;
}

Datei anzeigen

@ -17,12 +17,10 @@
package com.velocitypowered.proxy.connection.backend;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.event.player.ServerLoginPluginMessageEvent;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
import com.velocitypowered.proxy.config.VelocityConfiguration;
@ -51,9 +49,11 @@ import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
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 {
private static final Logger logger = LogManager.getLogger(LoginSessionHandler.class);
private static final Component MODERN_IP_FORWARDING_FAILURE = Component
.translatable("velocity.error.modern-forwarding-failed");
@ -177,6 +177,10 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
// Ensure we are in range
requested = Math.min(requested, VelocityConstants.MODERN_FORWARDING_MAX_VERSION);
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) {
// No enhanced switch on java 11
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
// 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();
assert key != null;
ProtocolUtils.writePlayerKey(forwarded, key);

Datei anzeigen

@ -190,7 +190,12 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
mc.setProtocolVersion(protocolVersion);
mc.setState(StateRegistry.LOGIN);
mc.delayedWrite(new ServerLogin(proxyPlayer.getUsername(), proxyPlayer.getIdentifiedKey()));
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.flush();
}

Datei anzeigen

@ -25,14 +25,11 @@ import static com.velocitypowered.proxy.protocol.util.PluginMessageUtil.construc
import com.google.common.collect.ImmutableList;
import com.mojang.brigadier.suggestion.Suggestion;
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.player.PlayerChannelRegisterEvent;
import com.velocitypowered.api.event.player.PlayerChatEvent;
import com.velocitypowered.api.event.player.PlayerClientBrandEvent;
import com.velocitypowered.api.event.player.TabCompleteEvent;
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.LegacyChannelIdentifier;
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.BungeeCordMessageResponder;
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.StateRegistry;
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.TabCompleteResponse;
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse.Offer;
import com.velocitypowered.proxy.protocol.packet.chat.ChatBuilder;
import com.velocitypowered.proxy.protocol.packet.chat.ChatQueue;
import com.velocitypowered.proxy.protocol.packet.chat.LegacyChat;
import com.velocitypowered.proxy.protocol.packet.chat.PlayerChat;
import com.velocitypowered.proxy.protocol.packet.chat.PlayerCommand;
import com.velocitypowered.proxy.protocol.packet.chat.ChatHandler;
import com.velocitypowered.proxy.protocol.packet.chat.ChatTimeKeeper;
import com.velocitypowered.proxy.protocol.packet.chat.CommandHandler;
import com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedChatHandler;
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.util.PluginMessageUtil;
import com.velocitypowered.proxy.util.CharacterUtil;
@ -73,10 +77,8 @@ import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Queue;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue;
import net.kyori.adventure.text.Component;
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 VelocityServer server;
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.
@ -109,127 +113,40 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
public ClientPlaySessionHandler(VelocityServer server, ConnectedPlayer player) {
this.player = player;
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;
private boolean tickLastMessage(SignedChatMessage nextMessage) {
if (lastChatMessage != null && lastChatMessage.isAfter(nextMessage.getExpiryTemporal())) {
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean updateTimeKeeper(Instant instant) {
if (!this.timeKeeper.update(instant)) {
player.disconnect(Component.translatable("multiplayer.disconnect.out_of_order_chat"));
return false;
}
lastChatMessage = nextMessage.getExpiryTemporal();
return true;
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean validateChat(String message) {
if (CharacterUtil.containsIllegalCharacters(message)) {
player.disconnect(Component.translatable("velocity.error.illegal-chat-characters",
NamedTextColor.RED));
player.disconnect(Component.translatable("velocity.error.illegal-chat-characters", NamedTextColor.RED));
return false;
}
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
public void activated() {
Collection<String> channels = server.getChannelRegistrar().getChannelsForProtocol(player
.getProtocolVersion());
Collection<String> channels = server.getChannelRegistrar().getChannelsForProtocol(player.getProtocolVersion());
if (!channels.isEmpty()) {
PluginMessage register = constructChannelsPacket(player.getProtocolVersion(), channels);
player.getConnection().write(register);
@ -267,46 +184,63 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
}
@Override
public boolean handle(PlayerCommand packet) {
public boolean handle(SessionPlayerCommand packet) {
player.ensureAndGetCurrentServer();
if (!updateTimeKeeper(packet.getTimeStamp())) {
return true;
}
if (!validateChat(packet.getCommand())) {
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 true;
return this.commandHandler.handlePlayerCommand(packet);
}
@Override
public boolean handle(PlayerChat packet) {
public boolean handle(SessionPlayerChat packet) {
player.ensureAndGetCurrentServer();
if (!updateTimeKeeper(packet.getTimestamp())) {
return true;
}
if (!validateChat(packet.getMessage())) {
return true;
}
if (!packet.isUnsigned()) {
// Bad if spoofed
SignedChatMessage signedChat = packet.signedContainer(player.getIdentifiedKey(), player.getUniqueId(), false);
if (signedChat != null) {
// Server doesn't care for expiry as long as order is correct
if (!tickLastMessage(signedChat)) {
return true;
}
return this.chatHandler.handlePlayerChat(packet);
}
processPlayerChat(packet.getMessage(), signedChat, packet);
return true;
}
@Override
public boolean handle(KeyedPlayerCommand packet) {
player.ensureAndGetCurrentServer();
if (!updateTimeKeeper(packet.getTimestamp())) {
return true;
}
processPlayerChat(packet.getMessage(), null, packet);
return true;
if (!validateChat(packet.getCommand())) {
return true;
}
return this.commandHandler.handlePlayerCommand(packet);
}
@Override
public boolean handle(KeyedPlayerChat packet) {
player.ensureAndGetCurrentServer();
if (!updateTimeKeeper(packet.getExpiry())) {
return true;
}
if (!validateChat(packet.getMessage())) {
return true;
}
return this.chatHandler.handlePlayerChat(packet);
}
@Override
@ -318,9 +252,9 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
}
if (msg.startsWith("/")) {
processCommandMessage(msg.substring(1), null, packet, Instant.now());
this.commandHandler.handlePlayerCommand(packet);
} else {
processPlayerChat(msg, null, packet);
this.chatHandler.handlePlayerChat(packet);
}
return true;
}
@ -342,8 +276,9 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
MinecraftConnection backendConn = serverConn != null ? serverConn.getConnection() : null;
if (serverConn != null && backendConn != null) {
if (backendConn.getState() != StateRegistry.PLAY) {
logger.warn("A plugin message was received while the backend server was not "
+ "ready. Channel: {}. Packet discarded.", packet.getChannel());
logger.warn(
"A plugin message was received while the backend server was not " + "ready. Channel: {}. Packet discarded.",
packet.getChannel());
} else if (PluginMessageUtil.isRegister(packet)) {
List<String> channels = PluginMessageUtil.getChannels(packet);
player.getKnownChannels().addAll(channels);
@ -355,8 +290,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
channelIdentifiers.add(new LegacyChannelIdentifier(channel));
}
}
server.getEventManager().fireAndForget(new PlayerChannelRegisterEvent(player,
ImmutableList.copyOf(channelIdentifiers)));
server.getEventManager()
.fireAndForget(new PlayerChannelRegisterEvent(player, ImmutableList.copyOf(channelIdentifiers)));
backendConn.write(packet.retain());
} else if (PluginMessageUtil.isUnregister(packet)) {
player.getKnownChannels().removeAll(PluginMessageUtil.getChannels(packet));
@ -365,8 +300,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
String brand = PluginMessageUtil.readBrandMessage(packet.content());
server.getEventManager().fireAndForget(new PlayerClientBrandEvent(player, brand));
player.setClientBrand(brand);
backendConn.write(PluginMessageUtil
.rewriteMinecraftBrand(packet, server.getVersion(), player.getProtocolVersion()));
backendConn.write(
PluginMessageUtil.rewriteMinecraftBrand(packet, server.getVersion(), player.getProtocolVersion()));
} else if (BungeeCordMessageResponder.isBungeeCordMessage(packet)) {
return true;
} else {
@ -401,8 +336,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
PluginMessageEvent event = new PluginMessageEvent(player, serverConn, id, copy);
server.getEventManager().fire(event).thenAcceptAsync(pme -> {
if (pme.getResult().isAllowed()) {
PluginMessage message = new PluginMessage(packet.getChannel(),
Unpooled.wrappedBuffer(copy));
PluginMessage message = new PluginMessage(packet.getChannel(), Unpooled.wrappedBuffer(copy));
if (!player.getPhase().consideredComplete() || !serverConn.getPhase().consideredComplete()) {
// We're still processing the connection (see above), enqueue the packet for now.
loginPluginMessages.add(message.retain());
@ -410,12 +344,10 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
backendConn.write(message);
}
}
}, backendConn.eventLoop())
.exceptionally((ex) -> {
logger.error("Exception while handling plugin message packet for {}",
player, ex);
return null;
});
}, backendConn.eventLoop()).exceptionally((ex) -> {
logger.error("Exception while handling plugin message packet for {}", player, ex);
return null;
});
}
}
}
@ -467,8 +399,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
@Override
public void exception(Throwable throwable) {
player.disconnect(Component.translatable("velocity.error.player-connection-error",
NamedTextColor.RED));
player.disconnect(Component.translatable("velocity.error.player-connection-error", NamedTextColor.RED));
}
@Override
@ -548,8 +479,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
// Clear any title from the previous server.
if (player.getProtocolVersion().compareTo(MINECRAFT_1_8) >= 0) {
player.getConnection().delayedWrite(GenericTitlePacket.constructTitlePacket(
GenericTitlePacket.ActionType.RESET, player.getProtocolVersion()));
player.getConnection().delayedWrite(
GenericTitlePacket.constructTitlePacket(GenericTitlePacket.ActionType.RESET, player.getProtocolVersion()));
}
// Flush everything
@ -619,37 +550,33 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
return false;
}
server.getCommandManager().offerBrigadierSuggestions(player, command)
.thenAcceptAsync(suggestions -> {
if (suggestions.isEmpty()) {
return;
}
server.getCommandManager().offerBrigadierSuggestions(player, command).thenAcceptAsync(suggestions -> {
if (suggestions.isEmpty()) {
return;
}
List<Offer> offers = new ArrayList<>();
for (Suggestion suggestion : suggestions.getList()) {
String offer = suggestion.getText();
Component tooltip = null;
if (suggestion.getTooltip() != null
&& suggestion.getTooltip() instanceof VelocityBrigadierMessage) {
tooltip = ((VelocityBrigadierMessage) suggestion.getTooltip()).asComponent();
}
offers.add(new Offer(offer, tooltip));
}
int startPos = packet.getCommand().lastIndexOf(' ') + 1;
if (startPos > 0) {
TabCompleteResponse resp = new TabCompleteResponse();
resp.setTransactionId(packet.getTransactionId());
resp.setStart(startPos);
resp.setLength(packet.getCommand().length() - startPos);
resp.getOffers().addAll(offers);
player.getConnection().write(resp);
}
}, player.getConnection().eventLoop())
.exceptionally((ex) -> {
logger.error("Exception while handling command tab completion for player {} executing {}",
player, command, ex);
return null;
});
List<Offer> offers = new ArrayList<>();
for (Suggestion suggestion : suggestions.getList()) {
String offer = suggestion.getText();
Component tooltip = null;
if (suggestion.getTooltip() != null && suggestion.getTooltip() instanceof VelocityBrigadierMessage) {
tooltip = ((VelocityBrigadierMessage) suggestion.getTooltip()).asComponent();
}
offers.add(new Offer(offer, tooltip));
}
int startPos = packet.getCommand().lastIndexOf(' ') + 1;
if (startPos > 0) {
TabCompleteResponse resp = new TabCompleteResponse();
resp.setTransactionId(packet.getTransactionId());
resp.setStart(startPos);
resp.setLength(packet.getCommand().length() - startPos);
resp.getOffers().addAll(offers);
player.getConnection().write(resp);
}
}, player.getConnection().eventLoop()).exceptionally((ex) -> {
logger.error("Exception while handling command tab completion for player {} executing {}", player, command, ex);
return null;
});
return true; // Sorry, handler; we're just gonna have to lie to you here.
}
@ -683,37 +610,32 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
private void finishCommandTabComplete(TabCompleteRequest request, TabCompleteResponse response) {
String command = request.getCommand().substring(1);
server.getCommandManager().offerBrigadierSuggestions(player, command)
.thenAcceptAsync(offers -> {
boolean legacy = player.getProtocolVersion().compareTo(MINECRAFT_1_13) < 0;
try {
for (Suggestion suggestion : offers.getList()) {
String offer = suggestion.getText();
offer = legacy && !offer.startsWith("/") ? "/" + offer : offer;
if (legacy && offer.startsWith(command)) {
offer = offer.substring(command.length());
}
Component tooltip = null;
if (suggestion.getTooltip() != null
&& suggestion.getTooltip() instanceof VelocityBrigadierMessage) {
tooltip = ((VelocityBrigadierMessage) suggestion.getTooltip()).asComponent();
}
response.getOffers().add(new Offer(offer, tooltip));
}
response.getOffers().sort(null);
player.getConnection().write(response);
} catch (Exception e) {
logger.error("Unable to provide tab list completions for {} for command '{}'",
player.getUsername(),
command, e);
server.getCommandManager().offerBrigadierSuggestions(player, command).thenAcceptAsync(offers -> {
boolean legacy = player.getProtocolVersion().compareTo(MINECRAFT_1_13) < 0;
try {
for (Suggestion suggestion : offers.getList()) {
String offer = suggestion.getText();
offer = legacy && !offer.startsWith("/") ? "/" + offer : offer;
if (legacy && offer.startsWith(command)) {
offer = offer.substring(command.length());
}
}, player.getConnection().eventLoop())
.exceptionally((ex) -> {
logger.error(
"Exception while finishing command tab completion, with request {} and response {}",
request, response, ex);
return null;
});
Component tooltip = null;
if (suggestion.getTooltip() != null && suggestion.getTooltip() instanceof VelocityBrigadierMessage) {
tooltip = ((VelocityBrigadierMessage) suggestion.getTooltip()).asComponent();
}
response.getOffers().add(new Offer(offer, tooltip));
}
response.getOffers().sort(null);
player.getConnection().write(response);
} catch (Exception e) {
logger.error("Unable to provide tab list completions for {} for command '{}'", player.getUsername(), command,
e);
}
}, player.getConnection().eventLoop()).exceptionally((ex) -> {
logger.error("Exception while finishing command tab completion, with request {} and response {}", request,
response, ex);
return null;
});
}
private void finishRegularTabComplete(TabCompleteRequest request, TabCompleteResponse response) {
@ -721,96 +643,17 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
for (Offer offer : response.getOffers()) {
offers.add(offer.getText());
}
server.getEventManager().fire(new TabCompleteEvent(player, request.getCommand(), offers))
.thenAcceptAsync(e -> {
response.getOffers().clear();
for (String s : e.getSuggestions()) {
response.getOffers().add(new Offer(s));
}
player.getConnection().write(response);
}, player.getConnection().eventLoop())
.exceptionally((ex) -> {
logger.error(
"Exception while finishing regular tab completion, with request {} and response{}",
request, response, ex);
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."));
}
server.getEventManager().fire(new TabCompleteEvent(player, request.getCommand(), offers)).thenAcceptAsync(e -> {
response.getOffers().clear();
for (String s : e.getSuggestions()) {
response.getOffers().add(new Offer(s));
}
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() {
player.getConnection().write(response);
}, player.getConnection().eventLoop()).exceptionally((ex) -> {
logger.error("Exception while finishing regular tab completion, with request {} and response{}", request,
response, ex);
return null;
});
}
/**

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.PluginMessage;
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.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.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.VelocityTabListLegacy;
import com.velocitypowered.proxy.util.ClosestLocaleMatcher;
@ -145,7 +148,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
private @Nullable ModInfo modInfo;
private Component playerListHeader = Component.empty();
private Component playerListFooter = Component.empty();
private final VelocityTabList tabList;
private final InternalTabList tabList;
private final VelocityServer server;
private ClientConnectionPhase connectionPhase;
private final Collection<String> knownChannels;
@ -166,7 +169,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
private @Nullable String clientBrand;
private @Nullable Locale effectiveLocale;
private @Nullable IdentifiedKey playerKey;
private ChatQueue chatQueue;
private final ChatQueue chatQueue;
private final ChatBuilderFactory chatBuilderFactory;
ConnectedPlayer(VelocityServer server, GameProfile profile, MinecraftConnection connection,
@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.onlineMode = onlineMode;
if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
this.tabList = new VelocityTabList(this, server);
if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19_3) >= 0) {
this.tabList = new VelocityTabList(this);
} else if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
this.tabList = new KeyedVelocityTabList(this, server);
} else {
this.tabList = new VelocityTabListLegacy(this, server);
}
this.playerKey = playerKey;
this.chatQueue = new ChatQueue(this);
this.chatBuilderFactory = new ChatBuilderFactory(this.getProtocolVersion());
}
ChatQueue getChatQueue() {
public ChatBuilderFactory getChatBuilderFactory() {
return chatBuilderFactory;
}
public ChatQueue getChatQueue() {
return chatQueue;
}
@ -327,7 +338,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
public void sendMessage(@NonNull Identity identity, @NonNull Component message) {
Component translated = translateMessage(message);
connection.write(ChatBuilder.builder(this.getProtocolVersion())
connection.write(getChatBuilderFactory().builder()
.component(translated).forIdentity(identity).toClient());
}
@ -339,9 +350,9 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
Component translated = translateMessage(message);
connection.write(ChatBuilder.builder(this.getProtocolVersion())
connection.write(getChatBuilderFactory().builder()
.component(translated).forIdentity(identity)
.setType(type == MessageType.CHAT ? ChatBuilder.ChatType.CHAT : ChatBuilder.ChatType.SYSTEM)
.setType(type == MessageType.CHAT ? ChatType.CHAT : ChatType.SYSTEM)
.toClient());
}
@ -525,7 +536,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
}
@Override
public VelocityTabList getTabList() {
public InternalTabList getTabList() {
return tabList;
}
@ -913,13 +924,13 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
"input cannot be greater than " + LegacyChat.MAX_SERVERBOUND_MESSAGE_LENGTH
+ " characters in length");
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) -> {
item.timestamp(instant);
item.setTimestamp(instant);
return item.toServer();
});
} else {
ensureBackendConnection().write(ChatBuilder.builder(getProtocolVersion())
ensureBackendConnection().write(getChatBuilderFactory().builder()
.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.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.PreLoginComponentResult;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.api.util.UuidUtils;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
@ -50,7 +46,6 @@ import java.security.KeyPair;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadLocalRandom;
import net.kyori.adventure.text.Component;
@ -60,7 +55,6 @@ import org.apache.logging.log4j.Logger;
import org.asynchttpclient.ListenableFuture;
import org.asynchttpclient.Response;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
public class InitialLoginSessionHandler implements MinecraftSessionHandler {
@ -111,7 +105,8 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
return true;
}
} 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"));
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_19;
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_8;
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.JoinGame;
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.LoginPluginResponse;
import com.velocitypowered.proxy.protocol.packet.PlayerListItem;
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.ResourcePackResponse;
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.TabCompleteRequest;
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse;
import com.velocitypowered.proxy.protocol.packet.chat.LegacyChat;
import com.velocitypowered.proxy.protocol.packet.chat.PlayerChat;
import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo;
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.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.TitleActionbarPacket;
import com.velocitypowered.proxy.protocol.packet.title.TitleClearPacket;
@ -124,19 +129,24 @@ public enum StateRegistry {
map(0x05, MINECRAFT_1_13, false),
map(0x06, MINECRAFT_1_14, 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,
map(0x01, MINECRAFT_1_7_2, false),
map(0x02, MINECRAFT_1_9, false),
map(0x03, MINECRAFT_1_12, false),
map(0x02, MINECRAFT_1_12_1, 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(0x04, MINECRAFT_1_19_1, false));
serverbound.register(PlayerChat.class, PlayerChat::new,
map(0x04, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false));
serverbound.register(KeyedPlayerChat.class, KeyedPlayerChat::new,
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,
map(0x15, MINECRAFT_1_7_2, false),
map(0x04, MINECRAFT_1_9, false),
@ -144,7 +154,8 @@ public enum StateRegistry {
map(0x04, MINECRAFT_1_12_1, false),
map(0x05, MINECRAFT_1_14, 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,
map(0x17, MINECRAFT_1_7_2, false),
map(0x09, MINECRAFT_1_9, false),
@ -154,7 +165,8 @@ public enum StateRegistry {
map(0x0B, MINECRAFT_1_14, false),
map(0x0A, MINECRAFT_1_17, 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,
map(0x00, MINECRAFT_1_7_2, false),
map(0x0B, MINECRAFT_1_9, false),
@ -165,7 +177,8 @@ public enum StateRegistry {
map(0x10, MINECRAFT_1_16, false),
map(0x0F, MINECRAFT_1_17, 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,
map(0x19, MINECRAFT_1_8, false),
map(0x16, MINECRAFT_1_9, false),
@ -198,14 +211,16 @@ public enum StateRegistry {
map(0x10, MINECRAFT_1_16, false),
map(0x0F, MINECRAFT_1_16_2, 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,
map(0x11, MINECRAFT_1_13, false),
map(0x12, MINECRAFT_1_15, false),
map(0x11, MINECRAFT_1_16, false),
map(0x10, MINECRAFT_1_16_2, 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,
map(0x3F, MINECRAFT_1_7_2, false),
map(0x18, MINECRAFT_1_9, false),
@ -216,7 +231,8 @@ public enum StateRegistry {
map(0x17, MINECRAFT_1_16_2, false),
map(0x18, MINECRAFT_1_17, 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,
map(0x40, MINECRAFT_1_7_2, false),
map(0x1A, MINECRAFT_1_9, false),
@ -227,7 +243,8 @@ public enum StateRegistry {
map(0x19, MINECRAFT_1_16_2, false),
map(0x1A, MINECRAFT_1_17, 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,
map(0x00, MINECRAFT_1_7_2, false),
map(0x1F, MINECRAFT_1_9, false),
@ -238,7 +255,8 @@ public enum StateRegistry {
map(0x1F, MINECRAFT_1_16_2, false),
map(0x21, MINECRAFT_1_17, 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,
map(0x01, MINECRAFT_1_7_2, false),
map(0x23, MINECRAFT_1_9, false),
@ -249,7 +267,8 @@ public enum StateRegistry {
map(0x24, MINECRAFT_1_16_2, false),
map(0x26, MINECRAFT_1_17, 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,
map(0x07, MINECRAFT_1_7_2, true),
map(0x33, MINECRAFT_1_9, true),
@ -262,7 +281,8 @@ public enum StateRegistry {
map(0x39, MINECRAFT_1_16_2, true),
map(0x3D, MINECRAFT_1_17, 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,
map(0x48, MINECRAFT_1_8, false),
map(0x32, MINECRAFT_1_9, false),
@ -275,7 +295,8 @@ public enum StateRegistry {
map(0x38, MINECRAFT_1_16_2, false),
map(0x3C, MINECRAFT_1_17, 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,
map(0x47, MINECRAFT_1_8, true),
map(0x48, MINECRAFT_1_9, true),
@ -289,7 +310,8 @@ public enum StateRegistry {
map(0x5E, MINECRAFT_1_17, true),
map(0x5F, MINECRAFT_1_18, 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,
map(0x45, MINECRAFT_1_8, true),
map(0x45, MINECRAFT_1_9, true),
@ -302,23 +324,28 @@ public enum StateRegistry {
clientbound.register(TitleSubtitlePacket.class, TitleSubtitlePacket::new,
map(0x57, MINECRAFT_1_17, 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,
map(0x59, MINECRAFT_1_17, 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,
map(0x41, MINECRAFT_1_17, 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,
map(0x5A, MINECRAFT_1_17, 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,
map(0x10, MINECRAFT_1_17, true),
map(0x0D, MINECRAFT_1_19, true));
clientbound.register(PlayerListItem.class, PlayerListItem::new,
map(0x0D, MINECRAFT_1_19, true),
map(0x0C, MINECRAFT_1_19_3, true));
clientbound.register(LegacyPlayerListItem.class, LegacyPlayerListItem::new,
map(0x38, MINECRAFT_1_7_2, false),
map(0x2D, MINECRAFT_1_9, false),
map(0x2E, MINECRAFT_1_12_1, false),
@ -329,15 +356,22 @@ public enum StateRegistry {
map(0x32, MINECRAFT_1_16_2, false),
map(0x36, MINECRAFT_1_17, 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,
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,
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,
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 {
@ -397,7 +431,7 @@ public enum StateRegistry {
}
<P extends MinecraftPacket> void register(Class<P> clazz, Supplier<P> packetSupplier,
PacketMapping... mappings) {
PacketMapping... mappings) {
if (mappings.length == 0) {
throw new IllegalArgumentException("At least one mapping must be provided.");
}
@ -414,11 +448,11 @@ public enum StateRegistry {
}
if (from.compareTo(lastValid) > 0) {
throw new IllegalArgumentException(
"Last mapping version cannot be higher than highest mapping version");
"Last mapping version cannot be higher than highest mapping version");
}
}
ProtocolVersion to = current == next ? lastValid != null
? lastValid : getLast(SUPPORTED_VERSIONS) : next.protocolVersion;
? lastValid : getLast(SUPPORTED_VERSIONS) : next.protocolVersion;
ProtocolVersion lastInList = lastValid != null ? lastValid : getLast(SUPPORTED_VERSIONS);
@ -563,14 +597,14 @@ public enum StateRegistry {
/**
* Creates a PacketMapping using the provided arguments.
*
* @param id Packet Id
* @param version Protocol version
* @param encodeOnly When true packet decoding will be disabled
* @param id Packet Id
* @param version Protocol version
* @param encodeOnly When true packet decoding will be disabled
* @param lastValidProtocolVersion Last version this Mapping is valid at
* @return PacketMapping with the provided arguments
*/
private static PacketMapping map(int id, ProtocolVersion version,
ProtocolVersion lastValidProtocolVersion, boolean encodeOnly) {
ProtocolVersion lastValidProtocolVersion, boolean encodeOnly) {
return new PacketMapping(id, version, lastValidProtocolVersion, encodeOnly);
}

Datei anzeigen

@ -67,6 +67,7 @@ public class EncryptionResponse implements MinecraftPacket {
this.sharedSecret = ProtocolUtils.readByteArray(buf, 128);
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0
&& version.compareTo(ProtocolVersion.MINECRAFT_1_19_3) < 0
&& !buf.readBoolean()) {
salt = buf.readLong();
}
@ -83,7 +84,8 @@ public class EncryptionResponse implements MinecraftPacket {
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
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) {
buf.writeBoolean(false);
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.
// The length prefix always winds up being 2 bytes.
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) {
// Verify token is twice as long on 1.19+
// 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 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 UPDATE_GAMEMODE = 1;
@ -43,12 +43,12 @@ public class PlayerListItem implements MinecraftPacket {
private int action;
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.items.addAll(items);
}
public PlayerListItem() {
public LegacyPlayerListItem() {
}
public int getAction() {

Datei anzeigen

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

Datei anzeigen

@ -36,7 +36,7 @@ public class Respawn implements MinecraftPacket {
private short difficulty;
private short gamemode;
private String levelType = "";
private boolean shouldKeepPlayerData; // 1.16+
private byte dataToKeep; // 1.16+
private DimensionInfo dimensionInfo; // 1.16-1.16.1
private short previousGamemode; // 1.16+
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,
String levelType, boolean shouldKeepPlayerData, DimensionInfo dimensionInfo,
short previousGamemode, DimensionData currentDimensionData, @Nullable Pair<String, Long> lastDeathPosition) {
String levelType, byte dataToKeep, DimensionInfo dimensionInfo,
short previousGamemode, DimensionData currentDimensionData,
@Nullable Pair<String, Long> lastDeathPosition) {
this.dimension = dimension;
this.partialHashedSeed = partialHashedSeed;
this.difficulty = difficulty;
this.gamemode = gamemode;
this.levelType = levelType;
this.shouldKeepPlayerData = shouldKeepPlayerData;
this.dataToKeep = dataToKeep;
this.dimensionInfo = dimensionInfo;
this.previousGamemode = previousGamemode;
this.currentDimensionData = currentDimensionData;
@ -63,7 +64,7 @@ public class Respawn implements MinecraftPacket {
public static Respawn fromJoinGame(JoinGame joinGame) {
return new Respawn(joinGame.getDimension(), joinGame.getPartialHashedSeed(),
joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(),
false, joinGame.getDimensionInfo(), joinGame.getPreviousGamemode(),
(byte) 0, joinGame.getDimensionInfo(), joinGame.getPreviousGamemode(),
joinGame.getCurrentDimensionData(), joinGame.getLastDeathPosition());
}
@ -107,12 +108,12 @@ public class Respawn implements MinecraftPacket {
this.levelType = levelType;
}
public boolean getShouldKeepPlayerData() {
return shouldKeepPlayerData;
public byte getDataToKeep() {
return dataToKeep;
}
public void setShouldKeepPlayerData(boolean shouldKeepPlayerData) {
this.shouldKeepPlayerData = shouldKeepPlayerData;
public void setDataToKeep(byte dataToKeep) {
this.dataToKeep = dataToKeep;
}
public short getPreviousGamemode() {
@ -139,7 +140,7 @@ public class Respawn implements MinecraftPacket {
+ ", difficulty=" + difficulty
+ ", gamemode=" + gamemode
+ ", levelType='" + levelType + '\''
+ ", shouldKeepPlayerData=" + shouldKeepPlayerData
+ ", dataToKeep=" + dataToKeep
+ ", dimensionRegistryName='" + dimensionInfo.toString() + '\''
+ ", dimensionInfo=" + dimensionInfo
+ ", previousGamemode=" + previousGamemode
@ -153,7 +154,7 @@ public class Respawn implements MinecraftPacket {
String levelName = null;
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0
&& version.compareTo(ProtocolVersion.MINECRAFT_1_19) < 0) {
&& version.compareTo(ProtocolVersion.MINECRAFT_1_19) < 0) {
CompoundBinaryTag dimDataTag = ProtocolUtils.readCompoundTag(buf, BinaryTagIO.reader());
dimensionIdentifier = ProtocolUtils.readString(buf);
this.currentDimensionData = DimensionData.decodeBaseCompoundTag(dimDataTag, version)
@ -177,7 +178,13 @@ public class Respawn implements MinecraftPacket {
boolean isDebug = buf.readBoolean();
boolean isFlat = buf.readBoolean();
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 {
this.levelType = ProtocolUtils.readString(buf, 16);
}
@ -190,7 +197,7 @@ public class Respawn implements MinecraftPacket {
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0
&& version.compareTo(ProtocolVersion.MINECRAFT_1_19) < 0) {
&& version.compareTo(ProtocolVersion.MINECRAFT_1_19) < 0) {
ProtocolUtils.writeCompoundTag(buf, currentDimensionData.serializeDimensionDetails());
ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier());
} else {
@ -211,7 +218,11 @@ public class Respawn implements MinecraftPacket {
buf.writeByte(previousGamemode);
buf.writeBoolean(dimensionInfo.isDebugType());
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 {
ProtocolUtils.writeString(buf, levelType);
}

Datei anzeigen

@ -30,17 +30,15 @@ public class ServerData implements MinecraftPacket {
private @Nullable Component description;
private @Nullable Favicon favicon;
private boolean previewsChat;
private boolean secureChatEnforced; // Added in 1.19.1
public ServerData() {
}
public ServerData(@Nullable Component description, @Nullable Favicon favicon,
boolean previewsChat, boolean secureChatEnforced) {
boolean secureChatEnforced) {
this.description = description;
this.favicon = favicon;
this.previewsChat = previewsChat;
this.secureChatEnforced = secureChatEnforced;
}
@ -54,7 +52,9 @@ public class ServerData implements MinecraftPacket {
if (buf.readBoolean()) {
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) {
this.secureChatEnforced = buf.readBoolean();
}
@ -77,7 +77,9 @@ public class ServerData implements MinecraftPacket {
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) {
buf.writeBoolean(this.secureChatEnforced);
}
@ -88,18 +90,14 @@ public class ServerData implements MinecraftPacket {
return handler.handle(this);
}
public Component getDescription() {
public @Nullable Component getDescription() {
return description;
}
public Favicon getFavicon() {
public @Nullable Favicon getFavicon() {
return favicon;
}
public boolean isPreviewsChat() {
return previewsChat;
}
public boolean isSecureChatEnforced() {
return secureChatEnforced;
}

Datei anzeigen

@ -35,7 +35,7 @@ public class ServerLogin implements MinecraftPacket {
private static final QuietDecoderException EMPTY_USERNAME = new QuietDecoderException("Empty 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
public ServerLogin() {
@ -46,6 +46,12 @@ public class ServerLogin implements MinecraftPacket {
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() {
if (username == null) {
throw new IllegalStateException("No username found!");
@ -53,15 +59,15 @@ public class ServerLogin implements MinecraftPacket {
return username;
}
public IdentifiedKey getPlayerKey() {
return playerKey;
public @Nullable IdentifiedKey getPlayerKey() {
return this.playerKey;
}
public void setPlayerKey(IdentifiedKey playerKey) {
this.playerKey = playerKey;
}
public UUID getHolderUuid() {
public @Nullable UUID getHolderUuid() {
return holderUuid;
}
@ -74,15 +80,21 @@ public class ServerLogin implements MinecraftPacket {
}
@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);
if (username.isEmpty()) {
throw EMPTY_USERNAME;
}
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
if (buf.readBoolean()) {
playerKey = ProtocolUtils.readPlayerKey(version, buf);
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_3) >= 0) {
playerKey = null;
} else {
if (buf.readBoolean()) {
playerKey = ProtocolUtils.readPlayerKey(version, buf);
} else {
playerKey = null;
}
}
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
@ -90,6 +102,8 @@ public class ServerLogin implements MinecraftPacket {
holderUuid = ProtocolUtils.readUuid(buf);
}
}
} else {
playerKey = null;
}
}
@ -101,17 +115,22 @@ public class ServerLogin implements MinecraftPacket {
ProtocolUtils.writeString(buf, username);
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
if (playerKey != null) {
buf.writeBoolean(true);
ProtocolUtils.writePlayerKey(buf, playerKey);
} else {
buf.writeBoolean(false);
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_3) < 0) {
if (playerKey != null) {
buf.writeBoolean(true);
ProtocolUtils.writePlayerKey(buf, playerKey);
} else {
buf.writeBoolean(false);
}
}
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
if (playerKey != null && playerKey.getSignatureHolder() != null) {
buf.writeBoolean(true);
ProtocolUtils.writeUuid(buf, playerKey.getSignatureHolder());
} else if (this.holderUuid != null) {
buf.writeBoolean(true);
ProtocolUtils.writeUuid(buf, this.holderUuid);
} else {
buf.writeBoolean(false);
}
@ -126,13 +145,15 @@ public class ServerLogin implements MinecraftPacket {
int base = 1 + (16 * 4);
// Adjustments for Key-authentication
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
// + 1 for the boolean present/ not present
// + 8 for the long expiry
// + 2 len for varint key size
// + 294 for the key
// + 2 len for varint signature size
// + 512 for signature
base += 1 + 8 + 2 + 294 + 2 + 512;
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_3) < 0) {
// + 1 for the boolean present/ not present
// + 8 for the long expiry
// + 2 len for varint key size
// + 294 for the key
// + 2 len for varint signature size
// + 512 for signature
base += 1 + 8 + 2 + 294 + 2 + 512;
}
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
// +1 boolean uuid optional
// + 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()) {
if (v.compareTo(current.getVersion()) >= 0) {
temp.put(v, current.getId());
temp.putIfAbsent(v, current.getId());
}
}
previous = current.getVersion();

Datei anzeigen

@ -18,6 +18,7 @@
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_3;
import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.id;
import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.mapSet;
import static com.velocitypowered.proxy.protocol.packet.brigadier.DoubleArgumentPropertySerializer.DOUBLE;
@ -48,14 +49,14 @@ public class ArgumentPropertyRegistry {
}
private static final Map<ArgumentIdentifier, ArgumentPropertySerializer<?>> byIdentifier =
new HashMap<>();
new HashMap<>();
private static final Map<Class<? extends ArgumentType>,
ArgumentPropertySerializer<?>> byClass = new HashMap<>();
private static final Map<Class<? extends ArgumentType>, ArgumentIdentifier> classToId =
new HashMap<>();
new HashMap<>();
private static <T extends ArgumentType<?>> void register(ArgumentIdentifier identifier,
Class<T> klazz, ArgumentPropertySerializer<T> serializer) {
Class<T> klazz, ArgumentPropertySerializer<T> serializer) {
byIdentifier.put(identifier, serializer);
byClass.put(klazz, serializer);
classToId.put(klazz, identifier);
@ -72,6 +73,7 @@ public class ArgumentPropertyRegistry {
/**
* Deserializes the {@link ArgumentType}.
*
* @param buf the buffer to deserialize
* @return the deserialized {@link ArgumentType}
*/
@ -93,7 +95,8 @@ public class ArgumentPropertyRegistry {
/**
* 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
*/
public static void serialize(ByteBuf buf, ArgumentType<?> type,
@ -122,12 +125,13 @@ public class ArgumentPropertyRegistry {
/**
* Writes the {@link ArgumentIdentifier} to a version-specific buffer.
* @param buf the buffer to write to
* @param identifier the identifier to write
*
* @param buf the buffer to write to
* @param identifier the identifier to write
* @param protocolVersion the protocol version to use
*/
public static void writeIdentifier(ByteBuf buf, ArgumentIdentifier identifier,
ProtocolVersion protocolVersion) {
ProtocolVersion protocolVersion) {
if (protocolVersion.compareTo(MINECRAFT_1_19) >= 0) {
Integer id = identifier.getIdByProtocolVersion(protocolVersion);
Preconditions.checkNotNull(id, "Don't know how to serialize type " + identifier);
@ -141,7 +145,8 @@ public class ArgumentPropertyRegistry {
/**
* 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
* @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:item_slot", mapSet(MINECRAFT_1_19, 32)));
empty(id("minecraft:resource_location", mapSet(MINECRAFT_1_19, 33)));
empty(id("minecraft:mob_effect", mapSet(MINECRAFT_1_19, 34)));
empty(id("minecraft:function", mapSet(MINECRAFT_1_19, 35)));
empty(id("minecraft:entity_anchor", mapSet(MINECRAFT_1_19, 36)));
empty(id("minecraft:int_range", mapSet(MINECRAFT_1_19, 37)));
empty(id("minecraft:float_range", mapSet(MINECRAFT_1_19, 38)));
empty(id("minecraft:item_enchantment", mapSet(MINECRAFT_1_19, 39)));
empty(id("minecraft:entity_summon", mapSet(MINECRAFT_1_19, 40)));
empty(id("minecraft:dimension", mapSet(MINECRAFT_1_19, 41)));
empty(id("minecraft:time", mapSet(MINECRAFT_1_19, 42))); // added in 1.14
empty(id("minecraft:mob_effect", mapSet(MINECRAFT_1_19_3, -1), mapSet(MINECRAFT_1_19, 34)));
empty(id("minecraft:function", mapSet(MINECRAFT_1_19_3, 34), mapSet(MINECRAFT_1_19, 35)));
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_3, 36), mapSet(MINECRAFT_1_19, 37)));
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_3, -1), mapSet(MINECRAFT_1_19, 39)));
empty(id("minecraft:entity_summon", mapSet(MINECRAFT_1_19_3, -1), mapSet(MINECRAFT_1_19, 40)));
empty(id("minecraft:dimension", mapSet(MINECRAFT_1_19_3, 38), mapSet(MINECRAFT_1_19, 41)));
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)),
RegistryKeyArgument.class, RegistryKeyArgumentSerializer.REGISTRY);
register(id("minecraft:resource", mapSet(MINECRAFT_1_19, 44)),
RegistryKeyArgument.class, RegistryKeyArgumentSerializer.REGISTRY);
register(id("minecraft:resource_or_tag", mapSet(MINECRAFT_1_19_3, 41), mapSet(MINECRAFT_1_19, 43)),
RegistryKeyArgument.class, RegistryKeyArgumentSerializer.REGISTRY);
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);
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_rotation", mapSet(MINECRAFT_1_19, 46))); // 1.19
empty(id("minecraft:uuid", mapSet(MINECRAFT_1_19, 47))); // added in 1.16
// Crossstitch support
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.util.concurrent.CompletableFuture;
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.

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(Component component, ChatBuilder.ChatType type) {
public SystemChat(Component component, ChatType type) {
this.component = component;
this.type = type;
}
private Component component;
private ChatBuilder.ChatType type;
private ChatType type;
public ChatBuilder.ChatType getType() {
public ChatType getType() {
return type;
}
@ -48,7 +48,7 @@ public class SystemChat implements MinecraftPacket {
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
component = ProtocolUtils.getJsonChatSerializer(protocolVersion).deserialize(ProtocolUtils.readString(buf));
// 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

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/>.
*/
package com.velocitypowered.proxy.protocol.packet.chat;
package com.velocitypowered.proxy.protocol.packet.chat.keyed;
import com.google.common.primitives.Longs;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.crypto.EncryptionUtils;
import com.velocitypowered.proxy.crypto.SignaturePair;
import com.velocitypowered.proxy.crypto.SignedChatMessage;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.util.except.QuietDecoderException;
import io.netty.buffer.ByteBuf;
import java.time.Instant;
import java.util.UUID;
import org.checkerframework.checker.nullness.qual.Nullable;
public class PlayerChat implements MinecraftPacket {
public class KeyedPlayerChat implements MinecraftPacket {
private String message;
private boolean signedPreview;
@ -46,29 +43,18 @@ public class PlayerChat implements MinecraftPacket {
public static final int MAXIMUM_PREVIOUS_MESSAGE_COUNT = 5;
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.unsigned = true;
}
/**
* Create new {@link PlayerChat} based on a previously {@link SignedChatMessage}.
*
* @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 void setExpiry(@Nullable Instant expiry) {
this.expiry = expiry;
}
public Instant getExpiry() {
@ -135,11 +121,11 @@ public class PlayerChat implements MinecraftPacket {
buf.writeLong(unsigned ? Instant.now().toEpochMilli() : expiry.toEpochMilli());
buf.writeLong(unsigned ? 0L : Longs.fromByteArray(salt));
ProtocolUtils.writeByteArray(buf, unsigned ? EncryptionUtils.EMPTY : signature);
buf.writeBoolean(signedPreview);
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
ProtocolUtils.writeVarInt(buf, previousMessages.length);
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
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);

Datei anzeigen

@ -15,20 +15,16 @@
* 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.PlayerChat.MAXIMUM_PREVIOUS_MESSAGE_COUNT;
import static com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedPlayerChat.INVALID_PREVIOUS_MESSAGES;
import static com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedPlayerChat.MAXIMUM_PREVIOUS_MESSAGE_COUNT;
import com.google.common.collect.ImmutableMap;
import com.google.common.primitives.Longs;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.crypto.EncryptionUtils;
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.ProtocolUtils;
import com.velocitypowered.proxy.util.except.QuietDecoderException;
@ -37,10 +33,9 @@ import java.time.Instant;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.UUID;
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_LENGTH_ARGUMENTS = 16;
@ -51,15 +46,11 @@ public class PlayerCommand implements MinecraftPacket {
private String command;
private Instant timestamp;
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 @Nullable SignaturePair lastMessage;
private Map<String, byte[]> arguments = ImmutableMap.of();
public boolean isSignedPreview() {
return signedPreview;
}
public Instant getTimestamp() {
return timestamp;
}
@ -72,17 +63,17 @@ public class PlayerCommand implements MinecraftPacket {
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 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;
ImmutableMap.Builder<String, byte[]> builder = ImmutableMap.builder();
arguments.forEach(entry -> builder.put(entry, EncryptionUtils.EMPTY));
@ -93,21 +84,6 @@ public class PlayerCommand implements MinecraftPacket {
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
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
command = ProtocolUtils.readString(buf, 256);
@ -127,7 +103,7 @@ public class PlayerCommand implements MinecraftPacket {
}
arguments = entries.build();
signedPreview = buf.readBoolean();
this.signedPreview = buf.readBoolean();
if (unsigned && signedPreview) {
throw EncryptionUtils.PREVIEW_SIGNATURE_MISSING;
}
@ -193,43 +169,17 @@ 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
public String toString() {
return "PlayerCommand{"
+ "unsigned=" + unsigned
+ ", command='" + command + '\''
+ ", timestamp=" + timestamp
+ ", salt=" + salt
+ ", signedPreview=" + signedPreview
+ ", previousMessages=" + Arrays.toString(previousMessages)
+ ", arguments=" + arguments
+ '}';
+ "unsigned=" + unsigned
+ ", command='" + command + '\''
+ ", timestamp=" + timestamp
+ ", salt=" + salt
+ ", signedPreview=" + signedPreview
+ ", previousMessages=" + Arrays.toString(previousMessages)
+ ", arguments=" + arguments
+ '}';
}
@Override

Datei anzeigen

@ -15,16 +15,14 @@
* 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.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import java.util.UUID;
import net.kyori.adventure.identity.Identity;
import org.checkerframework.checker.nullness.qual.Nullable;
public class LegacyChat implements MinecraftPacket {

Datei anzeigen

@ -15,41 +15,35 @@
* 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.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
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 {
private int id;
private String query;
public int getId() {
return id;
}
public String getQuery() {
return query;
public class LegacyChatBuilder extends ChatBuilderV2 {
public LegacyChatBuilder(ProtocolVersion version) {
super(version);
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
id = buf.readInt();
query = ProtocolUtils.readString(buf, 256);
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;
return new LegacyChat(ProtocolUtils.getJsonChatSerializer(version).serialize(msg), type.getId(), identity);
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
buf.writeInt(id);
ProtocolUtils.writeString(buf, query);
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
public MinecraftPacket toServer() {
LegacyChat chat = new LegacyChat();
chat.setMessage(message);
return chat;
}
}

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;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer;
import com.google.common.collect.Maps;
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.util.GameProfile;
import com.velocitypowered.proxy.connection.MinecraftConnection;
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.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.Collection;
import java.util.Collections;
import java.util.EnumSet;
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 java.util.stream.Collectors;
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;
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;
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) {
public VelocityTabList(ConnectedPlayer player) {
this.player = player;
this.proxyServer = proxyServer;
this.connection = player.getConnection();
this.entries = Maps.newHashMap();
}
@Deprecated
@Override
public void setHeaderAndFooter(Component header, Component footer) {
Preconditions.checkNotNull(header, "header");
@ -71,177 +70,178 @@ public class VelocityTabList implements TabList {
}
@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 VelocityTabListEntry,
"Not a Velocity tab list entry");
public void addEntry(TabListEntry entry1) {
VelocityTabListEntry entry;
if (entry1 instanceof VelocityTabListEntry) {
entry = (VelocityTabListEntry) entry1;
} else {
entry = new VelocityTabListEntry(this, entry1.getProfile(), entry1.getDisplayNameComponent().orElse(null),
entry1.getLatency(), entry1.getGameMode(), entry1.getChatSession(), entry1.isListed());
}
PlayerListItem.Item packetItem = PlayerListItem.Item.from(entry);
connection.write(
new PlayerListItem(PlayerListItem.ADD_PLAYER, Collections.singletonList(packetItem)));
entries.put(entry.getProfile().getId(), (VelocityTabListEntry) entry);
EnumSet<UpsertPlayerInfo.Action> actions = EnumSet.noneOf(UpsertPlayerInfo.Action.class);
UpsertPlayerInfo.Entry playerInfoEntry = new UpsertPlayerInfo.Entry(entry.getProfile().getId());
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
public Optional<TabListEntry> removeEntry(UUID uuid) {
Preconditions.checkNotNull(uuid, "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);
this.connection.write(new RemovePlayerInfo(List.of(uuid)));
return Optional.ofNullable(this.entries.remove(uuid));
}
@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()}.
*/
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));
return this.entries.containsKey(uuid);
}
@Override
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
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
public TabListEntry buildEntry(GameProfile profile,
net.kyori.adventure.text.@Nullable Component displayName,
int latency, int gameMode, @Nullable IdentifiedKey key) {
return new VelocityTabListEntry(this, profile, displayName, latency, gameMode, key);
public TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency, int gameMode,
@Nullable IdentifiedKey key) {
return new VelocityTabListEntry(this, profile, displayName, latency, gameMode, null, true);
}
/**
* Processes a tab list entry packet from the backend.
*
* @param packet the packet to process
*/
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!";
@Override
public TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency, int gameMode,
@Nullable ChatSession chatSession) {
return new VelocityTabListEntry(this, profile, displayName, latency, gameMode, chatSession, true);
}
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()) {
case PlayerListItem.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");
}
// 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(), (VelocityTabListEntry) TabListEntry.builder()
.tabList(this)
.profile(new GameProfile(uuid, name, properties))
.displayName(item.getDisplayName())
.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;
}
@Override
public void processUpdate(UpsertPlayerInfo infoPacket) {
for (UpsertPlayerInfo.Entry entry : infoPacket.getEntries()) {
processUpsert(infoPacket.getActions(), entry);
}
}
void updateEntry(int action, TabListEntry entry) {
if (entries.containsKey(entry.getProfile().getId())) {
PlayerListItem.Item packetItem = PlayerListItem.Item.from(entry);
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());
}
IdentifiedKey selectedKey = packetItem.getPlayerKey();
Optional<Player> existing = proxyServer.getPlayer(entry.getProfile().getId());
if (existing.isPresent()) {
selectedKey = existing.get().getIdentifiedKey();
}
protected void emitActionRaw(UpsertPlayerInfo.Action action, UpsertPlayerInfo.Entry entry) {
this.connection.write(new UpsertPlayerInfo(EnumSet.of(action), Collections.singletonList(entry)));
}
if (selectedKey != null
&& selectedKey.getKeyRevision().getApplicableTo().contains(connection.getProtocolVersion())
&& Objects.equals(selectedKey.getSignatureHolder(), entry.getProfile().getId())) {
packetItem.setPlayerKey(selectedKey);
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 {
packetItem.setPlayerKey(null);
logger.debug("Received an add player packet for an existing entry; this does nothing.");
}
} 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());
}
}
connection.write(new PlayerListItem(action, Collections.singletonList(packetItem)));
@Override
public void processRemove(RemovePlayerInfo infoPacket) {
for (UUID uuid : infoPacket.getProfilesToRemove()) {
this.entries.remove(uuid);
}
}
}

Datei anzeigen

@ -17,98 +17,108 @@
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.PlayerListItem;
import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo;
import com.velocitypowered.proxy.protocol.packet.chat.RemoteChatSession;
import java.util.Optional;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable;
public class VelocityTabListEntry implements TabListEntry {
private final VelocityTabList tabList;
private final GameProfile profile;
private net.kyori.adventure.text.Component displayName;
private Component displayName;
private int latency;
private int gameMode;
private @Nullable IdentifiedKey playerKey;
private boolean listed;
private @Nullable ChatSession session;
VelocityTabListEntry(VelocityTabList tabList, GameProfile profile,
net.kyori.adventure.text.@Nullable Component displayName, int latency, int gameMode,
@Nullable IdentifiedKey playerKey) {
public VelocityTabListEntry(VelocityTabList tabList, GameProfile profile, Component displayName, int latency,
int gameMode, @Nullable ChatSession session, boolean listed) {
this.tabList = tabList;
this.profile = profile;
this.displayName = displayName;
this.latency = latency;
this.gameMode = gameMode;
this.playerKey = playerKey;
this.session = session;
this.listed = listed;
}
@Override
public @Nullable ChatSession getChatSession() {
return this.session;
}
@Override
public TabList getTabList() {
return tabList;
return this.tabList;
}
@Override
public GameProfile getProfile() {
return profile;
return this.profile;
}
@Override
public Optional<net.kyori.adventure.text.Component> getDisplayNameComponent() {
public Optional<Component> getDisplayNameComponent() {
return Optional.ofNullable(displayName);
}
@Override
public TabListEntry setDisplayName(net.kyori.adventure.text.@Nullable Component displayName) {
public TabListEntry setDisplayName(@Nullable Component 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;
}
void setDisplayNameInternal(net.kyori.adventure.text.@Nullable Component displayName) {
this.displayName = displayName;
}
@Override
public int getLatency() {
return latency;
return this.latency;
}
@Override
public TabListEntry setLatency(int 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;
}
void setLatencyInternal(int latency) {
this.latency = latency;
}
@Override
public int getGameMode() {
return gameMode;
return this.gameMode;
}
@Override
public TabListEntry setGameMode(int 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;
}
void setGameModeInternal(int gameMode) {
this.gameMode = gameMode;
protected void setChatSession(@Nullable ChatSession session) {
this.session = session;
}
@Override
public IdentifiedKey getIdentifiedKey() {
return playerKey;
public boolean isListed() {
return listed;
}
void setPlayerKeyInternal(IdentifiedKey playerKey) {
this.playerKey = playerKey;
@Override
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 org.checkerframework.checker.nullness.qual.Nullable;
public class VelocityTabListEntryLegacy extends VelocityTabListEntry {
public class VelocityTabListEntryLegacy extends KeyedVelocityTabListEntry {
VelocityTabListEntryLegacy(VelocityTabListLegacy tabList, GameProfile profile,
@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.util.GameProfile;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.packet.PlayerListItem;
import com.velocitypowered.proxy.protocol.packet.PlayerListItem.Item;
import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem;
import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem.Item;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
@ -32,7 +32,7 @@ import java.util.concurrent.ConcurrentHashMap;
import net.kyori.adventure.text.Component;
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<>();
@ -65,35 +65,35 @@ public class VelocityTabListLegacy extends VelocityTabList {
@Override
public void clearAll() {
for (TabListEntry value : entries.values()) {
connection.delayedWrite(new PlayerListItem(PlayerListItem.REMOVE_PLAYER,
Collections.singletonList(PlayerListItem.Item.from(value))));
connection.delayedWrite(new LegacyPlayerListItem(LegacyPlayerListItem.REMOVE_PLAYER,
Collections.singletonList(LegacyPlayerListItem.Item.from(value))));
}
entries.clear();
nameMapping.clear();
}
@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
switch (packet.getAction()) {
case PlayerListItem.ADD_PLAYER:
case LegacyPlayerListItem.ADD_PLAYER:
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) {
entry.setLatencyInternal(item.getLatency());
}
} else {
UUID uuid = UUID.randomUUID(); // Use a fake uuid to preserve function of custom entries
nameMapping.put(item.getName(), uuid);
entries.put(uuid, (VelocityTabListEntry) TabListEntry.builder()
entries.put(uuid, (KeyedVelocityTabListEntry) TabListEntry.builder()
.tabList(this)
.profile(new GameProfile(uuid, item.getName(), ImmutableList.of()))
.latency(item.getLatency())
.build());
}
break;
case PlayerListItem.REMOVE_PLAYER:
case LegacyPlayerListItem.REMOVE_PLAYER:
UUID removedUuid = nameMapping.remove(item.getName());
if (removedUuid != null) {
entries.remove(removedUuid);
@ -109,11 +109,11 @@ public class VelocityTabListLegacy extends VelocityTabList {
void updateEntry(int action, TabListEntry entry) {
if (entries.containsKey(entry.getProfile().getId())) {
switch (action) {
case PlayerListItem.UPDATE_LATENCY:
case PlayerListItem.UPDATE_DISPLAY_NAME: // Add here because we removed beforehand
case LegacyPlayerListItem.UPDATE_LATENCY:
case LegacyPlayerListItem.UPDATE_DISPLAY_NAME: // Add here because we removed beforehand
connection
.write(new PlayerListItem(PlayerListItem.ADD_PLAYER, // ADD_PLAYER also updates ping
Collections.singletonList(PlayerListItem.Item.from(entry))));
.write(new LegacyPlayerListItem(LegacyPlayerListItem.ADD_PLAYER, // ADD_PLAYER also updates ping
Collections.singletonList(LegacyPlayerListItem.Item.from(entry))));
break;
default:
// Can't do anything else