Mirror von
https://github.com/PaperMC/Velocity.git
synchronisiert 2024-11-17 05:20:14 +01:00
Update to 1.19.3-rc3 (#893)
Dieser Commit ist enthalten in:
Ursprung
15216e5b00
Commit
b504e0857c
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,6 +5,7 @@
|
|||||||
*.iml
|
*.iml
|
||||||
.idea_modules/
|
.idea_modules/
|
||||||
atlassian-ide-plugin.xml
|
atlassian-ide-plugin.xml
|
||||||
|
.fleet/
|
||||||
|
|
||||||
### Eclipse ###
|
### Eclipse ###
|
||||||
.metadata
|
.metadata
|
||||||
|
@ -59,7 +59,8 @@ public enum ProtocolVersion {
|
|||||||
MINECRAFT_1_18(757, "1.18", "1.18.1"),
|
MINECRAFT_1_18(757, "1.18", "1.18.1"),
|
||||||
MINECRAFT_1_18_2(758, "1.18.2"),
|
MINECRAFT_1_18_2(758, "1.18.2"),
|
||||||
MINECRAFT_1_19(759, "1.19"),
|
MINECRAFT_1_19(759, "1.19"),
|
||||||
MINECRAFT_1_19_1(760, "1.19.1", "1.19.2");
|
MINECRAFT_1_19_1(760, "1.19.1", "1.19.2"),
|
||||||
|
MINECRAFT_1_19_3(761, 114, "1.19.3");
|
||||||
|
|
||||||
private static final int SNAPSHOT_BIT = 30;
|
private static final int SNAPSHOT_BIT = 30;
|
||||||
|
|
||||||
|
@ -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,6 +68,11 @@ public interface TabList {
|
|||||||
*/
|
*/
|
||||||
Collection<TabListEntry> getEntries();
|
Collection<TabListEntry> getEntries();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears all entries from the tab list.
|
||||||
|
*/
|
||||||
|
void clearAll();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds a tab list entry.
|
* Builds a tab list entry.
|
||||||
*
|
*
|
||||||
@ -96,4 +101,20 @@ public interface TabList {
|
|||||||
@Deprecated
|
@Deprecated
|
||||||
TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency,
|
TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency,
|
||||||
int gameMode, @Nullable IdentifiedKey key);
|
int gameMode, @Nullable IdentifiedKey key);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an entry in a {@link Player}'s tab list.
|
||||||
|
*
|
||||||
|
* @param profile the profile
|
||||||
|
* @param displayName the display name
|
||||||
|
* @param latency the latency
|
||||||
|
* @param gameMode the game mode
|
||||||
|
* @param chatSession the chat session
|
||||||
|
* @return the entry
|
||||||
|
* @deprecated Internal usage. Use {@link TabListEntry.Builder} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency,
|
||||||
|
int gameMode, @Nullable ChatSession chatSession);
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,21 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
|||||||
* Represents a single entry in a {@link TabList}.
|
* Represents a single entry in a {@link TabList}.
|
||||||
*/
|
*/
|
||||||
public interface TabListEntry extends KeyIdentifiable {
|
public interface TabListEntry extends KeyIdentifiable {
|
||||||
|
/**
|
||||||
|
* Returns the {@link ChatSession} associated with this entry.
|
||||||
|
*
|
||||||
|
* @return the chat session
|
||||||
|
*/
|
||||||
|
@Nullable ChatSession getChatSession();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default IdentifiedKey getIdentifiedKey() {
|
||||||
|
ChatSession session = getChatSession();
|
||||||
|
if (session == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return getChatSession().getIdentifiedKey();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the parent {@link TabList} of this {@code this} {@link TabListEntry}.
|
* Returns the parent {@link TabList} of this {@code this} {@link TabListEntry}.
|
||||||
@ -105,6 +120,25 @@ public interface TabListEntry extends KeyIdentifiable {
|
|||||||
*/
|
*/
|
||||||
TabListEntry setGameMode(int gameMode);
|
TabListEntry setGameMode(int gameMode);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the entry is listed, when listed they will be visible to other players in the tab list.
|
||||||
|
*
|
||||||
|
* @return Whether this entry is listed; only changeable in 1.19.3 and above
|
||||||
|
*/
|
||||||
|
default boolean isListed() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether this entry is listed.
|
||||||
|
*
|
||||||
|
* @param listed whether this entry is listed
|
||||||
|
* @return {@code this}, for chaining
|
||||||
|
*/
|
||||||
|
default TabListEntry setListed(boolean listed) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a {@link Builder} to create a {@link TabListEntry}.
|
* Returns a {@link Builder} to create a {@link TabListEntry}.
|
||||||
*
|
*
|
||||||
@ -127,7 +161,7 @@ public interface TabListEntry extends KeyIdentifiable {
|
|||||||
private int latency = 0;
|
private int latency = 0;
|
||||||
private int gameMode = 0;
|
private int gameMode = 0;
|
||||||
|
|
||||||
private @Nullable IdentifiedKey playerKey;
|
private @Nullable ChatSession chatSession;
|
||||||
|
|
||||||
private Builder() {
|
private Builder() {
|
||||||
}
|
}
|
||||||
@ -162,12 +196,12 @@ public interface TabListEntry extends KeyIdentifiable {
|
|||||||
* <p>For any player currently connected to this proxy this will be filled automatically.</p>
|
* <p>For any player currently connected to this proxy this will be filled automatically.</p>
|
||||||
* <p>Will ignore mismatching key revisions data.</p>
|
* <p>Will ignore mismatching key revisions data.</p>
|
||||||
*
|
*
|
||||||
* @param playerKey key to set
|
* @param chatSession session to set
|
||||||
* @return {@code this}, for chaining
|
* @return {@code this}, for chaining
|
||||||
* @see TabListEntry#getIdentifiedKey()
|
* @see TabListEntry#getChatSession()
|
||||||
*/
|
*/
|
||||||
public Builder playerKey(IdentifiedKey playerKey) {
|
public Builder chatSession(ChatSession chatSession) {
|
||||||
this.playerKey = playerKey;
|
this.chatSession = chatSession;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,7 +253,7 @@ public interface TabListEntry extends KeyIdentifiable {
|
|||||||
if (profile == null) {
|
if (profile == null) {
|
||||||
throw new IllegalStateException("The GameProfile must be set when building a TabListEntry");
|
throw new IllegalStateException("The GameProfile must be set when building a TabListEntry");
|
||||||
}
|
}
|
||||||
return tabList.buildEntry(profile, displayName, latency, gameMode, playerKey);
|
return tabList.buildEntry(profile, displayName, latency, gameMode, chatSession);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,10 +30,11 @@ import com.velocitypowered.proxy.protocol.packet.JoinGame;
|
|||||||
import com.velocitypowered.proxy.protocol.packet.KeepAlive;
|
import com.velocitypowered.proxy.protocol.packet.KeepAlive;
|
||||||
import com.velocitypowered.proxy.protocol.packet.LegacyHandshake;
|
import com.velocitypowered.proxy.protocol.packet.LegacyHandshake;
|
||||||
import com.velocitypowered.proxy.protocol.packet.LegacyPing;
|
import com.velocitypowered.proxy.protocol.packet.LegacyPing;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem;
|
||||||
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage;
|
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage;
|
||||||
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
|
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
|
||||||
import com.velocitypowered.proxy.protocol.packet.PlayerListItem;
|
|
||||||
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfo;
|
||||||
import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest;
|
import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest;
|
||||||
import com.velocitypowered.proxy.protocol.packet.ResourcePackResponse;
|
import com.velocitypowered.proxy.protocol.packet.ResourcePackResponse;
|
||||||
import com.velocitypowered.proxy.protocol.packet.Respawn;
|
import com.velocitypowered.proxy.protocol.packet.Respawn;
|
||||||
@ -46,14 +47,14 @@ import com.velocitypowered.proxy.protocol.packet.StatusRequest;
|
|||||||
import com.velocitypowered.proxy.protocol.packet.StatusResponse;
|
import com.velocitypowered.proxy.protocol.packet.StatusResponse;
|
||||||
import com.velocitypowered.proxy.protocol.packet.TabCompleteRequest;
|
import com.velocitypowered.proxy.protocol.packet.TabCompleteRequest;
|
||||||
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse;
|
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse;
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.LegacyChat;
|
import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo;
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.PlayerChat;
|
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.PlayerChatCompletion;
|
import com.velocitypowered.proxy.protocol.packet.chat.PlayerChatCompletion;
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.PlayerChatPreview;
|
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.PlayerCommand;
|
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.ServerChatPreview;
|
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.ServerPlayerChat;
|
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.SystemChat;
|
import com.velocitypowered.proxy.protocol.packet.chat.SystemChat;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedPlayerChat;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedPlayerCommand;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyChat;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerChat;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerCommand;
|
||||||
import com.velocitypowered.proxy.protocol.packet.title.LegacyTitlePacket;
|
import com.velocitypowered.proxy.protocol.packet.title.LegacyTitlePacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.title.TitleActionbarPacket;
|
import com.velocitypowered.proxy.protocol.packet.title.TitleActionbarPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.title.TitleClearPacket;
|
import com.velocitypowered.proxy.protocol.packet.title.TitleClearPacket;
|
||||||
@ -228,7 +229,7 @@ public interface MinecraftSessionHandler {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
default boolean handle(PlayerListItem packet) {
|
default boolean handle(LegacyPlayerListItem packet) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,7 +241,11 @@ public interface MinecraftSessionHandler {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
default boolean handle(PlayerChat packet) {
|
default boolean handle(KeyedPlayerChat packet) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean handle(SessionPlayerChat packet) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,19 +253,11 @@ public interface MinecraftSessionHandler {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
default boolean handle(ServerPlayerChat packet) {
|
default boolean handle(KeyedPlayerCommand packet) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
default boolean handle(PlayerChatPreview packet) {
|
default boolean handle(SessionPlayerCommand packet) {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
default boolean handle(ServerChatPreview packet) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
default boolean handle(PlayerCommand packet) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -271,4 +268,12 @@ public interface MinecraftSessionHandler {
|
|||||||
default boolean handle(ServerData serverData) {
|
default boolean handle(ServerData serverData) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default boolean handle(RemovePlayerInfo packet) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean handle(UpsertPlayerInfo packet) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,8 @@ public class VelocityConstants {
|
|||||||
public static final int MODERN_FORWARDING_DEFAULT = 1;
|
public static final int MODERN_FORWARDING_DEFAULT = 1;
|
||||||
public static final int MODERN_FORWARDING_WITH_KEY = 2;
|
public static final int MODERN_FORWARDING_WITH_KEY = 2;
|
||||||
public static final int MODERN_FORWARDING_WITH_KEY_V2 = 3;
|
public static final int MODERN_FORWARDING_WITH_KEY_V2 = 3;
|
||||||
public static final int MODERN_FORWARDING_MAX_VERSION = MODERN_FORWARDING_WITH_KEY_V2;
|
public static final int MODERN_LAZY_SESSION = 4;
|
||||||
|
public static final int MODERN_FORWARDING_MAX_VERSION = MODERN_LAZY_SESSION;
|
||||||
|
|
||||||
public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
|
public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
|
||||||
}
|
}
|
||||||
|
@ -43,12 +43,14 @@ import com.velocitypowered.proxy.protocol.packet.AvailableCommands;
|
|||||||
import com.velocitypowered.proxy.protocol.packet.BossBar;
|
import com.velocitypowered.proxy.protocol.packet.BossBar;
|
||||||
import com.velocitypowered.proxy.protocol.packet.Disconnect;
|
import com.velocitypowered.proxy.protocol.packet.Disconnect;
|
||||||
import com.velocitypowered.proxy.protocol.packet.KeepAlive;
|
import com.velocitypowered.proxy.protocol.packet.KeepAlive;
|
||||||
import com.velocitypowered.proxy.protocol.packet.PlayerListItem;
|
import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem;
|
||||||
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfo;
|
||||||
import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest;
|
import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest;
|
||||||
import com.velocitypowered.proxy.protocol.packet.ResourcePackResponse;
|
import com.velocitypowered.proxy.protocol.packet.ResourcePackResponse;
|
||||||
import com.velocitypowered.proxy.protocol.packet.ServerData;
|
import com.velocitypowered.proxy.protocol.packet.ServerData;
|
||||||
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse;
|
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo;
|
||||||
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
|
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufUtil;
|
import io.netty.buffer.ByteBufUtil;
|
||||||
@ -60,7 +62,6 @@ import org.apache.logging.log4j.LogManager;
|
|||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||||
|
|
||||||
private static final Pattern PLAUSIBLE_SHA1_HASH = Pattern.compile("^[a-z0-9]{40}$");
|
private static final Pattern PLAUSIBLE_SHA1_HASH = Pattern.compile("^[a-z0-9]{40}$");
|
||||||
private static final Logger logger = LogManager.getLogger(BackendPlaySessionHandler.class);
|
private static final Logger logger = LogManager.getLogger(BackendPlaySessionHandler.class);
|
||||||
private static final boolean BACKPRESSURE_LOG = Boolean
|
private static final boolean BACKPRESSURE_LOG = Boolean
|
||||||
@ -245,8 +246,20 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean handle(PlayerListItem packet) {
|
public boolean handle(LegacyPlayerListItem packet) {
|
||||||
serverConn.getPlayer().getTabList().processBackendPacket(packet);
|
serverConn.getPlayer().getTabList().processLegacy(packet);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handle(UpsertPlayerInfo packet) {
|
||||||
|
serverConn.getPlayer().getTabList().processUpdate(packet);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handle(RemovePlayerInfo packet) {
|
||||||
|
serverConn.getPlayer().getTabList().processRemove(packet);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -280,7 +293,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
this.playerConnection.write(
|
this.playerConnection.write(
|
||||||
new ServerData(pingEvent.getPing().getDescriptionComponent(),
|
new ServerData(pingEvent.getPing().getDescriptionComponent(),
|
||||||
pingEvent.getPing().getFavicon().orElse(null),
|
pingEvent.getPing().getFavicon().orElse(null),
|
||||||
packet.isPreviewsChat(), packet.isSecureChatEnforced())
|
packet.isSecureChatEnforced())
|
||||||
), playerConnection.eventLoop());
|
), playerConnection.eventLoop());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -17,12 +17,10 @@
|
|||||||
|
|
||||||
package com.velocitypowered.proxy.connection.backend;
|
package com.velocitypowered.proxy.connection.backend;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
|
||||||
import com.velocitypowered.api.event.player.ServerLoginPluginMessageEvent;
|
import com.velocitypowered.api.event.player.ServerLoginPluginMessageEvent;
|
||||||
import com.velocitypowered.api.network.ProtocolVersion;
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
|
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
|
||||||
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
|
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
|
||||||
import com.velocitypowered.api.util.GameProfile;
|
|
||||||
import com.velocitypowered.proxy.VelocityServer;
|
import com.velocitypowered.proxy.VelocityServer;
|
||||||
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
|
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
|
||||||
import com.velocitypowered.proxy.config.VelocityConfiguration;
|
import com.velocitypowered.proxy.config.VelocityConfiguration;
|
||||||
@ -51,9 +49,11 @@ import javax.crypto.Mac;
|
|||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
public class LoginSessionHandler implements MinecraftSessionHandler {
|
public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||||
|
private static final Logger logger = LogManager.getLogger(LoginSessionHandler.class);
|
||||||
|
|
||||||
private static final Component MODERN_IP_FORWARDING_FAILURE = Component
|
private static final Component MODERN_IP_FORWARDING_FAILURE = Component
|
||||||
.translatable("velocity.error.modern-forwarding-failed");
|
.translatable("velocity.error.modern-forwarding-failed");
|
||||||
@ -177,6 +177,10 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
|||||||
// Ensure we are in range
|
// Ensure we are in range
|
||||||
requested = Math.min(requested, VelocityConstants.MODERN_FORWARDING_MAX_VERSION);
|
requested = Math.min(requested, VelocityConstants.MODERN_FORWARDING_MAX_VERSION);
|
||||||
if (requested > VelocityConstants.MODERN_FORWARDING_DEFAULT) {
|
if (requested > VelocityConstants.MODERN_FORWARDING_DEFAULT) {
|
||||||
|
if (player.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19_3) >= 0) {
|
||||||
|
return requested >= VelocityConstants.MODERN_LAZY_SESSION ? VelocityConstants.MODERN_LAZY_SESSION
|
||||||
|
: VelocityConstants.MODERN_FORWARDING_DEFAULT;
|
||||||
|
}
|
||||||
if (player.getIdentifiedKey() != null) {
|
if (player.getIdentifiedKey() != null) {
|
||||||
// No enhanced switch on java 11
|
// No enhanced switch on java 11
|
||||||
switch (player.getIdentifiedKey().getKeyRevision()) {
|
switch (player.getIdentifiedKey().getKeyRevision()) {
|
||||||
@ -210,7 +214,8 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
|||||||
|
|
||||||
// This serves as additional redundancy. The key normally is stored in the
|
// This serves as additional redundancy. The key normally is stored in the
|
||||||
// login start to the server, but some setups require this.
|
// login start to the server, but some setups require this.
|
||||||
if (actualVersion >= VelocityConstants.MODERN_FORWARDING_WITH_KEY) {
|
if (actualVersion >= VelocityConstants.MODERN_FORWARDING_WITH_KEY
|
||||||
|
&& actualVersion < VelocityConstants.MODERN_LAZY_SESSION) {
|
||||||
IdentifiedKey key = player.getIdentifiedKey();
|
IdentifiedKey key = player.getIdentifiedKey();
|
||||||
assert key != null;
|
assert key != null;
|
||||||
ProtocolUtils.writePlayerKey(forwarded, key);
|
ProtocolUtils.writePlayerKey(forwarded, key);
|
||||||
|
@ -190,7 +190,12 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
|||||||
|
|
||||||
mc.setProtocolVersion(protocolVersion);
|
mc.setProtocolVersion(protocolVersion);
|
||||||
mc.setState(StateRegistry.LOGIN);
|
mc.setState(StateRegistry.LOGIN);
|
||||||
|
if (proxyPlayer.getIdentifiedKey() == null
|
||||||
|
&& proxyPlayer.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19_3) >= 0) {
|
||||||
|
mc.delayedWrite(new ServerLogin(proxyPlayer.getUsername(), proxyPlayer.getUniqueId()));
|
||||||
|
} else {
|
||||||
mc.delayedWrite(new ServerLogin(proxyPlayer.getUsername(), proxyPlayer.getIdentifiedKey()));
|
mc.delayedWrite(new ServerLogin(proxyPlayer.getUsername(), proxyPlayer.getIdentifiedKey()));
|
||||||
|
}
|
||||||
mc.flush();
|
mc.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,14 +25,11 @@ import static com.velocitypowered.proxy.protocol.util.PluginMessageUtil.construc
|
|||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.mojang.brigadier.suggestion.Suggestion;
|
import com.mojang.brigadier.suggestion.Suggestion;
|
||||||
import com.velocitypowered.api.command.VelocityBrigadierMessage;
|
import com.velocitypowered.api.command.VelocityBrigadierMessage;
|
||||||
import com.velocitypowered.api.event.command.CommandExecuteEvent.CommandResult;
|
|
||||||
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
||||||
import com.velocitypowered.api.event.player.PlayerChannelRegisterEvent;
|
import com.velocitypowered.api.event.player.PlayerChannelRegisterEvent;
|
||||||
import com.velocitypowered.api.event.player.PlayerChatEvent;
|
|
||||||
import com.velocitypowered.api.event.player.PlayerClientBrandEvent;
|
import com.velocitypowered.api.event.player.PlayerClientBrandEvent;
|
||||||
import com.velocitypowered.api.event.player.TabCompleteEvent;
|
import com.velocitypowered.api.event.player.TabCompleteEvent;
|
||||||
import com.velocitypowered.api.network.ProtocolVersion;
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
|
|
||||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||||
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
|
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
|
||||||
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
|
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
|
||||||
@ -43,8 +40,6 @@ import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
|||||||
import com.velocitypowered.proxy.connection.backend.BackendConnectionPhases;
|
import com.velocitypowered.proxy.connection.backend.BackendConnectionPhases;
|
||||||
import com.velocitypowered.proxy.connection.backend.BungeeCordMessageResponder;
|
import com.velocitypowered.proxy.connection.backend.BungeeCordMessageResponder;
|
||||||
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
||||||
import com.velocitypowered.proxy.crypto.SignedChatCommand;
|
|
||||||
import com.velocitypowered.proxy.crypto.SignedChatMessage;
|
|
||||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||||
import com.velocitypowered.proxy.protocol.packet.BossBar;
|
import com.velocitypowered.proxy.protocol.packet.BossBar;
|
||||||
@ -57,11 +52,20 @@ import com.velocitypowered.proxy.protocol.packet.Respawn;
|
|||||||
import com.velocitypowered.proxy.protocol.packet.TabCompleteRequest;
|
import com.velocitypowered.proxy.protocol.packet.TabCompleteRequest;
|
||||||
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse;
|
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse;
|
||||||
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse.Offer;
|
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse.Offer;
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.ChatBuilder;
|
import com.velocitypowered.proxy.protocol.packet.chat.ChatHandler;
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.ChatQueue;
|
import com.velocitypowered.proxy.protocol.packet.chat.ChatTimeKeeper;
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.LegacyChat;
|
import com.velocitypowered.proxy.protocol.packet.chat.CommandHandler;
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.PlayerChat;
|
import com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedChatHandler;
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.PlayerCommand;
|
import com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedCommandHandler;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedPlayerChat;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedPlayerCommand;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyChat;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyChatHandler;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyCommandHandler;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionChatHandler;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionCommandHandler;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerChat;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerCommand;
|
||||||
import com.velocitypowered.proxy.protocol.packet.title.GenericTitlePacket;
|
import com.velocitypowered.proxy.protocol.packet.title.GenericTitlePacket;
|
||||||
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
|
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
|
||||||
import com.velocitypowered.proxy.util.CharacterUtil;
|
import com.velocitypowered.proxy.util.CharacterUtil;
|
||||||
@ -73,10 +77,8 @@ import java.time.Instant;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
@ -98,7 +100,9 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
private final Queue<PluginMessage> loginPluginMessages = new ConcurrentLinkedQueue<>();
|
private final Queue<PluginMessage> loginPluginMessages = new ConcurrentLinkedQueue<>();
|
||||||
private final VelocityServer server;
|
private final VelocityServer server;
|
||||||
private @Nullable TabCompleteRequest outstandingTabComplete;
|
private @Nullable TabCompleteRequest outstandingTabComplete;
|
||||||
private @Nullable Instant lastChatMessage; // Added in 1.19
|
private final ChatHandler<? extends MinecraftPacket> chatHandler;
|
||||||
|
private final CommandHandler<? extends MinecraftPacket> commandHandler;
|
||||||
|
private final ChatTimeKeeper timeKeeper = new ChatTimeKeeper();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a client play session handler.
|
* Constructs a client play session handler.
|
||||||
@ -109,127 +113,40 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
public ClientPlaySessionHandler(VelocityServer server, ConnectedPlayer player) {
|
public ClientPlaySessionHandler(VelocityServer server, ConnectedPlayer player) {
|
||||||
this.player = player;
|
this.player = player;
|
||||||
this.server = server;
|
this.server = server;
|
||||||
|
|
||||||
|
if (this.player.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19_3) >= 0) {
|
||||||
|
this.chatHandler = new SessionChatHandler(this.player, this.server);
|
||||||
|
this.commandHandler = new SessionCommandHandler(this.player, this.server);
|
||||||
|
} else if (this.player.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
|
||||||
|
this.chatHandler = new KeyedChatHandler(this.server, this.player);
|
||||||
|
this.commandHandler = new KeyedCommandHandler(this.player, this.server);
|
||||||
|
} else {
|
||||||
|
this.chatHandler = new LegacyChatHandler(this.server, this.player);
|
||||||
|
this.commandHandler = new LegacyCommandHandler(this.player, this.server);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// I will not allow hacks to bypass this;
|
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||||
private boolean tickLastMessage(SignedChatMessage nextMessage) {
|
private boolean updateTimeKeeper(Instant instant) {
|
||||||
if (lastChatMessage != null && lastChatMessage.isAfter(nextMessage.getExpiryTemporal())) {
|
if (!this.timeKeeper.update(instant)) {
|
||||||
player.disconnect(Component.translatable("multiplayer.disconnect.out_of_order_chat"));
|
player.disconnect(Component.translatable("multiplayer.disconnect.out_of_order_chat"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
lastChatMessage = nextMessage.getExpiryTemporal();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||||
private boolean validateChat(String message) {
|
private boolean validateChat(String message) {
|
||||||
if (CharacterUtil.containsIllegalCharacters(message)) {
|
if (CharacterUtil.containsIllegalCharacters(message)) {
|
||||||
player.disconnect(Component.translatable("velocity.error.illegal-chat-characters",
|
player.disconnect(Component.translatable("velocity.error.illegal-chat-characters", NamedTextColor.RED));
|
||||||
NamedTextColor.RED));
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private MinecraftConnection retrieveServerConnection() {
|
|
||||||
VelocityServerConnection serverConnection = player.getConnectedServer();
|
|
||||||
if (serverConnection == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return serverConnection.getConnection();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processCommandMessage(String message, @Nullable SignedChatCommand signedCommand,
|
|
||||||
MinecraftPacket original, Instant passedTimestamp) {
|
|
||||||
this.player.getChatQueue().queuePacket(server.getCommandManager().callCommandEvent(player, message)
|
|
||||||
.thenComposeAsync(event -> processCommandExecuteResult(message,
|
|
||||||
event.getResult(), signedCommand, passedTimestamp))
|
|
||||||
.whenComplete((ignored, throwable) -> {
|
|
||||||
if (server.getConfiguration().isLogCommandExecutions()) {
|
|
||||||
logger.info("{} -> executed command /{}", player, message);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.exceptionally(e -> {
|
|
||||||
logger.info("Exception occurred while running command for {}",
|
|
||||||
player.getUsername(), e);
|
|
||||||
player.sendMessage(Component.translatable("velocity.command.generic-error",
|
|
||||||
NamedTextColor.RED));
|
|
||||||
return null;
|
|
||||||
}), passedTimestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processPlayerChat(String message, @Nullable SignedChatMessage signedMessage,
|
|
||||||
MinecraftPacket original) {
|
|
||||||
MinecraftConnection smc = retrieveServerConnection();
|
|
||||||
if (smc == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (signedMessage == null) {
|
|
||||||
PlayerChatEvent event = new PlayerChatEvent(player, message);
|
|
||||||
callChat(original, event, null).thenAccept(smc::write);
|
|
||||||
} else {
|
|
||||||
Instant messageTimestamp = signedMessage.getExpiryTemporal();
|
|
||||||
PlayerChatEvent event = new PlayerChatEvent(player, message);
|
|
||||||
this.player.getChatQueue().queuePacket(callChat(original, event, signedMessage), messageTimestamp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private CompletableFuture<MinecraftPacket> callChat(MinecraftPacket original, PlayerChatEvent event,
|
|
||||||
@Nullable SignedChatMessage signedMessage) {
|
|
||||||
return server.getEventManager().fire(event)
|
|
||||||
.thenApply(pme -> {
|
|
||||||
PlayerChatEvent.ChatResult chatResult = pme.getResult();
|
|
||||||
IdentifiedKey playerKey = player.getIdentifiedKey();
|
|
||||||
if (chatResult.isAllowed()) {
|
|
||||||
Optional<String> eventMsg = pme.getResult().getMessage();
|
|
||||||
if (eventMsg.isPresent()) {
|
|
||||||
String messageNew = eventMsg.get();
|
|
||||||
if (playerKey != null) {
|
|
||||||
if (signedMessage != null && !messageNew.equals(signedMessage.getMessage())) {
|
|
||||||
if (playerKey.getKeyRevision().compareTo(IdentifiedKey.Revision.LINKED_V2) >= 0) {
|
|
||||||
// Bad, very bad.
|
|
||||||
logger.fatal("A plugin tried to change a signed chat message. "
|
|
||||||
+ "This is no longer possible in 1.19.1 and newer. "
|
|
||||||
+ "Disconnecting player " + player.getUsername());
|
|
||||||
player.disconnect(Component.text("A proxy plugin caused an illegal protocol state. "
|
|
||||||
+ "Contact your network administrator."));
|
|
||||||
} else {
|
|
||||||
logger.warn("A plugin changed a signed chat message. The server may not accept it.");
|
|
||||||
return ChatBuilder.builder(player.getProtocolVersion())
|
|
||||||
.message(messageNew).toServer();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return original;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return ChatBuilder.builder(player.getProtocolVersion())
|
|
||||||
.message(messageNew).toServer();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return original;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (playerKey != null && playerKey.getKeyRevision().compareTo(IdentifiedKey.Revision.LINKED_V2) >= 0) {
|
|
||||||
logger.fatal("A plugin tried to cancel a signed chat message."
|
|
||||||
+ " This is no longer possible in 1.19.1 and newer. "
|
|
||||||
+ "Disconnecting player " + player.getUsername());
|
|
||||||
player.disconnect(Component.text("A proxy plugin caused an illegal protocol state. "
|
|
||||||
+ "Contact your network administrator."));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
})
|
|
||||||
.exceptionally((ex) -> {
|
|
||||||
logger.error("Exception while handling player chat for {}", player, ex);
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void activated() {
|
public void activated() {
|
||||||
Collection<String> channels = server.getChannelRegistrar().getChannelsForProtocol(player
|
Collection<String> channels = server.getChannelRegistrar().getChannelsForProtocol(player.getProtocolVersion());
|
||||||
.getProtocolVersion());
|
|
||||||
if (!channels.isEmpty()) {
|
if (!channels.isEmpty()) {
|
||||||
PluginMessage register = constructChannelsPacket(player.getProtocolVersion(), channels);
|
PluginMessage register = constructChannelsPacket(player.getProtocolVersion(), channels);
|
||||||
player.getConnection().write(register);
|
player.getConnection().write(register);
|
||||||
@ -267,48 +184,65 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean handle(PlayerCommand packet) {
|
public boolean handle(SessionPlayerCommand packet) {
|
||||||
player.ensureAndGetCurrentServer();
|
player.ensureAndGetCurrentServer();
|
||||||
|
|
||||||
|
if (!updateTimeKeeper(packet.getTimeStamp())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!validateChat(packet.getCommand())) {
|
if (!validateChat(packet.getCommand())) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!packet.isUnsigned()) {
|
|
||||||
SignedChatCommand signedCommand = packet.signedContainer(player.getIdentifiedKey(), player.getUniqueId(), false);
|
|
||||||
if (signedCommand != null) {
|
|
||||||
processCommandMessage(packet.getCommand(), signedCommand, packet, packet.getTimestamp());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
processCommandMessage(packet.getCommand(), null, packet, packet.getTimestamp());
|
return this.commandHandler.handlePlayerCommand(packet);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean handle(PlayerChat packet) {
|
public boolean handle(SessionPlayerChat packet) {
|
||||||
player.ensureAndGetCurrentServer();
|
player.ensureAndGetCurrentServer();
|
||||||
|
|
||||||
|
if (!updateTimeKeeper(packet.getTimestamp())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!validateChat(packet.getMessage())) {
|
if (!validateChat(packet.getMessage())) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!packet.isUnsigned()) {
|
return this.chatHandler.handlePlayerChat(packet);
|
||||||
// Bad if spoofed
|
}
|
||||||
SignedChatMessage signedChat = packet.signedContainer(player.getIdentifiedKey(), player.getUniqueId(), false);
|
|
||||||
if (signedChat != null) {
|
@Override
|
||||||
// Server doesn't care for expiry as long as order is correct
|
public boolean handle(KeyedPlayerCommand packet) {
|
||||||
if (!tickLastMessage(signedChat)) {
|
player.ensureAndGetCurrentServer();
|
||||||
|
|
||||||
|
if (!updateTimeKeeper(packet.getTimestamp())) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
processPlayerChat(packet.getMessage(), signedChat, packet);
|
if (!validateChat(packet.getCommand())) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
processPlayerChat(packet.getMessage(), null, packet);
|
return this.commandHandler.handlePlayerCommand(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handle(KeyedPlayerChat packet) {
|
||||||
|
player.ensureAndGetCurrentServer();
|
||||||
|
|
||||||
|
if (!updateTimeKeeper(packet.getExpiry())) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!validateChat(packet.getMessage())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.chatHandler.handlePlayerChat(packet);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean handle(LegacyChat packet) {
|
public boolean handle(LegacyChat packet) {
|
||||||
player.ensureAndGetCurrentServer();
|
player.ensureAndGetCurrentServer();
|
||||||
@ -318,9 +252,9 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (msg.startsWith("/")) {
|
if (msg.startsWith("/")) {
|
||||||
processCommandMessage(msg.substring(1), null, packet, Instant.now());
|
this.commandHandler.handlePlayerCommand(packet);
|
||||||
} else {
|
} else {
|
||||||
processPlayerChat(msg, null, packet);
|
this.chatHandler.handlePlayerChat(packet);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -342,8 +276,9 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
MinecraftConnection backendConn = serverConn != null ? serverConn.getConnection() : null;
|
MinecraftConnection backendConn = serverConn != null ? serverConn.getConnection() : null;
|
||||||
if (serverConn != null && backendConn != null) {
|
if (serverConn != null && backendConn != null) {
|
||||||
if (backendConn.getState() != StateRegistry.PLAY) {
|
if (backendConn.getState() != StateRegistry.PLAY) {
|
||||||
logger.warn("A plugin message was received while the backend server was not "
|
logger.warn(
|
||||||
+ "ready. Channel: {}. Packet discarded.", packet.getChannel());
|
"A plugin message was received while the backend server was not " + "ready. Channel: {}. Packet discarded.",
|
||||||
|
packet.getChannel());
|
||||||
} else if (PluginMessageUtil.isRegister(packet)) {
|
} else if (PluginMessageUtil.isRegister(packet)) {
|
||||||
List<String> channels = PluginMessageUtil.getChannels(packet);
|
List<String> channels = PluginMessageUtil.getChannels(packet);
|
||||||
player.getKnownChannels().addAll(channels);
|
player.getKnownChannels().addAll(channels);
|
||||||
@ -355,8 +290,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
channelIdentifiers.add(new LegacyChannelIdentifier(channel));
|
channelIdentifiers.add(new LegacyChannelIdentifier(channel));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
server.getEventManager().fireAndForget(new PlayerChannelRegisterEvent(player,
|
server.getEventManager()
|
||||||
ImmutableList.copyOf(channelIdentifiers)));
|
.fireAndForget(new PlayerChannelRegisterEvent(player, ImmutableList.copyOf(channelIdentifiers)));
|
||||||
backendConn.write(packet.retain());
|
backendConn.write(packet.retain());
|
||||||
} else if (PluginMessageUtil.isUnregister(packet)) {
|
} else if (PluginMessageUtil.isUnregister(packet)) {
|
||||||
player.getKnownChannels().removeAll(PluginMessageUtil.getChannels(packet));
|
player.getKnownChannels().removeAll(PluginMessageUtil.getChannels(packet));
|
||||||
@ -365,8 +300,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
String brand = PluginMessageUtil.readBrandMessage(packet.content());
|
String brand = PluginMessageUtil.readBrandMessage(packet.content());
|
||||||
server.getEventManager().fireAndForget(new PlayerClientBrandEvent(player, brand));
|
server.getEventManager().fireAndForget(new PlayerClientBrandEvent(player, brand));
|
||||||
player.setClientBrand(brand);
|
player.setClientBrand(brand);
|
||||||
backendConn.write(PluginMessageUtil
|
backendConn.write(
|
||||||
.rewriteMinecraftBrand(packet, server.getVersion(), player.getProtocolVersion()));
|
PluginMessageUtil.rewriteMinecraftBrand(packet, server.getVersion(), player.getProtocolVersion()));
|
||||||
} else if (BungeeCordMessageResponder.isBungeeCordMessage(packet)) {
|
} else if (BungeeCordMessageResponder.isBungeeCordMessage(packet)) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@ -401,8 +336,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
PluginMessageEvent event = new PluginMessageEvent(player, serverConn, id, copy);
|
PluginMessageEvent event = new PluginMessageEvent(player, serverConn, id, copy);
|
||||||
server.getEventManager().fire(event).thenAcceptAsync(pme -> {
|
server.getEventManager().fire(event).thenAcceptAsync(pme -> {
|
||||||
if (pme.getResult().isAllowed()) {
|
if (pme.getResult().isAllowed()) {
|
||||||
PluginMessage message = new PluginMessage(packet.getChannel(),
|
PluginMessage message = new PluginMessage(packet.getChannel(), Unpooled.wrappedBuffer(copy));
|
||||||
Unpooled.wrappedBuffer(copy));
|
|
||||||
if (!player.getPhase().consideredComplete() || !serverConn.getPhase().consideredComplete()) {
|
if (!player.getPhase().consideredComplete() || !serverConn.getPhase().consideredComplete()) {
|
||||||
// We're still processing the connection (see above), enqueue the packet for now.
|
// We're still processing the connection (see above), enqueue the packet for now.
|
||||||
loginPluginMessages.add(message.retain());
|
loginPluginMessages.add(message.retain());
|
||||||
@ -410,10 +344,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
backendConn.write(message);
|
backendConn.write(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, backendConn.eventLoop())
|
}, backendConn.eventLoop()).exceptionally((ex) -> {
|
||||||
.exceptionally((ex) -> {
|
logger.error("Exception while handling plugin message packet for {}", player, ex);
|
||||||
logger.error("Exception while handling plugin message packet for {}",
|
|
||||||
player, ex);
|
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -467,8 +399,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void exception(Throwable throwable) {
|
public void exception(Throwable throwable) {
|
||||||
player.disconnect(Component.translatable("velocity.error.player-connection-error",
|
player.disconnect(Component.translatable("velocity.error.player-connection-error", NamedTextColor.RED));
|
||||||
NamedTextColor.RED));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -548,8 +479,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
|
|
||||||
// Clear any title from the previous server.
|
// Clear any title from the previous server.
|
||||||
if (player.getProtocolVersion().compareTo(MINECRAFT_1_8) >= 0) {
|
if (player.getProtocolVersion().compareTo(MINECRAFT_1_8) >= 0) {
|
||||||
player.getConnection().delayedWrite(GenericTitlePacket.constructTitlePacket(
|
player.getConnection().delayedWrite(
|
||||||
GenericTitlePacket.ActionType.RESET, player.getProtocolVersion()));
|
GenericTitlePacket.constructTitlePacket(GenericTitlePacket.ActionType.RESET, player.getProtocolVersion()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flush everything
|
// Flush everything
|
||||||
@ -619,8 +550,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
server.getCommandManager().offerBrigadierSuggestions(player, command)
|
server.getCommandManager().offerBrigadierSuggestions(player, command).thenAcceptAsync(suggestions -> {
|
||||||
.thenAcceptAsync(suggestions -> {
|
|
||||||
if (suggestions.isEmpty()) {
|
if (suggestions.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -629,8 +559,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
for (Suggestion suggestion : suggestions.getList()) {
|
for (Suggestion suggestion : suggestions.getList()) {
|
||||||
String offer = suggestion.getText();
|
String offer = suggestion.getText();
|
||||||
Component tooltip = null;
|
Component tooltip = null;
|
||||||
if (suggestion.getTooltip() != null
|
if (suggestion.getTooltip() != null && suggestion.getTooltip() instanceof VelocityBrigadierMessage) {
|
||||||
&& suggestion.getTooltip() instanceof VelocityBrigadierMessage) {
|
|
||||||
tooltip = ((VelocityBrigadierMessage) suggestion.getTooltip()).asComponent();
|
tooltip = ((VelocityBrigadierMessage) suggestion.getTooltip()).asComponent();
|
||||||
}
|
}
|
||||||
offers.add(new Offer(offer, tooltip));
|
offers.add(new Offer(offer, tooltip));
|
||||||
@ -644,10 +573,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
resp.getOffers().addAll(offers);
|
resp.getOffers().addAll(offers);
|
||||||
player.getConnection().write(resp);
|
player.getConnection().write(resp);
|
||||||
}
|
}
|
||||||
}, player.getConnection().eventLoop())
|
}, player.getConnection().eventLoop()).exceptionally((ex) -> {
|
||||||
.exceptionally((ex) -> {
|
logger.error("Exception while handling command tab completion for player {} executing {}", player, command, ex);
|
||||||
logger.error("Exception while handling command tab completion for player {} executing {}",
|
|
||||||
player, command, ex);
|
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
return true; // Sorry, handler; we're just gonna have to lie to you here.
|
return true; // Sorry, handler; we're just gonna have to lie to you here.
|
||||||
@ -683,8 +610,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
|
|
||||||
private void finishCommandTabComplete(TabCompleteRequest request, TabCompleteResponse response) {
|
private void finishCommandTabComplete(TabCompleteRequest request, TabCompleteResponse response) {
|
||||||
String command = request.getCommand().substring(1);
|
String command = request.getCommand().substring(1);
|
||||||
server.getCommandManager().offerBrigadierSuggestions(player, command)
|
server.getCommandManager().offerBrigadierSuggestions(player, command).thenAcceptAsync(offers -> {
|
||||||
.thenAcceptAsync(offers -> {
|
|
||||||
boolean legacy = player.getProtocolVersion().compareTo(MINECRAFT_1_13) < 0;
|
boolean legacy = player.getProtocolVersion().compareTo(MINECRAFT_1_13) < 0;
|
||||||
try {
|
try {
|
||||||
for (Suggestion suggestion : offers.getList()) {
|
for (Suggestion suggestion : offers.getList()) {
|
||||||
@ -694,8 +620,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
offer = offer.substring(command.length());
|
offer = offer.substring(command.length());
|
||||||
}
|
}
|
||||||
Component tooltip = null;
|
Component tooltip = null;
|
||||||
if (suggestion.getTooltip() != null
|
if (suggestion.getTooltip() != null && suggestion.getTooltip() instanceof VelocityBrigadierMessage) {
|
||||||
&& suggestion.getTooltip() instanceof VelocityBrigadierMessage) {
|
|
||||||
tooltip = ((VelocityBrigadierMessage) suggestion.getTooltip()).asComponent();
|
tooltip = ((VelocityBrigadierMessage) suggestion.getTooltip()).asComponent();
|
||||||
}
|
}
|
||||||
response.getOffers().add(new Offer(offer, tooltip));
|
response.getOffers().add(new Offer(offer, tooltip));
|
||||||
@ -703,15 +628,12 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
response.getOffers().sort(null);
|
response.getOffers().sort(null);
|
||||||
player.getConnection().write(response);
|
player.getConnection().write(response);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Unable to provide tab list completions for {} for command '{}'",
|
logger.error("Unable to provide tab list completions for {} for command '{}'", player.getUsername(), command,
|
||||||
player.getUsername(),
|
e);
|
||||||
command, e);
|
|
||||||
}
|
}
|
||||||
}, player.getConnection().eventLoop())
|
}, player.getConnection().eventLoop()).exceptionally((ex) -> {
|
||||||
.exceptionally((ex) -> {
|
logger.error("Exception while finishing command tab completion, with request {} and response {}", request,
|
||||||
logger.error(
|
response, ex);
|
||||||
"Exception while finishing command tab completion, with request {} and response {}",
|
|
||||||
request, response, ex);
|
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -721,98 +643,19 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
for (Offer offer : response.getOffers()) {
|
for (Offer offer : response.getOffers()) {
|
||||||
offers.add(offer.getText());
|
offers.add(offer.getText());
|
||||||
}
|
}
|
||||||
server.getEventManager().fire(new TabCompleteEvent(player, request.getCommand(), offers))
|
server.getEventManager().fire(new TabCompleteEvent(player, request.getCommand(), offers)).thenAcceptAsync(e -> {
|
||||||
.thenAcceptAsync(e -> {
|
|
||||||
response.getOffers().clear();
|
response.getOffers().clear();
|
||||||
for (String s : e.getSuggestions()) {
|
for (String s : e.getSuggestions()) {
|
||||||
response.getOffers().add(new Offer(s));
|
response.getOffers().add(new Offer(s));
|
||||||
}
|
}
|
||||||
player.getConnection().write(response);
|
player.getConnection().write(response);
|
||||||
}, player.getConnection().eventLoop())
|
}, player.getConnection().eventLoop()).exceptionally((ex) -> {
|
||||||
.exceptionally((ex) -> {
|
logger.error("Exception while finishing regular tab completion, with request {} and response{}", request,
|
||||||
logger.error(
|
response, ex);
|
||||||
"Exception while finishing regular tab completion, with request {} and response{}",
|
|
||||||
request, response, ex);
|
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private CompletableFuture<MinecraftPacket> processCommandExecuteResult(String originalCommand,
|
|
||||||
CommandResult result,
|
|
||||||
@Nullable SignedChatCommand signedCommand,
|
|
||||||
Instant passedTimestamp) {
|
|
||||||
IdentifiedKey playerKey = player.getIdentifiedKey();
|
|
||||||
if (result == CommandResult.denied()) {
|
|
||||||
if (playerKey != null) {
|
|
||||||
if (signedCommand != null && playerKey.getKeyRevision()
|
|
||||||
.compareTo(IdentifiedKey.Revision.LINKED_V2) >= 0) {
|
|
||||||
logger.fatal("A plugin tried to deny a command with signable component(s). "
|
|
||||||
+ "This is not supported. "
|
|
||||||
+ "Disconnecting player " + player.getUsername());
|
|
||||||
player.disconnect(Component.text("A proxy plugin caused an illegal protocol state. "
|
|
||||||
+ "Contact your network administrator."));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return CompletableFuture.completedFuture(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
String commandToRun = result.getCommand().orElse(originalCommand);
|
|
||||||
if (result.isForwardToServer()) {
|
|
||||||
ChatBuilder write = ChatBuilder
|
|
||||||
.builder(player.getProtocolVersion())
|
|
||||||
.timestamp(passedTimestamp)
|
|
||||||
.asPlayer(player);
|
|
||||||
|
|
||||||
if (signedCommand != null && commandToRun.equals(signedCommand.getBaseCommand())) {
|
|
||||||
write.message(signedCommand);
|
|
||||||
} else {
|
|
||||||
if (signedCommand != null && playerKey != null && playerKey.getKeyRevision()
|
|
||||||
.compareTo(IdentifiedKey.Revision.LINKED_V2) >= 0) {
|
|
||||||
logger.fatal("A plugin tried to change a command with signed component(s). "
|
|
||||||
+ "This is not supported. "
|
|
||||||
+ "Disconnecting player " + player.getUsername());
|
|
||||||
player.disconnect(Component.text("A proxy plugin caused an illegal protocol state. "
|
|
||||||
+ "Contact your network administrator."));
|
|
||||||
return CompletableFuture.completedFuture(null);
|
|
||||||
}
|
|
||||||
write.message("/" + commandToRun);
|
|
||||||
}
|
|
||||||
return CompletableFuture.completedFuture(write.toServer());
|
|
||||||
} else {
|
|
||||||
return server.getCommandManager().executeImmediatelyAsync(player, commandToRun)
|
|
||||||
.thenApply(hasRun -> {
|
|
||||||
if (!hasRun) {
|
|
||||||
ChatBuilder write = ChatBuilder
|
|
||||||
.builder(player.getProtocolVersion())
|
|
||||||
.timestamp(passedTimestamp)
|
|
||||||
.asPlayer(player);
|
|
||||||
|
|
||||||
if (signedCommand != null && commandToRun.equals(signedCommand.getBaseCommand())) {
|
|
||||||
write.message(signedCommand);
|
|
||||||
} else {
|
|
||||||
if (signedCommand != null && playerKey != null && playerKey.getKeyRevision()
|
|
||||||
.compareTo(IdentifiedKey.Revision.LINKED_V2) >= 0) {
|
|
||||||
logger.fatal("A plugin tried to change a command with signed component(s). "
|
|
||||||
+ "This is not supported. "
|
|
||||||
+ "Disconnecting player " + player.getUsername());
|
|
||||||
player.disconnect(Component.text("A proxy plugin caused an illegal protocol state. "
|
|
||||||
+ "Contact your network administrator."));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
write.message("/" + commandToRun);
|
|
||||||
}
|
|
||||||
return write.toServer();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleCommandForward() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Immediately send any queued messages to the server.
|
* Immediately send any queued messages to the server.
|
||||||
*/
|
*/
|
||||||
|
@ -65,11 +65,14 @@ import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter;
|
|||||||
import com.velocitypowered.proxy.protocol.packet.KeepAlive;
|
import com.velocitypowered.proxy.protocol.packet.KeepAlive;
|
||||||
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
||||||
import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest;
|
import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest;
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.ChatBuilder;
|
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.ChatQueue;
|
import com.velocitypowered.proxy.protocol.packet.chat.ChatQueue;
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.LegacyChat;
|
import com.velocitypowered.proxy.protocol.packet.chat.ChatType;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.chat.builder.ChatBuilderFactory;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyChat;
|
||||||
import com.velocitypowered.proxy.protocol.packet.title.GenericTitlePacket;
|
import com.velocitypowered.proxy.protocol.packet.title.GenericTitlePacket;
|
||||||
import com.velocitypowered.proxy.server.VelocityRegisteredServer;
|
import com.velocitypowered.proxy.server.VelocityRegisteredServer;
|
||||||
|
import com.velocitypowered.proxy.tablist.InternalTabList;
|
||||||
|
import com.velocitypowered.proxy.tablist.KeyedVelocityTabList;
|
||||||
import com.velocitypowered.proxy.tablist.VelocityTabList;
|
import com.velocitypowered.proxy.tablist.VelocityTabList;
|
||||||
import com.velocitypowered.proxy.tablist.VelocityTabListLegacy;
|
import com.velocitypowered.proxy.tablist.VelocityTabListLegacy;
|
||||||
import com.velocitypowered.proxy.util.ClosestLocaleMatcher;
|
import com.velocitypowered.proxy.util.ClosestLocaleMatcher;
|
||||||
@ -145,7 +148,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
|||||||
private @Nullable ModInfo modInfo;
|
private @Nullable ModInfo modInfo;
|
||||||
private Component playerListHeader = Component.empty();
|
private Component playerListHeader = Component.empty();
|
||||||
private Component playerListFooter = Component.empty();
|
private Component playerListFooter = Component.empty();
|
||||||
private final VelocityTabList tabList;
|
private final InternalTabList tabList;
|
||||||
private final VelocityServer server;
|
private final VelocityServer server;
|
||||||
private ClientConnectionPhase connectionPhase;
|
private ClientConnectionPhase connectionPhase;
|
||||||
private final Collection<String> knownChannels;
|
private final Collection<String> knownChannels;
|
||||||
@ -166,7 +169,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
|||||||
private @Nullable String clientBrand;
|
private @Nullable String clientBrand;
|
||||||
private @Nullable Locale effectiveLocale;
|
private @Nullable Locale effectiveLocale;
|
||||||
private @Nullable IdentifiedKey playerKey;
|
private @Nullable IdentifiedKey playerKey;
|
||||||
private ChatQueue chatQueue;
|
private final ChatQueue chatQueue;
|
||||||
|
private final ChatBuilderFactory chatBuilderFactory;
|
||||||
|
|
||||||
ConnectedPlayer(VelocityServer server, GameProfile profile, MinecraftConnection connection,
|
ConnectedPlayer(VelocityServer server, GameProfile profile, MinecraftConnection connection,
|
||||||
@Nullable InetSocketAddress virtualHost, boolean onlineMode, @Nullable IdentifiedKey playerKey) {
|
@Nullable InetSocketAddress virtualHost, boolean onlineMode, @Nullable IdentifiedKey playerKey) {
|
||||||
@ -179,16 +183,23 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
|||||||
this.knownChannels = CappedSet.create(MAX_PLUGIN_CHANNELS);
|
this.knownChannels = CappedSet.create(MAX_PLUGIN_CHANNELS);
|
||||||
this.onlineMode = onlineMode;
|
this.onlineMode = onlineMode;
|
||||||
|
|
||||||
if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
|
if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19_3) >= 0) {
|
||||||
this.tabList = new VelocityTabList(this, server);
|
this.tabList = new VelocityTabList(this);
|
||||||
|
} else if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
|
||||||
|
this.tabList = new KeyedVelocityTabList(this, server);
|
||||||
} else {
|
} else {
|
||||||
this.tabList = new VelocityTabListLegacy(this, server);
|
this.tabList = new VelocityTabListLegacy(this, server);
|
||||||
}
|
}
|
||||||
this.playerKey = playerKey;
|
this.playerKey = playerKey;
|
||||||
this.chatQueue = new ChatQueue(this);
|
this.chatQueue = new ChatQueue(this);
|
||||||
|
this.chatBuilderFactory = new ChatBuilderFactory(this.getProtocolVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
ChatQueue getChatQueue() {
|
public ChatBuilderFactory getChatBuilderFactory() {
|
||||||
|
return chatBuilderFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChatQueue getChatQueue() {
|
||||||
return chatQueue;
|
return chatQueue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -327,7 +338,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
|||||||
public void sendMessage(@NonNull Identity identity, @NonNull Component message) {
|
public void sendMessage(@NonNull Identity identity, @NonNull Component message) {
|
||||||
Component translated = translateMessage(message);
|
Component translated = translateMessage(message);
|
||||||
|
|
||||||
connection.write(ChatBuilder.builder(this.getProtocolVersion())
|
connection.write(getChatBuilderFactory().builder()
|
||||||
.component(translated).forIdentity(identity).toClient());
|
.component(translated).forIdentity(identity).toClient());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -339,9 +350,9 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
|||||||
|
|
||||||
Component translated = translateMessage(message);
|
Component translated = translateMessage(message);
|
||||||
|
|
||||||
connection.write(ChatBuilder.builder(this.getProtocolVersion())
|
connection.write(getChatBuilderFactory().builder()
|
||||||
.component(translated).forIdentity(identity)
|
.component(translated).forIdentity(identity)
|
||||||
.setType(type == MessageType.CHAT ? ChatBuilder.ChatType.CHAT : ChatBuilder.ChatType.SYSTEM)
|
.setType(type == MessageType.CHAT ? ChatType.CHAT : ChatType.SYSTEM)
|
||||||
.toClient());
|
.toClient());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -525,7 +536,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public VelocityTabList getTabList() {
|
public InternalTabList getTabList() {
|
||||||
return tabList;
|
return tabList;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -913,13 +924,13 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
|||||||
"input cannot be greater than " + LegacyChat.MAX_SERVERBOUND_MESSAGE_LENGTH
|
"input cannot be greater than " + LegacyChat.MAX_SERVERBOUND_MESSAGE_LENGTH
|
||||||
+ " characters in length");
|
+ " characters in length");
|
||||||
if (getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
|
if (getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
|
||||||
this.chatQueue.hijack(ChatBuilder.builder(getProtocolVersion()).asPlayer(this).message(input),
|
this.chatQueue.hijack(getChatBuilderFactory().builder().asPlayer(this).message(input),
|
||||||
(instant, item) -> {
|
(instant, item) -> {
|
||||||
item.timestamp(instant);
|
item.setTimestamp(instant);
|
||||||
return item.toServer();
|
return item.toServer();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
ensureBackendConnection().write(ChatBuilder.builder(getProtocolVersion())
|
ensureBackendConnection().write(getChatBuilderFactory().builder()
|
||||||
.asPlayer(this).message(input).toServer());
|
.asPlayer(this).message(input).toServer());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,15 +25,11 @@ import static com.velocitypowered.proxy.crypto.EncryptionUtils.generateServerId;
|
|||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.primitives.Longs;
|
import com.google.common.primitives.Longs;
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import com.google.gson.JsonParser;
|
|
||||||
import com.google.gson.stream.JsonReader;
|
|
||||||
import com.velocitypowered.api.event.connection.PreLoginEvent;
|
import com.velocitypowered.api.event.connection.PreLoginEvent;
|
||||||
import com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult;
|
import com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult;
|
||||||
import com.velocitypowered.api.network.ProtocolVersion;
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
|
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
|
||||||
import com.velocitypowered.api.util.GameProfile;
|
import com.velocitypowered.api.util.GameProfile;
|
||||||
import com.velocitypowered.api.util.UuidUtils;
|
|
||||||
import com.velocitypowered.proxy.VelocityServer;
|
import com.velocitypowered.proxy.VelocityServer;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||||
@ -50,7 +46,6 @@ import java.security.KeyPair;
|
|||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
@ -60,7 +55,6 @@ import org.apache.logging.log4j.Logger;
|
|||||||
import org.asynchttpclient.ListenableFuture;
|
import org.asynchttpclient.ListenableFuture;
|
||||||
import org.asynchttpclient.Response;
|
import org.asynchttpclient.Response;
|
||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
|
||||||
|
|
||||||
public class InitialLoginSessionHandler implements MinecraftSessionHandler {
|
public class InitialLoginSessionHandler implements MinecraftSessionHandler {
|
||||||
|
|
||||||
@ -111,7 +105,8 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else if (mcConnection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0
|
} else if (mcConnection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0
|
||||||
&& forceKeyAuthentication) {
|
&& forceKeyAuthentication
|
||||||
|
&& mcConnection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19_3) < 0) {
|
||||||
inbound.disconnect(Component.translatable("multiplayer.disconnect.missing_public_key"));
|
inbound.disconnect(Component.translatable("multiplayer.disconnect.missing_public_key"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -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_18_2;
|
||||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19;
|
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19;
|
||||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_1;
|
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_1;
|
||||||
|
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_3;
|
||||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_7_2;
|
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_7_2;
|
||||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8;
|
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8;
|
||||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9;
|
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9;
|
||||||
@ -50,10 +51,11 @@ import com.velocitypowered.proxy.protocol.packet.Handshake;
|
|||||||
import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter;
|
import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter;
|
||||||
import com.velocitypowered.proxy.protocol.packet.JoinGame;
|
import com.velocitypowered.proxy.protocol.packet.JoinGame;
|
||||||
import com.velocitypowered.proxy.protocol.packet.KeepAlive;
|
import com.velocitypowered.proxy.protocol.packet.KeepAlive;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem;
|
||||||
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage;
|
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage;
|
||||||
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
|
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
|
||||||
import com.velocitypowered.proxy.protocol.packet.PlayerListItem;
|
|
||||||
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfo;
|
||||||
import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest;
|
import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest;
|
||||||
import com.velocitypowered.proxy.protocol.packet.ResourcePackResponse;
|
import com.velocitypowered.proxy.protocol.packet.ResourcePackResponse;
|
||||||
import com.velocitypowered.proxy.protocol.packet.Respawn;
|
import com.velocitypowered.proxy.protocol.packet.Respawn;
|
||||||
@ -66,11 +68,14 @@ import com.velocitypowered.proxy.protocol.packet.StatusRequest;
|
|||||||
import com.velocitypowered.proxy.protocol.packet.StatusResponse;
|
import com.velocitypowered.proxy.protocol.packet.StatusResponse;
|
||||||
import com.velocitypowered.proxy.protocol.packet.TabCompleteRequest;
|
import com.velocitypowered.proxy.protocol.packet.TabCompleteRequest;
|
||||||
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse;
|
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse;
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.LegacyChat;
|
import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo;
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.PlayerChat;
|
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.PlayerChatCompletion;
|
import com.velocitypowered.proxy.protocol.packet.chat.PlayerChatCompletion;
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.PlayerCommand;
|
|
||||||
import com.velocitypowered.proxy.protocol.packet.chat.SystemChat;
|
import com.velocitypowered.proxy.protocol.packet.chat.SystemChat;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedPlayerChat;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedPlayerCommand;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyChat;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerChat;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerCommand;
|
||||||
import com.velocitypowered.proxy.protocol.packet.title.LegacyTitlePacket;
|
import com.velocitypowered.proxy.protocol.packet.title.LegacyTitlePacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.title.TitleActionbarPacket;
|
import com.velocitypowered.proxy.protocol.packet.title.TitleActionbarPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.title.TitleClearPacket;
|
import com.velocitypowered.proxy.protocol.packet.title.TitleClearPacket;
|
||||||
@ -124,19 +129,24 @@ public enum StateRegistry {
|
|||||||
map(0x05, MINECRAFT_1_13, false),
|
map(0x05, MINECRAFT_1_13, false),
|
||||||
map(0x06, MINECRAFT_1_14, false),
|
map(0x06, MINECRAFT_1_14, false),
|
||||||
map(0x08, MINECRAFT_1_19, false),
|
map(0x08, MINECRAFT_1_19, false),
|
||||||
map(0x09, MINECRAFT_1_19_1, false));
|
map(0x09, MINECRAFT_1_19_1, false),
|
||||||
|
map(0x08, MINECRAFT_1_19_3, false));
|
||||||
serverbound.register(LegacyChat.class, LegacyChat::new,
|
serverbound.register(LegacyChat.class, LegacyChat::new,
|
||||||
map(0x01, MINECRAFT_1_7_2, false),
|
map(0x01, MINECRAFT_1_7_2, false),
|
||||||
map(0x02, MINECRAFT_1_9, false),
|
map(0x02, MINECRAFT_1_9, false),
|
||||||
map(0x03, MINECRAFT_1_12, false),
|
map(0x03, MINECRAFT_1_12, false),
|
||||||
map(0x02, MINECRAFT_1_12_1, false),
|
map(0x02, MINECRAFT_1_12_1, false),
|
||||||
map(0x03, MINECRAFT_1_14, MINECRAFT_1_18_2, false));
|
map(0x03, MINECRAFT_1_14, MINECRAFT_1_18_2, false));
|
||||||
serverbound.register(PlayerCommand.class, PlayerCommand::new,
|
serverbound.register(KeyedPlayerCommand.class, KeyedPlayerCommand::new,
|
||||||
map(0x03, MINECRAFT_1_19, false),
|
map(0x03, MINECRAFT_1_19, false),
|
||||||
map(0x04, MINECRAFT_1_19_1, false));
|
map(0x04, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false));
|
||||||
serverbound.register(PlayerChat.class, PlayerChat::new,
|
serverbound.register(KeyedPlayerChat.class, KeyedPlayerChat::new,
|
||||||
map(0x04, MINECRAFT_1_19, false),
|
map(0x04, MINECRAFT_1_19, false),
|
||||||
map(0x05, MINECRAFT_1_19_1, false));
|
map(0x05, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false));
|
||||||
|
serverbound.register(SessionPlayerCommand.class, SessionPlayerCommand::new,
|
||||||
|
map(0x04, MINECRAFT_1_19_3, false));
|
||||||
|
serverbound.register(SessionPlayerChat.class, SessionPlayerChat::new,
|
||||||
|
map(0x05, MINECRAFT_1_19_3, false));
|
||||||
serverbound.register(ClientSettings.class, ClientSettings::new,
|
serverbound.register(ClientSettings.class, ClientSettings::new,
|
||||||
map(0x15, MINECRAFT_1_7_2, false),
|
map(0x15, MINECRAFT_1_7_2, false),
|
||||||
map(0x04, MINECRAFT_1_9, false),
|
map(0x04, MINECRAFT_1_9, false),
|
||||||
@ -144,7 +154,8 @@ public enum StateRegistry {
|
|||||||
map(0x04, MINECRAFT_1_12_1, false),
|
map(0x04, MINECRAFT_1_12_1, false),
|
||||||
map(0x05, MINECRAFT_1_14, false),
|
map(0x05, MINECRAFT_1_14, false),
|
||||||
map(0x07, MINECRAFT_1_19, false),
|
map(0x07, MINECRAFT_1_19, false),
|
||||||
map(0x08, MINECRAFT_1_19_1, false));
|
map(0x08, MINECRAFT_1_19_1, false),
|
||||||
|
map(0x07, MINECRAFT_1_19_3, false));
|
||||||
serverbound.register(PluginMessage.class, PluginMessage::new,
|
serverbound.register(PluginMessage.class, PluginMessage::new,
|
||||||
map(0x17, MINECRAFT_1_7_2, false),
|
map(0x17, MINECRAFT_1_7_2, false),
|
||||||
map(0x09, MINECRAFT_1_9, false),
|
map(0x09, MINECRAFT_1_9, false),
|
||||||
@ -154,7 +165,8 @@ public enum StateRegistry {
|
|||||||
map(0x0B, MINECRAFT_1_14, false),
|
map(0x0B, MINECRAFT_1_14, false),
|
||||||
map(0x0A, MINECRAFT_1_17, false),
|
map(0x0A, MINECRAFT_1_17, false),
|
||||||
map(0x0C, MINECRAFT_1_19, false),
|
map(0x0C, MINECRAFT_1_19, false),
|
||||||
map(0x0D, MINECRAFT_1_19_1, false));
|
map(0x0D, MINECRAFT_1_19_1, false),
|
||||||
|
map(0x0C, MINECRAFT_1_19_3, false));
|
||||||
serverbound.register(KeepAlive.class, KeepAlive::new,
|
serverbound.register(KeepAlive.class, KeepAlive::new,
|
||||||
map(0x00, MINECRAFT_1_7_2, false),
|
map(0x00, MINECRAFT_1_7_2, false),
|
||||||
map(0x0B, MINECRAFT_1_9, false),
|
map(0x0B, MINECRAFT_1_9, false),
|
||||||
@ -165,7 +177,8 @@ public enum StateRegistry {
|
|||||||
map(0x10, MINECRAFT_1_16, false),
|
map(0x10, MINECRAFT_1_16, false),
|
||||||
map(0x0F, MINECRAFT_1_17, false),
|
map(0x0F, MINECRAFT_1_17, false),
|
||||||
map(0x11, MINECRAFT_1_19, false),
|
map(0x11, MINECRAFT_1_19, false),
|
||||||
map(0x12, MINECRAFT_1_19_1, false));
|
map(0x12, MINECRAFT_1_19_1, false),
|
||||||
|
map(0x11, MINECRAFT_1_19_3, false));
|
||||||
serverbound.register(ResourcePackResponse.class, ResourcePackResponse::new,
|
serverbound.register(ResourcePackResponse.class, ResourcePackResponse::new,
|
||||||
map(0x19, MINECRAFT_1_8, false),
|
map(0x19, MINECRAFT_1_8, false),
|
||||||
map(0x16, MINECRAFT_1_9, false),
|
map(0x16, MINECRAFT_1_9, false),
|
||||||
@ -198,14 +211,16 @@ public enum StateRegistry {
|
|||||||
map(0x10, MINECRAFT_1_16, false),
|
map(0x10, MINECRAFT_1_16, false),
|
||||||
map(0x0F, MINECRAFT_1_16_2, false),
|
map(0x0F, MINECRAFT_1_16_2, false),
|
||||||
map(0x11, MINECRAFT_1_17, false),
|
map(0x11, MINECRAFT_1_17, false),
|
||||||
map(0x0E, MINECRAFT_1_19, false));
|
map(0x0E, MINECRAFT_1_19, false),
|
||||||
|
map(0x0D, MINECRAFT_1_19_3, false));
|
||||||
clientbound.register(AvailableCommands.class, AvailableCommands::new,
|
clientbound.register(AvailableCommands.class, AvailableCommands::new,
|
||||||
map(0x11, MINECRAFT_1_13, false),
|
map(0x11, MINECRAFT_1_13, false),
|
||||||
map(0x12, MINECRAFT_1_15, false),
|
map(0x12, MINECRAFT_1_15, false),
|
||||||
map(0x11, MINECRAFT_1_16, false),
|
map(0x11, MINECRAFT_1_16, false),
|
||||||
map(0x10, MINECRAFT_1_16_2, false),
|
map(0x10, MINECRAFT_1_16_2, false),
|
||||||
map(0x12, MINECRAFT_1_17, false),
|
map(0x12, MINECRAFT_1_17, false),
|
||||||
map(0x0F, MINECRAFT_1_19, false));
|
map(0x0F, MINECRAFT_1_19, false),
|
||||||
|
map(0x0E, MINECRAFT_1_19_3, false));
|
||||||
clientbound.register(PluginMessage.class, PluginMessage::new,
|
clientbound.register(PluginMessage.class, PluginMessage::new,
|
||||||
map(0x3F, MINECRAFT_1_7_2, false),
|
map(0x3F, MINECRAFT_1_7_2, false),
|
||||||
map(0x18, MINECRAFT_1_9, false),
|
map(0x18, MINECRAFT_1_9, false),
|
||||||
@ -216,7 +231,8 @@ public enum StateRegistry {
|
|||||||
map(0x17, MINECRAFT_1_16_2, false),
|
map(0x17, MINECRAFT_1_16_2, false),
|
||||||
map(0x18, MINECRAFT_1_17, false),
|
map(0x18, MINECRAFT_1_17, false),
|
||||||
map(0x15, MINECRAFT_1_19, false),
|
map(0x15, MINECRAFT_1_19, false),
|
||||||
map(0x16, MINECRAFT_1_19_1, false));
|
map(0x16, MINECRAFT_1_19_1, false),
|
||||||
|
map(0x15, MINECRAFT_1_19_3, false));
|
||||||
clientbound.register(Disconnect.class, Disconnect::new,
|
clientbound.register(Disconnect.class, Disconnect::new,
|
||||||
map(0x40, MINECRAFT_1_7_2, false),
|
map(0x40, MINECRAFT_1_7_2, false),
|
||||||
map(0x1A, MINECRAFT_1_9, false),
|
map(0x1A, MINECRAFT_1_9, false),
|
||||||
@ -227,7 +243,8 @@ public enum StateRegistry {
|
|||||||
map(0x19, MINECRAFT_1_16_2, false),
|
map(0x19, MINECRAFT_1_16_2, false),
|
||||||
map(0x1A, MINECRAFT_1_17, false),
|
map(0x1A, MINECRAFT_1_17, false),
|
||||||
map(0x17, MINECRAFT_1_19, false),
|
map(0x17, MINECRAFT_1_19, false),
|
||||||
map(0x19, MINECRAFT_1_19_1, false));
|
map(0x19, MINECRAFT_1_19_1, false),
|
||||||
|
map(0x17, MINECRAFT_1_19_3, false));
|
||||||
clientbound.register(KeepAlive.class, KeepAlive::new,
|
clientbound.register(KeepAlive.class, KeepAlive::new,
|
||||||
map(0x00, MINECRAFT_1_7_2, false),
|
map(0x00, MINECRAFT_1_7_2, false),
|
||||||
map(0x1F, MINECRAFT_1_9, false),
|
map(0x1F, MINECRAFT_1_9, false),
|
||||||
@ -238,7 +255,8 @@ public enum StateRegistry {
|
|||||||
map(0x1F, MINECRAFT_1_16_2, false),
|
map(0x1F, MINECRAFT_1_16_2, false),
|
||||||
map(0x21, MINECRAFT_1_17, false),
|
map(0x21, MINECRAFT_1_17, false),
|
||||||
map(0x1E, MINECRAFT_1_19, false),
|
map(0x1E, MINECRAFT_1_19, false),
|
||||||
map(0x20, MINECRAFT_1_19_1, false));
|
map(0x20, MINECRAFT_1_19_1, false),
|
||||||
|
map(0x1F, MINECRAFT_1_19_3, false));
|
||||||
clientbound.register(JoinGame.class, JoinGame::new,
|
clientbound.register(JoinGame.class, JoinGame::new,
|
||||||
map(0x01, MINECRAFT_1_7_2, false),
|
map(0x01, MINECRAFT_1_7_2, false),
|
||||||
map(0x23, MINECRAFT_1_9, false),
|
map(0x23, MINECRAFT_1_9, false),
|
||||||
@ -249,7 +267,8 @@ public enum StateRegistry {
|
|||||||
map(0x24, MINECRAFT_1_16_2, false),
|
map(0x24, MINECRAFT_1_16_2, false),
|
||||||
map(0x26, MINECRAFT_1_17, false),
|
map(0x26, MINECRAFT_1_17, false),
|
||||||
map(0x23, MINECRAFT_1_19, false),
|
map(0x23, MINECRAFT_1_19, false),
|
||||||
map(0x25, MINECRAFT_1_19_1, false));
|
map(0x25, MINECRAFT_1_19_1, false),
|
||||||
|
map(0x24, MINECRAFT_1_19_3, false));
|
||||||
clientbound.register(Respawn.class, Respawn::new,
|
clientbound.register(Respawn.class, Respawn::new,
|
||||||
map(0x07, MINECRAFT_1_7_2, true),
|
map(0x07, MINECRAFT_1_7_2, true),
|
||||||
map(0x33, MINECRAFT_1_9, true),
|
map(0x33, MINECRAFT_1_9, true),
|
||||||
@ -262,7 +281,8 @@ public enum StateRegistry {
|
|||||||
map(0x39, MINECRAFT_1_16_2, true),
|
map(0x39, MINECRAFT_1_16_2, true),
|
||||||
map(0x3D, MINECRAFT_1_17, true),
|
map(0x3D, MINECRAFT_1_17, true),
|
||||||
map(0x3B, MINECRAFT_1_19, true),
|
map(0x3B, MINECRAFT_1_19, true),
|
||||||
map(0x3E, MINECRAFT_1_19_1, true));
|
map(0x3E, MINECRAFT_1_19_1, true),
|
||||||
|
map(0x3D, MINECRAFT_1_19_3, true));
|
||||||
clientbound.register(ResourcePackRequest.class, ResourcePackRequest::new,
|
clientbound.register(ResourcePackRequest.class, ResourcePackRequest::new,
|
||||||
map(0x48, MINECRAFT_1_8, false),
|
map(0x48, MINECRAFT_1_8, false),
|
||||||
map(0x32, MINECRAFT_1_9, false),
|
map(0x32, MINECRAFT_1_9, false),
|
||||||
@ -275,7 +295,8 @@ public enum StateRegistry {
|
|||||||
map(0x38, MINECRAFT_1_16_2, false),
|
map(0x38, MINECRAFT_1_16_2, false),
|
||||||
map(0x3C, MINECRAFT_1_17, false),
|
map(0x3C, MINECRAFT_1_17, false),
|
||||||
map(0x3A, MINECRAFT_1_19, false),
|
map(0x3A, MINECRAFT_1_19, false),
|
||||||
map(0x3D, MINECRAFT_1_19_1, false));
|
map(0x3D, MINECRAFT_1_19_1, false),
|
||||||
|
map(0x3C, MINECRAFT_1_19_3, false));
|
||||||
clientbound.register(HeaderAndFooter.class, HeaderAndFooter::new,
|
clientbound.register(HeaderAndFooter.class, HeaderAndFooter::new,
|
||||||
map(0x47, MINECRAFT_1_8, true),
|
map(0x47, MINECRAFT_1_8, true),
|
||||||
map(0x48, MINECRAFT_1_9, true),
|
map(0x48, MINECRAFT_1_9, true),
|
||||||
@ -289,7 +310,8 @@ public enum StateRegistry {
|
|||||||
map(0x5E, MINECRAFT_1_17, true),
|
map(0x5E, MINECRAFT_1_17, true),
|
||||||
map(0x5F, MINECRAFT_1_18, true),
|
map(0x5F, MINECRAFT_1_18, true),
|
||||||
map(0x60, MINECRAFT_1_19, true),
|
map(0x60, MINECRAFT_1_19, true),
|
||||||
map(0x63, MINECRAFT_1_19_1, true));
|
map(0x63, MINECRAFT_1_19_1, true),
|
||||||
|
map(0x61, MINECRAFT_1_19_3, true));
|
||||||
clientbound.register(LegacyTitlePacket.class, LegacyTitlePacket::new,
|
clientbound.register(LegacyTitlePacket.class, LegacyTitlePacket::new,
|
||||||
map(0x45, MINECRAFT_1_8, true),
|
map(0x45, MINECRAFT_1_8, true),
|
||||||
map(0x45, MINECRAFT_1_9, true),
|
map(0x45, MINECRAFT_1_9, true),
|
||||||
@ -302,23 +324,28 @@ public enum StateRegistry {
|
|||||||
clientbound.register(TitleSubtitlePacket.class, TitleSubtitlePacket::new,
|
clientbound.register(TitleSubtitlePacket.class, TitleSubtitlePacket::new,
|
||||||
map(0x57, MINECRAFT_1_17, true),
|
map(0x57, MINECRAFT_1_17, true),
|
||||||
map(0x58, MINECRAFT_1_18, true),
|
map(0x58, MINECRAFT_1_18, true),
|
||||||
map(0x5B, MINECRAFT_1_19_1, true));
|
map(0x5B, MINECRAFT_1_19_1, true),
|
||||||
|
map(0x59, MINECRAFT_1_19_3, true));
|
||||||
clientbound.register(TitleTextPacket.class, TitleTextPacket::new,
|
clientbound.register(TitleTextPacket.class, TitleTextPacket::new,
|
||||||
map(0x59, MINECRAFT_1_17, true),
|
map(0x59, MINECRAFT_1_17, true),
|
||||||
map(0x5A, MINECRAFT_1_18, true),
|
map(0x5A, MINECRAFT_1_18, true),
|
||||||
map(0x5D, MINECRAFT_1_19_1, true));
|
map(0x5D, MINECRAFT_1_19_1, true),
|
||||||
|
map(0x5B, MINECRAFT_1_19_3, true));
|
||||||
clientbound.register(TitleActionbarPacket.class, TitleActionbarPacket::new,
|
clientbound.register(TitleActionbarPacket.class, TitleActionbarPacket::new,
|
||||||
map(0x41, MINECRAFT_1_17, true),
|
map(0x41, MINECRAFT_1_17, true),
|
||||||
map(0x40, MINECRAFT_1_19, true),
|
map(0x40, MINECRAFT_1_19, true),
|
||||||
map(0x43, MINECRAFT_1_19_1, true));
|
map(0x43, MINECRAFT_1_19_1, true),
|
||||||
|
map(0x42, MINECRAFT_1_19_3, true));
|
||||||
clientbound.register(TitleTimesPacket.class, TitleTimesPacket::new,
|
clientbound.register(TitleTimesPacket.class, TitleTimesPacket::new,
|
||||||
map(0x5A, MINECRAFT_1_17, true),
|
map(0x5A, MINECRAFT_1_17, true),
|
||||||
map(0x5B, MINECRAFT_1_18, true),
|
map(0x5B, MINECRAFT_1_18, true),
|
||||||
map(0x5E, MINECRAFT_1_19_1, true));
|
map(0x5E, MINECRAFT_1_19_1, true),
|
||||||
|
map(0x5A, MINECRAFT_1_19_3, true));
|
||||||
clientbound.register(TitleClearPacket.class, TitleClearPacket::new,
|
clientbound.register(TitleClearPacket.class, TitleClearPacket::new,
|
||||||
map(0x10, MINECRAFT_1_17, true),
|
map(0x10, MINECRAFT_1_17, true),
|
||||||
map(0x0D, MINECRAFT_1_19, true));
|
map(0x0D, MINECRAFT_1_19, true),
|
||||||
clientbound.register(PlayerListItem.class, PlayerListItem::new,
|
map(0x0C, MINECRAFT_1_19_3, true));
|
||||||
|
clientbound.register(LegacyPlayerListItem.class, LegacyPlayerListItem::new,
|
||||||
map(0x38, MINECRAFT_1_7_2, false),
|
map(0x38, MINECRAFT_1_7_2, false),
|
||||||
map(0x2D, MINECRAFT_1_9, false),
|
map(0x2D, MINECRAFT_1_9, false),
|
||||||
map(0x2E, MINECRAFT_1_12_1, false),
|
map(0x2E, MINECRAFT_1_12_1, false),
|
||||||
@ -329,15 +356,22 @@ public enum StateRegistry {
|
|||||||
map(0x32, MINECRAFT_1_16_2, false),
|
map(0x32, MINECRAFT_1_16_2, false),
|
||||||
map(0x36, MINECRAFT_1_17, false),
|
map(0x36, MINECRAFT_1_17, false),
|
||||||
map(0x34, MINECRAFT_1_19, false),
|
map(0x34, MINECRAFT_1_19, false),
|
||||||
map(0x37, MINECRAFT_1_19_1, false));
|
map(0x37, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false));
|
||||||
|
clientbound.register(RemovePlayerInfo.class, RemovePlayerInfo::new,
|
||||||
|
map(0x35, MINECRAFT_1_19_3, false));
|
||||||
|
clientbound.register(UpsertPlayerInfo.class, UpsertPlayerInfo::new,
|
||||||
|
map(0x36, MINECRAFT_1_19_3, false));
|
||||||
clientbound.register(SystemChat.class, SystemChat::new,
|
clientbound.register(SystemChat.class, SystemChat::new,
|
||||||
map(0x5F, MINECRAFT_1_19, true),
|
map(0x5F, MINECRAFT_1_19, true),
|
||||||
map(0x62, MINECRAFT_1_19_1, true));
|
map(0x62, MINECRAFT_1_19_1, true),
|
||||||
|
map(0x60, MINECRAFT_1_19_3, true));
|
||||||
clientbound.register(PlayerChatCompletion.class, PlayerChatCompletion::new,
|
clientbound.register(PlayerChatCompletion.class, PlayerChatCompletion::new,
|
||||||
StateRegistry.map(0x15, MINECRAFT_1_19_1, true));
|
map(0x15, MINECRAFT_1_19_1, true),
|
||||||
|
map(0x14, MINECRAFT_1_19_3, true));
|
||||||
clientbound.register(ServerData.class, ServerData::new,
|
clientbound.register(ServerData.class, ServerData::new,
|
||||||
map(0x3F, MINECRAFT_1_19, false),
|
map(0x3F, MINECRAFT_1_19, false),
|
||||||
map(0x42, MINECRAFT_1_19_1, false));
|
map(0x42, MINECRAFT_1_19_1, false),
|
||||||
|
map(0x41, MINECRAFT_1_19_3, false));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
LOGIN {
|
LOGIN {
|
||||||
|
@ -67,6 +67,7 @@ public class EncryptionResponse implements MinecraftPacket {
|
|||||||
this.sharedSecret = ProtocolUtils.readByteArray(buf, 128);
|
this.sharedSecret = ProtocolUtils.readByteArray(buf, 128);
|
||||||
|
|
||||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0
|
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0
|
||||||
|
&& version.compareTo(ProtocolVersion.MINECRAFT_1_19_3) < 0
|
||||||
&& !buf.readBoolean()) {
|
&& !buf.readBoolean()) {
|
||||||
salt = buf.readLong();
|
salt = buf.readLong();
|
||||||
}
|
}
|
||||||
@ -83,7 +84,8 @@ public class EncryptionResponse implements MinecraftPacket {
|
|||||||
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
|
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
|
||||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
|
if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
|
||||||
ProtocolUtils.writeByteArray(buf, sharedSecret);
|
ProtocolUtils.writeByteArray(buf, sharedSecret);
|
||||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
|
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0
|
||||||
|
&& version.compareTo(ProtocolVersion.MINECRAFT_1_19_3) < 0) {
|
||||||
if (salt != null) {
|
if (salt != null) {
|
||||||
buf.writeBoolean(false);
|
buf.writeBoolean(false);
|
||||||
buf.writeLong(salt);
|
buf.writeLong(salt);
|
||||||
@ -108,6 +110,9 @@ public class EncryptionResponse implements MinecraftPacket {
|
|||||||
// It turns out these come out to the same length, whether we're talking >=1.8 or not.
|
// It turns out these come out to the same length, whether we're talking >=1.8 or not.
|
||||||
// The length prefix always winds up being 2 bytes.
|
// The length prefix always winds up being 2 bytes.
|
||||||
int base = 256 + 2 + 2;
|
int base = 256 + 2 + 2;
|
||||||
|
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_3) >= 0) {
|
||||||
|
return base + 128;
|
||||||
|
}
|
||||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
|
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
|
||||||
// Verify token is twice as long on 1.19+
|
// Verify token is twice as long on 1.19+
|
||||||
// Additional 1 byte for left <> right and 8 bytes for salt
|
// Additional 1 byte for left <> right and 8 bytes for salt
|
||||||
|
@ -33,7 +33,7 @@ import net.kyori.adventure.text.Component;
|
|||||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
public class PlayerListItem implements MinecraftPacket {
|
public class LegacyPlayerListItem implements MinecraftPacket {
|
||||||
|
|
||||||
public static final int ADD_PLAYER = 0;
|
public static final int ADD_PLAYER = 0;
|
||||||
public static final int UPDATE_GAMEMODE = 1;
|
public static final int UPDATE_GAMEMODE = 1;
|
||||||
@ -43,12 +43,12 @@ public class PlayerListItem implements MinecraftPacket {
|
|||||||
private int action;
|
private int action;
|
||||||
private final List<Item> items = new ArrayList<>();
|
private final List<Item> items = new ArrayList<>();
|
||||||
|
|
||||||
public PlayerListItem(int action, List<Item> items) {
|
public LegacyPlayerListItem(int action, List<Item> items) {
|
||||||
this.action = action;
|
this.action = action;
|
||||||
this.items.addAll(items);
|
this.items.addAll(items);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PlayerListItem() {
|
public LegacyPlayerListItem() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getAction() {
|
public int getAction() {
|
@ -15,46 +15,52 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.velocitypowered.proxy.protocol.packet.chat;
|
package com.velocitypowered.proxy.protocol.packet;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
import com.velocitypowered.api.network.ProtocolVersion;
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import net.kyori.adventure.text.Component;
|
import java.util.ArrayList;
|
||||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
import java.util.Collection;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class ServerChatPreview implements MinecraftPacket {
|
public class RemovePlayerInfo implements MinecraftPacket {
|
||||||
|
private Collection<UUID> profilesToRemove;
|
||||||
|
|
||||||
private int id;
|
public RemovePlayerInfo() {
|
||||||
private @Nullable Component preview;
|
this.profilesToRemove = new ArrayList<>();
|
||||||
|
|
||||||
public Component getPreview() {
|
|
||||||
return preview;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getId() {
|
public RemovePlayerInfo(Collection<UUID> profilesToRemove) {
|
||||||
return id;
|
this.profilesToRemove = profilesToRemove;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<UUID> getProfilesToRemove() {
|
||||||
|
return profilesToRemove;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProfilesToRemove(Collection<UUID> profilesToRemove) {
|
||||||
|
this.profilesToRemove = profilesToRemove;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
|
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
|
||||||
id = buf.readInt();
|
int length = ProtocolUtils.readVarInt(buf);
|
||||||
if (buf.readBoolean()) {
|
Collection<UUID> profilesToRemove = Lists.newArrayListWithCapacity(length);
|
||||||
preview = GsonComponentSerializer.gson().deserialize(ProtocolUtils.readString(buf));
|
for (int idx = 0; idx < length; idx++) {
|
||||||
|
profilesToRemove.add(ProtocolUtils.readUuid(buf));
|
||||||
}
|
}
|
||||||
|
this.profilesToRemove = profilesToRemove;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
|
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
|
||||||
buf.writeInt(id);
|
ProtocolUtils.writeVarInt(buf, this.profilesToRemove.size());
|
||||||
if (preview != null) {
|
for (UUID uuid : this.profilesToRemove) {
|
||||||
buf.writeBoolean(true);
|
ProtocolUtils.writeUuid(buf, uuid);
|
||||||
ProtocolUtils.writeString(buf, GsonComponentSerializer.gson().serialize(preview));
|
|
||||||
} else {
|
|
||||||
buf.writeBoolean(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -36,7 +36,7 @@ public class Respawn implements MinecraftPacket {
|
|||||||
private short difficulty;
|
private short difficulty;
|
||||||
private short gamemode;
|
private short gamemode;
|
||||||
private String levelType = "";
|
private String levelType = "";
|
||||||
private boolean shouldKeepPlayerData; // 1.16+
|
private byte dataToKeep; // 1.16+
|
||||||
private DimensionInfo dimensionInfo; // 1.16-1.16.1
|
private DimensionInfo dimensionInfo; // 1.16-1.16.1
|
||||||
private short previousGamemode; // 1.16+
|
private short previousGamemode; // 1.16+
|
||||||
private DimensionData currentDimensionData; // 1.16.2+
|
private DimensionData currentDimensionData; // 1.16.2+
|
||||||
@ -46,14 +46,15 @@ public class Respawn implements MinecraftPacket {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Respawn(int dimension, long partialHashedSeed, short difficulty, short gamemode,
|
public Respawn(int dimension, long partialHashedSeed, short difficulty, short gamemode,
|
||||||
String levelType, boolean shouldKeepPlayerData, DimensionInfo dimensionInfo,
|
String levelType, byte dataToKeep, DimensionInfo dimensionInfo,
|
||||||
short previousGamemode, DimensionData currentDimensionData, @Nullable Pair<String, Long> lastDeathPosition) {
|
short previousGamemode, DimensionData currentDimensionData,
|
||||||
|
@Nullable Pair<String, Long> lastDeathPosition) {
|
||||||
this.dimension = dimension;
|
this.dimension = dimension;
|
||||||
this.partialHashedSeed = partialHashedSeed;
|
this.partialHashedSeed = partialHashedSeed;
|
||||||
this.difficulty = difficulty;
|
this.difficulty = difficulty;
|
||||||
this.gamemode = gamemode;
|
this.gamemode = gamemode;
|
||||||
this.levelType = levelType;
|
this.levelType = levelType;
|
||||||
this.shouldKeepPlayerData = shouldKeepPlayerData;
|
this.dataToKeep = dataToKeep;
|
||||||
this.dimensionInfo = dimensionInfo;
|
this.dimensionInfo = dimensionInfo;
|
||||||
this.previousGamemode = previousGamemode;
|
this.previousGamemode = previousGamemode;
|
||||||
this.currentDimensionData = currentDimensionData;
|
this.currentDimensionData = currentDimensionData;
|
||||||
@ -63,7 +64,7 @@ public class Respawn implements MinecraftPacket {
|
|||||||
public static Respawn fromJoinGame(JoinGame joinGame) {
|
public static Respawn fromJoinGame(JoinGame joinGame) {
|
||||||
return new Respawn(joinGame.getDimension(), joinGame.getPartialHashedSeed(),
|
return new Respawn(joinGame.getDimension(), joinGame.getPartialHashedSeed(),
|
||||||
joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(),
|
joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(),
|
||||||
false, joinGame.getDimensionInfo(), joinGame.getPreviousGamemode(),
|
(byte) 0, joinGame.getDimensionInfo(), joinGame.getPreviousGamemode(),
|
||||||
joinGame.getCurrentDimensionData(), joinGame.getLastDeathPosition());
|
joinGame.getCurrentDimensionData(), joinGame.getLastDeathPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,12 +108,12 @@ public class Respawn implements MinecraftPacket {
|
|||||||
this.levelType = levelType;
|
this.levelType = levelType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getShouldKeepPlayerData() {
|
public byte getDataToKeep() {
|
||||||
return shouldKeepPlayerData;
|
return dataToKeep;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setShouldKeepPlayerData(boolean shouldKeepPlayerData) {
|
public void setDataToKeep(byte dataToKeep) {
|
||||||
this.shouldKeepPlayerData = shouldKeepPlayerData;
|
this.dataToKeep = dataToKeep;
|
||||||
}
|
}
|
||||||
|
|
||||||
public short getPreviousGamemode() {
|
public short getPreviousGamemode() {
|
||||||
@ -139,7 +140,7 @@ public class Respawn implements MinecraftPacket {
|
|||||||
+ ", difficulty=" + difficulty
|
+ ", difficulty=" + difficulty
|
||||||
+ ", gamemode=" + gamemode
|
+ ", gamemode=" + gamemode
|
||||||
+ ", levelType='" + levelType + '\''
|
+ ", levelType='" + levelType + '\''
|
||||||
+ ", shouldKeepPlayerData=" + shouldKeepPlayerData
|
+ ", dataToKeep=" + dataToKeep
|
||||||
+ ", dimensionRegistryName='" + dimensionInfo.toString() + '\''
|
+ ", dimensionRegistryName='" + dimensionInfo.toString() + '\''
|
||||||
+ ", dimensionInfo=" + dimensionInfo
|
+ ", dimensionInfo=" + dimensionInfo
|
||||||
+ ", previousGamemode=" + previousGamemode
|
+ ", previousGamemode=" + previousGamemode
|
||||||
@ -177,7 +178,13 @@ public class Respawn implements MinecraftPacket {
|
|||||||
boolean isDebug = buf.readBoolean();
|
boolean isDebug = buf.readBoolean();
|
||||||
boolean isFlat = buf.readBoolean();
|
boolean isFlat = buf.readBoolean();
|
||||||
this.dimensionInfo = new DimensionInfo(dimensionIdentifier, levelName, isFlat, isDebug);
|
this.dimensionInfo = new DimensionInfo(dimensionIdentifier, levelName, isFlat, isDebug);
|
||||||
this.shouldKeepPlayerData = buf.readBoolean();
|
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_3) >= 0) {
|
||||||
|
this.dataToKeep = buf.readByte();
|
||||||
|
} else if (buf.readBoolean()) {
|
||||||
|
this.dataToKeep = 1;
|
||||||
|
} else {
|
||||||
|
this.dataToKeep = 0;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.levelType = ProtocolUtils.readString(buf, 16);
|
this.levelType = ProtocolUtils.readString(buf, 16);
|
||||||
}
|
}
|
||||||
@ -211,7 +218,11 @@ public class Respawn implements MinecraftPacket {
|
|||||||
buf.writeByte(previousGamemode);
|
buf.writeByte(previousGamemode);
|
||||||
buf.writeBoolean(dimensionInfo.isDebugType());
|
buf.writeBoolean(dimensionInfo.isDebugType());
|
||||||
buf.writeBoolean(dimensionInfo.isFlat());
|
buf.writeBoolean(dimensionInfo.isFlat());
|
||||||
buf.writeBoolean(shouldKeepPlayerData);
|
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_3) >= 0) {
|
||||||
|
buf.writeByte(dataToKeep);
|
||||||
|
} else {
|
||||||
|
buf.writeBoolean(dataToKeep != 0);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ProtocolUtils.writeString(buf, levelType);
|
ProtocolUtils.writeString(buf, levelType);
|
||||||
}
|
}
|
||||||
|
@ -30,17 +30,15 @@ public class ServerData implements MinecraftPacket {
|
|||||||
|
|
||||||
private @Nullable Component description;
|
private @Nullable Component description;
|
||||||
private @Nullable Favicon favicon;
|
private @Nullable Favicon favicon;
|
||||||
private boolean previewsChat;
|
|
||||||
private boolean secureChatEnforced; // Added in 1.19.1
|
private boolean secureChatEnforced; // Added in 1.19.1
|
||||||
|
|
||||||
public ServerData() {
|
public ServerData() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServerData(@Nullable Component description, @Nullable Favicon favicon,
|
public ServerData(@Nullable Component description, @Nullable Favicon favicon,
|
||||||
boolean previewsChat, boolean secureChatEnforced) {
|
boolean secureChatEnforced) {
|
||||||
this.description = description;
|
this.description = description;
|
||||||
this.favicon = favicon;
|
this.favicon = favicon;
|
||||||
this.previewsChat = previewsChat;
|
|
||||||
this.secureChatEnforced = secureChatEnforced;
|
this.secureChatEnforced = secureChatEnforced;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,7 +52,9 @@ public class ServerData implements MinecraftPacket {
|
|||||||
if (buf.readBoolean()) {
|
if (buf.readBoolean()) {
|
||||||
this.favicon = new Favicon(ProtocolUtils.readString(buf));
|
this.favicon = new Favicon(ProtocolUtils.readString(buf));
|
||||||
}
|
}
|
||||||
this.previewsChat = buf.readBoolean();
|
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_3) < 0) {
|
||||||
|
buf.readBoolean();
|
||||||
|
}
|
||||||
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
|
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
|
||||||
this.secureChatEnforced = buf.readBoolean();
|
this.secureChatEnforced = buf.readBoolean();
|
||||||
}
|
}
|
||||||
@ -77,7 +77,9 @@ public class ServerData implements MinecraftPacket {
|
|||||||
ProtocolUtils.writeString(buf, favicon.getBase64Url());
|
ProtocolUtils.writeString(buf, favicon.getBase64Url());
|
||||||
}
|
}
|
||||||
|
|
||||||
buf.writeBoolean(this.previewsChat);
|
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_3) < 0) {
|
||||||
|
buf.writeBoolean(false);
|
||||||
|
}
|
||||||
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
|
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
|
||||||
buf.writeBoolean(this.secureChatEnforced);
|
buf.writeBoolean(this.secureChatEnforced);
|
||||||
}
|
}
|
||||||
@ -88,18 +90,14 @@ public class ServerData implements MinecraftPacket {
|
|||||||
return handler.handle(this);
|
return handler.handle(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Component getDescription() {
|
public @Nullable Component getDescription() {
|
||||||
return description;
|
return description;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Favicon getFavicon() {
|
public @Nullable Favicon getFavicon() {
|
||||||
return favicon;
|
return favicon;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isPreviewsChat() {
|
|
||||||
return previewsChat;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSecureChatEnforced() {
|
public boolean isSecureChatEnforced() {
|
||||||
return secureChatEnforced;
|
return secureChatEnforced;
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ public class ServerLogin implements MinecraftPacket {
|
|||||||
private static final QuietDecoderException EMPTY_USERNAME = new QuietDecoderException("Empty username!");
|
private static final QuietDecoderException EMPTY_USERNAME = new QuietDecoderException("Empty username!");
|
||||||
|
|
||||||
private @Nullable String username;
|
private @Nullable String username;
|
||||||
private @Nullable IdentifiedKey playerKey; // Introduced in 1.19
|
private @Nullable IdentifiedKey playerKey; // Introduced in 1.19.3
|
||||||
private @Nullable UUID holderUuid; // Used for key revision 2
|
private @Nullable UUID holderUuid; // Used for key revision 2
|
||||||
|
|
||||||
public ServerLogin() {
|
public ServerLogin() {
|
||||||
@ -46,6 +46,12 @@ public class ServerLogin implements MinecraftPacket {
|
|||||||
this.playerKey = playerKey;
|
this.playerKey = playerKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ServerLogin(String username, @Nullable UUID holderUuid) {
|
||||||
|
this.username = Preconditions.checkNotNull(username, "username");
|
||||||
|
this.holderUuid = holderUuid;
|
||||||
|
this.playerKey = null;
|
||||||
|
}
|
||||||
|
|
||||||
public String getUsername() {
|
public String getUsername() {
|
||||||
if (username == null) {
|
if (username == null) {
|
||||||
throw new IllegalStateException("No username found!");
|
throw new IllegalStateException("No username found!");
|
||||||
@ -53,15 +59,15 @@ public class ServerLogin implements MinecraftPacket {
|
|||||||
return username;
|
return username;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IdentifiedKey getPlayerKey() {
|
public @Nullable IdentifiedKey getPlayerKey() {
|
||||||
return playerKey;
|
return this.playerKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPlayerKey(IdentifiedKey playerKey) {
|
public void setPlayerKey(IdentifiedKey playerKey) {
|
||||||
this.playerKey = playerKey;
|
this.playerKey = playerKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UUID getHolderUuid() {
|
public @Nullable UUID getHolderUuid() {
|
||||||
return holderUuid;
|
return holderUuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,15 +80,21 @@ public class ServerLogin implements MinecraftPacket {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
|
public void decode(ByteBuf buf, Direction direction, ProtocolVersion version) {
|
||||||
username = ProtocolUtils.readString(buf, 16);
|
username = ProtocolUtils.readString(buf, 16);
|
||||||
if (username.isEmpty()) {
|
if (username.isEmpty()) {
|
||||||
throw EMPTY_USERNAME;
|
throw EMPTY_USERNAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
|
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
|
||||||
|
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_3) >= 0) {
|
||||||
|
playerKey = null;
|
||||||
|
} else {
|
||||||
if (buf.readBoolean()) {
|
if (buf.readBoolean()) {
|
||||||
playerKey = ProtocolUtils.readPlayerKey(version, buf);
|
playerKey = ProtocolUtils.readPlayerKey(version, buf);
|
||||||
|
} else {
|
||||||
|
playerKey = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
|
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
|
||||||
@ -90,6 +102,8 @@ public class ServerLogin implements MinecraftPacket {
|
|||||||
holderUuid = ProtocolUtils.readUuid(buf);
|
holderUuid = ProtocolUtils.readUuid(buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
playerKey = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,17 +115,22 @@ public class ServerLogin implements MinecraftPacket {
|
|||||||
ProtocolUtils.writeString(buf, username);
|
ProtocolUtils.writeString(buf, username);
|
||||||
|
|
||||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
|
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
|
||||||
|
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_3) < 0) {
|
||||||
if (playerKey != null) {
|
if (playerKey != null) {
|
||||||
buf.writeBoolean(true);
|
buf.writeBoolean(true);
|
||||||
ProtocolUtils.writePlayerKey(buf, playerKey);
|
ProtocolUtils.writePlayerKey(buf, playerKey);
|
||||||
} else {
|
} else {
|
||||||
buf.writeBoolean(false);
|
buf.writeBoolean(false);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
|
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
|
||||||
if (playerKey != null && playerKey.getSignatureHolder() != null) {
|
if (playerKey != null && playerKey.getSignatureHolder() != null) {
|
||||||
buf.writeBoolean(true);
|
buf.writeBoolean(true);
|
||||||
ProtocolUtils.writeUuid(buf, playerKey.getSignatureHolder());
|
ProtocolUtils.writeUuid(buf, playerKey.getSignatureHolder());
|
||||||
|
} else if (this.holderUuid != null) {
|
||||||
|
buf.writeBoolean(true);
|
||||||
|
ProtocolUtils.writeUuid(buf, this.holderUuid);
|
||||||
} else {
|
} else {
|
||||||
buf.writeBoolean(false);
|
buf.writeBoolean(false);
|
||||||
}
|
}
|
||||||
@ -126,6 +145,7 @@ public class ServerLogin implements MinecraftPacket {
|
|||||||
int base = 1 + (16 * 4);
|
int base = 1 + (16 * 4);
|
||||||
// Adjustments for Key-authentication
|
// Adjustments for Key-authentication
|
||||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
|
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
|
||||||
|
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_3) < 0) {
|
||||||
// + 1 for the boolean present/ not present
|
// + 1 for the boolean present/ not present
|
||||||
// + 8 for the long expiry
|
// + 8 for the long expiry
|
||||||
// + 2 len for varint key size
|
// + 2 len for varint key size
|
||||||
@ -133,6 +153,7 @@ public class ServerLogin implements MinecraftPacket {
|
|||||||
// + 2 len for varint signature size
|
// + 2 len for varint signature size
|
||||||
// + 512 for signature
|
// + 512 for signature
|
||||||
base += 1 + 8 + 2 + 294 + 2 + 512;
|
base += 1 + 8 + 2 + 294 + 2 + 512;
|
||||||
|
}
|
||||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
|
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
|
||||||
// +1 boolean uuid optional
|
// +1 boolean uuid optional
|
||||||
// + 2 * 8 for the long msb/lsb
|
// + 2 * 8 for the long msb/lsb
|
||||||
|
@ -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()) {
|
for (ProtocolVersion v : ProtocolVersion.values()) {
|
||||||
if (v.compareTo(current.getVersion()) >= 0) {
|
if (v.compareTo(current.getVersion()) >= 0) {
|
||||||
temp.put(v, current.getId());
|
temp.putIfAbsent(v, current.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
previous = current.getVersion();
|
previous = current.getVersion();
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
package com.velocitypowered.proxy.protocol.packet.brigadier;
|
package com.velocitypowered.proxy.protocol.packet.brigadier;
|
||||||
|
|
||||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19;
|
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19;
|
||||||
|
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_3;
|
||||||
import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.id;
|
import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.id;
|
||||||
import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.mapSet;
|
import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.mapSet;
|
||||||
import static com.velocitypowered.proxy.protocol.packet.brigadier.DoubleArgumentPropertySerializer.DOUBLE;
|
import static com.velocitypowered.proxy.protocol.packet.brigadier.DoubleArgumentPropertySerializer.DOUBLE;
|
||||||
@ -72,6 +73,7 @@ public class ArgumentPropertyRegistry {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Deserializes the {@link ArgumentType}.
|
* Deserializes the {@link ArgumentType}.
|
||||||
|
*
|
||||||
* @param buf the buffer to deserialize
|
* @param buf the buffer to deserialize
|
||||||
* @return the deserialized {@link ArgumentType}
|
* @return the deserialized {@link ArgumentType}
|
||||||
*/
|
*/
|
||||||
@ -93,6 +95,7 @@ public class ArgumentPropertyRegistry {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Serializes the {@code type} into the provided {@code buf}.
|
* Serializes the {@code type} into the provided {@code buf}.
|
||||||
|
*
|
||||||
* @param buf the buffer to serialize into
|
* @param buf the buffer to serialize into
|
||||||
* @param type the type to serialize
|
* @param type the type to serialize
|
||||||
*/
|
*/
|
||||||
@ -122,6 +125,7 @@ public class ArgumentPropertyRegistry {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes the {@link ArgumentIdentifier} to a version-specific buffer.
|
* Writes the {@link ArgumentIdentifier} to a version-specific buffer.
|
||||||
|
*
|
||||||
* @param buf the buffer to write to
|
* @param buf the buffer to write to
|
||||||
* @param identifier the identifier to write
|
* @param identifier the identifier to write
|
||||||
* @param protocolVersion the protocol version to use
|
* @param protocolVersion the protocol version to use
|
||||||
@ -141,6 +145,7 @@ public class ArgumentPropertyRegistry {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads the {@link ArgumentIdentifier} from a version-specific buffer.
|
* Reads the {@link ArgumentIdentifier} from a version-specific buffer.
|
||||||
|
*
|
||||||
* @param buf the buffer to write to
|
* @param buf the buffer to write to
|
||||||
* @param protocolVersion the protocol version to use
|
* @param protocolVersion the protocol version to use
|
||||||
* @return the identifier read from the buffer
|
* @return the identifier read from the buffer
|
||||||
@ -214,27 +219,31 @@ public class ArgumentPropertyRegistry {
|
|||||||
empty(id("minecraft:team", mapSet(MINECRAFT_1_19, 31)));
|
empty(id("minecraft:team", mapSet(MINECRAFT_1_19, 31)));
|
||||||
empty(id("minecraft:item_slot", mapSet(MINECRAFT_1_19, 32)));
|
empty(id("minecraft:item_slot", mapSet(MINECRAFT_1_19, 32)));
|
||||||
empty(id("minecraft:resource_location", mapSet(MINECRAFT_1_19, 33)));
|
empty(id("minecraft:resource_location", mapSet(MINECRAFT_1_19, 33)));
|
||||||
empty(id("minecraft:mob_effect", mapSet(MINECRAFT_1_19, 34)));
|
empty(id("minecraft:mob_effect", mapSet(MINECRAFT_1_19_3, -1), mapSet(MINECRAFT_1_19, 34)));
|
||||||
empty(id("minecraft:function", mapSet(MINECRAFT_1_19, 35)));
|
empty(id("minecraft:function", mapSet(MINECRAFT_1_19_3, 34), mapSet(MINECRAFT_1_19, 35)));
|
||||||
empty(id("minecraft:entity_anchor", mapSet(MINECRAFT_1_19, 36)));
|
empty(id("minecraft:entity_anchor", mapSet(MINECRAFT_1_19_3, 35), mapSet(MINECRAFT_1_19, 36)));
|
||||||
empty(id("minecraft:int_range", mapSet(MINECRAFT_1_19, 37)));
|
empty(id("minecraft:int_range", mapSet(MINECRAFT_1_19_3, 36), mapSet(MINECRAFT_1_19, 37)));
|
||||||
empty(id("minecraft:float_range", mapSet(MINECRAFT_1_19, 38)));
|
empty(id("minecraft:float_range", mapSet(MINECRAFT_1_19_3, 37), mapSet(MINECRAFT_1_19, 38)));
|
||||||
empty(id("minecraft:item_enchantment", mapSet(MINECRAFT_1_19, 39)));
|
empty(id("minecraft:item_enchantment", mapSet(MINECRAFT_1_19_3, -1), mapSet(MINECRAFT_1_19, 39)));
|
||||||
empty(id("minecraft:entity_summon", mapSet(MINECRAFT_1_19, 40)));
|
empty(id("minecraft:entity_summon", mapSet(MINECRAFT_1_19_3, -1), mapSet(MINECRAFT_1_19, 40)));
|
||||||
empty(id("minecraft:dimension", mapSet(MINECRAFT_1_19, 41)));
|
empty(id("minecraft:dimension", mapSet(MINECRAFT_1_19_3, 38), mapSet(MINECRAFT_1_19, 41)));
|
||||||
empty(id("minecraft:time", mapSet(MINECRAFT_1_19, 42))); // added in 1.14
|
empty(id("minecraft:gamemode", mapSet(MINECRAFT_1_19_3, 39))); // 1.19.3
|
||||||
|
empty(id("minecraft:time", mapSet(MINECRAFT_1_19_3, 40), mapSet(MINECRAFT_1_19, 42))); // added in 1.14
|
||||||
|
|
||||||
register(id("minecraft:resource_or_tag", mapSet(MINECRAFT_1_19, 43)),
|
register(id("minecraft:resource_or_tag", mapSet(MINECRAFT_1_19_3, 41), mapSet(MINECRAFT_1_19, 43)),
|
||||||
RegistryKeyArgument.class, RegistryKeyArgumentSerializer.REGISTRY);
|
RegistryKeyArgument.class, RegistryKeyArgumentSerializer.REGISTRY);
|
||||||
register(id("minecraft:resource", mapSet(MINECRAFT_1_19, 44)),
|
register(id("minecraft:resource_or_tag_key", mapSet(MINECRAFT_1_19_3, 42)),
|
||||||
|
RegistryKeyArgumentList.ResourceOrTagKey.class, RegistryKeyArgumentList.ResourceOrTagKey.Serializer.REGISTRY);
|
||||||
|
register(id("minecraft:resource", mapSet(MINECRAFT_1_19_3, 43), mapSet(MINECRAFT_1_19, 44)),
|
||||||
RegistryKeyArgument.class, RegistryKeyArgumentSerializer.REGISTRY);
|
RegistryKeyArgument.class, RegistryKeyArgumentSerializer.REGISTRY);
|
||||||
|
register(id("minecraft:resource_key", mapSet(MINECRAFT_1_19_3, 44)),
|
||||||
|
RegistryKeyArgumentList.ResourceKey.class, RegistryKeyArgumentList.ResourceKey.Serializer.REGISTRY);
|
||||||
|
|
||||||
empty(id("minecraft:template_mirror", mapSet(MINECRAFT_1_19, 45))); // 1.19
|
empty(id("minecraft:template_mirror", mapSet(MINECRAFT_1_19, 45))); // 1.19
|
||||||
empty(id("minecraft:template_rotation", mapSet(MINECRAFT_1_19, 46))); // 1.19
|
empty(id("minecraft:template_rotation", mapSet(MINECRAFT_1_19, 46))); // 1.19
|
||||||
|
|
||||||
empty(id("minecraft:uuid", mapSet(MINECRAFT_1_19, 47))); // added in 1.16
|
empty(id("minecraft:uuid", mapSet(MINECRAFT_1_19, 47))); // added in 1.16
|
||||||
|
|
||||||
|
|
||||||
// Crossstitch support
|
// Crossstitch support
|
||||||
register(id("crossstitch:mod_argument", mapSet(MINECRAFT_1_19, -256)), ModArgumentProperty.class, MOD);
|
register(id("crossstitch:mod_argument", mapSet(MINECRAFT_1_19, -256)), ModArgumentProperty.class, MOD);
|
||||||
|
|
||||||
|
@ -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.time.Instant;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A precisely ordered queue which allows for outside entries into the ordered queue through piggybacking timestamps.
|
* A precisely ordered queue which allows for outside entries into the ordered queue through piggybacking timestamps.
|
||||||
|
@ -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() {}
|
||||||
|
|
||||||
public SystemChat(Component component, ChatBuilder.ChatType type) {
|
public SystemChat(Component component, ChatType type) {
|
||||||
this.component = component;
|
this.component = component;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Component component;
|
private Component component;
|
||||||
private ChatBuilder.ChatType type;
|
private ChatType type;
|
||||||
|
|
||||||
public ChatBuilder.ChatType getType() {
|
public ChatType getType() {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ public class SystemChat implements MinecraftPacket {
|
|||||||
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
|
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
|
||||||
component = ProtocolUtils.getJsonChatSerializer(protocolVersion).deserialize(ProtocolUtils.readString(buf));
|
component = ProtocolUtils.getJsonChatSerializer(protocolVersion).deserialize(ProtocolUtils.readString(buf));
|
||||||
// System chat is never decoded so this doesn't matter for now
|
// System chat is never decoded so this doesn't matter for now
|
||||||
type = ChatBuilder.ChatType.values()[ProtocolUtils.readVarInt(buf)];
|
type = ChatType.values()[ProtocolUtils.readVarInt(buf)];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -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/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.velocitypowered.proxy.protocol.packet.chat;
|
package com.velocitypowered.proxy.protocol.packet.chat.keyed;
|
||||||
|
|
||||||
import com.google.common.primitives.Longs;
|
import com.google.common.primitives.Longs;
|
||||||
import com.velocitypowered.api.network.ProtocolVersion;
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
|
|
||||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||||
import com.velocitypowered.proxy.crypto.EncryptionUtils;
|
import com.velocitypowered.proxy.crypto.EncryptionUtils;
|
||||||
import com.velocitypowered.proxy.crypto.SignaturePair;
|
import com.velocitypowered.proxy.crypto.SignaturePair;
|
||||||
import com.velocitypowered.proxy.crypto.SignedChatMessage;
|
|
||||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||||
import com.velocitypowered.proxy.util.except.QuietDecoderException;
|
import com.velocitypowered.proxy.util.except.QuietDecoderException;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.UUID;
|
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
public class PlayerChat implements MinecraftPacket {
|
public class KeyedPlayerChat implements MinecraftPacket {
|
||||||
|
|
||||||
private String message;
|
private String message;
|
||||||
private boolean signedPreview;
|
private boolean signedPreview;
|
||||||
@ -48,27 +45,16 @@ public class PlayerChat implements MinecraftPacket {
|
|||||||
public static final QuietDecoderException INVALID_PREVIOUS_MESSAGES =
|
public static final QuietDecoderException INVALID_PREVIOUS_MESSAGES =
|
||||||
new QuietDecoderException("Invalid previous messages");
|
new QuietDecoderException("Invalid previous messages");
|
||||||
|
|
||||||
public PlayerChat() {
|
public KeyedPlayerChat() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public PlayerChat(String message) {
|
public KeyedPlayerChat(String message) {
|
||||||
this.message = message;
|
this.message = message;
|
||||||
this.unsigned = true;
|
this.unsigned = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public void setExpiry(@Nullable Instant expiry) {
|
||||||
* Create new {@link PlayerChat} based on a previously {@link SignedChatMessage}.
|
this.expiry = expiry;
|
||||||
*
|
|
||||||
* @param message The {@link SignedChatMessage} to turn into {@link PlayerChat}.
|
|
||||||
*/
|
|
||||||
public PlayerChat(SignedChatMessage message) {
|
|
||||||
this.message = message.getMessage();
|
|
||||||
this.expiry = message.getExpiryTemporal();
|
|
||||||
this.salt = message.getSalt();
|
|
||||||
this.signature = message.getSignature();
|
|
||||||
this.signedPreview = message.isPreviewSigned();
|
|
||||||
this.lastMessage = message.getPreviousSignature();
|
|
||||||
this.previousMessages = message.getPreviousSignatures();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Instant getExpiry() {
|
public Instant getExpiry() {
|
||||||
@ -135,11 +121,11 @@ public class PlayerChat implements MinecraftPacket {
|
|||||||
|
|
||||||
buf.writeLong(unsigned ? Instant.now().toEpochMilli() : expiry.toEpochMilli());
|
buf.writeLong(unsigned ? Instant.now().toEpochMilli() : expiry.toEpochMilli());
|
||||||
buf.writeLong(unsigned ? 0L : Longs.fromByteArray(salt));
|
buf.writeLong(unsigned ? 0L : Longs.fromByteArray(salt));
|
||||||
|
|
||||||
ProtocolUtils.writeByteArray(buf, unsigned ? EncryptionUtils.EMPTY : signature);
|
ProtocolUtils.writeByteArray(buf, unsigned ? EncryptionUtils.EMPTY : signature);
|
||||||
|
|
||||||
buf.writeBoolean(signedPreview);
|
buf.writeBoolean(signedPreview);
|
||||||
|
|
||||||
|
|
||||||
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
|
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
|
||||||
ProtocolUtils.writeVarInt(buf, previousMessages.length);
|
ProtocolUtils.writeVarInt(buf, previousMessages.length);
|
||||||
for (SignaturePair previousMessage : previousMessages) {
|
for (SignaturePair previousMessage : previousMessages) {
|
||||||
@ -157,28 +143,6 @@ public class PlayerChat implements MinecraftPacket {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates a signature and creates a {@link SignedChatMessage} from the given signature.
|
|
||||||
*
|
|
||||||
* @param signer the signer's information
|
|
||||||
* @param sender the sender of the message
|
|
||||||
* @param mustSign instructs the function to throw if the signature is invalid.
|
|
||||||
* @return The {@link SignedChatMessage} or null if the signature couldn't be verified.
|
|
||||||
* @throws com.velocitypowered.proxy.util.except.QuietDecoderException when mustSign is {@code true} and the signature
|
|
||||||
* is invalid.
|
|
||||||
*/
|
|
||||||
public SignedChatMessage signedContainer(IdentifiedKey signer, UUID sender, boolean mustSign) {
|
|
||||||
if (unsigned) {
|
|
||||||
if (mustSign) {
|
|
||||||
throw EncryptionUtils.INVALID_SIGNATURE;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new SignedChatMessage(message, signer.getSignedPublicKey(), sender, expiry, signature,
|
|
||||||
salt, signedPreview, previousMessages, lastMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean handle(MinecraftSessionHandler handler) {
|
public boolean handle(MinecraftSessionHandler handler) {
|
||||||
return handler.handle(this);
|
return handler.handle(this);
|
@ -15,20 +15,16 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.velocitypowered.proxy.protocol.packet.chat;
|
package com.velocitypowered.proxy.protocol.packet.chat.keyed;
|
||||||
|
|
||||||
import static com.velocitypowered.proxy.protocol.packet.chat.PlayerChat.INVALID_PREVIOUS_MESSAGES;
|
import static com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedPlayerChat.INVALID_PREVIOUS_MESSAGES;
|
||||||
import static com.velocitypowered.proxy.protocol.packet.chat.PlayerChat.MAXIMUM_PREVIOUS_MESSAGE_COUNT;
|
import static com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedPlayerChat.MAXIMUM_PREVIOUS_MESSAGE_COUNT;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.primitives.Longs;
|
|
||||||
import com.velocitypowered.api.network.ProtocolVersion;
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
|
|
||||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||||
import com.velocitypowered.proxy.crypto.EncryptionUtils;
|
import com.velocitypowered.proxy.crypto.EncryptionUtils;
|
||||||
import com.velocitypowered.proxy.crypto.SignaturePair;
|
import com.velocitypowered.proxy.crypto.SignaturePair;
|
||||||
import com.velocitypowered.proxy.crypto.SignedChatCommand;
|
|
||||||
import com.velocitypowered.proxy.crypto.SignedChatMessage;
|
|
||||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||||
import com.velocitypowered.proxy.util.except.QuietDecoderException;
|
import com.velocitypowered.proxy.util.except.QuietDecoderException;
|
||||||
@ -37,10 +33,9 @@ import java.time.Instant;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
public class PlayerCommand implements MinecraftPacket {
|
public class KeyedPlayerCommand implements MinecraftPacket {
|
||||||
|
|
||||||
private static final int MAX_NUM_ARGUMENTS = 8;
|
private static final int MAX_NUM_ARGUMENTS = 8;
|
||||||
private static final int MAX_LENGTH_ARGUMENTS = 16;
|
private static final int MAX_LENGTH_ARGUMENTS = 16;
|
||||||
@ -51,15 +46,11 @@ public class PlayerCommand implements MinecraftPacket {
|
|||||||
private String command;
|
private String command;
|
||||||
private Instant timestamp;
|
private Instant timestamp;
|
||||||
private long salt;
|
private long salt;
|
||||||
private boolean signedPreview; // Good god. Please no.
|
private boolean signedPreview; // purely for pass through for 1.19 -> 1.19.2 - this will never be implemented
|
||||||
private SignaturePair[] previousMessages = new SignaturePair[0];
|
private SignaturePair[] previousMessages = new SignaturePair[0];
|
||||||
private @Nullable SignaturePair lastMessage;
|
private @Nullable SignaturePair lastMessage;
|
||||||
private Map<String, byte[]> arguments = ImmutableMap.of();
|
private Map<String, byte[]> arguments = ImmutableMap.of();
|
||||||
|
|
||||||
public boolean isSignedPreview() {
|
|
||||||
return signedPreview;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Instant getTimestamp() {
|
public Instant getTimestamp() {
|
||||||
return timestamp;
|
return timestamp;
|
||||||
}
|
}
|
||||||
@ -72,17 +63,17 @@ public class PlayerCommand implements MinecraftPacket {
|
|||||||
return command;
|
return command;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PlayerCommand() {
|
public KeyedPlayerCommand() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an {@link PlayerCommand} packet based on a command and list of arguments.
|
* Creates an {@link KeyedPlayerCommand} packet based on a command and list of arguments.
|
||||||
*
|
*
|
||||||
* @param command the command to run
|
* @param command the command to run
|
||||||
* @param arguments the arguments of the command
|
* @param arguments the arguments of the command
|
||||||
* @param timestamp the timestamp of the command execution
|
* @param timestamp the timestamp of the command execution
|
||||||
*/
|
*/
|
||||||
public PlayerCommand(String command, List<String> arguments, Instant timestamp) {
|
public KeyedPlayerCommand(String command, List<String> arguments, Instant timestamp) {
|
||||||
this.unsigned = true;
|
this.unsigned = true;
|
||||||
ImmutableMap.Builder<String, byte[]> builder = ImmutableMap.builder();
|
ImmutableMap.Builder<String, byte[]> builder = ImmutableMap.builder();
|
||||||
arguments.forEach(entry -> builder.put(entry, EncryptionUtils.EMPTY));
|
arguments.forEach(entry -> builder.put(entry, EncryptionUtils.EMPTY));
|
||||||
@ -93,21 +84,6 @@ public class PlayerCommand implements MinecraftPacket {
|
|||||||
this.salt = 0L;
|
this.salt = 0L;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create new {@link PlayerCommand} based on a previously {@link SignedChatCommand}.
|
|
||||||
*
|
|
||||||
* @param signedCommand The {@link SignedChatCommand} to turn into {@link PlayerCommand}.
|
|
||||||
*/
|
|
||||||
public PlayerCommand(SignedChatCommand signedCommand) {
|
|
||||||
this.command = signedCommand.getBaseCommand();
|
|
||||||
this.arguments = ImmutableMap.copyOf(signedCommand.getSignatures());
|
|
||||||
this.timestamp = signedCommand.getExpiryTemporal();
|
|
||||||
this.salt = Longs.fromByteArray(signedCommand.getSalt());
|
|
||||||
this.signedPreview = signedCommand.isPreviewSigned();
|
|
||||||
this.lastMessage = signedCommand.getLastSignature();
|
|
||||||
this.previousMessages = signedCommand.getPreviousSignatures();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
|
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
|
||||||
command = ProtocolUtils.readString(buf, 256);
|
command = ProtocolUtils.readString(buf, 256);
|
||||||
@ -127,7 +103,7 @@ public class PlayerCommand implements MinecraftPacket {
|
|||||||
}
|
}
|
||||||
arguments = entries.build();
|
arguments = entries.build();
|
||||||
|
|
||||||
signedPreview = buf.readBoolean();
|
this.signedPreview = buf.readBoolean();
|
||||||
if (unsigned && signedPreview) {
|
if (unsigned && signedPreview) {
|
||||||
throw EncryptionUtils.PREVIEW_SIGNATURE_MISSING;
|
throw EncryptionUtils.PREVIEW_SIGNATURE_MISSING;
|
||||||
}
|
}
|
||||||
@ -193,32 +169,6 @@ public class PlayerCommand implements MinecraftPacket {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates a signature and creates a {@link SignedChatCommand} from the given signature.
|
|
||||||
*
|
|
||||||
* @param signer the signer's information
|
|
||||||
* @param sender the sender of the message
|
|
||||||
* @param mustSign instructs the function to throw if the signature is invalid.
|
|
||||||
* @return The {@link SignedChatCommand} or null if the signature couldn't be verified.
|
|
||||||
* @throws com.velocitypowered.proxy.util.except.QuietDecoderException when mustSign is {@code true} and the signature
|
|
||||||
* is invalid.
|
|
||||||
*/
|
|
||||||
public SignedChatCommand signedContainer(
|
|
||||||
@Nullable IdentifiedKey signer, UUID sender, boolean mustSign) {
|
|
||||||
// There's a certain mod that is very broken that still signs messages but
|
|
||||||
// doesn't provide the player key. This is broken and wrong, but we need to
|
|
||||||
// work around that.
|
|
||||||
if (unsigned || signer == null) {
|
|
||||||
if (mustSign) {
|
|
||||||
throw EncryptionUtils.INVALID_SIGNATURE;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new SignedChatCommand(command, signer.getSignedPublicKey(), sender, timestamp,
|
|
||||||
arguments, Longs.toByteArray(salt), signedPreview, previousMessages, lastMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "PlayerCommand{"
|
return "PlayerCommand{"
|
@ -15,16 +15,14 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.velocitypowered.proxy.protocol.packet.chat;
|
package com.velocitypowered.proxy.protocol.packet.chat.legacy;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
|
||||||
import com.velocitypowered.api.network.ProtocolVersion;
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import net.kyori.adventure.identity.Identity;
|
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
public class LegacyChat implements MinecraftPacket {
|
public class LegacyChat implements MinecraftPacket {
|
@ -15,41 +15,35 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.velocitypowered.proxy.protocol.packet.chat;
|
package com.velocitypowered.proxy.protocol.packet.chat.legacy;
|
||||||
|
|
||||||
import com.velocitypowered.api.network.ProtocolVersion;
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
|
||||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||||
import io.netty.buffer.ByteBuf;
|
import com.velocitypowered.proxy.protocol.packet.chat.builder.ChatBuilderV2;
|
||||||
|
import java.util.UUID;
|
||||||
|
import net.kyori.adventure.identity.Identity;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
|
||||||
public class PlayerChatPreview implements MinecraftPacket {
|
public class LegacyChatBuilder extends ChatBuilderV2 {
|
||||||
|
public LegacyChatBuilder(ProtocolVersion version) {
|
||||||
private int id;
|
super(version);
|
||||||
private String query;
|
|
||||||
|
|
||||||
public int getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getQuery() {
|
|
||||||
return query;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
|
public MinecraftPacket toClient() {
|
||||||
id = buf.readInt();
|
// This is temporary
|
||||||
query = ProtocolUtils.readString(buf, 256);
|
UUID identity = sender == null ? (senderIdentity == null ? Identity.nil().uuid()
|
||||||
|
: senderIdentity.uuid()) : sender.getUniqueId();
|
||||||
|
Component msg = component == null ? Component.text(message) : component;
|
||||||
|
|
||||||
|
return new LegacyChat(ProtocolUtils.getJsonChatSerializer(version).serialize(msg), type.getId(), identity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
|
public MinecraftPacket toServer() {
|
||||||
buf.writeInt(id);
|
LegacyChat chat = new LegacyChat();
|
||||||
ProtocolUtils.writeString(buf, query);
|
chat.setMessage(message);
|
||||||
}
|
return chat;
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean handle(MinecraftSessionHandler handler) {
|
|
||||||
return handler.handle(this);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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;
|
package com.velocitypowered.proxy.tablist;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.velocitypowered.api.network.ProtocolVersion;
|
import com.google.common.collect.Maps;
|
||||||
import com.velocitypowered.api.proxy.Player;
|
|
||||||
import com.velocitypowered.api.proxy.ProxyServer;
|
|
||||||
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
|
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
|
||||||
import com.velocitypowered.api.proxy.player.TabList;
|
import com.velocitypowered.api.proxy.player.ChatSession;
|
||||||
import com.velocitypowered.api.proxy.player.TabListEntry;
|
import com.velocitypowered.api.proxy.player.TabListEntry;
|
||||||
import com.velocitypowered.api.util.GameProfile;
|
import com.velocitypowered.api.util.GameProfile;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||||
|
import com.velocitypowered.proxy.console.VelocityConsole;
|
||||||
import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter;
|
import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter;
|
||||||
import com.velocitypowered.proxy.protocol.packet.PlayerListItem;
|
import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfo;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.chat.RemoteChatSession;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.stream.Collectors;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
public class VelocityTabList implements TabList {
|
public class VelocityTabList implements InternalTabList {
|
||||||
|
private static final Logger logger = LogManager.getLogger(VelocityConsole.class);
|
||||||
|
private final ConnectedPlayer player;
|
||||||
|
private final MinecraftConnection connection;
|
||||||
|
private final Map<UUID, VelocityTabListEntry> entries;
|
||||||
|
|
||||||
protected final ConnectedPlayer player;
|
public VelocityTabList(ConnectedPlayer player) {
|
||||||
protected final MinecraftConnection connection;
|
|
||||||
protected final ProxyServer proxyServer;
|
|
||||||
protected final Map<UUID, VelocityTabListEntry> entries = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new VelocityTabList.
|
|
||||||
*/
|
|
||||||
public VelocityTabList(final ConnectedPlayer player, final ProxyServer proxyServer) {
|
|
||||||
this.player = player;
|
this.player = player;
|
||||||
this.proxyServer = proxyServer;
|
|
||||||
this.connection = player.getConnection();
|
this.connection = player.getConnection();
|
||||||
|
this.entries = Maps.newHashMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
@Override
|
@Override
|
||||||
public void setHeaderAndFooter(Component header, Component footer) {
|
public void setHeaderAndFooter(Component header, Component footer) {
|
||||||
Preconditions.checkNotNull(header, "header");
|
Preconditions.checkNotNull(header, "header");
|
||||||
@ -71,177 +70,178 @@ public class VelocityTabList implements TabList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addEntry(TabListEntry entry) {
|
public void addEntry(TabListEntry entry1) {
|
||||||
Preconditions.checkNotNull(entry, "entry");
|
VelocityTabListEntry entry;
|
||||||
Preconditions.checkArgument(entry.getTabList().equals(this),
|
if (entry1 instanceof VelocityTabListEntry) {
|
||||||
"The provided entry was not created by this tab list");
|
entry = (VelocityTabListEntry) entry1;
|
||||||
Preconditions.checkArgument(!entries.containsKey(entry.getProfile().getId()),
|
} else {
|
||||||
"this TabList already contains an entry with the same uuid");
|
entry = new VelocityTabListEntry(this, entry1.getProfile(), entry1.getDisplayNameComponent().orElse(null),
|
||||||
Preconditions.checkArgument(entry instanceof VelocityTabListEntry,
|
entry1.getLatency(), entry1.getGameMode(), entry1.getChatSession(), entry1.isListed());
|
||||||
"Not a Velocity tab list entry");
|
}
|
||||||
|
|
||||||
PlayerListItem.Item packetItem = PlayerListItem.Item.from(entry);
|
EnumSet<UpsertPlayerInfo.Action> actions = EnumSet.noneOf(UpsertPlayerInfo.Action.class);
|
||||||
connection.write(
|
UpsertPlayerInfo.Entry playerInfoEntry = new UpsertPlayerInfo.Entry(entry.getProfile().getId());
|
||||||
new PlayerListItem(PlayerListItem.ADD_PLAYER, Collections.singletonList(packetItem)));
|
|
||||||
entries.put(entry.getProfile().getId(), (VelocityTabListEntry) entry);
|
Preconditions.checkNotNull(entry.getProfile(), "Profile cannot be null");
|
||||||
|
Preconditions.checkNotNull(entry.getProfile().getId(), "Profile ID cannot be null");
|
||||||
|
|
||||||
|
TabListEntry previousEntry = this.entries.put(entry.getProfile().getId(), entry);
|
||||||
|
|
||||||
|
if (previousEntry != null) {
|
||||||
|
// we should merge entries here
|
||||||
|
if (previousEntry.equals(entry)) {
|
||||||
|
return; // nothing else to do, this entry is perfect
|
||||||
|
}
|
||||||
|
if (!Objects.equals(previousEntry.getDisplayNameComponent().orElse(null),
|
||||||
|
entry.getDisplayNameComponent().orElse(null))) {
|
||||||
|
actions.add(UpsertPlayerInfo.Action.UPDATE_DISPLAY_NAME);
|
||||||
|
playerInfoEntry.setDisplayName(entry.getDisplayNameComponent().get());
|
||||||
|
}
|
||||||
|
if (!Objects.equals(previousEntry.getLatency(), entry.getLatency())) {
|
||||||
|
actions.add(UpsertPlayerInfo.Action.UPDATE_LATENCY);
|
||||||
|
playerInfoEntry.setLatency(entry.getLatency());
|
||||||
|
}
|
||||||
|
if (!Objects.equals(previousEntry.getGameMode(), entry.getGameMode())) {
|
||||||
|
actions.add(UpsertPlayerInfo.Action.UPDATE_GAME_MODE);
|
||||||
|
playerInfoEntry.setGameMode(entry.getGameMode());
|
||||||
|
}
|
||||||
|
if (!Objects.equals(previousEntry.isListed(), entry.isListed())) {
|
||||||
|
actions.add(UpsertPlayerInfo.Action.UPDATE_LISTED);
|
||||||
|
playerInfoEntry.setListed(entry.isListed());
|
||||||
|
}
|
||||||
|
if (!Objects.equals(previousEntry.getChatSession(), entry.getChatSession())) {
|
||||||
|
ChatSession from = entry.getChatSession();
|
||||||
|
if (from != null) {
|
||||||
|
actions.add(UpsertPlayerInfo.Action.INITIALIZE_CHAT);
|
||||||
|
playerInfoEntry.setChatSession(new RemoteChatSession(from.getSessionId(), from.getIdentifiedKey()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
actions.addAll(EnumSet.of(UpsertPlayerInfo.Action.ADD_PLAYER,
|
||||||
|
UpsertPlayerInfo.Action.UPDATE_LATENCY,
|
||||||
|
UpsertPlayerInfo.Action.UPDATE_LISTED));
|
||||||
|
if (entry.getDisplayNameComponent().isPresent()) {
|
||||||
|
actions.add(UpsertPlayerInfo.Action.UPDATE_DISPLAY_NAME);
|
||||||
|
playerInfoEntry.setDisplayName(entry.getDisplayNameComponent().get());
|
||||||
|
}
|
||||||
|
if (entry.getChatSession() != null) {
|
||||||
|
actions.add(UpsertPlayerInfo.Action.INITIALIZE_CHAT);
|
||||||
|
ChatSession from = entry.getChatSession();
|
||||||
|
playerInfoEntry.setChatSession(new RemoteChatSession(from.getSessionId(), from.getIdentifiedKey()));
|
||||||
|
}
|
||||||
|
if (entry.getGameMode() != -1 && entry.getGameMode() != 256) {
|
||||||
|
actions.add(UpsertPlayerInfo.Action.UPDATE_GAME_MODE);
|
||||||
|
playerInfoEntry.setGameMode(entry.getGameMode());
|
||||||
|
}
|
||||||
|
playerInfoEntry.setLatency(entry.getLatency());
|
||||||
|
playerInfoEntry.setListed(entry.isListed());
|
||||||
|
}
|
||||||
|
this.connection.write(new UpsertPlayerInfo(actions, List.of(playerInfoEntry)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<TabListEntry> removeEntry(UUID uuid) {
|
public Optional<TabListEntry> removeEntry(UUID uuid) {
|
||||||
Preconditions.checkNotNull(uuid, "uuid");
|
this.connection.write(new RemovePlayerInfo(List.of(uuid)));
|
||||||
|
return Optional.ofNullable(this.entries.remove(uuid));
|
||||||
TabListEntry entry = entries.remove(uuid);
|
|
||||||
if (entry != null) {
|
|
||||||
PlayerListItem.Item packetItem = PlayerListItem.Item.from(entry);
|
|
||||||
connection.write(
|
|
||||||
new PlayerListItem(PlayerListItem.REMOVE_PLAYER, Collections.singletonList(packetItem)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Optional.ofNullable(entry);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean containsEntry(UUID uuid) {
|
public boolean containsEntry(UUID uuid) {
|
||||||
Preconditions.checkNotNull(uuid, "uuid");
|
return this.entries.containsKey(uuid);
|
||||||
return entries.containsKey(uuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears all entries from the tab list. Note that the entries are written with {@link
|
|
||||||
* MinecraftConnection#delayedWrite(Object)}, so make sure to do an explicit {@link
|
|
||||||
* MinecraftConnection#flush()}.
|
|
||||||
*/
|
|
||||||
public void clearAll() {
|
|
||||||
Collection<VelocityTabListEntry> listEntries = entries.values();
|
|
||||||
if (listEntries.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
List<PlayerListItem.Item> items = new ArrayList<>(listEntries.size());
|
|
||||||
for (TabListEntry value : listEntries) {
|
|
||||||
items.add(PlayerListItem.Item.from(value));
|
|
||||||
}
|
|
||||||
entries.clear();
|
|
||||||
connection.delayedWrite(new PlayerListItem(PlayerListItem.REMOVE_PLAYER, items));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<TabListEntry> getEntries() {
|
public Collection<TabListEntry> getEntries() {
|
||||||
return Collections.unmodifiableCollection(this.entries.values());
|
return this.entries.values().stream().map(e -> (TabListEntry) e).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearAll() {
|
||||||
|
this.connection.delayedWrite(new RemovePlayerInfo(new ArrayList<>(this.entries.keySet())));
|
||||||
|
this.entries.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency, int gameMode) {
|
public TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency, int gameMode) {
|
||||||
return buildEntry(profile, displayName, latency, gameMode, null);
|
return new VelocityTabListEntry(this, profile, displayName, latency, gameMode, null, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TabListEntry buildEntry(GameProfile profile,
|
public TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency, int gameMode,
|
||||||
net.kyori.adventure.text.@Nullable Component displayName,
|
@Nullable IdentifiedKey key) {
|
||||||
int latency, int gameMode, @Nullable IdentifiedKey key) {
|
return new VelocityTabListEntry(this, profile, displayName, latency, gameMode, null, true);
|
||||||
return new VelocityTabListEntry(this, profile, displayName, latency, gameMode, key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Processes a tab list entry packet from the backend.
|
public TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency, int gameMode,
|
||||||
*
|
@Nullable ChatSession chatSession) {
|
||||||
* @param packet the packet to process
|
return new VelocityTabListEntry(this, profile, displayName, latency, gameMode, chatSession, true);
|
||||||
*/
|
|
||||||
public void processBackendPacket(PlayerListItem packet) {
|
|
||||||
// Packets are already forwarded on, so no need to do that here
|
|
||||||
for (PlayerListItem.Item item : packet.getItems()) {
|
|
||||||
UUID uuid = item.getUuid();
|
|
||||||
assert uuid != null : "1.7 tab list entry given to modern tab list handler!";
|
|
||||||
|
|
||||||
if (packet.getAction() != PlayerListItem.ADD_PLAYER && !entries.containsKey(uuid)) {
|
|
||||||
// Sometimes UPDATE_GAMEMODE is sent before ADD_PLAYER so don't want to warn here
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (packet.getAction()) {
|
@Override
|
||||||
case PlayerListItem.ADD_PLAYER: {
|
public void processUpdate(UpsertPlayerInfo infoPacket) {
|
||||||
// ensure that name and properties are available
|
for (UpsertPlayerInfo.Entry entry : infoPacket.getEntries()) {
|
||||||
String name = item.getName();
|
processUpsert(infoPacket.getActions(), entry);
|
||||||
List<GameProfile.Property> properties = item.getProperties();
|
|
||||||
if (name == null || properties == null) {
|
|
||||||
throw new IllegalStateException("Got null game profile for ADD_PLAYER");
|
|
||||||
}
|
}
|
||||||
// Verify key
|
|
||||||
IdentifiedKey providedKey = item.getPlayerKey();
|
|
||||||
Optional<Player> connected = proxyServer.getPlayer(uuid);
|
|
||||||
if (connected.isPresent()) {
|
|
||||||
IdentifiedKey expectedKey = connected.get().getIdentifiedKey();
|
|
||||||
if (providedKey != null) {
|
|
||||||
if (!Objects.equals(expectedKey, providedKey)) {
|
|
||||||
throw new IllegalStateException("Server provided incorrect player key in playerlist for "
|
|
||||||
+ name + " UUID: " + uuid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected UpsertPlayerInfo.Entry createRawEntry(VelocityTabListEntry entry) {
|
||||||
|
Preconditions.checkNotNull(entry, "entry");
|
||||||
|
Preconditions.checkNotNull(entry.getProfile(), "Profile cannot be null");
|
||||||
|
Preconditions.checkNotNull(entry.getProfile().getId(), "Profile ID cannot be null");
|
||||||
|
return new UpsertPlayerInfo.Entry(entry.getProfile().getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void emitActionRaw(UpsertPlayerInfo.Action action, UpsertPlayerInfo.Entry entry) {
|
||||||
|
this.connection.write(new UpsertPlayerInfo(EnumSet.of(action), Collections.singletonList(entry)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processUpsert(EnumSet<UpsertPlayerInfo.Action> actions, UpsertPlayerInfo.Entry entry) {
|
||||||
|
Preconditions.checkNotNull(entry.getProfileId(), "Profile ID cannot be null");
|
||||||
|
UUID profileId = entry.getProfileId();
|
||||||
|
VelocityTabListEntry currentEntry = this.entries.get(profileId);
|
||||||
|
if (actions.contains(UpsertPlayerInfo.Action.ADD_PLAYER)) {
|
||||||
|
if (currentEntry == null) {
|
||||||
|
this.entries.put(profileId,
|
||||||
|
currentEntry = new VelocityTabListEntry(
|
||||||
|
this,
|
||||||
|
entry.getProfile(),
|
||||||
|
null,
|
||||||
|
0,
|
||||||
|
-1,
|
||||||
|
null,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// Substitute the key
|
logger.debug("Received an add player packet for an existing entry; this does nothing.");
|
||||||
// It shouldn't be propagated to remove the signature.
|
}
|
||||||
providedKey = expectedKey;
|
} else if (currentEntry == null) {
|
||||||
|
logger.debug(
|
||||||
|
"Received a partial player before an ADD_PLAYER action; profile could not be built. {}", entry);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (actions.contains(UpsertPlayerInfo.Action.UPDATE_GAME_MODE)) {
|
||||||
|
currentEntry.setGameMode(entry.getGameMode());
|
||||||
|
}
|
||||||
|
if (actions.contains(UpsertPlayerInfo.Action.UPDATE_LATENCY)) {
|
||||||
|
currentEntry.setLatency(entry.getLatency());
|
||||||
|
}
|
||||||
|
if (actions.contains(UpsertPlayerInfo.Action.UPDATE_DISPLAY_NAME)) {
|
||||||
|
currentEntry.setDisplayName(entry.getDisplayName());
|
||||||
|
}
|
||||||
|
if (actions.contains(UpsertPlayerInfo.Action.INITIALIZE_CHAT)) {
|
||||||
|
currentEntry.setChatSession(entry.getChatSession());
|
||||||
|
}
|
||||||
|
if (actions.contains(UpsertPlayerInfo.Action.UPDATE_LISTED)) {
|
||||||
|
currentEntry.setListed(entry.isListed());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
entries.putIfAbsent(item.getUuid(), (VelocityTabListEntry) TabListEntry.builder()
|
@Override
|
||||||
.tabList(this)
|
public void processRemove(RemovePlayerInfo infoPacket) {
|
||||||
.profile(new GameProfile(uuid, name, properties))
|
for (UUID uuid : infoPacket.getProfilesToRemove()) {
|
||||||
.displayName(item.getDisplayName())
|
this.entries.remove(uuid);
|
||||||
.latency(item.getLatency())
|
|
||||||
.playerKey(providedKey)
|
|
||||||
.gameMode(item.getGameMode())
|
|
||||||
.build());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PlayerListItem.REMOVE_PLAYER:
|
|
||||||
entries.remove(uuid);
|
|
||||||
break;
|
|
||||||
case PlayerListItem.UPDATE_DISPLAY_NAME: {
|
|
||||||
VelocityTabListEntry entry = entries.get(uuid);
|
|
||||||
if (entry != null) {
|
|
||||||
entry.setDisplayNameInternal(item.getDisplayName());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PlayerListItem.UPDATE_LATENCY: {
|
|
||||||
VelocityTabListEntry entry = entries.get(uuid);
|
|
||||||
if (entry != null) {
|
|
||||||
entry.setLatencyInternal(item.getLatency());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PlayerListItem.UPDATE_GAMEMODE: {
|
|
||||||
VelocityTabListEntry entry = entries.get(uuid);
|
|
||||||
if (entry != null) {
|
|
||||||
entry.setGameModeInternal(item.getGameMode());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// Nothing we can do here
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateEntry(int action, TabListEntry entry) {
|
|
||||||
if (entries.containsKey(entry.getProfile().getId())) {
|
|
||||||
PlayerListItem.Item packetItem = PlayerListItem.Item.from(entry);
|
|
||||||
|
|
||||||
IdentifiedKey selectedKey = packetItem.getPlayerKey();
|
|
||||||
Optional<Player> existing = proxyServer.getPlayer(entry.getProfile().getId());
|
|
||||||
if (existing.isPresent()) {
|
|
||||||
selectedKey = existing.get().getIdentifiedKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedKey != null
|
|
||||||
&& selectedKey.getKeyRevision().getApplicableTo().contains(connection.getProtocolVersion())
|
|
||||||
&& Objects.equals(selectedKey.getSignatureHolder(), entry.getProfile().getId())) {
|
|
||||||
packetItem.setPlayerKey(selectedKey);
|
|
||||||
} else {
|
|
||||||
packetItem.setPlayerKey(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.write(new PlayerListItem(action, Collections.singletonList(packetItem)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,98 +17,108 @@
|
|||||||
|
|
||||||
package com.velocitypowered.proxy.tablist;
|
package com.velocitypowered.proxy.tablist;
|
||||||
|
|
||||||
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
|
import com.velocitypowered.api.proxy.player.ChatSession;
|
||||||
import com.velocitypowered.api.proxy.player.TabList;
|
import com.velocitypowered.api.proxy.player.TabList;
|
||||||
import com.velocitypowered.api.proxy.player.TabListEntry;
|
import com.velocitypowered.api.proxy.player.TabListEntry;
|
||||||
import com.velocitypowered.api.util.GameProfile;
|
import com.velocitypowered.api.util.GameProfile;
|
||||||
import com.velocitypowered.proxy.protocol.packet.PlayerListItem;
|
import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.chat.RemoteChatSession;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
public class VelocityTabListEntry implements TabListEntry {
|
public class VelocityTabListEntry implements TabListEntry {
|
||||||
|
|
||||||
private final VelocityTabList tabList;
|
private final VelocityTabList tabList;
|
||||||
private final GameProfile profile;
|
private final GameProfile profile;
|
||||||
private net.kyori.adventure.text.Component displayName;
|
private Component displayName;
|
||||||
private int latency;
|
private int latency;
|
||||||
private int gameMode;
|
private int gameMode;
|
||||||
private @Nullable IdentifiedKey playerKey;
|
private boolean listed;
|
||||||
|
private @Nullable ChatSession session;
|
||||||
|
|
||||||
VelocityTabListEntry(VelocityTabList tabList, GameProfile profile,
|
public VelocityTabListEntry(VelocityTabList tabList, GameProfile profile, Component displayName, int latency,
|
||||||
net.kyori.adventure.text.@Nullable Component displayName, int latency, int gameMode,
|
int gameMode, @Nullable ChatSession session, boolean listed) {
|
||||||
@Nullable IdentifiedKey playerKey) {
|
|
||||||
this.tabList = tabList;
|
this.tabList = tabList;
|
||||||
this.profile = profile;
|
this.profile = profile;
|
||||||
this.displayName = displayName;
|
this.displayName = displayName;
|
||||||
this.latency = latency;
|
this.latency = latency;
|
||||||
this.gameMode = gameMode;
|
this.gameMode = gameMode;
|
||||||
this.playerKey = playerKey;
|
this.session = session;
|
||||||
|
this.listed = listed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable ChatSession getChatSession() {
|
||||||
|
return this.session;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TabList getTabList() {
|
public TabList getTabList() {
|
||||||
return tabList;
|
return this.tabList;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GameProfile getProfile() {
|
public GameProfile getProfile() {
|
||||||
return profile;
|
return this.profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<net.kyori.adventure.text.Component> getDisplayNameComponent() {
|
public Optional<Component> getDisplayNameComponent() {
|
||||||
return Optional.ofNullable(displayName);
|
return Optional.ofNullable(displayName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TabListEntry setDisplayName(net.kyori.adventure.text.@Nullable Component displayName) {
|
public TabListEntry setDisplayName(@Nullable Component displayName) {
|
||||||
this.displayName = displayName;
|
this.displayName = displayName;
|
||||||
tabList.updateEntry(PlayerListItem.UPDATE_DISPLAY_NAME, this);
|
UpsertPlayerInfo.Entry upsertEntry = this.tabList.createRawEntry(this);
|
||||||
|
upsertEntry.setDisplayName(displayName);
|
||||||
|
this.tabList.emitActionRaw(UpsertPlayerInfo.Action.UPDATE_DISPLAY_NAME, upsertEntry);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setDisplayNameInternal(net.kyori.adventure.text.@Nullable Component displayName) {
|
|
||||||
this.displayName = displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getLatency() {
|
public int getLatency() {
|
||||||
return latency;
|
return this.latency;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TabListEntry setLatency(int latency) {
|
public TabListEntry setLatency(int latency) {
|
||||||
this.latency = latency;
|
this.latency = latency;
|
||||||
tabList.updateEntry(PlayerListItem.UPDATE_LATENCY, this);
|
UpsertPlayerInfo.Entry upsertEntry = this.tabList.createRawEntry(this);
|
||||||
|
upsertEntry.setLatency(latency);
|
||||||
|
this.tabList.emitActionRaw(UpsertPlayerInfo.Action.UPDATE_LATENCY, upsertEntry);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setLatencyInternal(int latency) {
|
|
||||||
this.latency = latency;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getGameMode() {
|
public int getGameMode() {
|
||||||
return gameMode;
|
return this.gameMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TabListEntry setGameMode(int gameMode) {
|
public TabListEntry setGameMode(int gameMode) {
|
||||||
this.gameMode = gameMode;
|
this.gameMode = gameMode;
|
||||||
tabList.updateEntry(PlayerListItem.UPDATE_GAMEMODE, this);
|
UpsertPlayerInfo.Entry upsertEntry = this.tabList.createRawEntry(this);
|
||||||
|
upsertEntry.setGameMode(gameMode);
|
||||||
|
this.tabList.emitActionRaw(UpsertPlayerInfo.Action.UPDATE_GAME_MODE, upsertEntry);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setGameModeInternal(int gameMode) {
|
protected void setChatSession(@Nullable ChatSession session) {
|
||||||
this.gameMode = gameMode;
|
this.session = session;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IdentifiedKey getIdentifiedKey() {
|
public boolean isListed() {
|
||||||
return playerKey;
|
return listed;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setPlayerKeyInternal(IdentifiedKey playerKey) {
|
@Override
|
||||||
this.playerKey = playerKey;
|
public VelocityTabListEntry setListed(boolean listed) {
|
||||||
|
this.listed = listed;
|
||||||
|
UpsertPlayerInfo.Entry upsertEntry = this.tabList.createRawEntry(this);
|
||||||
|
upsertEntry.setListed(listed);
|
||||||
|
this.tabList.emitActionRaw(UpsertPlayerInfo.Action.UPDATE_LISTED, upsertEntry);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ import com.velocitypowered.api.util.GameProfile;
|
|||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
public class VelocityTabListEntryLegacy extends VelocityTabListEntry {
|
public class VelocityTabListEntryLegacy extends KeyedVelocityTabListEntry {
|
||||||
|
|
||||||
VelocityTabListEntryLegacy(VelocityTabListLegacy tabList, GameProfile profile,
|
VelocityTabListEntryLegacy(VelocityTabListLegacy tabList, GameProfile profile,
|
||||||
@Nullable Component displayName, int latency, int gameMode) {
|
@Nullable Component displayName, int latency, int gameMode) {
|
||||||
|
@ -22,8 +22,8 @@ import com.velocitypowered.api.proxy.ProxyServer;
|
|||||||
import com.velocitypowered.api.proxy.player.TabListEntry;
|
import com.velocitypowered.api.proxy.player.TabListEntry;
|
||||||
import com.velocitypowered.api.util.GameProfile;
|
import com.velocitypowered.api.util.GameProfile;
|
||||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||||
import com.velocitypowered.proxy.protocol.packet.PlayerListItem;
|
import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem;
|
||||||
import com.velocitypowered.proxy.protocol.packet.PlayerListItem.Item;
|
import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem.Item;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@ -32,7 +32,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
public class VelocityTabListLegacy extends VelocityTabList {
|
public class VelocityTabListLegacy extends KeyedVelocityTabList {
|
||||||
|
|
||||||
private final Map<String, UUID> nameMapping = new ConcurrentHashMap<>();
|
private final Map<String, UUID> nameMapping = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@ -65,35 +65,35 @@ public class VelocityTabListLegacy extends VelocityTabList {
|
|||||||
@Override
|
@Override
|
||||||
public void clearAll() {
|
public void clearAll() {
|
||||||
for (TabListEntry value : entries.values()) {
|
for (TabListEntry value : entries.values()) {
|
||||||
connection.delayedWrite(new PlayerListItem(PlayerListItem.REMOVE_PLAYER,
|
connection.delayedWrite(new LegacyPlayerListItem(LegacyPlayerListItem.REMOVE_PLAYER,
|
||||||
Collections.singletonList(PlayerListItem.Item.from(value))));
|
Collections.singletonList(LegacyPlayerListItem.Item.from(value))));
|
||||||
}
|
}
|
||||||
entries.clear();
|
entries.clear();
|
||||||
nameMapping.clear();
|
nameMapping.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void processBackendPacket(PlayerListItem packet) {
|
public void processLegacy(LegacyPlayerListItem packet) {
|
||||||
Item item = packet.getItems().get(0); // Only one item per packet in 1.7
|
Item item = packet.getItems().get(0); // Only one item per packet in 1.7
|
||||||
|
|
||||||
switch (packet.getAction()) {
|
switch (packet.getAction()) {
|
||||||
case PlayerListItem.ADD_PLAYER:
|
case LegacyPlayerListItem.ADD_PLAYER:
|
||||||
if (nameMapping.containsKey(item.getName())) { // ADD_PLAYER also used for updating ping
|
if (nameMapping.containsKey(item.getName())) { // ADD_PLAYER also used for updating ping
|
||||||
VelocityTabListEntry entry = entries.get(nameMapping.get(item.getName()));
|
KeyedVelocityTabListEntry entry = entries.get(nameMapping.get(item.getName()));
|
||||||
if (entry != null) {
|
if (entry != null) {
|
||||||
entry.setLatencyInternal(item.getLatency());
|
entry.setLatencyInternal(item.getLatency());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
UUID uuid = UUID.randomUUID(); // Use a fake uuid to preserve function of custom entries
|
UUID uuid = UUID.randomUUID(); // Use a fake uuid to preserve function of custom entries
|
||||||
nameMapping.put(item.getName(), uuid);
|
nameMapping.put(item.getName(), uuid);
|
||||||
entries.put(uuid, (VelocityTabListEntry) TabListEntry.builder()
|
entries.put(uuid, (KeyedVelocityTabListEntry) TabListEntry.builder()
|
||||||
.tabList(this)
|
.tabList(this)
|
||||||
.profile(new GameProfile(uuid, item.getName(), ImmutableList.of()))
|
.profile(new GameProfile(uuid, item.getName(), ImmutableList.of()))
|
||||||
.latency(item.getLatency())
|
.latency(item.getLatency())
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case PlayerListItem.REMOVE_PLAYER:
|
case LegacyPlayerListItem.REMOVE_PLAYER:
|
||||||
UUID removedUuid = nameMapping.remove(item.getName());
|
UUID removedUuid = nameMapping.remove(item.getName());
|
||||||
if (removedUuid != null) {
|
if (removedUuid != null) {
|
||||||
entries.remove(removedUuid);
|
entries.remove(removedUuid);
|
||||||
@ -109,11 +109,11 @@ public class VelocityTabListLegacy extends VelocityTabList {
|
|||||||
void updateEntry(int action, TabListEntry entry) {
|
void updateEntry(int action, TabListEntry entry) {
|
||||||
if (entries.containsKey(entry.getProfile().getId())) {
|
if (entries.containsKey(entry.getProfile().getId())) {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case PlayerListItem.UPDATE_LATENCY:
|
case LegacyPlayerListItem.UPDATE_LATENCY:
|
||||||
case PlayerListItem.UPDATE_DISPLAY_NAME: // Add here because we removed beforehand
|
case LegacyPlayerListItem.UPDATE_DISPLAY_NAME: // Add here because we removed beforehand
|
||||||
connection
|
connection
|
||||||
.write(new PlayerListItem(PlayerListItem.ADD_PLAYER, // ADD_PLAYER also updates ping
|
.write(new LegacyPlayerListItem(LegacyPlayerListItem.ADD_PLAYER, // ADD_PLAYER also updates ping
|
||||||
Collections.singletonList(PlayerListItem.Item.from(entry))));
|
Collections.singletonList(LegacyPlayerListItem.Item.from(entry))));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// Can't do anything else
|
// Can't do anything else
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren