Mirror von
https://github.com/PaperMC/Velocity.git
synchronisiert 2024-11-16 21:10:30 +01:00
Update to 1.19.3-rc3 (#893)
Dieser Commit ist enthalten in:
Ursprung
15216e5b00
Commit
b504e0857c
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,6 +5,7 @@
|
||||
*.iml
|
||||
.idea_modules/
|
||||
atlassian-ide-plugin.xml
|
||||
.fleet/
|
||||
|
||||
### Eclipse ###
|
||||
.metadata
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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];
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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() {
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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() {
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
};
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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);
|
@ -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
|
@ -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 {
|
@ -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;
|
||||
}
|
||||
}
|
@ -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());
|
||||
});
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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) {
|
||||
}
|
||||
}
|
@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren