3
0
Mirror von https://github.com/PaperMC/Velocity.git synchronisiert 2024-11-16 21:10:30 +01:00
Co-authored-by: FivePB <admin@fivepb.me>
Dieser Commit ist enthalten in:
Corey Shupe 2022-06-07 21:00:24 -04:00 committet von GitHub
Ursprung 377e6b6626
Commit d97ed956a7
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: 4AEE18F83AFDEB23
65 geänderte Dateien mit 2613 neuen und 391 gelöschten Zeilen

Datei anzeigen

@ -57,7 +57,8 @@ public enum ProtocolVersion {
MINECRAFT_1_17(755, "1.17"), MINECRAFT_1_17(755, "1.17"),
MINECRAFT_1_17_1(756, "1.17.1"), MINECRAFT_1_17_1(756, "1.17.1"),
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");
private static final int SNAPSHOT_BIT = 30; private static final int SNAPSHOT_BIT = 30;

Datei anzeigen

@ -7,6 +7,7 @@
package com.velocitypowered.api.proxy; package com.velocitypowered.api.proxy;
import com.velocitypowered.api.proxy.crypto.KeyIdentifiable;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier; import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
@ -14,7 +15,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
* Allows the server to communicate with a client logging into the proxy using login plugin * Allows the server to communicate with a client logging into the proxy using login plugin
* messages. * messages.
*/ */
public interface LoginPhaseConnection extends InboundConnection { public interface LoginPhaseConnection extends InboundConnection, KeyIdentifiable {
void sendLoginPluginMessage(ChannelIdentifier identifier, byte[] contents, void sendLoginPluginMessage(ChannelIdentifier identifier, byte[] contents,
MessageConsumer consumer); MessageConsumer consumer);

Datei anzeigen

@ -9,6 +9,7 @@ package com.velocitypowered.api.proxy;
import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent; import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent;
import com.velocitypowered.api.proxy.crypto.KeyIdentifiable;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier; import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.messages.ChannelMessageSink; import com.velocitypowered.api.proxy.messages.ChannelMessageSink;
import com.velocitypowered.api.proxy.messages.ChannelMessageSource; import com.velocitypowered.api.proxy.messages.ChannelMessageSource;
@ -37,7 +38,7 @@ import org.jetbrains.annotations.NotNull;
* Represents a player who is connected to the proxy. * Represents a player who is connected to the proxy.
*/ */
public interface Player extends CommandSource, Identified, InboundConnection, public interface Player extends CommandSource, Identified, InboundConnection,
ChannelMessageSource, ChannelMessageSink, HoverEventSource<HoverEvent.ShowEntity>, Keyed { ChannelMessageSource, ChannelMessageSink, HoverEventSource<HoverEvent.ShowEntity>, Keyed, KeyIdentifiable {
/** /**
* Returns the player's current username. * Returns the player's current username.

Datei anzeigen

@ -0,0 +1,35 @@
/*
* 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.crypto;
import java.security.PublicKey;
/**
* Represents session-server cross-signed dated RSA public key.
*/
public interface IdentifiedKey extends KeySigned {
/**
* Returns RSA public key.
* Note: this key is at least 2048 bits but may be larger.
*
* @return the RSA public key in question
*/
PublicKey getSignedPublicKey();
/**
* Validates a signature against this public key.
* @param signature the signature data
* @param toVerify the signed data
*
* @return validity of the signature
*/
boolean verifyDataSignature(byte[] signature, byte[]... toVerify);
}

Datei anzeigen

@ -0,0 +1,21 @@
/*
* 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.crypto;
/**
* Identifies a type with a public RSA signature.
*/
public interface KeyIdentifiable {
/**
* Returns the timed identified key of the object context.
* <p>Only available in 1.19 and newer</p>
* @return the key or null if not available
*/
IdentifiedKey getIdentifiedKey();
}

Datei anzeigen

@ -0,0 +1,76 @@
/*
* 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.crypto;
import com.google.common.annotations.Beta;
import java.security.PublicKey;
import java.time.Instant;
import org.checkerframework.checker.nullness.qual.Nullable;
public interface KeySigned {
/**
* Returns the key used to sign the object.
*
* @return the key
*/
PublicKey getSigner();
/**
* Returns the expiry time point of the key.
* Note: this limit is arbitrary. RSA keys don't expire,
* but the signature of this key as provided by the session
* server will expire.
*
* @return the expiry time point
*/
Instant getExpiryTemporal();
/**
* Check if the signature has expired.
*
* @return true if proxy time is after expiry time
*/
default boolean hasExpired() {
return Instant.now().isAfter(getExpiryTemporal());
}
/**
* Retrieves the signature of the signed object.
*
* @return an RSA signature
*/
@Nullable
byte[] getSignature();
/**
* Validates the signature, expiry temporal and key against the
* signer public key. Note: This will **not** check for
* expiry. You can check for expiry with {@link KeySigned#hasExpired()}.
* <p>DOES NOT WORK YET FOR MESSAGES AND COMMANDS!</p>
*
* @return validity of the signature
*/
@Beta
default boolean isSignatureValid() {
return false;
}
/**
* Returns the signature salt or null if not salted.
*
* @return signature salt or null
*/
default byte[] getSalt() {
return null;
}
}

Datei anzeigen

@ -0,0 +1,35 @@
/*
* 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.crypto;
import java.util.UUID;
public interface SignedMessage extends KeySigned {
/**
* Returns the signed message.
*
* @return the message
*/
String getMessage();
/**
* Returns the signers UUID.
*
* @return the uuid
*/
UUID getSignerUuid();
/**
* If true the signature of this message applies to a stylized component instead.
*
* @return signature signs preview
*/
boolean isPreviewSigned();
}

Datei anzeigen

@ -8,6 +8,7 @@
package com.velocitypowered.api.proxy.player; package com.velocitypowered.api.proxy.player;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.api.util.GameProfile;
import java.util.Collection; import java.util.Collection;
import java.util.Optional; import java.util.Optional;
@ -80,4 +81,19 @@ 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); int gameMode);
/**
* Builds a tab list entry.
*
* @deprecated Internal usage. Use {@link TabListEntry.Builder} instead.
* @param profile profile
* @param displayName display name
* @param latency latency
* @param gameMode game mode
* @param key the player key
* @return entry
*/
@Deprecated
TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency,
int gameMode, @Nullable IdentifiedKey key);
} }

Datei anzeigen

@ -7,6 +7,8 @@
package com.velocitypowered.api.proxy.player; package com.velocitypowered.api.proxy.player;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
import com.velocitypowered.api.proxy.crypto.KeyIdentifiable;
import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.api.util.GameProfile;
import java.util.Optional; import java.util.Optional;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
@ -15,7 +17,7 @@ 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 { public interface TabListEntry extends KeyIdentifiable {
/** /**
* Returns the parent {@link TabList} of this {@code this} {@link TabListEntry}. * Returns the parent {@link TabList} of this {@code this} {@link TabListEntry}.
@ -125,6 +127,8 @@ public interface TabListEntry {
private int latency = 0; private int latency = 0;
private int gameMode = 0; private int gameMode = 0;
private @Nullable IdentifiedKey playerKey;
private Builder() { private Builder() {
} }
@ -152,6 +156,18 @@ public interface TabListEntry {
return this; return this;
} }
/**
* Sets the {@link IdentifiedKey} of the {@link TabListEntry}.
*
* @param playerKey key to set
* @return {@code this}, for chaining
* @see TabListEntry#getIdentifiedKey()
*/
public Builder playerKey(IdentifiedKey playerKey) {
this.playerKey = playerKey;
return this;
}
/** /**
* Sets the displayed name of the {@link TabListEntry}. * Sets the displayed name of the {@link TabListEntry}.
* *
@ -200,7 +216,7 @@ public interface TabListEntry {
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); return tabList.buildEntry(profile, displayName, latency, gameMode, playerKey);
} }
} }
} }

Datei anzeigen

@ -45,6 +45,7 @@ import com.velocitypowered.proxy.config.VelocityConfiguration;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo; import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo;
import com.velocitypowered.proxy.console.VelocityConsole; import com.velocitypowered.proxy.console.VelocityConsole;
import com.velocitypowered.proxy.crypto.EncryptionUtils;
import com.velocitypowered.proxy.event.VelocityEventManager; import com.velocitypowered.proxy.event.VelocityEventManager;
import com.velocitypowered.proxy.network.ConnectionManager; import com.velocitypowered.proxy.network.ConnectionManager;
import com.velocitypowered.proxy.plugin.VelocityPluginManager; import com.velocitypowered.proxy.plugin.VelocityPluginManager;
@ -55,7 +56,6 @@ import com.velocitypowered.proxy.scheduler.VelocityScheduler;
import com.velocitypowered.proxy.server.ServerMap; import com.velocitypowered.proxy.server.ServerMap;
import com.velocitypowered.proxy.util.AddressUtil; import com.velocitypowered.proxy.util.AddressUtil;
import com.velocitypowered.proxy.util.ClosestLocaleMatcher; import com.velocitypowered.proxy.util.ClosestLocaleMatcher;
import com.velocitypowered.proxy.util.EncryptionUtils;
import com.velocitypowered.proxy.util.FileSystemUtils; import com.velocitypowered.proxy.util.FileSystemUtils;
import com.velocitypowered.proxy.util.VelocityChannelRegistrar; import com.velocitypowered.proxy.util.VelocityChannelRegistrar;
import com.velocitypowered.proxy.util.bossbar.AdventureBossBarManager; import com.velocitypowered.proxy.util.bossbar.AdventureBossBarManager;
@ -80,7 +80,6 @@ import java.util.Locale;
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.ResourceBundle;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -97,7 +96,6 @@ import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.translation.GlobalTranslator; import net.kyori.adventure.translation.GlobalTranslator;
import net.kyori.adventure.translation.TranslationRegistry; import net.kyori.adventure.translation.TranslationRegistry;
import net.kyori.adventure.util.UTF8ResourceBundleControl;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.asynchttpclient.AsyncHttpClient; import org.asynchttpclient.AsyncHttpClient;

Datei anzeigen

@ -75,6 +75,7 @@ public class VelocityConfiguration implements ProxyConfig {
@Expose private boolean enablePlayerAddressLogging = true; @Expose private boolean enablePlayerAddressLogging = true;
private net.kyori.adventure.text.@MonotonicNonNull Component motdAsComponent; private net.kyori.adventure.text.@MonotonicNonNull Component motdAsComponent;
private @Nullable Favicon favicon; private @Nullable Favicon favicon;
@Expose private boolean forceKeyAuthentication = true; // Added in 1.19
private VelocityConfiguration(Servers servers, ForcedHosts forcedHosts, Advanced advanced, private VelocityConfiguration(Servers servers, ForcedHosts forcedHosts, Advanced advanced,
Query query, Metrics metrics) { Query query, Metrics metrics) {
@ -90,7 +91,7 @@ public class VelocityConfiguration implements ProxyConfig {
PlayerInfoForwarding playerInfoForwardingMode, byte[] forwardingSecret, PlayerInfoForwarding playerInfoForwardingMode, byte[] forwardingSecret,
boolean onlineModeKickExistingPlayers, PingPassthroughMode pingPassthrough, boolean onlineModeKickExistingPlayers, PingPassthroughMode pingPassthrough,
boolean enablePlayerAddressLogging, Servers servers,ForcedHosts forcedHosts, boolean enablePlayerAddressLogging, Servers servers,ForcedHosts forcedHosts,
Advanced advanced, Query query, Metrics metrics) { Advanced advanced, Query query, Metrics metrics, boolean forceKeyAuthentication) {
this.bind = bind; this.bind = bind;
this.motd = motd; this.motd = motd;
this.showMaxPlayers = showMaxPlayers; this.showMaxPlayers = showMaxPlayers;
@ -107,6 +108,7 @@ public class VelocityConfiguration implements ProxyConfig {
this.advanced = advanced; this.advanced = advanced;
this.query = query; this.query = query;
this.metrics = metrics; this.metrics = metrics;
this.forceKeyAuthentication = forceKeyAuthentication;
} }
/** /**
@ -381,6 +383,10 @@ public class VelocityConfiguration implements ProxyConfig {
return advanced.isLogPlayerConnections(); return advanced.isLogPlayerConnections();
} }
public boolean isForceKeyAuthentication() {
return forceKeyAuthentication;
}
@Override @Override
public String toString() { public String toString() {
return MoreObjects.toStringHelper(this) return MoreObjects.toStringHelper(this)
@ -397,6 +403,7 @@ public class VelocityConfiguration implements ProxyConfig {
.add("query", query) .add("query", query)
.add("favicon", favicon) .add("favicon", favicon)
.add("enablePlayerAddressLogging", enablePlayerAddressLogging) .add("enablePlayerAddressLogging", enablePlayerAddressLogging)
.add("forceKeyAuthentication", forceKeyAuthentication)
.toString(); .toString();
} }
@ -466,6 +473,7 @@ public class VelocityConfiguration implements ProxyConfig {
String motd = config.getOrElse("motd", "&#09add3A Velocity Server"); String motd = config.getOrElse("motd", "&#09add3A Velocity Server");
int maxPlayers = config.getIntOrElse("show-max-players", 500); int maxPlayers = config.getIntOrElse("show-max-players", 500);
Boolean onlineMode = config.getOrElse("online-mode", true); Boolean onlineMode = config.getOrElse("online-mode", true);
Boolean forceKeyAuthentication = config.getOrElse("force-key-authentication", true);
Boolean announceForge = config.getOrElse("announce-forge", true); Boolean announceForge = config.getOrElse("announce-forge", true);
Boolean preventClientProxyConnections = config.getOrElse("prevent-client-proxy-connections", Boolean preventClientProxyConnections = config.getOrElse("prevent-client-proxy-connections",
true); true);
@ -488,7 +496,8 @@ public class VelocityConfiguration implements ProxyConfig {
new ForcedHosts(forcedHostsConfig), new ForcedHosts(forcedHostsConfig),
new Advanced(advancedConfig), new Advanced(advancedConfig),
new Query(queryConfig), new Query(queryConfig),
new Metrics(metricsConfig) new Metrics(metricsConfig),
forceKeyAuthentication
); );
} }

Datei anzeigen

@ -20,7 +20,6 @@ package com.velocitypowered.proxy.connection;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.packet.AvailableCommands; 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.Chat;
import com.velocitypowered.proxy.protocol.packet.ClientSettings; import com.velocitypowered.proxy.protocol.packet.ClientSettings;
import com.velocitypowered.proxy.protocol.packet.Disconnect; import com.velocitypowered.proxy.protocol.packet.Disconnect;
import com.velocitypowered.proxy.protocol.packet.EncryptionRequest; import com.velocitypowered.proxy.protocol.packet.EncryptionRequest;
@ -46,6 +45,13 @@ 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.chat.PlayerChat;
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.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;
@ -104,7 +110,7 @@ public interface MinecraftSessionHandler {
return false; return false;
} }
default boolean handle(Chat packet) { default boolean handle(LegacyChat packet) {
return false; return false;
} }
@ -231,4 +237,28 @@ public interface MinecraftSessionHandler {
default boolean handle(ResourcePackResponse packet) { default boolean handle(ResourcePackResponse packet) {
return false; return false;
} }
default boolean handle(PlayerChat packet) {
return false;
}
default boolean handle(SystemChat packet) {
return false;
}
default boolean handle(ServerPlayerChat packet) {
return false;
}
default boolean handle(PlayerChatPreview packet) {
return false;
}
default boolean handle(ServerChatPreview packet) {
return false;
}
default boolean handle(PlayerCommand packet) {
return false;
}
} }

Datei anzeigen

@ -24,7 +24,8 @@ public class VelocityConstants {
} }
public static final String VELOCITY_IP_FORWARDING_CHANNEL = "velocity:player_info"; public static final String VELOCITY_IP_FORWARDING_CHANNEL = "velocity:player_info";
public static final int FORWARDING_VERSION = 1; public static final int MODERN_FORWARDING_DEFAULT = 1;
public static final int MODERN_FORWARDING_WITH_KEY = 2;
public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
} }

Datei anzeigen

@ -18,6 +18,8 @@
package com.velocitypowered.proxy.connection.backend; package com.velocitypowered.proxy.connection.backend;
import com.velocitypowered.api.event.player.ServerLoginPluginMessageEvent; import com.velocitypowered.api.event.player.ServerLoginPluginMessageEvent;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
@ -47,6 +49,7 @@ 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;
public class LoginSessionHandler implements MinecraftSessionHandler { public class LoginSessionHandler implements MinecraftSessionHandler {
@ -78,7 +81,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
&& packet.getChannel().equals(VelocityConstants.VELOCITY_IP_FORWARDING_CHANNEL)) { && packet.getChannel().equals(VelocityConstants.VELOCITY_IP_FORWARDING_CHANNEL)) {
ByteBuf forwardingData = createForwardingData(configuration.getForwardingSecret(), ByteBuf forwardingData = createForwardingData(configuration.getForwardingSecret(),
serverConn.getPlayerRemoteAddressAsString(), serverConn.getPlayerRemoteAddressAsString(),
serverConn.getPlayer().getGameProfile()); serverConn.getPlayer().getGameProfile(), mc.getProtocolVersion(), serverConn.getPlayer().getIdentifiedKey());
LoginPluginResponse response = new LoginPluginResponse(packet.getId(), true, forwardingData); LoginPluginResponse response = new LoginPluginResponse(packet.getId(), true, forwardingData);
mc.write(response); mc.write(response);
informationForwarded = true; informationForwarded = true;
@ -163,15 +166,23 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
} }
private static ByteBuf createForwardingData(byte[] hmacSecret, String address, private static ByteBuf createForwardingData(byte[] hmacSecret, String address,
GameProfile profile) { GameProfile profile, ProtocolVersion version,
@Nullable IdentifiedKey playerKey) {
ByteBuf forwarded = Unpooled.buffer(2048); ByteBuf forwarded = Unpooled.buffer(2048);
try { try {
ProtocolUtils.writeVarInt(forwarded, VelocityConstants.FORWARDING_VERSION); int forwardingVersion = version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0 && playerKey != null
? VelocityConstants.MODERN_FORWARDING_WITH_KEY : VelocityConstants.MODERN_FORWARDING_DEFAULT;
ProtocolUtils.writeVarInt(forwarded, forwardingVersion);
ProtocolUtils.writeString(forwarded, address); ProtocolUtils.writeString(forwarded, address);
ProtocolUtils.writeUuid(forwarded, profile.getId()); ProtocolUtils.writeUuid(forwarded, profile.getId());
ProtocolUtils.writeString(forwarded, profile.getName()); ProtocolUtils.writeString(forwarded, profile.getName());
ProtocolUtils.writeProperties(forwarded, profile.getProperties()); ProtocolUtils.writeProperties(forwarded, profile.getProperties());
if (forwardingVersion >= VelocityConstants.MODERN_FORWARDING_WITH_KEY) {
ProtocolUtils.writePlayerKey(forwarded, playerKey);
}
SecretKey key = new SecretKeySpec(hmacSecret, "HmacSHA256"); SecretKey key = new SecretKeySpec(hmacSecret, "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256"); Mac mac = Mac.getInstance("HmacSHA256");
mac.init(key); mac.init(key);

Datei anzeigen

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

Datei anzeigen

@ -85,7 +85,7 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
// Initiate a regular connection and move over to it. // Initiate a regular connection and move over to it.
ConnectedPlayer player = new ConnectedPlayer(server, profileEvent.getGameProfile(), ConnectedPlayer player = new ConnectedPlayer(server, profileEvent.getGameProfile(),
mcConnection, inbound.getVirtualHost().orElse(null), onlineMode); mcConnection, inbound.getVirtualHost().orElse(null), onlineMode, inbound.getIdentifiedKey());
this.connectedPlayer = player; this.connectedPlayer = player;
if (!server.canRegisterConnection(player)) { if (!server.canRegisterConnection(player)) {
player.disconnect0(Component.translatable("velocity.error.already-connected-proxy", player.disconnect0(Component.translatable("velocity.error.already-connected-proxy",
@ -133,6 +133,7 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
} }
ServerLoginSuccess success = new ServerLoginSuccess(); ServerLoginSuccess success = new ServerLoginSuccess();
success.setUsername(player.getUsername()); success.setUsername(player.getUsername());
success.setProperties(player.getGameProfileProperties());
success.setUuid(playerUniqueId); success.setUuid(playerUniqueId);
mcConnection.write(success); mcConnection.write(success);

Datei anzeigen

@ -42,10 +42,11 @@ 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;
import com.velocitypowered.proxy.protocol.packet.Chat;
import com.velocitypowered.proxy.protocol.packet.ClientSettings; import com.velocitypowered.proxy.protocol.packet.ClientSettings;
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;
@ -55,6 +56,10 @@ 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.LegacyChat;
import com.velocitypowered.proxy.protocol.packet.chat.PlayerChat;
import com.velocitypowered.proxy.protocol.packet.chat.PlayerCommand;
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;
@ -62,6 +67,8 @@ import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil; import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.util.ReferenceCountUtil; import io.netty.util.ReferenceCountUtil;
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;
@ -90,9 +97,11 @@ 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
/** /**
* Constructs a client play session handler. * Constructs a client play session handler.
*
* @param server the Velocity server instance * @param server the Velocity server instance
* @param player the player * @param player the player
*/ */
@ -101,6 +110,83 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
this.server = server; this.server = server;
} }
// I will not allow hacks to bypass this;
private boolean tickLastMessage(SignedChatMessage nextMessage) {
if (lastChatMessage != null && lastChatMessage.isAfter(nextMessage.getExpiryTemporal())) {
player.disconnect(Component.translatable("multiplayer.disconnect.out_of_order_chat"));
return false;
}
lastChatMessage = nextMessage.getExpiryTemporal();
return true;
}
private boolean validateChat(String message) {
if (CharacterUtil.containsIllegalCharacters(message)) {
player.disconnect(Component.translatable("velocity.error.illegal-chat-characters",
NamedTextColor.RED));
return false;
}
return true;
}
private MinecraftConnection retrieveServerConnection() {
VelocityServerConnection serverConnection = player.getConnectedServer();
if (serverConnection == null) {
return null;
}
return serverConnection.getConnection();
}
private void processCommandMessage(String message, @Nullable SignedChatCommand signedCommand,
MinecraftPacket original) {
server.getCommandManager().callCommandEvent(player, message)
.thenComposeAsync(event -> processCommandExecuteResult(message,
event.getResult(), signedCommand))
.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;
});
}
private void processPlayerChat(String message, @Nullable SignedChatMessage signedMessage,
MinecraftPacket original) {
MinecraftConnection smc = retrieveServerConnection();
if (smc == null) {
return;
}
PlayerChatEvent event = new PlayerChatEvent(player, message);
server.getEventManager().fire(event)
.thenAcceptAsync(pme -> {
PlayerChatEvent.ChatResult chatResult = pme.getResult();
if (chatResult.isAllowed()) {
Optional<String> eventMsg = pme.getResult().getMessage();
if (eventMsg.isPresent()) {
if (player.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0
&& player.getIdentifiedKey() != null) {
logger.warn("A plugin changed a signed chat message. The server may not accept it.");
}
smc.write(ChatBuilder.builder(player.getProtocolVersion())
.message(event.getMessage()).toServer());
} else {
smc.write(original);
}
}
}, smc.eventLoop())
.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
@ -142,58 +228,57 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
} }
@Override @Override
public boolean handle(Chat packet) { public boolean handle(PlayerCommand packet) {
VelocityServerConnection serverConnection = player.getConnectedServer(); if (!validateChat(packet.getCommand())) {
if (serverConnection == null) {
return true; return true;
} }
MinecraftConnection smc = serverConnection.getConnection(); if (!packet.isUnsigned()) {
if (smc == null) { SignedChatCommand signedCommand = packet.signedContainer(player.getIdentifiedKey(), player.getUniqueId(), false);
if (signedCommand != null) {
processCommandMessage(packet.getCommand(), signedCommand, packet);
return true;
}
}
processCommandMessage(packet.getCommand(), null, packet);
return true;
}
@Override
public boolean handle(PlayerChat packet) {
if (!validateChat(packet.getMessage())) {
return true; return true;
} }
if (!packet.isUnsigned()) {
// Bad if spoofed
SignedChatMessage signedChat = packet.signedContainer(player.getIdentifiedKey(), player.getUniqueId(), false);
if (signedChat != null) {
// Server doesn't care for expiry as long as order is correct
if (!tickLastMessage(signedChat)) {
return true;
}
processPlayerChat(packet.getMessage(), signedChat, packet);
return true;
}
}
processPlayerChat(packet.getMessage(), null, packet);
return true;
}
@Override
public boolean handle(LegacyChat packet) {
String msg = packet.getMessage(); String msg = packet.getMessage();
if (CharacterUtil.containsIllegalCharacters(msg)) { if (!validateChat(msg)) {
player.disconnect(Component.translatable("velocity.error.illegal-chat-characters",
NamedTextColor.RED));
return true; return true;
} }
if (msg.startsWith("/")) { if (msg.startsWith("/")) {
String originalCommand = msg.substring(1); processCommandMessage(msg.substring(1), null, packet);
server.getCommandManager().callCommandEvent(player, msg.substring(1))
.thenComposeAsync(event -> processCommandExecuteResult(originalCommand,
event.getResult()))
.whenComplete((ignored, throwable) -> {
if (server.getConfiguration().isLogCommandExecutions()) {
logger.info("{} -> executed command /{}", player, originalCommand);
}
})
.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;
});
} else { } else {
PlayerChatEvent event = new PlayerChatEvent(player, msg); processPlayerChat(msg, null, packet);
server.getEventManager().fire(event)
.thenAcceptAsync(pme -> {
PlayerChatEvent.ChatResult chatResult = pme.getResult();
if (chatResult.isAllowed()) {
Optional<String> eventMsg = pme.getResult().getMessage();
if (eventMsg.isPresent()) {
smc.write(Chat.createServerbound(eventMsg.get()));
} else {
smc.write(packet);
}
}
}, smc.eventLoop())
.exceptionally((ex) -> {
logger.error("Exception while handling player chat for {}", player, ex);
return null;
});
} }
return true; return true;
} }
@ -229,7 +314,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
} }
} }
server.getEventManager().fireAndForget(new PlayerChannelRegisterEvent(player, server.getEventManager().fireAndForget(new PlayerChannelRegisterEvent(player,
ImmutableList.copyOf(channelIdentifiers))); 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));
@ -368,7 +453,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
/** /**
* Handles the {@code JoinGame} packet. This function is responsible for handling the client-side * Handles the {@code JoinGame} packet. This function is responsible for handling the client-side
* switching servers in Velocity. * switching servers in Velocity.
* @param joinGame the join game packet *
* @param joinGame the join game packet
* @param destination the new server we are connecting to * @param destination the new server we are connecting to
*/ */
public void handleBackendJoinGame(JoinGame joinGame, VelocityServerConnection destination) { public void handleBackendJoinGame(JoinGame joinGame, VelocityServerConnection destination) {
@ -452,7 +538,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
new Respawn(sentOldDim, joinGame.getPartialHashedSeed(), new Respawn(sentOldDim, joinGame.getPartialHashedSeed(),
joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(), joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(),
false, joinGame.getDimensionInfo(), joinGame.getPreviousGamemode(), false, joinGame.getDimensionInfo(), joinGame.getPreviousGamemode(),
joinGame.getCurrentDimensionData())); joinGame.getCurrentDimensionData(), joinGame.getLastDeathPosition()));
} }
private void doSafeClientServerSwitch(JoinGame joinGame) { private void doSafeClientServerSwitch(JoinGame joinGame) {
@ -469,14 +555,14 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
new Respawn(tempDim, joinGame.getPartialHashedSeed(), joinGame.getDifficulty(), new Respawn(tempDim, joinGame.getPartialHashedSeed(), joinGame.getDifficulty(),
joinGame.getGamemode(), joinGame.getLevelType(), joinGame.getGamemode(), joinGame.getLevelType(),
false, joinGame.getDimensionInfo(), joinGame.getPreviousGamemode(), false, joinGame.getDimensionInfo(), joinGame.getPreviousGamemode(),
joinGame.getCurrentDimensionData())); joinGame.getCurrentDimensionData(), joinGame.getLastDeathPosition()));
// Now send a respawn packet in the correct dimension. // Now send a respawn packet in the correct dimension.
player.getConnection().delayedWrite( player.getConnection().delayedWrite(
new Respawn(joinGame.getDimension(), joinGame.getPartialHashedSeed(), new Respawn(joinGame.getDimension(), joinGame.getPartialHashedSeed(),
joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(), joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(),
false, joinGame.getDimensionInfo(), joinGame.getPreviousGamemode(), false, joinGame.getDimensionInfo(), joinGame.getPreviousGamemode(),
joinGame.getCurrentDimensionData())); joinGame.getCurrentDimensionData(), joinGame.getLastDeathPosition()));
} }
public List<UUID> getServerBossBars() { public List<UUID> getServerBossBars() {
@ -619,8 +705,10 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
}); });
} }
private CompletableFuture<Void> processCommandExecuteResult(String originalCommand, private CompletableFuture<Void> processCommandExecuteResult(String originalCommand,
CommandResult result) { CommandResult result,
@Nullable SignedChatCommand signedCommand) {
if (result == CommandResult.denied()) { if (result == CommandResult.denied()) {
return CompletableFuture.completedFuture(null); return CompletableFuture.completedFuture(null);
} }
@ -628,13 +716,30 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
MinecraftConnection smc = player.ensureAndGetCurrentServer().ensureConnected(); MinecraftConnection smc = player.ensureAndGetCurrentServer().ensureConnected();
String commandToRun = result.getCommand().orElse(originalCommand); String commandToRun = result.getCommand().orElse(originalCommand);
if (result.isForwardToServer()) { if (result.isForwardToServer()) {
return CompletableFuture.runAsync(() -> smc.write(Chat.createServerbound("/" ChatBuilder write = ChatBuilder
+ commandToRun)), smc.eventLoop()); .builder(player.getProtocolVersion())
.asPlayer(player);
if (signedCommand != null && commandToRun.equals(signedCommand.getBaseCommand())) {
write.message(signedCommand);
} else {
write.message("/" + commandToRun);
}
return CompletableFuture.runAsync(() -> smc.write(write.toServer()), smc.eventLoop());
} else { } else {
return server.getCommandManager().executeImmediatelyAsync(player, commandToRun) return server.getCommandManager().executeImmediatelyAsync(player, commandToRun)
.thenAcceptAsync(hasRun -> { .thenAcceptAsync(hasRun -> {
if (!hasRun) { if (!hasRun) {
smc.write(Chat.createServerbound("/" + commandToRun)); ChatBuilder write = ChatBuilder
.builder(player.getProtocolVersion())
.asPlayer(player);
if (signedCommand != null && commandToRun.equals(signedCommand.getBaseCommand())) {
write.message(signedCommand);
} else {
write.message("/" + commandToRun);
}
smc.write(write.toServer());
} }
}, smc.eventLoop()); }, smc.eventLoop());
} }

Datei anzeigen

@ -41,6 +41,8 @@ import com.velocitypowered.api.permission.Tristate;
import com.velocitypowered.api.proxy.ConnectionRequestBuilder; import com.velocitypowered.api.proxy.ConnectionRequestBuilder;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection; import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
import com.velocitypowered.api.proxy.crypto.KeyIdentifiable;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier; import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.player.PlayerSettings; import com.velocitypowered.api.proxy.player.PlayerSettings;
import com.velocitypowered.api.proxy.player.ResourcePackInfo; import com.velocitypowered.api.proxy.player.ResourcePackInfo;
@ -56,13 +58,14 @@ import com.velocitypowered.proxy.connection.util.ConnectionMessages;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.Chat;
import com.velocitypowered.proxy.protocol.packet.ClientSettings; import com.velocitypowered.proxy.protocol.packet.ClientSettings;
import com.velocitypowered.proxy.protocol.packet.Disconnect; import com.velocitypowered.proxy.protocol.packet.Disconnect;
import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter; 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.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.VelocityTabList; import com.velocitypowered.proxy.tablist.VelocityTabList;
@ -109,7 +112,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, KeyIdentifiable {
private static final int MAX_PLUGIN_CHANNELS = 1024; private static final int MAX_PLUGIN_CHANNELS = 1024;
private static final PlainTextComponentSerializer PASS_THRU_TRANSLATE = PlainTextComponentSerializer.builder() private static final PlainTextComponentSerializer PASS_THRU_TRANSLATE = PlainTextComponentSerializer.builder()
@ -159,9 +162,10 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
.build(); .build();
private @Nullable String clientBrand; private @Nullable String clientBrand;
private @Nullable Locale effectiveLocale; private @Nullable Locale effectiveLocale;
private @Nullable IdentifiedKey playerKey;
ConnectedPlayer(VelocityServer server, GameProfile profile, MinecraftConnection connection, ConnectedPlayer(VelocityServer server, GameProfile profile, MinecraftConnection connection,
@Nullable InetSocketAddress virtualHost, boolean onlineMode) { @Nullable InetSocketAddress virtualHost, boolean onlineMode, @Nullable IdentifiedKey playerKey) {
this.server = server; this.server = server;
this.profile = profile; this.profile = profile;
this.connection = connection; this.connection = connection;
@ -176,6 +180,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
} else { } else {
this.tabList = new VelocityTabListLegacy(this); this.tabList = new VelocityTabListLegacy(this);
} }
this.playerKey = playerKey;
} }
@Override @Override
@ -311,7 +316,9 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
@Override @Override
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(Chat.createClientbound(identity, translated, this.getProtocolVersion()));
connection.write(ChatBuilder.builder(this.getProtocolVersion())
.component(translated).forIdentity(identity).toClient());
} }
@Override @Override
@ -321,9 +328,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
Preconditions.checkNotNull(type, "type"); Preconditions.checkNotNull(type, "type");
Component translated = translateMessage(message); Component translated = translateMessage(message);
Chat packet = Chat.createClientbound(identity, translated, this.getProtocolVersion());
packet.setType(type == MessageType.CHAT ? Chat.CHAT_TYPE : Chat.SYSTEM_TYPE); connection.write(ChatBuilder.builder(this.getProtocolVersion())
connection.write(packet); .component(translated).forIdentity(identity)
.setType(type == MessageType.CHAT ? ChatBuilder.ChatType.CHAT : ChatBuilder.ChatType.SYSTEM)
.toClient());
} }
@Override @Override
@ -344,10 +353,10 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
JsonObject object = new JsonObject(); JsonObject object = new JsonObject();
object.addProperty("text", LegacyComponentSerializer.legacySection() object.addProperty("text", LegacyComponentSerializer.legacySection()
.serialize(translated)); .serialize(translated));
Chat chat = new Chat(); LegacyChat legacyChat = new LegacyChat();
chat.setMessage(object.toString()); legacyChat.setMessage(object.toString());
chat.setType(Chat.GAME_INFO_TYPE); legacyChat.setType(LegacyChat.GAME_INFO_TYPE);
connection.write(chat); connection.write(legacyChat);
} }
} }
@ -883,10 +892,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
@Override @Override
public void spoofChatInput(String input) { public void spoofChatInput(String input) {
Preconditions.checkArgument(input.length() <= Chat.MAX_SERVERBOUND_MESSAGE_LENGTH, Preconditions.checkArgument(input.length() <= LegacyChat.MAX_SERVERBOUND_MESSAGE_LENGTH,
"input cannot be greater than " + Chat.MAX_SERVERBOUND_MESSAGE_LENGTH "input cannot be greater than " + LegacyChat.MAX_SERVERBOUND_MESSAGE_LENGTH
+ " characters in length"); + " characters in length");
ensureBackendConnection().write(Chat.createServerbound(input)); ensureBackendConnection().write(ChatBuilder.builder(getProtocolVersion())
.asPlayer(this).message(input).toServer());
} }
@Override @Override
@ -1055,6 +1065,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
return knownChannels; return knownChannels;
} }
@Override
public IdentifiedKey getIdentifiedKey() {
return playerKey;
}
private class IdentityImpl implements Identity { private class IdentityImpl implements Identity {
@Override @Override
public @NonNull UUID uuid() { public @NonNull UUID uuid() {

Datei anzeigen

@ -20,12 +20,15 @@ package com.velocitypowered.proxy.connection.client;
import static com.google.common.net.UrlEscapers.urlFormParameterEscaper; import static com.google.common.net.UrlEscapers.urlFormParameterEscaper;
import static com.velocitypowered.proxy.VelocityServer.GENERAL_GSON; import static com.velocitypowered.proxy.VelocityServer.GENERAL_GSON;
import static com.velocitypowered.proxy.connection.VelocityConstants.EMPTY_BYTE_ARRAY; import static com.velocitypowered.proxy.connection.VelocityConstants.EMPTY_BYTE_ARRAY;
import static com.velocitypowered.proxy.util.EncryptionUtils.decryptRsa; import static com.velocitypowered.proxy.crypto.EncryptionUtils.decryptRsa;
import static com.velocitypowered.proxy.util.EncryptionUtils.generateServerId; 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.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.proxy.crypto.IdentifiedKey;
import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnection;
@ -77,6 +80,23 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
public boolean handle(ServerLogin packet) { public boolean handle(ServerLogin packet) {
assertState(LoginState.LOGIN_PACKET_EXPECTED); assertState(LoginState.LOGIN_PACKET_EXPECTED);
this.currentState = LoginState.LOGIN_PACKET_RECEIVED; this.currentState = LoginState.LOGIN_PACKET_RECEIVED;
IdentifiedKey playerKey = packet.getPlayerKey();
if (playerKey != null) {
if (playerKey.hasExpired()) {
inbound.disconnect(Component.translatable("multiplayer.disconnect.invalid_public_key_signature"));
return true;
}
if (!playerKey.isSignatureValid()) {
inbound.disconnect(Component.translatable("multiplayer.disconnect.invalid_public_key"));
return true;
}
} else if (mcConnection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0
&& server.getConfiguration().isForceKeyAuthentication()) {
inbound.disconnect(Component.translatable("multiplayer.disconnect.missing_public_key"));
return true;
}
inbound.setPlayerKey(playerKey);
this.login = packet; this.login = packet;
PreLoginEvent event = new PreLoginEvent(inbound, login.getUsername()); PreLoginEvent event = new PreLoginEvent(inbound, login.getUsername());
@ -135,7 +155,6 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
public boolean handle(EncryptionResponse packet) { public boolean handle(EncryptionResponse packet) {
assertState(LoginState.ENCRYPTION_REQUEST_SENT); assertState(LoginState.ENCRYPTION_REQUEST_SENT);
this.currentState = LoginState.ENCRYPTION_RESPONSE_RECEIVED; this.currentState = LoginState.ENCRYPTION_RESPONSE_RECEIVED;
ServerLogin login = this.login; ServerLogin login = this.login;
if (login == null) { if (login == null) {
throw new IllegalStateException("No ServerLogin packet received yet."); throw new IllegalStateException("No ServerLogin packet received yet.");
@ -147,9 +166,16 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
try { try {
KeyPair serverKeyPair = server.getServerKeyPair(); KeyPair serverKeyPair = server.getServerKeyPair();
byte[] decryptedVerifyToken = decryptRsa(serverKeyPair, packet.getVerifyToken()); if (inbound.getIdentifiedKey() != null) {
if (!MessageDigest.isEqual(verify, decryptedVerifyToken)) { IdentifiedKey playerKey = inbound.getIdentifiedKey();
throw new IllegalStateException("Unable to successfully decrypt the verification token."); if (!playerKey.verifyDataSignature(packet.getVerifyToken(), verify, Longs.toByteArray(packet.getSalt()))) {
throw new IllegalStateException("Invalid client public signature.");
}
} else {
byte[] decryptedVerifyToken = decryptRsa(serverKeyPair, packet.getVerifyToken());
if (!MessageDigest.isEqual(verify, decryptedVerifyToken)) {
throw new IllegalStateException("Unable to successfully decrypt the verification token.");
}
} }
byte[] decryptedSharedSecret = decryptRsa(serverKeyPair, packet.getSharedSecret()); byte[] decryptedSharedSecret = decryptRsa(serverKeyPair, packet.getSharedSecret());

Datei anzeigen

@ -19,6 +19,8 @@ package com.velocitypowered.proxy.connection.client;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.LoginPhaseConnection; import com.velocitypowered.api.proxy.LoginPhaseConnection;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
import com.velocitypowered.api.proxy.crypto.KeyIdentifiable;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier; import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage; import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage;
@ -32,9 +34,10 @@ import java.util.Optional;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import space.vectrix.flare.fastutil.Int2ObjectSyncMap; import space.vectrix.flare.fastutil.Int2ObjectSyncMap;
public class LoginInboundConnection implements LoginPhaseConnection { public class LoginInboundConnection implements LoginPhaseConnection, KeyIdentifiable {
private static final AtomicIntegerFieldUpdater<LoginInboundConnection> SEQUENCE_UPDATER = private static final AtomicIntegerFieldUpdater<LoginInboundConnection> SEQUENCE_UPDATER =
AtomicIntegerFieldUpdater.newUpdater(LoginInboundConnection.class, "sequenceCounter"); AtomicIntegerFieldUpdater.newUpdater(LoginInboundConnection.class, "sequenceCounter");
@ -45,6 +48,7 @@ public class LoginInboundConnection implements LoginPhaseConnection {
private final Queue<LoginPluginMessage> loginMessagesToSend; private final Queue<LoginPluginMessage> loginMessagesToSend;
private volatile Runnable onAllMessagesHandled; private volatile Runnable onAllMessagesHandled;
private volatile boolean loginEventFired; private volatile boolean loginEventFired;
private @MonotonicNonNull IdentifiedKey playerKey;
LoginInboundConnection( LoginInboundConnection(
InitialInboundConnection delegate) { InitialInboundConnection delegate) {
@ -149,4 +153,13 @@ public class LoginInboundConnection implements LoginPhaseConnection {
MinecraftConnection delegatedConnection() { MinecraftConnection delegatedConnection() {
return delegate.getConnection(); return delegate.getConnection();
} }
public void setPlayerKey(IdentifiedKey playerKey) {
this.playerKey = playerKey;
}
@Override
public IdentifiedKey getIdentifiedKey() {
return playerKey;
}
} }

Datei anzeigen

@ -0,0 +1,109 @@
/*
* 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.connection.registry;
/*
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import net.kyori.adventure.nbt.*;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.text.format.TextFormat;
import net.kyori.adventure.translation.Translatable;
import org.jetbrains.annotations.NotNull;
import java.util.List;
*/
// TODO Implement
public class ChatData {
/*
private static final ListBinaryTag EMPTY_LIST_TAG = ListBinaryTag.empty();
private final String identifier;
private final int id;
private final Map<>
public static class Decoration implements Translatable {
private final List<String> parameters;
private final List<TextFormat> style;
private final String translationKey;
public List<String> getParameters() {
return parameters;
}
public List<TextFormat> getStyle() {
return style;
}
@Override
public @NotNull String translationKey() {
return translationKey;
}
public Decoration(List<String> parameters, List<TextFormat> style, String translationKey) {
this.parameters = Preconditions.checkNotNull(parameters);
this.style = Preconditions.checkNotNull(style);
this.translationKey = Preconditions.checkNotNull(translationKey);
Preconditions.checkArgument(translationKey.length() > 0);
}
public static Decoration decodeRegistryEntry(CompoundBinaryTag toDecode) {
ImmutableList.Builder<String> parameters = ImmutableList.builder();
ListBinaryTag paramList = toDecode.getList("parameters", EMPTY_LIST_TAG);
if (paramList != EMPTY_LIST_TAG) {
paramList.forEach(binaryTag -> parameters.add(binaryTag.toString()));
}
ImmutableList.Builder<TextFormat> style = ImmutableList.builder();
CompoundBinaryTag styleList = toDecode.getCompound("style");
for (String key : styleList.keySet()) {
if ("color".equals(key)) {
NamedTextColor color = Preconditions.checkNotNull(
NamedTextColor.NAMES.value(styleList.getString(key)));
style.add(color);
} else {
// Key is a Style option instead
TextDecoration deco = TextDecoration.NAMES.value(key);
// This wouldn't be here if it wasn't applied, but here it goes anyway:
byte val = styleList.getByte(key);
if (val != 0) {
style.add(deco);
}
}
}
String translationKey = toDecode.getString("translation_key");
return new Decoration(parameters.build(), style.build(), translationKey);
}
public void encodeRegistryEntry(CompoundBinaryTag )
}
public static enum Priority {
SYSTEM,
CHAT
}
*/
}

Datei anzeigen

@ -45,42 +45,51 @@ public final class DimensionData {
private final @Nullable String effects; private final @Nullable String effects;
private final @Nullable Integer minY; // Required and added by 1.17 private final @Nullable Integer minY; // Required and added by 1.17
private final @Nullable Integer height; // Required and added by 1.17 private final @Nullable Integer height; // Required and added by 1.17
private final @Nullable Integer monsterSpawnBlockLightLimit; // Required and added by 1.19
private final @Nullable Integer monsterSpawnLightLevel; // Required and added by 1.19
/** /**
* Initializes a new {@link DimensionData} instance. * Initializes a new {@link DimensionData} instance.
* @param registryIdentifier the identifier for the dimension from the registry. *
* @param dimensionId the dimension ID contained in the registry (the "id" tag) * @param registryIdentifier the identifier for the dimension from the registry.
* @param isNatural indicates if the dimension use natural world generation (e.g. overworld) * @param dimensionId the dimension ID contained in the registry (the "id" tag)
* @param ambientLight the light level the client sees without external lighting * @param isNatural indicates if the dimension use natural world generation (e.g. overworld)
* @param isShrunk indicates if the world is shrunk, aka not the full 256 blocks (e.g. nether) * @param ambientLight the light level the client sees without external lighting
* @param isUltrawarm internal dimension warmth flag * @param isShrunk indicates if the world is shrunk, aka not the full 256 blocks (e.g. nether)
* @param hasCeiling indicates if the dimension has a ceiling layer * @param isUltrawarm internal dimension warmth flag
* @param hasSkylight indicates if the dimension should display the sun * @param hasCeiling indicates if the dimension has a ceiling layer
* @param isPiglinSafe indicates if piglins should naturally zombify in this dimension * @param hasSkylight indicates if the dimension should display the sun
* @param doBedsWork indicates if players should be able to sleep in beds in this dimension * @param isPiglinSafe indicates if piglins should naturally zombify in this dimension
* @param doRespawnAnchorsWork indicates if player respawn points can be used in this dimension * @param doBedsWork indicates if players should be able to sleep in beds in this dimension
* @param hasRaids indicates if raids can be spawned in the dimension * @param doRespawnAnchorsWork indicates if player respawn points can be used in this dimension
* @param logicalHeight the natural max height for the given dimension * @param hasRaids indicates if raids can be spawned in the dimension
* @param burningBehaviourIdentifier the identifier for how burning blocks work in the dimension * @param logicalHeight the natural max height for the given dimension
* @param fixedTime optional. If set to any game daytime value will deactivate time cycle * @param burningBehaviourIdentifier the identifier for how burning blocks work in the dimension
* @param createDragonFight optional. Internal flag used in the end dimension * @param fixedTime optional. If set to any game daytime value will deactivate time cycle
* @param coordinateScale optional, unknown purpose * @param createDragonFight optional. Internal flag used in the end dimension
* @param effects optional, unknown purpose * @param coordinateScale optional, unknown purpose
* @param minY the world effective lowest build-level * @param effects optional, unknown purpose
* @param height the world height above zero * @param minY the world effective lowest build-level
* @param height the world height above zero
* @param monsterSpawnBlockLightLimit an integer controlling the block light needed to prevent monster spawns.
* @param monsterSpawnLightLevel an int provider which is evaluated to find a value to compare the current
* overall brightness with to determine if a monster should be allowed to spawn.
*/ */
public DimensionData(String registryIdentifier, public DimensionData(String registryIdentifier,
@Nullable Integer dimensionId, @Nullable Integer dimensionId,
boolean isNatural, boolean isNatural,
float ambientLight, boolean isShrunk, boolean isUltrawarm, float ambientLight, boolean isShrunk, boolean isUltrawarm,
boolean hasCeiling, boolean hasSkylight, boolean hasCeiling, boolean hasSkylight,
boolean isPiglinSafe, boolean doBedsWork, boolean isPiglinSafe, boolean doBedsWork,
boolean doRespawnAnchorsWork, boolean hasRaids, boolean doRespawnAnchorsWork, boolean hasRaids,
int logicalHeight, String burningBehaviourIdentifier, int logicalHeight, String burningBehaviourIdentifier,
@Nullable Long fixedTime, @Nullable Boolean createDragonFight, @Nullable Long fixedTime, @Nullable Boolean createDragonFight,
@Nullable Double coordinateScale, @Nullable Double coordinateScale,
@Nullable String effects, @Nullable String effects,
@Nullable Integer minY, @Nullable Integer height) { @Nullable Integer minY, @Nullable Integer height, @Nullable Integer monsterSpawnBlockLightLimit,
@Nullable Integer monsterSpawnLightLevel) {
this.monsterSpawnBlockLightLimit = monsterSpawnBlockLightLimit;
this.monsterSpawnLightLevel = monsterSpawnLightLevel;
Preconditions.checkNotNull( Preconditions.checkNotNull(
registryIdentifier, "registryIdentifier cannot be null"); registryIdentifier, "registryIdentifier cannot be null");
Preconditions.checkArgument(registryIdentifier.length() > 0, Preconditions.checkArgument(registryIdentifier.length() > 0,
@ -193,15 +202,15 @@ public final class DimensionData {
* and {@code dimensionId}. * and {@code dimensionId}.
* *
* @param registryIdentifier the identifier for the dimension from the registry * @param registryIdentifier the identifier for the dimension from the registry
* @param dimensionId optional, dimension ID * @param dimensionId optional, dimension ID
* @return a new {@link DimensionData} * @return a new {@link DimensionData}
*/ */
public DimensionData annotateWith(String registryIdentifier, public DimensionData annotateWith(String registryIdentifier,
@Nullable Integer dimensionId) { @Nullable Integer dimensionId) {
return new DimensionData(registryIdentifier, dimensionId, isNatural, ambientLight, isShrunk, return new DimensionData(registryIdentifier, dimensionId, isNatural, ambientLight, isShrunk,
isUltrawarm, hasCeiling, hasSkylight, isPiglinSafe, doBedsWork, doRespawnAnchorsWork, isUltrawarm, hasCeiling, hasSkylight, isPiglinSafe, doBedsWork, doRespawnAnchorsWork,
hasRaids, logicalHeight, burningBehaviourIdentifier, fixedTime, createDragonFight, hasRaids, logicalHeight, burningBehaviourIdentifier, fixedTime, createDragonFight,
coordinateScale, effects, minY, height); coordinateScale, effects, minY, height, monsterSpawnBlockLightLimit, monsterSpawnLightLevel);
} }
public boolean isUnannotated() { public boolean isUnannotated() {
@ -217,7 +226,7 @@ public final class DimensionData {
* @return game dimension data * @return game dimension data
*/ */
public static DimensionData decodeBaseCompoundTag(CompoundBinaryTag details, public static DimensionData decodeBaseCompoundTag(CompoundBinaryTag details,
ProtocolVersion version) { ProtocolVersion version) {
boolean isNatural = details.getBoolean("natural"); boolean isNatural = details.getBoolean("natural");
float ambientLight = details.getFloat("ambient_light"); float ambientLight = details.getFloat("ambient_light");
boolean isShrunk = details.getBoolean("shrunk"); boolean isShrunk = details.getBoolean("shrunk");
@ -240,28 +249,40 @@ public final class DimensionData {
: null; : null;
Integer minY = details.keySet().contains("min_y") ? details.getInt("min_y") : null; Integer minY = details.keySet().contains("min_y") ? details.getInt("min_y") : null;
Integer height = details.keySet().contains("height") ? details.getInt("height") : null; Integer height = details.keySet().contains("height") ? details.getInt("height") : null;
Integer monsterSpawnBlockLightLimit = details.keySet().contains("monster_spawn_block_light_limit")
? details.getInt("monster_spawn_block_light_limit") : null;
Integer monsterSpawnLightLevel =
details.keySet().contains("monster_spawn_light_level") ? details.getInt("monster_spawn_block_light_limit") :
null;
if (version.compareTo(ProtocolVersion.MINECRAFT_1_17) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_17) >= 0) {
Preconditions.checkNotNull(height, Preconditions.checkNotNull(height,
"DimensionData requires 'height' to be present for this version"); "DimensionData requires 'height' to be present for this version");
Preconditions.checkNotNull(minY, Preconditions.checkNotNull(minY,
"DimensionData requires 'minY' to be present for this version"); "DimensionData requires 'minY' to be present for this version");
}
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
Preconditions.checkNotNull(monsterSpawnBlockLightLimit,
"DimensionData requires 'monster_spawn_block_light_limit' to be present for this version.");
Preconditions.checkNotNull(monsterSpawnLightLevel,
"DimensionData requires 'monster_spawn_light_level' to be present for this version.");
} }
return new DimensionData( return new DimensionData(
UNKNOWN_DIMENSION_ID, null, isNatural, ambientLight, isShrunk, UNKNOWN_DIMENSION_ID, null, isNatural, ambientLight, isShrunk,
isUltrawarm, hasCeiling, hasSkylight, isPiglinSafe, doBedsWork, doRespawnAnchorsWork, isUltrawarm, hasCeiling, hasSkylight, isPiglinSafe, doBedsWork, doRespawnAnchorsWork,
hasRaids, logicalHeight, burningBehaviourIdentifier, fixedTime, hasEnderdragonFight, hasRaids, logicalHeight, burningBehaviourIdentifier, fixedTime, hasEnderdragonFight,
coordinateScale, effects, minY, height); coordinateScale, effects, minY, height, monsterSpawnBlockLightLimit, monsterSpawnLightLevel);
} }
/** /**
* Parses a given CompoundTag to a DimensionData instance. Assumes the data is part of a * Parses a given CompoundTag to a DimensionData instance. Assumes the data is part of a
* dimension registry. * dimension registry.
* @param dimTag the compound from the registry to read *
* @param dimTag the compound from the registry to read
* @param version the protocol version * @param version the protocol version
* @return game dimension data * @return game dimension data
*/ */
public static DimensionData decodeRegistryEntry(CompoundBinaryTag dimTag, public static DimensionData decodeRegistryEntry(CompoundBinaryTag dimTag,
ProtocolVersion version) { ProtocolVersion version) {
String registryIdentifier = dimTag.getString("name"); String registryIdentifier = dimTag.getString("name");
CompoundBinaryTag details; CompoundBinaryTag details;
Integer dimensionId = null; Integer dimensionId = null;
@ -278,6 +299,7 @@ public final class DimensionData {
/** /**
* Encodes the Dimension data as CompoundTag. * Encodes the Dimension data as CompoundTag.
*
* @param version the version to serialize as * @param version the version to serialize as
* @return compound containing the dimension data * @return compound containing the dimension data
*/ */
@ -301,6 +323,7 @@ public final class DimensionData {
/** /**
* Serializes details of this dimension. * Serializes details of this dimension.
*
* @return serialized details of this dimension * @return serialized details of this dimension
*/ */
public CompoundBinaryTag serializeDimensionDetails() { public CompoundBinaryTag serializeDimensionDetails() {
@ -335,6 +358,12 @@ public final class DimensionData {
if (height != null) { if (height != null) {
ret.putInt("height", height); ret.putInt("height", height);
} }
if (monsterSpawnBlockLightLimit != null) {
ret.putInt("monster_spawn_block_light_limit", monsterSpawnBlockLightLimit);
}
if (monsterSpawnLightLevel != null) {
ret.putInt("monster_spawn_light_level", monsterSpawnLightLevel);
}
return ret.build(); return ret.build();
} }
} }

Datei anzeigen

@ -0,0 +1,263 @@
/*
* 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.google.common.io.ByteStreams;
import com.velocitypowered.proxy.util.except.QuietDecoderException;
import it.unimi.dsi.fastutil.Pair;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import javax.crypto.Cipher;
public enum EncryptionUtils {
;
public static final Pair<String, String> PEM_RSA_PUBLIC_KEY_DESCRIPTOR =
Pair.of("-----BEGIN RSA PUBLIC KEY-----", "-----END RSA PUBLIC KEY-----");
public static final Pair<String, String> PEM_RSA_PRIVATE_KEY_DESCRIPTOR =
Pair.of("-----BEGIN RSA PRIVATE KEY-----", "-----END RSA PRIVATE KEY-----");
public static final String SHA1_WITH_RSA = "SHA1withRSA";
public static final String SHA256_WITH_RSA = "SHA256withRSA";
public static final QuietDecoderException INVALID_SIGNATURE
= new QuietDecoderException("Incorrectly signed chat message");
public static final QuietDecoderException PREVIEW_SIGNATURE_MISSING
= new QuietDecoderException("Unsigned chat message requested signed preview");
public static final byte[] EMPTY = new byte[0];
private static PublicKey YGGDRASIL_SESSION_KEY;
private static KeyFactory RSA_KEY_FACTORY;
private static final Base64.Encoder MIME_SPECIAL_ENCODER
= Base64.getMimeEncoder(76, "\n".getBytes(StandardCharsets.UTF_8));
static {
try {
RSA_KEY_FACTORY = KeyFactory.getInstance("RSA");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
try {
byte[] bytes = ByteStreams.toByteArray(
EncryptionUtils.class.getClassLoader().getResourceAsStream("yggdrasil_session_pubkey.der"));
YGGDRASIL_SESSION_KEY = parseRsaPublicKey(bytes);
} catch (IOException | NullPointerException err) {
throw new RuntimeException(err);
}
}
public static PublicKey getYggdrasilSessionKey() {
return YGGDRASIL_SESSION_KEY;
}
/**
* Verifies a key signature.
*
* @param algorithm the signature algorithm
* @param base the public key to verify with
* @param signature the signature to verify against
* @param toVerify the byte array(s) of data to verify
* @return validity of the signature
*/
public static boolean verifySignature(String algorithm, PublicKey base, byte[] signature, byte[]... toVerify) {
Preconditions.checkArgument(toVerify.length > 0);
try {
Signature construct = Signature.getInstance(algorithm);
construct.initVerify(base);
for (byte[] bytes : toVerify) {
construct.update(bytes);
}
return construct.verify(signature);
} catch (GeneralSecurityException e) {
throw new IllegalArgumentException("Invalid signature parameters");
}
}
/**
* Generates a signature for input data.
*
* @param algorithm the signature algorithm
* @param base the private key to sign with
* @param toSign the byte array(s) of data to sign
* @return the generated signature
*/
public static byte[] generateSignature(String algorithm, PrivateKey base, byte[]... toSign) {
Preconditions.checkArgument(toSign.length > 0);
try {
Signature construct = Signature.getInstance(algorithm);
construct.initSign(base);
for (byte[] bytes : toSign) {
construct.update(bytes);
}
return construct.sign();
} catch (GeneralSecurityException e) {
throw new IllegalArgumentException("Invalid signature parameters");
}
}
/**
* Encodes a long array as Big-endian byte array.
*
* @param bits the long (array) of numbers to encode
* @return the encoded bytes
*/
public static byte[] longToBigEndianByteArray(long... bits) {
ByteBuffer ret = ByteBuffer.allocate(8 * bits.length).order(ByteOrder.BIG_ENDIAN);
for (long put : bits) {
ret.putLong(put);
}
return ret.array();
}
public static String encodeUrlEncoded(byte[] data) {
return MIME_SPECIAL_ENCODER.encodeToString(data);
}
public static byte[] decodeUrlEncoded(String toParse) {
return Base64.getMimeDecoder().decode(toParse);
}
/**
* Parse a cer-encoded RSA key into its key bytes.
*
* @param toParse the cer-encoded key String
* @param descriptors the type of key
* @return the parsed key bytes
*/
public static byte[] parsePemEncoded(String toParse, Pair<String, String> descriptors) {
int startIdx = toParse.indexOf(descriptors.first());
Preconditions.checkArgument(startIdx >= 0);
int firstLen = descriptors.first().length();
int endIdx = toParse.indexOf(descriptors.second(), firstLen + startIdx) + 1;
Preconditions.checkArgument(endIdx > 0);
return decodeUrlEncoded(toParse.substring(startIdx + firstLen, endIdx));
}
/**
* Encodes an RSA key as String cer format.
*
* @param toEncode the private or public RSA key
* @return the encoded key cer
*/
public static String pemEncodeRsaKey(Key toEncode) {
Preconditions.checkNotNull(toEncode);
Pair<String, String> encoder;
if (toEncode instanceof PublicKey) {
encoder = PEM_RSA_PUBLIC_KEY_DESCRIPTOR;
} else if (toEncode instanceof PrivateKey) {
encoder = PEM_RSA_PRIVATE_KEY_DESCRIPTOR;
} else {
throw new IllegalArgumentException("Invalid key type");
}
return encoder.first() + "\n"
+ encodeUrlEncoded(toEncode.getEncoded()) + "\n"
+ encoder.second() + "\n";
}
/**
* Parse an RSA public key from key bytes.
*
* @param keyValue the key bytes
* @return the generated key
*/
public static PublicKey parseRsaPublicKey(byte[] keyValue) {
try {
return RSA_KEY_FACTORY.generatePublic(new X509EncodedKeySpec(keyValue));
} catch (InvalidKeySpecException e) {
throw new IllegalArgumentException("Invalid key bytes");
}
}
/**
* Generates an RSA key pair.
*
* @param keysize the key size (in bits) for the RSA key pair
* @return the generated key pair
*/
public static KeyPair createRsaKeyPair(final int keysize) {
try {
final KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(keysize);
return generator.generateKeyPair();
} catch (final NoSuchAlgorithmException e) {
throw new RuntimeException("Unable to generate RSA keypair", e);
}
}
/**
* Generates a hex digest in two's complement form for use with the Mojang joinedServer endpoint.
*
* @param digest the bytes to digest
* @return the hex digest
*/
public static String twosComplementHexdigest(byte[] digest) {
return new BigInteger(digest).toString(16);
}
/**
* Decrypts an RSA message.
*
* @param keyPair the key pair to use
* @param bytes the bytes of the encrypted message
* @return the decrypted message
* @throws GeneralSecurityException if the message couldn't be decoded
*/
public static byte[] decryptRsa(KeyPair keyPair, byte[] bytes) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
return cipher.doFinal(bytes);
}
/**
* Generates the server ID for the hasJoined endpoint.
*
* @param sharedSecret the shared secret between the client and the proxy
* @param key the RSA public key
* @return the server ID
*/
public static String generateServerId(byte[] sharedSecret, PublicKey key) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.update(sharedSecret);
digest.update(key.getEncoded());
return twosComplementHexdigest(digest.digest());
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
}
}

Datei anzeigen

@ -0,0 +1,98 @@
/*
* 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.velocitypowered.api.proxy.crypto.IdentifiedKey;
import java.nio.charset.StandardCharsets;
import java.security.PublicKey;
import java.time.Instant;
import java.util.Arrays;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
public class IdentifiedKeyImpl implements IdentifiedKey {
private final PublicKey publicKey;
private final byte[] signature;
private final Instant expiryTemporal;
private @MonotonicNonNull Boolean isSignatureValid;
public IdentifiedKeyImpl(byte[] keyBits, long expiry,
byte[] signature) {
this(EncryptionUtils.parseRsaPublicKey(keyBits), Instant.ofEpochMilli(expiry), signature);
}
/**
* Creates an Identified key from data.
*/
public IdentifiedKeyImpl(PublicKey publicKey, Instant expiryTemporal, byte[] signature) {
this.publicKey = publicKey;
this.expiryTemporal = expiryTemporal;
this.signature = signature;
}
@Override
public PublicKey getSignedPublicKey() {
return publicKey;
}
@Override
public PublicKey getSigner() {
return EncryptionUtils.getYggdrasilSessionKey();
}
@Override
public Instant getExpiryTemporal() {
return expiryTemporal;
}
@Override
public byte[] getSignature() {
return signature;
}
@Override
public boolean isSignatureValid() {
if (isSignatureValid == null) {
String pemKey = EncryptionUtils.pemEncodeRsaKey(publicKey);
long expires = expiryTemporal.toEpochMilli();
byte[] toVerify = ("" + expires + pemKey).getBytes(StandardCharsets.US_ASCII);
isSignatureValid = EncryptionUtils.verifySignature(
EncryptionUtils.SHA1_WITH_RSA, EncryptionUtils.getYggdrasilSessionKey(), signature, toVerify);
}
return isSignatureValid;
}
@Override
public boolean verifyDataSignature(byte[] signature, byte[]... toVerify) {
try {
return EncryptionUtils.verifySignature(EncryptionUtils.SHA256_WITH_RSA, publicKey, signature, toVerify);
} catch (IllegalArgumentException e) {
return false;
}
}
@Override
public String toString() {
return "IdentifiedKeyImpl{"
+ "publicKey=" + publicKey
+ ", signature=" + Arrays.toString(signature)
+ ", expiryTemporal=" + expiryTemporal
+ ", isSignatureValid=" + isSignatureValid
+ '}';
}
}

Datei anzeigen

@ -0,0 +1,86 @@
/*
* 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.KeySigned;
import java.security.PublicKey;
import java.time.Instant;
import java.util.Map;
import java.util.UUID;
import org.checkerframework.checker.nullness.qual.Nullable;
public class SignedChatCommand implements KeySigned {
private final String command;
private final PublicKey signer;
private final Instant expiry;
private final byte[] salt;
private final UUID sender;
//private final boolean isValid;
private final boolean isPreviewSigned;
private final Map<String, byte[]> signatures;
/**
* Create a signed command from data.
*/
public SignedChatCommand(String command, PublicKey signer, UUID sender,
Instant expiry, Map<String, byte[]> signature, byte[] salt, boolean isPreviewSigned) {
this.command = Preconditions.checkNotNull(command);
this.signer = Preconditions.checkNotNull(signer);
this.sender = Preconditions.checkNotNull(sender);
this.signatures = Preconditions.checkNotNull(signature);
this.expiry = Preconditions.checkNotNull(expiry);
this.salt = Preconditions.checkNotNull(salt);
this.isPreviewSigned = isPreviewSigned;
}
@Override
public PublicKey getSigner() {
return signer;
}
@Override
public Instant getExpiryTemporal() {
return expiry;
}
@Override
public @Nullable byte[] getSignature() {
return null;
}
@Override
public byte[] getSalt() {
return salt;
}
public String getBaseCommand() {
return command;
}
public Map<String, byte[]> getSignatures() {
return signatures;
}
public boolean isPreviewSigned() {
return isPreviewSigned;
}
}

Datei anzeigen

@ -0,0 +1,111 @@
/*
* 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.google.common.primitives.Longs;
import com.velocitypowered.api.proxy.crypto.SignedMessage;
import com.velocitypowered.proxy.util.except.QuietDecoderException;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.PublicKey;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.TemporalAmount;
import java.util.UUID;
import net.kyori.adventure.text.Component;
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 boolean isPreviewSigned;
/**
* Create a signed message from data.
*/
public SignedChatMessage(String message, PublicKey signer, UUID sender,
Instant expiry, byte[] signature, byte[] salt, boolean isPreviewSigned) {
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.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;
}
//@Override
//public boolean isSignatureValid() {
// return isValid;
//}
@Override
public String getMessage() {
return message;
}
@Override
public UUID getSignerUuid() {
return sender;
}
@Override
public boolean isPreviewSigned() {
return isPreviewSigned;
}
@Override
public byte[] getSalt() {
return salt;
}
}

Datei anzeigen

@ -21,7 +21,9 @@ import static com.google.common.base.Preconditions.checkArgument;
import static com.velocitypowered.proxy.protocol.util.NettyPreconditions.checkFrame; import static com.velocitypowered.proxy.protocol.util.NettyPreconditions.checkFrame;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.crypto.IdentifiedKeyImpl;
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.util.VelocityLegacyHoverEventSerializer; import com.velocitypowered.proxy.protocol.util.VelocityLegacyHoverEventSerializer;
import com.velocitypowered.proxy.util.except.QuietDecoderException; import com.velocitypowered.proxy.util.except.QuietDecoderException;
@ -59,7 +61,7 @@ public enum ProtocolUtils {
.legacyHoverEventSerializer(VelocityLegacyHoverEventSerializer.INSTANCE) .legacyHoverEventSerializer(VelocityLegacyHoverEventSerializer.INSTANCE)
.build(); .build();
private static final int DEFAULT_MAX_STRING_SIZE = 65536; // 64KiB public static final int DEFAULT_MAX_STRING_SIZE = 65536; // 64KiB
private static final QuietDecoderException BAD_VARINT_CACHED = private static final QuietDecoderException BAD_VARINT_CACHED =
new QuietDecoderException("Bad VarInt decoded"); new QuietDecoderException("Bad VarInt decoded");
private static final int[] VARINT_EXACT_BYTE_LENGTHS = new int[33]; private static final int[] VARINT_EXACT_BYTE_LENGTHS = new int[33];
@ -537,6 +539,31 @@ public enum ProtocolUtils {
return PRE_1_16_SERIALIZER; return PRE_1_16_SERIALIZER;
} }
/**
* Writes a players {@link IdentifiedKey} to the buffer.
*
* @param buf the buffer
* @param playerKey the key to write
*/
public static void writePlayerKey(ByteBuf buf, IdentifiedKey playerKey) {
buf.writeLong(playerKey.getExpiryTemporal().toEpochMilli());
ProtocolUtils.writeByteArray(buf, playerKey.getSignedPublicKey().getEncoded());
ProtocolUtils.writeByteArray(buf, playerKey.getSignature());
}
/**
* Reads a players {@link IdentifiedKey} from the buffer.
*
* @param buf the buffer
* @return the key
*/
public static IdentifiedKey readPlayerKey(ByteBuf buf) {
long expiry = buf.readLong();
byte[] key = ProtocolUtils.readByteArray(buf);
byte[] signature = ProtocolUtils.readByteArray(buf, 4096);
return new IdentifiedKeyImpl(key, expiry, signature);
}
public enum Direction { public enum Direction {
SERVERBOUND, SERVERBOUND,
CLIENTBOUND; CLIENTBOUND;

Datei anzeigen

@ -28,6 +28,8 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_16_2;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_16_4; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_16_4;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_17; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_17;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_18; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_18;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_18_2;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_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;
@ -39,7 +41,6 @@ import static com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.packet.AvailableCommands; 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.Chat;
import com.velocitypowered.proxy.protocol.packet.ClientSettings; import com.velocitypowered.proxy.protocol.packet.ClientSettings;
import com.velocitypowered.proxy.protocol.packet.Disconnect; import com.velocitypowered.proxy.protocol.packet.Disconnect;
import com.velocitypowered.proxy.protocol.packet.EncryptionRequest; import com.velocitypowered.proxy.protocol.packet.EncryptionRequest;
@ -63,6 +64,10 @@ 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.chat.PlayerChat;
import com.velocitypowered.proxy.protocol.packet.chat.PlayerCommand;
import com.velocitypowered.proxy.protocol.packet.chat.SystemChat;
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;
@ -114,19 +119,25 @@ public enum StateRegistry {
map(0x02, MINECRAFT_1_12, false), map(0x02, MINECRAFT_1_12, false),
map(0x01, MINECRAFT_1_12_1, false), map(0x01, MINECRAFT_1_12_1, false),
map(0x05, MINECRAFT_1_13, false), map(0x05, MINECRAFT_1_13, false),
map(0x06, MINECRAFT_1_14, false)); map(0x06, MINECRAFT_1_14, false),
serverbound.register(Chat.class, Chat::new, map(0x08, MINECRAFT_1_19, false));
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, false)); map(0x03, MINECRAFT_1_14, MINECRAFT_1_18_2, false));
serverbound.register(PlayerCommand.class, PlayerCommand::new,
map(0x03, MINECRAFT_1_19, false));
serverbound.register(PlayerChat.class, PlayerChat::new,
map(0x04, MINECRAFT_1_19, 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),
map(0x05, MINECRAFT_1_12, false), map(0x05, MINECRAFT_1_12, false),
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));
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),
@ -134,7 +145,8 @@ public enum StateRegistry {
map(0x09, MINECRAFT_1_12_1, false), map(0x09, MINECRAFT_1_12_1, false),
map(0x0A, MINECRAFT_1_13, false), map(0x0A, MINECRAFT_1_13, false),
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));
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),
@ -143,7 +155,8 @@ public enum StateRegistry {
map(0x0E, MINECRAFT_1_13, false), map(0x0E, MINECRAFT_1_13, false),
map(0x0F, MINECRAFT_1_14, false), map(0x0F, MINECRAFT_1_14, false),
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));
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),
@ -151,20 +164,22 @@ public enum StateRegistry {
map(0x1D, MINECRAFT_1_13, false), map(0x1D, MINECRAFT_1_13, false),
map(0x1F, MINECRAFT_1_14, false), map(0x1F, MINECRAFT_1_14, false),
map(0x20, MINECRAFT_1_16, false), map(0x20, MINECRAFT_1_16, false),
map(0x21, MINECRAFT_1_16_2, false)); map(0x21, MINECRAFT_1_16_2, false),
map(0x23, MINECRAFT_1_19, false));
clientbound.register(BossBar.class, BossBar::new, clientbound.register(BossBar.class, BossBar::new,
map(0x0C, MINECRAFT_1_9, false), map(0x0C, MINECRAFT_1_9, false),
map(0x0D, MINECRAFT_1_15, false), map(0x0D, MINECRAFT_1_15, false),
map(0x0C, MINECRAFT_1_16, false), map(0x0C, MINECRAFT_1_16, false),
map(0x0D, MINECRAFT_1_17, false)); map(0x0D, MINECRAFT_1_17, false),
clientbound.register(Chat.class, Chat::new, map(0x0A, MINECRAFT_1_19, false));
clientbound.register(LegacyChat.class, LegacyChat::new,
map(0x02, MINECRAFT_1_7_2, true), map(0x02, MINECRAFT_1_7_2, true),
map(0x0F, MINECRAFT_1_9, true), map(0x0F, MINECRAFT_1_9, true),
map(0x0E, MINECRAFT_1_13, true), map(0x0E, MINECRAFT_1_13, true),
map(0x0F, MINECRAFT_1_15, true), map(0x0F, MINECRAFT_1_15, true),
map(0x0E, MINECRAFT_1_16, true), map(0x0E, MINECRAFT_1_16, true),
map(0x0F, MINECRAFT_1_17, true)); map(0x0F, MINECRAFT_1_17, MINECRAFT_1_18_2, true));
clientbound.register(TabCompleteResponse.class, TabCompleteResponse::new, clientbound.register(TabCompleteResponse.class, TabCompleteResponse::new,
map(0x3A, MINECRAFT_1_7_2, false), map(0x3A, MINECRAFT_1_7_2, false),
map(0x0E, MINECRAFT_1_9, false), map(0x0E, MINECRAFT_1_9, false),
@ -172,13 +187,15 @@ public enum StateRegistry {
map(0x11, MINECRAFT_1_15, false), map(0x11, MINECRAFT_1_15, false),
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));
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));
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),
@ -187,7 +204,8 @@ public enum StateRegistry {
map(0x19, MINECRAFT_1_15, false), map(0x19, MINECRAFT_1_15, false),
map(0x18, MINECRAFT_1_16, false), map(0x18, MINECRAFT_1_16, false),
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));
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),
@ -196,7 +214,8 @@ public enum StateRegistry {
map(0x1B, MINECRAFT_1_15, false), map(0x1B, MINECRAFT_1_15, false),
map(0x1A, MINECRAFT_1_16, false), map(0x1A, MINECRAFT_1_16, false),
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));
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),
@ -205,7 +224,8 @@ public enum StateRegistry {
map(0x21, MINECRAFT_1_15, false), map(0x21, MINECRAFT_1_15, false),
map(0x20, MINECRAFT_1_16, false), map(0x20, MINECRAFT_1_16, false),
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));
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),
@ -214,7 +234,8 @@ public enum StateRegistry {
map(0x26, MINECRAFT_1_15, false), map(0x26, MINECRAFT_1_15, false),
map(0x25, MINECRAFT_1_16, false), map(0x25, MINECRAFT_1_16, false),
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));
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),
@ -225,7 +246,8 @@ public enum StateRegistry {
map(0x3B, MINECRAFT_1_15, true), map(0x3B, MINECRAFT_1_15, true),
map(0x3A, MINECRAFT_1_16, true), map(0x3A, MINECRAFT_1_16, true),
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));
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),
@ -236,7 +258,8 @@ public enum StateRegistry {
map(0x3A, MINECRAFT_1_15, false), map(0x3A, MINECRAFT_1_15, false),
map(0x39, MINECRAFT_1_16, false), map(0x39, MINECRAFT_1_16, false),
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));
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),
@ -248,7 +271,8 @@ public enum StateRegistry {
map(0x54, MINECRAFT_1_15, true), map(0x54, MINECRAFT_1_15, true),
map(0x53, MINECRAFT_1_16, true), map(0x53, MINECRAFT_1_16, true),
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));
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),
@ -265,12 +289,14 @@ public enum StateRegistry {
map(0x59, MINECRAFT_1_17, true), map(0x59, MINECRAFT_1_17, true),
map(0x5A, MINECRAFT_1_18, true)); map(0x5A, MINECRAFT_1_18, 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));
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));
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));
clientbound.register(PlayerListItem.class, PlayerListItem::new, clientbound.register(PlayerListItem.class, PlayerListItem::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),
@ -280,7 +306,10 @@ public enum StateRegistry {
map(0x34, MINECRAFT_1_15, false), map(0x34, MINECRAFT_1_15, false),
map(0x33, MINECRAFT_1_16, false), map(0x33, MINECRAFT_1_16, false),
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));
clientbound.register(SystemChat.class, SystemChat::new,
map(0x5F, MINECRAFT_1_19, true));
} }
}, },
LOGIN { LOGIN {

Datei anzeigen

@ -83,7 +83,7 @@ public class AvailableCommands implements MinecraftPacket {
int commands = ProtocolUtils.readVarInt(buf); int commands = ProtocolUtils.readVarInt(buf);
WireNode[] wireNodes = new WireNode[commands]; WireNode[] wireNodes = new WireNode[commands];
for (int i = 0; i < commands; i++) { for (int i = 0; i < commands; i++) {
wireNodes[i] = deserializeNode(buf, i); wireNodes[i] = deserializeNode(buf, i, protocolVersion);
} }
// Iterate over the deserialized nodes and attempt to form a graph. We also resolve any cycles // Iterate over the deserialized nodes and attempt to form a graph. We also resolve any cycles
@ -130,13 +130,13 @@ public class AvailableCommands implements MinecraftPacket {
// Now serialize the children. // Now serialize the children.
ProtocolUtils.writeVarInt(buf, idMappings.size()); ProtocolUtils.writeVarInt(buf, idMappings.size());
for (CommandNode<CommandSource> child : idMappings.keySet()) { for (CommandNode<CommandSource> child : idMappings.keySet()) {
serializeNode(child, buf, idMappings); serializeNode(child, buf, idMappings, protocolVersion);
} }
ProtocolUtils.writeVarInt(buf, idMappings.getInt(rootNode)); ProtocolUtils.writeVarInt(buf, idMappings.getInt(rootNode));
} }
private static void serializeNode(CommandNode<CommandSource> node, ByteBuf buf, private static void serializeNode(CommandNode<CommandSource> node, ByteBuf buf,
Object2IntMap<CommandNode<CommandSource>> idMappings) { Object2IntMap<CommandNode<CommandSource>> idMappings, ProtocolVersion protocolVersion) {
byte flags = 0; byte flags = 0;
if (node.getRedirect() != null) { if (node.getRedirect() != null) {
flags |= FLAG_IS_REDIRECT; flags |= FLAG_IS_REDIRECT;
@ -168,7 +168,7 @@ public class AvailableCommands implements MinecraftPacket {
if (node instanceof ArgumentCommandNode<?, ?>) { if (node instanceof ArgumentCommandNode<?, ?>) {
ProtocolUtils.writeString(buf, node.getName()); ProtocolUtils.writeString(buf, node.getName());
ArgumentPropertyRegistry.serialize(buf, ArgumentPropertyRegistry.serialize(buf,
((ArgumentCommandNode<CommandSource, ?>) node).getType()); ((ArgumentCommandNode<CommandSource, ?>) node).getType(), protocolVersion);
if (((ArgumentCommandNode<CommandSource, ?>) node).getCustomSuggestions() != null) { if (((ArgumentCommandNode<CommandSource, ?>) node).getCustomSuggestions() != null) {
SuggestionProvider<CommandSource> provider = ((ArgumentCommandNode<CommandSource, ?>) node) SuggestionProvider<CommandSource> provider = ((ArgumentCommandNode<CommandSource, ?>) node)
@ -189,7 +189,7 @@ public class AvailableCommands implements MinecraftPacket {
return handler.handle(this); return handler.handle(this);
} }
private static WireNode deserializeNode(ByteBuf buf, int idx) { private static WireNode deserializeNode(ByteBuf buf, int idx, ProtocolVersion version) {
byte flags = buf.readByte(); byte flags = buf.readByte();
int[] children = ProtocolUtils.readIntegerArray(buf); int[] children = ProtocolUtils.readIntegerArray(buf);
int redirectTo = -1; int redirectTo = -1;
@ -205,14 +205,13 @@ public class AvailableCommands implements MinecraftPacket {
.literal(ProtocolUtils.readString(buf))); .literal(ProtocolUtils.readString(buf)));
case NODE_TYPE_ARGUMENT: case NODE_TYPE_ARGUMENT:
String name = ProtocolUtils.readString(buf); String name = ProtocolUtils.readString(buf);
ArgumentType<?> argumentType = ArgumentPropertyRegistry.deserialize(buf); ArgumentType<?> argumentType = ArgumentPropertyRegistry.deserialize(buf, version);
RequiredArgumentBuilder<CommandSource, ?> argumentBuilder = RequiredArgumentBuilder RequiredArgumentBuilder<CommandSource, ?> argumentBuilder = RequiredArgumentBuilder
.argument(name, argumentType); .argument(name, argumentType);
if ((flags & FLAG_HAS_SUGGESTIONS) != 0) { if ((flags & FLAG_HAS_SUGGESTIONS) != 0) {
argumentBuilder.suggests(new ProtocolSuggestionProvider(ProtocolUtils.readString(buf))); argumentBuilder.suggests(new ProtocolSuggestionProvider(ProtocolUtils.readString(buf)));
} }
return new WireNode(idx, flags, children, redirectTo, argumentBuilder); return new WireNode(idx, flags, children, redirectTo, argumentBuilder);
default: default:
throw new IllegalArgumentException("Unknown node type " + (flags & FLAG_NODE_TYPE)); throw new IllegalArgumentException("Unknown node type " + (flags & FLAG_NODE_TYPE));

Datei anzeigen

@ -24,13 +24,19 @@ 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 com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import com.velocitypowered.proxy.util.except.QuietDecoderException;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Arrays; import java.util.Arrays;
public class EncryptionResponse implements MinecraftPacket { public class EncryptionResponse implements MinecraftPacket {
private final static QuietDecoderException NO_SALT = new QuietDecoderException("Encryption response didn't contain salt");
private byte[] sharedSecret = EMPTY_BYTE_ARRAY; private byte[] sharedSecret = EMPTY_BYTE_ARRAY;
private byte[] verifyToken = EMPTY_BYTE_ARRAY; private byte[] verifyToken = EMPTY_BYTE_ARRAY;
private @Nullable Long salt;
public byte[] getSharedSecret() { public byte[] getSharedSecret() {
return sharedSecret.clone(); return sharedSecret.clone();
@ -40,6 +46,13 @@ public class EncryptionResponse implements MinecraftPacket {
return verifyToken.clone(); return verifyToken.clone();
} }
public long getSalt() {
if (salt == null) {
throw NO_SALT;
}
return salt;
}
@Override @Override
public String toString() { public String toString() {
return "EncryptionResponse{" return "EncryptionResponse{"
@ -52,7 +65,14 @@ public class EncryptionResponse implements MinecraftPacket {
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
this.sharedSecret = ProtocolUtils.readByteArray(buf, 128); this.sharedSecret = ProtocolUtils.readByteArray(buf, 128);
this.verifyToken = ProtocolUtils.readByteArray(buf, 128);
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0
&& !buf.readBoolean()) {
salt = buf.readLong();
}
this.verifyToken = ProtocolUtils.readByteArray(buf,
version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0 ? 256 : 128);
} else { } else {
this.sharedSecret = ProtocolUtils.readByteArray17(buf); this.sharedSecret = ProtocolUtils.readByteArray17(buf);
this.verifyToken = ProtocolUtils.readByteArray17(buf); this.verifyToken = ProtocolUtils.readByteArray17(buf);
@ -63,6 +83,14 @@ 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 (salt != null) {
buf.writeBoolean(false);
buf.writeLong(salt);
} else {
buf.writeBoolean(true);
}
}
ProtocolUtils.writeByteArray(buf, verifyToken); ProtocolUtils.writeByteArray(buf, verifyToken);
} else { } else {
ProtocolUtils.writeByteArray17(sharedSecret, buf, false); ProtocolUtils.writeByteArray17(sharedSecret, buf, false);
@ -79,11 +107,22 @@ public class EncryptionResponse implements MinecraftPacket {
public int expectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) { public int expectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) {
// 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.
return 260; int base = 256 + 2 + 2;
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
// Verify token is twice as long on 1.19+
// Additional 1 byte for left <> right and 8 bytes for salt
base += 128 + 8 + 1;
}
return base;
} }
@Override @Override
public int expectedMinLength(ByteBuf buf, Direction direction, ProtocolVersion version) { public int expectedMinLength(ByteBuf buf, Direction direction, ProtocolVersion version) {
return expectedMaxLength(buf, direction, version); int base = expectedMaxLength(buf, direction, version);
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
// These are "optional"
base -= 128 + 8;
}
return base;
} }
} }

Datei anzeigen

@ -25,6 +25,7 @@ import com.velocitypowered.proxy.connection.registry.DimensionInfo;
import com.velocitypowered.proxy.connection.registry.DimensionRegistry; import com.velocitypowered.proxy.connection.registry.DimensionRegistry;
import com.velocitypowered.proxy.protocol.*; import com.velocitypowered.proxy.protocol.*;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import it.unimi.dsi.fastutil.Pair;
import net.kyori.adventure.nbt.BinaryTagIO; import net.kyori.adventure.nbt.BinaryTagIO;
import net.kyori.adventure.nbt.BinaryTagTypes; import net.kyori.adventure.nbt.BinaryTagTypes;
import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.nbt.CompoundBinaryTag;
@ -33,7 +34,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
public class JoinGame implements MinecraftPacket { public class JoinGame implements MinecraftPacket {
private static final BinaryTagIO.Reader JOINGAME_READER = BinaryTagIO.reader(2 * 1024 * 1024); private static final BinaryTagIO.Reader JOINGAME_READER = BinaryTagIO.reader(4 * 1024 * 1024);
private int entityId; private int entityId;
private short gamemode; private short gamemode;
private int dimension; private int dimension;
@ -51,6 +52,8 @@ public class JoinGame implements MinecraftPacket {
private short previousGamemode; // 1.16+ private short previousGamemode; // 1.16+
private CompoundBinaryTag biomeRegistry; // 1.16.2+ private CompoundBinaryTag biomeRegistry; // 1.16.2+
private int simulationDistance; // 1.18+ private int simulationDistance; // 1.18+
private @Nullable Pair<String, Long> lastDeathPosition;
private CompoundBinaryTag chatTypeRegistry; // placeholder, 1.19+
public int getEntityId() { public int getEntityId() {
return entityId; return entityId;
@ -172,6 +175,22 @@ public class JoinGame implements MinecraftPacket {
this.simulationDistance = simulationDistance; this.simulationDistance = simulationDistance;
} }
public Pair<String, Long> getLastDeathPosition() {
return lastDeathPosition;
}
public void setLastDeathPosition(Pair<String, Long> lastDeathPosition) {
this.lastDeathPosition = lastDeathPosition;
}
public CompoundBinaryTag getChatTypeRegistry() {
return chatTypeRegistry;
}
public void setChatTypeRegistry(CompoundBinaryTag chatTypeRegistry) {
this.chatTypeRegistry = chatTypeRegistry;
}
@Override @Override
public String toString() { public String toString() {
return "JoinGame{" return "JoinGame{"
@ -188,6 +207,7 @@ public class JoinGame implements MinecraftPacket {
+ ", dimensionInfo='" + dimensionInfo + '\'' + ", dimensionInfo='" + dimensionInfo + '\''
+ ", previousGamemode=" + previousGamemode + ", previousGamemode=" + previousGamemode
+ ", simulationDistance=" + simulationDistance + ", simulationDistance=" + simulationDistance
+ ", lastDeathPosition='" + lastDeathPosition + '\''
+ '}'; + '}';
} }
@ -253,6 +273,7 @@ public class JoinGame implements MinecraftPacket {
dimensionRegistryContainer = registryContainer.getCompound("minecraft:dimension_type") dimensionRegistryContainer = registryContainer.getCompound("minecraft:dimension_type")
.getList("value", BinaryTagTypes.COMPOUND); .getList("value", BinaryTagTypes.COMPOUND);
this.biomeRegistry = registryContainer.getCompound("minecraft:worldgen/biome"); this.biomeRegistry = registryContainer.getCompound("minecraft:worldgen/biome");
this.chatTypeRegistry = registryContainer.getCompound("minecraft:chat_type");
} else { } else {
dimensionRegistryContainer = registryContainer.getList("dimension", dimensionRegistryContainer = registryContainer.getList("dimension",
BinaryTagTypes.COMPOUND); BinaryTagTypes.COMPOUND);
@ -263,7 +284,8 @@ public class JoinGame implements MinecraftPacket {
String dimensionIdentifier; String dimensionIdentifier;
String levelName = null; String levelName = null;
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0
&& version.compareTo(ProtocolVersion.MINECRAFT_1_19) < 0) {
CompoundBinaryTag currentDimDataTag = ProtocolUtils.readCompoundTag(buf, JOINGAME_READER); CompoundBinaryTag currentDimDataTag = ProtocolUtils.readCompoundTag(buf, JOINGAME_READER);
dimensionIdentifier = ProtocolUtils.readString(buf); dimensionIdentifier = ProtocolUtils.readString(buf);
this.currentDimensionData = DimensionData.decodeBaseCompoundTag(currentDimDataTag, version) this.currentDimensionData = DimensionData.decodeBaseCompoundTag(currentDimDataTag, version)
@ -290,6 +312,10 @@ public class JoinGame 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);
// optional death location
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0 && buf.readBoolean()) {
this.lastDeathPosition = Pair.of(ProtocolUtils.readString(buf), buf.readLong());
}
} }
@Override @Override
@ -356,11 +382,13 @@ public class JoinGame implements MinecraftPacket {
dimensionRegistryEntry.put("value", encodedDimensionRegistry); dimensionRegistryEntry.put("value", encodedDimensionRegistry);
registryContainer.put("minecraft:dimension_type", dimensionRegistryEntry.build()); registryContainer.put("minecraft:dimension_type", dimensionRegistryEntry.build());
registryContainer.put("minecraft:worldgen/biome", biomeRegistry); registryContainer.put("minecraft:worldgen/biome", biomeRegistry);
registryContainer.put("minecraft:chat_type", chatTypeRegistry);
} else { } else {
registryContainer.put("dimension", encodedDimensionRegistry); registryContainer.put("dimension", encodedDimensionRegistry);
} }
ProtocolUtils.writeCompoundTag(buf, registryContainer.build()); ProtocolUtils.writeCompoundTag(buf, registryContainer.build());
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0
&& version.compareTo(ProtocolVersion.MINECRAFT_1_19) < 0) {
ProtocolUtils.writeCompoundTag(buf, currentDimensionData.serializeDimensionDetails()); ProtocolUtils.writeCompoundTag(buf, currentDimensionData.serializeDimensionDetails());
ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier()); ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier());
} else { } else {
@ -382,6 +410,17 @@ public class JoinGame implements MinecraftPacket {
buf.writeBoolean(showRespawnScreen); buf.writeBoolean(showRespawnScreen);
buf.writeBoolean(dimensionInfo.isDebugType()); buf.writeBoolean(dimensionInfo.isDebugType());
buf.writeBoolean(dimensionInfo.isFlat()); buf.writeBoolean(dimensionInfo.isFlat());
// optional death location
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
if (lastDeathPosition != null) {
buf.writeBoolean(true);
ProtocolUtils.writeString(buf, lastDeathPosition.key());
buf.writeLong(lastDeathPosition.value());
} else {
buf.writeBoolean(false);
}
}
} }
@Override @Override

Datei anzeigen

@ -19,6 +19,7 @@ package com.velocitypowered.proxy.protocol.packet;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
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.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
@ -74,6 +75,12 @@ public class PlayerListItem implements MinecraftPacket {
item.setGameMode(ProtocolUtils.readVarInt(buf)); item.setGameMode(ProtocolUtils.readVarInt(buf));
item.setLatency(ProtocolUtils.readVarInt(buf)); item.setLatency(ProtocolUtils.readVarInt(buf));
item.setDisplayName(readOptionalComponent(buf, version)); item.setDisplayName(readOptionalComponent(buf, version));
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
if (buf.readBoolean()) {
item.setPlayerKey(ProtocolUtils.readPlayerKey(buf));
}
}
break; break;
case UPDATE_GAMEMODE: case UPDATE_GAMEMODE:
item.setGameMode(ProtocolUtils.readVarInt(buf)); item.setGameMode(ProtocolUtils.readVarInt(buf));
@ -124,8 +131,15 @@ public class PlayerListItem implements MinecraftPacket {
ProtocolUtils.writeProperties(buf, item.getProperties()); ProtocolUtils.writeProperties(buf, item.getProperties());
ProtocolUtils.writeVarInt(buf, item.getGameMode()); ProtocolUtils.writeVarInt(buf, item.getGameMode());
ProtocolUtils.writeVarInt(buf, item.getLatency()); ProtocolUtils.writeVarInt(buf, item.getLatency());
writeDisplayName(buf, item.getDisplayName(), version); writeDisplayName(buf, item.getDisplayName(), version);
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
if (item.getPlayerKey() != null) {
buf.writeBoolean(true);
ProtocolUtils.writePlayerKey(buf, item.getPlayerKey());
} else {
buf.writeBoolean(false);
}
}
break; break;
case UPDATE_GAMEMODE: case UPDATE_GAMEMODE:
ProtocolUtils.writeVarInt(buf, item.getGameMode()); ProtocolUtils.writeVarInt(buf, item.getGameMode());
@ -181,6 +195,7 @@ public class PlayerListItem implements MinecraftPacket {
private int gameMode; private int gameMode;
private int latency; private int latency;
private @Nullable Component displayName; private @Nullable Component displayName;
private @Nullable IdentifiedKey playerKey;
public Item() { public Item() {
uuid = null; uuid = null;
@ -196,6 +211,7 @@ public class PlayerListItem implements MinecraftPacket {
.setProperties(entry.getProfile().getProperties()) .setProperties(entry.getProfile().getProperties())
.setLatency(entry.getLatency()) .setLatency(entry.getLatency())
.setGameMode(entry.getGameMode()) .setGameMode(entry.getGameMode())
.setPlayerKey(entry.getIdentifiedKey())
.setDisplayName(entry.getDisplayNameComponent().orElse(null)); .setDisplayName(entry.getDisplayNameComponent().orElse(null));
} }
@ -247,5 +263,14 @@ public class PlayerListItem implements MinecraftPacket {
this.displayName = displayName; this.displayName = displayName;
return this; return this;
} }
public Item setPlayerKey(IdentifiedKey playerKey) {
this.playerKey = playerKey;
return this;
}
public IdentifiedKey getPlayerKey() {
return playerKey;
}
} }
} }

Datei anzeigen

@ -24,8 +24,10 @@ import com.velocitypowered.proxy.connection.registry.DimensionInfo;
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 it.unimi.dsi.fastutil.Pair;
import net.kyori.adventure.nbt.BinaryTagIO; import net.kyori.adventure.nbt.BinaryTagIO;
import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.nbt.CompoundBinaryTag;
import org.checkerframework.checker.nullness.qual.Nullable;
public class Respawn implements MinecraftPacket { public class Respawn implements MinecraftPacket {
@ -38,13 +40,14 @@ public class Respawn implements MinecraftPacket {
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+
private @Nullable Pair<String, Long> lastDeathPosition; // 1.19+
public Respawn() { public Respawn() {
} }
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, boolean shouldKeepPlayerData, DimensionInfo dimensionInfo,
short previousGamemode, DimensionData currentDimensionData) { 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;
@ -54,6 +57,7 @@ public class Respawn implements MinecraftPacket {
this.dimensionInfo = dimensionInfo; this.dimensionInfo = dimensionInfo;
this.previousGamemode = previousGamemode; this.previousGamemode = previousGamemode;
this.currentDimensionData = currentDimensionData; this.currentDimensionData = currentDimensionData;
this.lastDeathPosition = lastDeathPosition;
} }
public int getDimension() { public int getDimension() {
@ -112,6 +116,14 @@ public class Respawn implements MinecraftPacket {
this.previousGamemode = previousGamemode; this.previousGamemode = previousGamemode;
} }
public void setLastDeathPosition(Pair<String, Long> lastDeathPosition) {
this.lastDeathPosition = lastDeathPosition;
}
public Pair<String, Long> getLastDeathPosition() {
return lastDeathPosition;
}
@Override @Override
public String toString() { public String toString() {
return "Respawn{" return "Respawn{"
@ -133,7 +145,8 @@ public class Respawn implements MinecraftPacket {
String dimensionIdentifier = null; String dimensionIdentifier = null;
String levelName = null; String levelName = null;
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0
&& version.compareTo(ProtocolVersion.MINECRAFT_1_19) < 0) {
CompoundBinaryTag dimDataTag = ProtocolUtils.readCompoundTag(buf, BinaryTagIO.reader()); CompoundBinaryTag dimDataTag = ProtocolUtils.readCompoundTag(buf, BinaryTagIO.reader());
dimensionIdentifier = ProtocolUtils.readString(buf); dimensionIdentifier = ProtocolUtils.readString(buf);
this.currentDimensionData = DimensionData.decodeBaseCompoundTag(dimDataTag, version) this.currentDimensionData = DimensionData.decodeBaseCompoundTag(dimDataTag, version)
@ -161,12 +174,16 @@ public class Respawn implements MinecraftPacket {
} else { } else {
this.levelType = ProtocolUtils.readString(buf, 16); this.levelType = ProtocolUtils.readString(buf, 16);
} }
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0 && buf.readBoolean()) {
this.lastDeathPosition = Pair.of(ProtocolUtils.readString(buf), buf.readLong());
}
} }
@Override @Override
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_16) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0
&& version.compareTo(ProtocolVersion.MINECRAFT_1_19) < 0) {
ProtocolUtils.writeCompoundTag(buf, currentDimensionData.serializeDimensionDetails()); ProtocolUtils.writeCompoundTag(buf, currentDimensionData.serializeDimensionDetails());
ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier()); ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier());
} else { } else {
@ -191,6 +208,17 @@ public class Respawn implements MinecraftPacket {
} else { } else {
ProtocolUtils.writeString(buf, levelType); ProtocolUtils.writeString(buf, levelType);
} }
// optional death location
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
if (lastDeathPosition != null) {
buf.writeBoolean(true);
ProtocolUtils.writeString(buf, lastDeathPosition.key());
buf.writeLong(lastDeathPosition.value());
} else {
buf.writeBoolean(false);
}
}
} }
@Override @Override

Datei anzeigen

@ -19,6 +19,7 @@ package com.velocitypowered.proxy.protocol.packet;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
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.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
@ -32,11 +33,12 @@ 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
public ServerLogin() { public ServerLogin() {
} }
public ServerLogin(String username) { public ServerLogin(String username, @Nullable IdentifiedKey playerKey) {
this.username = Preconditions.checkNotNull(username, "username"); this.username = Preconditions.checkNotNull(username, "username");
} }
@ -47,10 +49,19 @@ public class ServerLogin implements MinecraftPacket {
return username; return username;
} }
public IdentifiedKey getPlayerKey() {
return playerKey;
}
public void setPlayerKey(IdentifiedKey playerKey) {
this.playerKey = playerKey;
}
@Override @Override
public String toString() { public String toString() {
return "ServerLogin{" return "ServerLogin{"
+ "username='" + username + '\'' + "username='" + username + '\''
+ "playerKey='" + playerKey + '\''
+ '}'; + '}';
} }
@ -60,6 +71,12 @@ public class ServerLogin implements MinecraftPacket {
if (username.isEmpty()) { if (username.isEmpty()) {
throw EMPTY_USERNAME; throw EMPTY_USERNAME;
} }
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
if (buf.readBoolean()) {
playerKey = ProtocolUtils.readPlayerKey(buf);
}
}
} }
@Override @Override
@ -68,13 +85,27 @@ public class ServerLogin implements MinecraftPacket {
throw new IllegalStateException("No username found!"); throw new IllegalStateException("No username found!");
} }
ProtocolUtils.writeString(buf, username); ProtocolUtils.writeString(buf, username);
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
if (playerKey != null) {
buf.writeBoolean(true);
ProtocolUtils.writePlayerKey(buf, playerKey);
} else {
buf.writeBoolean(false);
}
}
} }
@Override @Override
public int expectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) { public int expectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) {
// Accommodate the rare (but likely malicious) use of UTF-8 usernames, since it is technically // Accommodate the rare (but likely malicious) use of UTF-8 usernames, since it is technically
// legal on the protocol level. // legal on the protocol level.
return 1 + (16 * 4); int base = 1 + (16 * 4);
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
return -1;
//TODO: ## 19
}
return base;
} }
@Override @Override

Datei anzeigen

@ -18,11 +18,14 @@
package com.velocitypowered.proxy.protocol.packet; package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.api.util.UuidUtils; import com.velocitypowered.api.util.UuidUtils;
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.List;
import java.util.UUID; import java.util.UUID;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
@ -30,6 +33,7 @@ public class ServerLoginSuccess implements MinecraftPacket {
private @Nullable UUID uuid; private @Nullable UUID uuid;
private @Nullable String username; private @Nullable String username;
private @Nullable List<GameProfile.Property> properties;
public UUID getUuid() { public UUID getUuid() {
if (uuid == null) { if (uuid == null) {
@ -53,17 +57,28 @@ public class ServerLoginSuccess implements MinecraftPacket {
this.username = username; this.username = username;
} }
public List<GameProfile.Property> getProperties() {
return properties;
}
public void setProperties(List<GameProfile.Property> properties) {
this.properties = properties;
}
@Override @Override
public String toString() { public String toString() {
return "ServerLoginSuccess{" return "ServerLoginSuccess{"
+ "uuid=" + uuid + "uuid=" + uuid
+ ", username='" + username + '\'' + ", username='" + username + '\''
+ ", properties='" + properties + '\''
+ '}'; + '}';
} }
@Override @Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
uuid = ProtocolUtils.readUuid(buf);
} else if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) {
uuid = ProtocolUtils.readUuidIntArray(buf); uuid = ProtocolUtils.readUuidIntArray(buf);
} else if (version.compareTo(ProtocolVersion.MINECRAFT_1_7_6) >= 0) { } else if (version.compareTo(ProtocolVersion.MINECRAFT_1_7_6) >= 0) {
uuid = UUID.fromString(ProtocolUtils.readString(buf, 36)); uuid = UUID.fromString(ProtocolUtils.readString(buf, 36));
@ -71,6 +86,10 @@ public class ServerLoginSuccess implements MinecraftPacket {
uuid = UuidUtils.fromUndashed(ProtocolUtils.readString(buf, 32)); uuid = UuidUtils.fromUndashed(ProtocolUtils.readString(buf, 32));
} }
username = ProtocolUtils.readString(buf, 16); username = ProtocolUtils.readString(buf, 16);
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
properties = ProtocolUtils.readProperties(buf);
}
} }
@Override @Override
@ -78,7 +97,9 @@ public class ServerLoginSuccess implements MinecraftPacket {
if (uuid == null) { if (uuid == null) {
throw new IllegalStateException("No UUID specified!"); throw new IllegalStateException("No UUID specified!");
} }
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
ProtocolUtils.writeUuid(buf, uuid);
} else if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) {
ProtocolUtils.writeUuidIntArray(buf, uuid); ProtocolUtils.writeUuidIntArray(buf, uuid);
} else if (version.compareTo(ProtocolVersion.MINECRAFT_1_7_6) >= 0) { } else if (version.compareTo(ProtocolVersion.MINECRAFT_1_7_6) >= 0) {
ProtocolUtils.writeString(buf, uuid.toString()); ProtocolUtils.writeString(buf, uuid.toString());
@ -89,6 +110,14 @@ public class ServerLoginSuccess implements MinecraftPacket {
throw new IllegalStateException("No username specified!"); throw new IllegalStateException("No username specified!");
} }
ProtocolUtils.writeString(buf, username); ProtocolUtils.writeString(buf, username);
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
if (properties == null) {
ProtocolUtils.writeVarInt(buf, 0);
} else {
ProtocolUtils.writeProperties(buf, properties);
}
}
} }
@Override @Override

Datei anzeigen

@ -0,0 +1,99 @@
/*
* Copyright (C) 2018 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol.packet.brigadier;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.velocitypowered.api.network.ProtocolVersion;
import java.util.HashMap;
import java.util.Map;
import org.checkerframework.checker.nullness.qual.Nullable;
public class ArgumentIdentifier {
private final String identifier;
private final Map<ProtocolVersion, Integer> versionById;
private ArgumentIdentifier(String identifier, VersionSet... versions) {
this.identifier = Preconditions.checkNotNull(identifier);
Preconditions.checkNotNull(versions);
Map<ProtocolVersion, Integer> temp = new HashMap<>();
ProtocolVersion previous = null;
for (int i = 0; i < versions.length; i++) {
VersionSet current = Preconditions.checkNotNull(versions[i]);
Preconditions.checkArgument(current.getVersion().compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0,
"Version too old for ID index");
Preconditions.checkArgument(previous == null || previous.compareTo(current.getVersion()) > 0,
"Invalid protocol version order");
for (ProtocolVersion v : ProtocolVersion.values()) {
if (v.compareTo(current.getVersion()) >= 0) {
temp.put(v, current.getId());
}
}
previous = current.getVersion();
}
this.versionById = ImmutableMap.copyOf(temp);
}
public String getIdentifier() {
return identifier;
}
public @Nullable Integer getIdByProtocolVersion(ProtocolVersion version) {
return versionById.get(Preconditions.checkNotNull(version));
}
public static VersionSet mapSet(ProtocolVersion version, int id) {
return new VersionSet(version, id);
}
public static ArgumentIdentifier id(String identifier, VersionSet... versions) {
return new ArgumentIdentifier(identifier, versions);
}
/**
* This class is purely for convenience.
*/
public static class VersionSet {
private final ProtocolVersion version;
private final int id;
private VersionSet(ProtocolVersion version, int id) {
this.version = Preconditions.checkNotNull(version);
this.id = id;
}
public int getId() {
return id;
}
public ProtocolVersion getVersion() {
return version;
}
}
}

Datei anzeigen

@ -17,6 +17,9 @@
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.proxy.protocol.packet.brigadier.ArgumentIdentifier.id;
import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.mapSet;
import static com.velocitypowered.proxy.protocol.packet.brigadier.DoubleArgumentPropertySerializer.DOUBLE; import static com.velocitypowered.proxy.protocol.packet.brigadier.DoubleArgumentPropertySerializer.DOUBLE;
import static com.velocitypowered.proxy.protocol.packet.brigadier.EmptyArgumentPropertySerializer.EMPTY; import static com.velocitypowered.proxy.protocol.packet.brigadier.EmptyArgumentPropertySerializer.EMPTY;
import static com.velocitypowered.proxy.protocol.packet.brigadier.FloatArgumentPropertySerializer.FLOAT; import static com.velocitypowered.proxy.protocol.packet.brigadier.FloatArgumentPropertySerializer.FLOAT;
@ -25,6 +28,7 @@ import static com.velocitypowered.proxy.protocol.packet.brigadier.LongArgumentPr
import static com.velocitypowered.proxy.protocol.packet.brigadier.ModArgumentPropertySerializer.MOD; import static com.velocitypowered.proxy.protocol.packet.brigadier.ModArgumentPropertySerializer.MOD;
import static com.velocitypowered.proxy.protocol.packet.brigadier.StringArgumentPropertySerializer.STRING; import static com.velocitypowered.proxy.protocol.packet.brigadier.StringArgumentPropertySerializer.STRING;
import com.google.common.base.Preconditions;
import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.arguments.BoolArgumentType; import com.mojang.brigadier.arguments.BoolArgumentType;
import com.mojang.brigadier.arguments.DoubleArgumentType; import com.mojang.brigadier.arguments.DoubleArgumentType;
@ -32,35 +36,38 @@ import com.mojang.brigadier.arguments.FloatArgumentType;
import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.LongArgumentType; import com.mojang.brigadier.arguments.LongArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.arguments.StringArgumentType;
import com.velocitypowered.api.network.ProtocolVersion;
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.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.checkerframework.checker.nullness.qual.Nullable;
public class ArgumentPropertyRegistry { public class ArgumentPropertyRegistry {
private ArgumentPropertyRegistry() { private ArgumentPropertyRegistry() {
throw new AssertionError(); throw new AssertionError();
} }
private static final Map<String, ArgumentPropertySerializer<?>> byId = new HashMap<>(); private static final Map<ArgumentIdentifier, ArgumentPropertySerializer<?>> byIdentifier =
new HashMap<>();
private static final Map<Class<? extends ArgumentType>, private static final Map<Class<? extends ArgumentType>,
ArgumentPropertySerializer<?>> byClass = new HashMap<>(); ArgumentPropertySerializer<?>> byClass = new HashMap<>();
private static final Map<Class<? extends ArgumentType>, String> classToId = new HashMap<>(); private static final Map<Class<? extends ArgumentType>, ArgumentIdentifier> classToId =
new HashMap<>();
private static <T extends ArgumentType<?>> void register(String identifier, Class<T> klazz, private static <T extends ArgumentType<?>> void register(ArgumentIdentifier identifier,
ArgumentPropertySerializer<T> serializer) { Class<T> klazz, ArgumentPropertySerializer<T> serializer) {
byId.put(identifier, serializer); byIdentifier.put(identifier, serializer);
byClass.put(klazz, serializer); byClass.put(klazz, serializer);
classToId.put(klazz, identifier); classToId.put(klazz, identifier);
} }
private static <T> void empty(String identifier) { private static <T> void empty(ArgumentIdentifier identifier) {
empty(identifier, EMPTY); empty(identifier, EMPTY);
} }
private static <T> void empty(String identifier, ArgumentPropertySerializer<T> serializer) { private static <T> void empty(ArgumentIdentifier identifier,
byId.put(identifier, serializer); ArgumentPropertySerializer<T> serializer) {
byIdentifier.put(identifier, serializer);
} }
/** /**
@ -68,13 +75,14 @@ public class ArgumentPropertyRegistry {
* @param buf the buffer to deserialize * @param buf the buffer to deserialize
* @return the deserialized {@link ArgumentType} * @return the deserialized {@link ArgumentType}
*/ */
public static ArgumentType<?> deserialize(ByteBuf buf) { public static ArgumentType<?> deserialize(ByteBuf buf, ProtocolVersion protocolVersion) {
String identifier = ProtocolUtils.readString(buf); ArgumentIdentifier identifier = readIdentifier(buf, protocolVersion);
ArgumentPropertySerializer<?> serializer = byId.get(identifier);
ArgumentPropertySerializer<?> serializer = byIdentifier.get(identifier);
if (serializer == null) { if (serializer == null) {
throw new IllegalArgumentException("Argument type identifier " + identifier + " unknown."); throw new IllegalArgumentException("Argument type identifier " + identifier + " unknown.");
} }
Object result = serializer.deserialize(buf); Object result = serializer.deserialize(buf, protocolVersion);
if (result instanceof ArgumentType) { if (result instanceof ArgumentType) {
return (ArgumentType<?>) result; return (ArgumentType<?>) result;
@ -88,95 +96,144 @@ public class ArgumentPropertyRegistry {
* @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
*/ */
public static void serialize(ByteBuf buf, ArgumentType<?> type) { public static void serialize(ByteBuf buf, ArgumentType<?> type,
ProtocolVersion protocolVersion) {
if (type instanceof PassthroughProperty) { if (type instanceof PassthroughProperty) {
PassthroughProperty property = (PassthroughProperty) type; PassthroughProperty property = (PassthroughProperty) type;
ProtocolUtils.writeString(buf, property.getIdentifier()); writeIdentifier(buf, property.getIdentifier(), protocolVersion);
if (property.getResult() != null) { if (property.getResult() != null) {
property.getSerializer().serialize(property.getResult(), buf); property.getSerializer().serialize(property.getResult(), buf, protocolVersion);
} }
} else if (type instanceof ModArgumentProperty) { } else if (type instanceof ModArgumentProperty) {
ModArgumentProperty property = (ModArgumentProperty) type; ModArgumentProperty property = (ModArgumentProperty) type;
ProtocolUtils.writeString(buf, property.getIdentifier()); writeIdentifier(buf, property.getIdentifier(), protocolVersion);
buf.writeBytes(property.getData()); buf.writeBytes(property.getData());
} else { } else {
ArgumentPropertySerializer serializer = byClass.get(type.getClass()); ArgumentPropertySerializer serializer = byClass.get(type.getClass());
String id = classToId.get(type.getClass()); ArgumentIdentifier id = classToId.get(type.getClass());
if (serializer == null || id == null) { if (serializer == null || id == null) {
throw new IllegalArgumentException("Don't know how to serialize " throw new IllegalArgumentException("Don't know how to serialize "
+ type.getClass().getName()); + type.getClass().getName());
} }
ProtocolUtils.writeString(buf, id); writeIdentifier(buf, id, protocolVersion);
serializer.serialize(type, buf); serializer.serialize(type, buf, protocolVersion);
} }
} }
/**
* Writes the {@link ArgumentIdentifier} to a version-specific buffer.
* @param buf the buffer to write to
* @param identifier the identifier to write
* @param protocolVersion the protocol version to use
*/
public static void writeIdentifier(ByteBuf buf, ArgumentIdentifier identifier,
ProtocolVersion protocolVersion) {
if (protocolVersion.compareTo(MINECRAFT_1_19) >= 0) {
Integer id = identifier.getIdByProtocolVersion(protocolVersion);
Preconditions.checkNotNull(id, "Don't know how to serialize type " + identifier);
ProtocolUtils.writeVarInt(buf, id);
} else {
ProtocolUtils.writeString(buf, identifier.getIdentifier());
}
}
/**
* Reads the {@link ArgumentIdentifier} from a version-specific buffer.
* @param buf the buffer to write to
* @param protocolVersion the protocol version to use
* @return the identifier read from the buffer
*/
public static ArgumentIdentifier readIdentifier(ByteBuf buf, ProtocolVersion protocolVersion) {
if (protocolVersion.compareTo(MINECRAFT_1_19) >= 0) {
int id = ProtocolUtils.readVarInt(buf);
for (ArgumentIdentifier i : byIdentifier.keySet()) {
Integer v = i.getIdByProtocolVersion(protocolVersion);
if (v != null && v == id) {
return i;
}
}
} else {
String identifier = ProtocolUtils.readString(buf);
for (ArgumentIdentifier i : byIdentifier.keySet()) {
if (i.getIdentifier().equals(identifier)) {
return i;
}
}
}
return null;
}
static { static {
// Base Brigadier argument types // Base Brigadier argument types
register("brigadier:string", StringArgumentType.class, STRING); register(id("brigadier:bool", mapSet(MINECRAFT_1_19, 0)), BoolArgumentType.class,
register("brigadier:integer", IntegerArgumentType.class, INTEGER);
register("brigadier:float", FloatArgumentType.class, FLOAT);
register("brigadier:double", DoubleArgumentType.class, DOUBLE);
register("brigadier:bool", BoolArgumentType.class,
new ArgumentPropertySerializer<>() { new ArgumentPropertySerializer<>() {
@Override @Override
public BoolArgumentType deserialize(ByteBuf buf) { public BoolArgumentType deserialize(ByteBuf buf, ProtocolVersion protocolVersion) {
return BoolArgumentType.bool(); return BoolArgumentType.bool();
} }
@Override @Override
public void serialize(BoolArgumentType object, ByteBuf buf) { public void serialize(BoolArgumentType object, ByteBuf buf,
ProtocolVersion protocolVersion) {
} }
}); });
register("brigadier:long", LongArgumentType.class, LONG); register(id("brigadier:float", mapSet(MINECRAFT_1_19, 1)), FloatArgumentType.class, FLOAT);
register("minecraft:resource", RegistryKeyArgument.class, RegistryKeyArgumentSerializer.REGISTRY); register(id("brigadier:double", mapSet(MINECRAFT_1_19, 2)), DoubleArgumentType.class, DOUBLE);
register("minecraft:resource_or_tag", RegistryKeyArgument.class, RegistryKeyArgumentSerializer.REGISTRY); register(id("brigadier:integer", mapSet(MINECRAFT_1_19, 3)), IntegerArgumentType.class, INTEGER);
register(id("brigadier:long", mapSet(MINECRAFT_1_19, 4)), LongArgumentType.class, LONG);
register(id("brigadier:string", mapSet(MINECRAFT_1_19, 5)), StringArgumentType.class, STRING);
empty(id("minecraft:entity", mapSet(MINECRAFT_1_19, 6)), ByteArgumentPropertySerializer.BYTE);
empty(id("minecraft:game_profile", mapSet(MINECRAFT_1_19, 7)));
empty(id("minecraft:block_pos", mapSet(MINECRAFT_1_19, 8)));
empty(id("minecraft:column_pos", mapSet(MINECRAFT_1_19, 9)));
empty(id("minecraft:vec3", mapSet(MINECRAFT_1_19, 10)));
empty(id("minecraft:vec2", mapSet(MINECRAFT_1_19, 11)));
empty(id("minecraft:block_state", mapSet(MINECRAFT_1_19, 12)));
empty(id("minecraft:block_predicate", mapSet(MINECRAFT_1_19, 13)));
empty(id("minecraft:item_stack", mapSet(MINECRAFT_1_19, 14)));
empty(id("minecraft:item_predicate", mapSet(MINECRAFT_1_19, 15)));
empty(id("minecraft:color", mapSet(MINECRAFT_1_19, 16)));
empty(id("minecraft:component", mapSet(MINECRAFT_1_19, 17)));
empty(id("minecraft:message", mapSet(MINECRAFT_1_19, 18)));
empty(id("minecraft:nbt_compound_tag", mapSet(MINECRAFT_1_19, 19))); // added in 1.14
empty(id("minecraft:nbt_tag", mapSet(MINECRAFT_1_19, 20))); // added in 1.14
empty(id("minecraft:nbt_path", mapSet(MINECRAFT_1_19, 21)));
empty(id("minecraft:objective", mapSet(MINECRAFT_1_19, 22)));
empty(id("minecraft:objective_criteria", mapSet(MINECRAFT_1_19, 23)));
empty(id("minecraft:operation", mapSet(MINECRAFT_1_19, 24)));
empty(id("minecraft:particle", mapSet(MINECRAFT_1_19, 25)));
empty(id("minecraft:angle", mapSet(MINECRAFT_1_19, 26))); // added in 1.16.2
empty(id("minecraft:rotation", mapSet(MINECRAFT_1_19, 27)));
empty(id("minecraft:scoreboard_slot", mapSet(MINECRAFT_1_19, 28)));
empty(id("minecraft:score_holder", mapSet(MINECRAFT_1_19, 29)), ByteArgumentPropertySerializer.BYTE);
empty(id("minecraft:swizzle", mapSet(MINECRAFT_1_19, 30)));
empty(id("minecraft:team", mapSet(MINECRAFT_1_19, 31)));
empty(id("minecraft:item_slot", mapSet(MINECRAFT_1_19, 32)));
empty(id("minecraft:resource_location", mapSet(MINECRAFT_1_19, 33)));
empty(id("minecraft:mob_effect", mapSet(MINECRAFT_1_19, 34)));
empty(id("minecraft:function", mapSet(MINECRAFT_1_19, 35)));
empty(id("minecraft:entity_anchor", mapSet(MINECRAFT_1_19, 36)));
empty(id("minecraft:int_range", mapSet(MINECRAFT_1_19, 37)));
empty(id("minecraft:float_range", mapSet(MINECRAFT_1_19, 38)));
empty(id("minecraft:item_enchantment", mapSet(MINECRAFT_1_19, 39)));
empty(id("minecraft:entity_summon", mapSet(MINECRAFT_1_19, 40)));
empty(id("minecraft:dimension", mapSet(MINECRAFT_1_19, 41)));
empty(id("minecraft:time", mapSet(MINECRAFT_1_19, 42))); // added in 1.14
register(id("minecraft:resource_or_tag", mapSet(MINECRAFT_1_19, 43)),
RegistryKeyArgument.class, RegistryKeyArgumentSerializer.REGISTRY);
register(id("minecraft:resource", mapSet(MINECRAFT_1_19, 44)),
RegistryKeyArgument.class, RegistryKeyArgumentSerializer.REGISTRY);
empty(id("minecraft:uuid", mapSet(MINECRAFT_1_19, 45))); // added in 1.16
// Crossstitch support // Crossstitch support
register("crossstitch:mod_argument", ModArgumentProperty.class, MOD); register(id("crossstitch:mod_argument", mapSet(MINECRAFT_1_19, -256)), ModArgumentProperty.class, MOD);
// Minecraft argument types with extra properties empty(id("minecraft:nbt")); // No longer in 1.19+
empty("minecraft:entity", ByteArgumentPropertySerializer.BYTE);
empty("minecraft:score_holder", ByteArgumentPropertySerializer.BYTE);
// Minecraft argument types
empty("minecraft:game_profile");
empty("minecraft:block_pos");
empty("minecraft:column_pos");
empty("minecraft:vec3");
empty("minecraft:vec2");
empty("minecraft:block_state");
empty("minecraft:block_predicate");
empty("minecraft:item_stack");
empty("minecraft:item_predicate");
empty("minecraft:color");
empty("minecraft:component");
empty("minecraft:message");
empty("minecraft:nbt");
empty("minecraft:nbt_compound_tag"); // added in 1.14
empty("minecraft:nbt_tag"); // added in 1.14
empty("minecraft:nbt_path");
empty("minecraft:objective");
empty("minecraft:objective_criteria");
empty("minecraft:operation");
empty("minecraft:particle");
empty("minecraft:rotation");
empty("minecraft:scoreboard_slot");
empty("minecraft:swizzle");
empty("minecraft:team");
empty("minecraft:item_slot");
empty("minecraft:resource_location");
empty("minecraft:mob_effect");
empty("minecraft:function");
empty("minecraft:entity_anchor");
empty("minecraft:item_enchantment");
empty("minecraft:entity_summon");
empty("minecraft:dimension");
empty("minecraft:int_range");
empty("minecraft:float_range");
empty("minecraft:time"); // added in 1.14
empty("minecraft:uuid"); // added in 1.16
empty("minecraft:angle"); // added in 1.16.2
} }
} }

Datei anzeigen

@ -17,11 +17,12 @@
package com.velocitypowered.proxy.protocol.packet.brigadier; package com.velocitypowered.proxy.protocol.packet.brigadier;
import com.velocitypowered.api.network.ProtocolVersion;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
public interface ArgumentPropertySerializer<T> { public interface ArgumentPropertySerializer<T> {
@Nullable T deserialize(ByteBuf buf); @Nullable T deserialize(ByteBuf buf, ProtocolVersion protocolVersion);
void serialize(T object, ByteBuf buf); void serialize(T object, ByteBuf buf, ProtocolVersion protocolVersion);
} }

Datei anzeigen

@ -17,6 +17,7 @@
package com.velocitypowered.proxy.protocol.packet.brigadier; package com.velocitypowered.proxy.protocol.packet.brigadier;
import com.velocitypowered.api.network.ProtocolVersion;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
class ByteArgumentPropertySerializer implements ArgumentPropertySerializer<Byte> { class ByteArgumentPropertySerializer implements ArgumentPropertySerializer<Byte> {
@ -28,12 +29,12 @@ class ByteArgumentPropertySerializer implements ArgumentPropertySerializer<Byte>
} }
@Override @Override
public Byte deserialize(ByteBuf buf) { public Byte deserialize(ByteBuf buf, ProtocolVersion protocolVersion) {
return buf.readByte(); return buf.readByte();
} }
@Override @Override
public void serialize(Byte object, ByteBuf buf) { public void serialize(Byte object, ByteBuf buf, ProtocolVersion protocolVersion) {
buf.writeByte(object); buf.writeByte(object);
} }
} }

Datei anzeigen

@ -22,6 +22,7 @@ import static com.velocitypowered.proxy.protocol.packet.brigadier.IntegerArgumen
import static com.velocitypowered.proxy.protocol.packet.brigadier.IntegerArgumentPropertySerializer.getFlags; import static com.velocitypowered.proxy.protocol.packet.brigadier.IntegerArgumentPropertySerializer.getFlags;
import com.mojang.brigadier.arguments.DoubleArgumentType; import com.mojang.brigadier.arguments.DoubleArgumentType;
import com.velocitypowered.api.network.ProtocolVersion;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
class DoubleArgumentPropertySerializer implements ArgumentPropertySerializer<DoubleArgumentType> { class DoubleArgumentPropertySerializer implements ArgumentPropertySerializer<DoubleArgumentType> {
@ -32,7 +33,7 @@ class DoubleArgumentPropertySerializer implements ArgumentPropertySerializer<Dou
} }
@Override @Override
public DoubleArgumentType deserialize(ByteBuf buf) { public DoubleArgumentType deserialize(ByteBuf buf, ProtocolVersion protocolVersion) {
byte flags = buf.readByte(); byte flags = buf.readByte();
double minimum = (flags & HAS_MINIMUM) != 0 ? buf.readDouble() : Double.MIN_VALUE; double minimum = (flags & HAS_MINIMUM) != 0 ? buf.readDouble() : Double.MIN_VALUE;
double maximum = (flags & HAS_MAXIMUM) != 0 ? buf.readDouble() : Double.MAX_VALUE; double maximum = (flags & HAS_MAXIMUM) != 0 ? buf.readDouble() : Double.MAX_VALUE;
@ -40,7 +41,7 @@ class DoubleArgumentPropertySerializer implements ArgumentPropertySerializer<Dou
} }
@Override @Override
public void serialize(DoubleArgumentType object, ByteBuf buf) { public void serialize(DoubleArgumentType object, ByteBuf buf, ProtocolVersion protocolVersion) {
boolean hasMinimum = Double.compare(object.getMinimum(), Double.MIN_VALUE) != 0; boolean hasMinimum = Double.compare(object.getMinimum(), Double.MIN_VALUE) != 0;
boolean hasMaximum = Double.compare(object.getMaximum(), Double.MAX_VALUE) != 0; boolean hasMaximum = Double.compare(object.getMaximum(), Double.MAX_VALUE) != 0;
byte flag = getFlags(hasMinimum, hasMaximum); byte flag = getFlags(hasMinimum, hasMaximum);

Datei anzeigen

@ -17,6 +17,7 @@
package com.velocitypowered.proxy.protocol.packet.brigadier; package com.velocitypowered.proxy.protocol.packet.brigadier;
import com.velocitypowered.api.network.ProtocolVersion;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
@ -32,12 +33,12 @@ class EmptyArgumentPropertySerializer implements ArgumentPropertySerializer<Void
} }
@Override @Override
public @Nullable Void deserialize(ByteBuf buf) { public @Nullable Void deserialize(ByteBuf buf, ProtocolVersion protocolVersion) {
return null; return null;
} }
@Override @Override
public void serialize(Void object, ByteBuf buf) { public void serialize(Void object, ByteBuf buf, ProtocolVersion protocolVersion) {
} }
} }

Datei anzeigen

@ -22,6 +22,7 @@ import static com.velocitypowered.proxy.protocol.packet.brigadier.IntegerArgumen
import static com.velocitypowered.proxy.protocol.packet.brigadier.IntegerArgumentPropertySerializer.getFlags; import static com.velocitypowered.proxy.protocol.packet.brigadier.IntegerArgumentPropertySerializer.getFlags;
import com.mojang.brigadier.arguments.FloatArgumentType; import com.mojang.brigadier.arguments.FloatArgumentType;
import com.velocitypowered.api.network.ProtocolVersion;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
class FloatArgumentPropertySerializer implements ArgumentPropertySerializer<FloatArgumentType> { class FloatArgumentPropertySerializer implements ArgumentPropertySerializer<FloatArgumentType> {
@ -33,7 +34,7 @@ class FloatArgumentPropertySerializer implements ArgumentPropertySerializer<Floa
} }
@Override @Override
public FloatArgumentType deserialize(ByteBuf buf) { public FloatArgumentType deserialize(ByteBuf buf, ProtocolVersion protocolVersion) {
byte flags = buf.readByte(); byte flags = buf.readByte();
float minimum = (flags & HAS_MINIMUM) != 0 ? buf.readFloat() : Float.MIN_VALUE; float minimum = (flags & HAS_MINIMUM) != 0 ? buf.readFloat() : Float.MIN_VALUE;
float maximum = (flags & HAS_MAXIMUM) != 0 ? buf.readFloat() : Float.MAX_VALUE; float maximum = (flags & HAS_MAXIMUM) != 0 ? buf.readFloat() : Float.MAX_VALUE;
@ -41,7 +42,7 @@ class FloatArgumentPropertySerializer implements ArgumentPropertySerializer<Floa
} }
@Override @Override
public void serialize(FloatArgumentType object, ByteBuf buf) { public void serialize(FloatArgumentType object, ByteBuf buf, ProtocolVersion protocolVersion) {
boolean hasMinimum = Float.compare(object.getMinimum(), Float.MIN_VALUE) != 0; boolean hasMinimum = Float.compare(object.getMinimum(), Float.MIN_VALUE) != 0;
boolean hasMaximum = Float.compare(object.getMaximum(), Float.MAX_VALUE) != 0; boolean hasMaximum = Float.compare(object.getMaximum(), Float.MAX_VALUE) != 0;
byte flag = getFlags(hasMinimum, hasMaximum); byte flag = getFlags(hasMinimum, hasMaximum);

Datei anzeigen

@ -18,6 +18,7 @@
package com.velocitypowered.proxy.protocol.packet.brigadier; package com.velocitypowered.proxy.protocol.packet.brigadier;
import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.velocitypowered.api.network.ProtocolVersion;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
class IntegerArgumentPropertySerializer implements ArgumentPropertySerializer<IntegerArgumentType> { class IntegerArgumentPropertySerializer implements ArgumentPropertySerializer<IntegerArgumentType> {
@ -32,7 +33,7 @@ class IntegerArgumentPropertySerializer implements ArgumentPropertySerializer<In
} }
@Override @Override
public IntegerArgumentType deserialize(ByteBuf buf) { public IntegerArgumentType deserialize(ByteBuf buf, ProtocolVersion protocolVersion) {
byte flags = buf.readByte(); byte flags = buf.readByte();
int minimum = (flags & HAS_MINIMUM) != 0 ? buf.readInt() : Integer.MIN_VALUE; int minimum = (flags & HAS_MINIMUM) != 0 ? buf.readInt() : Integer.MIN_VALUE;
int maximum = (flags & HAS_MAXIMUM) != 0 ? buf.readInt() : Integer.MAX_VALUE; int maximum = (flags & HAS_MAXIMUM) != 0 ? buf.readInt() : Integer.MAX_VALUE;
@ -40,7 +41,7 @@ class IntegerArgumentPropertySerializer implements ArgumentPropertySerializer<In
} }
@Override @Override
public void serialize(IntegerArgumentType object, ByteBuf buf) { public void serialize(IntegerArgumentType object, ByteBuf buf, ProtocolVersion protocolVersion) {
boolean hasMinimum = object.getMinimum() != Integer.MIN_VALUE; boolean hasMinimum = object.getMinimum() != Integer.MIN_VALUE;
boolean hasMaximum = object.getMaximum() != Integer.MAX_VALUE; boolean hasMaximum = object.getMaximum() != Integer.MAX_VALUE;
byte flag = getFlags(hasMinimum, hasMaximum); byte flag = getFlags(hasMinimum, hasMaximum);

Datei anzeigen

@ -22,6 +22,7 @@ import static com.velocitypowered.proxy.protocol.packet.brigadier.IntegerArgumen
import static com.velocitypowered.proxy.protocol.packet.brigadier.IntegerArgumentPropertySerializer.getFlags; import static com.velocitypowered.proxy.protocol.packet.brigadier.IntegerArgumentPropertySerializer.getFlags;
import com.mojang.brigadier.arguments.LongArgumentType; import com.mojang.brigadier.arguments.LongArgumentType;
import com.velocitypowered.api.network.ProtocolVersion;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
class LongArgumentPropertySerializer implements ArgumentPropertySerializer<LongArgumentType> { class LongArgumentPropertySerializer implements ArgumentPropertySerializer<LongArgumentType> {
@ -33,7 +34,7 @@ class LongArgumentPropertySerializer implements ArgumentPropertySerializer<LongA
} }
@Override @Override
public LongArgumentType deserialize(ByteBuf buf) { public LongArgumentType deserialize(ByteBuf buf, ProtocolVersion protocolVersion) {
byte flags = buf.readByte(); byte flags = buf.readByte();
long minimum = (flags & HAS_MINIMUM) != 0 ? buf.readLong() : Long.MIN_VALUE; long minimum = (flags & HAS_MINIMUM) != 0 ? buf.readLong() : Long.MIN_VALUE;
long maximum = (flags & HAS_MAXIMUM) != 0 ? buf.readLong() : Long.MAX_VALUE; long maximum = (flags & HAS_MAXIMUM) != 0 ? buf.readLong() : Long.MAX_VALUE;
@ -41,7 +42,7 @@ class LongArgumentPropertySerializer implements ArgumentPropertySerializer<LongA
} }
@Override @Override
public void serialize(LongArgumentType object, ByteBuf buf) { public void serialize(LongArgumentType object, ByteBuf buf, ProtocolVersion protocolVersion) {
boolean hasMinimum = object.getMinimum() != Long.MIN_VALUE; boolean hasMinimum = object.getMinimum() != Long.MIN_VALUE;
boolean hasMaximum = object.getMaximum() != Long.MAX_VALUE; boolean hasMaximum = object.getMaximum() != Long.MAX_VALUE;
byte flag = getFlags(hasMinimum, hasMaximum); byte flag = getFlags(hasMinimum, hasMaximum);

Datei anzeigen

@ -30,15 +30,15 @@ import java.util.concurrent.CompletableFuture;
public class ModArgumentProperty implements ArgumentType<ByteBuf> { public class ModArgumentProperty implements ArgumentType<ByteBuf> {
private final String identifier; private final ArgumentIdentifier identifier;
private final ByteBuf data; private final ByteBuf data;
public ModArgumentProperty(String identifier, ByteBuf data) { public ModArgumentProperty(ArgumentIdentifier identifier, ByteBuf data) {
this.identifier = identifier; this.identifier = identifier;
this.data = Unpooled.unreleasableBuffer(data.asReadOnly()); this.data = Unpooled.unreleasableBuffer(data.asReadOnly());
} }
public String getIdentifier() { public ArgumentIdentifier getIdentifier() {
return identifier; return identifier;
} }

Datei anzeigen

@ -17,6 +17,7 @@
package com.velocitypowered.proxy.protocol.packet.brigadier; package com.velocitypowered.proxy.protocol.packet.brigadier;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
@ -30,14 +31,22 @@ class ModArgumentPropertySerializer implements ArgumentPropertySerializer<ModArg
} }
@Override @Override
public @Nullable ModArgumentProperty deserialize(ByteBuf buf) { public @Nullable ModArgumentProperty deserialize(ByteBuf buf, ProtocolVersion version) {
String identifier = ProtocolUtils.readString(buf); ArgumentIdentifier identifier;
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
int idx = ProtocolUtils.readVarInt(buf);
identifier = ArgumentIdentifier.id("crossstitch:identified_" + (idx < 0 ? "n" + (-idx) : idx),
ArgumentIdentifier.mapSet(version, idx));
} else {
identifier = ArgumentIdentifier.id(ProtocolUtils.readString(buf));
}
byte[] extraData = ProtocolUtils.readByteArray(buf); byte[] extraData = ProtocolUtils.readByteArray(buf);
return new ModArgumentProperty(identifier, Unpooled.wrappedBuffer(extraData)); return new ModArgumentProperty(identifier, Unpooled.wrappedBuffer(extraData));
} }
@Override @Override
public void serialize(ModArgumentProperty object, ByteBuf buf) { public void serialize(ModArgumentProperty object, ByteBuf buf, ProtocolVersion version) {
// This is special-cased by ArgumentPropertyRegistry // This is special-cased by ArgumentPropertyRegistry
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }

Datei anzeigen

@ -23,18 +23,18 @@ import org.checkerframework.checker.nullness.qual.Nullable;
class PassthroughProperty<T> implements ArgumentType<T> { class PassthroughProperty<T> implements ArgumentType<T> {
private final String identifier; private final ArgumentIdentifier identifier;
private final ArgumentPropertySerializer<T> serializer; private final ArgumentPropertySerializer<T> serializer;
private final @Nullable T result; private final @Nullable T result;
PassthroughProperty(String identifier, ArgumentPropertySerializer<T> serializer, PassthroughProperty(ArgumentIdentifier identifier, ArgumentPropertySerializer<T> serializer,
@Nullable T result) { @Nullable T result) {
this.identifier = identifier; this.identifier = identifier;
this.serializer = serializer; this.serializer = serializer;
this.result = result; this.result = result;
} }
public String getIdentifier() { public ArgumentIdentifier getIdentifier() {
return identifier; return identifier;
} }

Datei anzeigen

@ -17,6 +17,7 @@
package com.velocitypowered.proxy.protocol.packet.brigadier; package com.velocitypowered.proxy.protocol.packet.brigadier;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
@ -25,12 +26,12 @@ public class RegistryKeyArgumentSerializer implements ArgumentPropertySerializer
static final RegistryKeyArgumentSerializer REGISTRY = new RegistryKeyArgumentSerializer(); static final RegistryKeyArgumentSerializer REGISTRY = new RegistryKeyArgumentSerializer();
@Override @Override
public RegistryKeyArgument deserialize(ByteBuf buf) { public RegistryKeyArgument deserialize(ByteBuf buf, ProtocolVersion protocolVersion) {
return new RegistryKeyArgument(ProtocolUtils.readString(buf)); return new RegistryKeyArgument(ProtocolUtils.readString(buf));
} }
@Override @Override
public void serialize(RegistryKeyArgument object, ByteBuf buf) { public void serialize(RegistryKeyArgument object, ByteBuf buf, ProtocolVersion protocolVersion) {
ProtocolUtils.writeString(buf, object.getIdentifier()); ProtocolUtils.writeString(buf, object.getIdentifier());
} }
} }

Datei anzeigen

@ -18,6 +18,7 @@
package com.velocitypowered.proxy.protocol.packet.brigadier; package com.velocitypowered.proxy.protocol.packet.brigadier;
import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.arguments.StringArgumentType;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
@ -34,7 +35,7 @@ class StringArgumentPropertySerializer implements ArgumentPropertySerializer<Str
} }
@Override @Override
public StringArgumentType deserialize(ByteBuf buf) { public StringArgumentType deserialize(ByteBuf buf, ProtocolVersion protocolVersion) {
int type = ProtocolUtils.readVarInt(buf); int type = ProtocolUtils.readVarInt(buf);
switch (type) { switch (type) {
case 0: case 0:
@ -49,7 +50,7 @@ class StringArgumentPropertySerializer implements ArgumentPropertySerializer<Str
} }
@Override @Override
public void serialize(StringArgumentType object, ByteBuf buf) { public void serialize(StringArgumentType object, ByteBuf buf, ProtocolVersion protocolVersion) {
switch (object.getType()) { switch (object.getType()) {
case SINGLE_WORD: case SINGLE_WORD:
ProtocolUtils.writeVarInt(buf, 0); ProtocolUtils.writeVarInt(buf, 0);

Datei anzeigen

@ -0,0 +1,182 @@
/*
* 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;
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 ChatType type = ChatType.CHAT;
private ChatBuilder(ProtocolVersion version) {
this.version = version;
}
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 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) {
return new SystemChat(msg, type.getId());
} 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(), Instant.now());
} else {
// This will produce an error on the server, but needs to be here.
return new PlayerChat(message);
}
}
}
LegacyChat chat = new LegacyChat();
chat.setMessage(message);
return chat;
}
public static enum ChatType {
CHAT((byte) 0),
SYSTEM((byte) 1),
GAME_INFO((byte) 2);
private final byte raw;
ChatType(byte raw) {
this.raw = raw;
}
public byte getId() {
return raw;
}
}
}

Datei anzeigen

@ -15,7 +15,7 @@
* 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; package com.velocitypowered.proxy.protocol.packet.chat;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
@ -23,12 +23,11 @@ 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 net.kyori.adventure.identity.Identity; import net.kyori.adventure.identity.Identity;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.UUID; public class LegacyChat implements MinecraftPacket {
public class Chat implements MinecraftPacket {
public static final byte CHAT_TYPE = (byte) 0; public static final byte CHAT_TYPE = (byte) 0;
public static final byte SYSTEM_TYPE = (byte) 1; public static final byte SYSTEM_TYPE = (byte) 1;
@ -41,15 +40,21 @@ public class Chat implements MinecraftPacket {
private byte type; private byte type;
private @Nullable UUID sender; private @Nullable UUID sender;
public Chat() { public LegacyChat() {
} }
public Chat(String message, byte type, UUID sender) { /**
* Creates a Chat packet.
*/
public LegacyChat(String message, byte type, UUID sender) {
this.message = message; this.message = message;
this.type = type; this.type = type;
this.sender = sender; this.sender = sender;
} }
/**
* Retrieves the Chat message.
*/
public String getMessage() { public String getMessage() {
if (message == null) { if (message == null) {
throw new IllegalStateException("Message is not specified"); throw new IllegalStateException("Message is not specified");
@ -115,20 +120,4 @@ public class Chat implements MinecraftPacket {
public boolean handle(MinecraftSessionHandler handler) { public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this); return handler.handle(this);
} }
public static Chat createClientbound(Identity identity,
net.kyori.adventure.text.Component component, ProtocolVersion version) {
return createClientbound(component, CHAT_TYPE, identity.uuid(), version);
}
public static Chat createClientbound(net.kyori.adventure.text.Component component, byte type,
UUID sender, ProtocolVersion version) {
Preconditions.checkNotNull(component, "component");
return new Chat(ProtocolUtils.getJsonChatSerializer(version).serialize(component), type,
sender);
}
public static Chat createServerbound(String message) {
return new Chat(message, CHAT_TYPE, EMPTY_SENDER);
}
} }

Datei anzeigen

@ -0,0 +1,139 @@
/*
* 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.primitives.Longs;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.crypto.EncryptionUtils;
import com.velocitypowered.proxy.crypto.SignedChatMessage;
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 org.checkerframework.checker.nullness.qual.Nullable;
public class PlayerChat implements MinecraftPacket {
private String message;
private boolean signedPreview;
private boolean unsigned = false;
private @Nullable Instant expiry;
private @Nullable byte[] signature;
private @Nullable byte[] salt;
public PlayerChat() {
}
public PlayerChat(String message) {
this.message = message;
this.unsigned = true;
}
/**
* Create new {@link PlayerChat} based on a previously {@link SignedChatMessage}.
*
* @param message The {@link SignedChatMessage} to turn into {@link PlayerChat}.
*/
public PlayerChat(SignedChatMessage message) {
this.message = message.getMessage();
this.expiry = message.getExpiryTemporal();
this.salt = message.getSalt();
this.signature = message.getSignature();
this.signedPreview = message.isPreviewSigned();
}
public Instant getExpiry() {
return expiry;
}
public boolean isUnsigned() {
return unsigned;
}
public String getMessage() {
return message;
}
public boolean isSignedPreview() {
return signedPreview;
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
message = ProtocolUtils.readString(buf, 256);
long expiresAt = buf.readLong();
long saltLong = buf.readLong();
byte[] signatureBytes = ProtocolUtils.readByteArray(buf);
if (saltLong != 0L && signatureBytes.length > 0) {
salt = Longs.toByteArray(saltLong);
signature = signatureBytes;
expiry = Instant.ofEpochMilli(expiresAt);
} else if (saltLong == 0L && signature.length == 0) {
unsigned = true;
} else {
throw EncryptionUtils.INVALID_SIGNATURE;
}
signedPreview = buf.readBoolean();
if (signedPreview && unsigned) {
throw EncryptionUtils.PREVIEW_SIGNATURE_MISSING;
}
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeString(buf, message);
buf.writeLong(unsigned ? Instant.now().toEpochMilli() : expiry.toEpochMilli());
buf.writeLong(unsigned ? 0L : Longs.fromByteArray(salt));
ProtocolUtils.writeByteArray(buf, unsigned ? EncryptionUtils.EMPTY : signature);
buf.writeBoolean(signedPreview);
}
/**
* 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);
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
}

Datei anzeigen

@ -0,0 +1,55 @@
/*
* Copyright (C) 2018 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol.packet.chat;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public class PlayerChatPreview implements MinecraftPacket {
private int id;
private String query;
public int getId() {
return id;
}
public String getQuery() {
return query;
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
id = buf.readInt();
query = ProtocolUtils.readString(buf, 256);
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
buf.writeInt(id);
ProtocolUtils.writeString(buf, query);
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
}

Datei anzeigen

@ -0,0 +1,177 @@
/*
* 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.collect.ImmutableMap;
import com.google.common.primitives.Longs;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.crypto.EncryptionUtils;
import com.velocitypowered.proxy.crypto.SignedChatCommand;
import com.velocitypowered.proxy.crypto.SignedChatMessage;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.util.except.QuietDecoderException;
import io.netty.buffer.ByteBuf;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public class PlayerCommand implements MinecraftPacket {
private static final int MAX_NUM_ARGUMENTS = 8;
private static final int MAX_LENGTH_ARGUMENTS = 16;
private static final QuietDecoderException LIMITS_VIOLATION =
new QuietDecoderException("Command arguments incorrect size");
private boolean unsigned = false;
private String command;
private Instant timestamp;
private long salt;
private boolean signedPreview; // Good god. Please no.
private Map<String, byte[]> arguments = ImmutableMap.of();
public boolean isSignedPreview() {
return signedPreview;
}
public Instant getTimestamp() {
return timestamp;
}
public boolean isUnsigned() {
return unsigned;
}
public String getCommand() {
return command;
}
public PlayerCommand() {
}
/**
* Creates an {@link PlayerCommand} packet based on a command and list of arguments.
*
* @param command the command to run
* @param arguments the arguments of the command
* @param timestamp the timestamp of the command execution
*/
public PlayerCommand(String command, List<String> arguments, Instant timestamp) {
this.unsigned = true;
ImmutableMap.Builder<String, byte[]> builder = ImmutableMap.builder();
arguments.forEach(entry -> builder.put(entry, EncryptionUtils.EMPTY));
this.arguments = builder.build();
this.timestamp = timestamp;
this.command = command;
this.signedPreview = false;
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();
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
command = ProtocolUtils.readString(buf, 256);
timestamp = Instant.ofEpochMilli(buf.readLong());
salt = buf.readLong();
if (salt == 0L) {
unsigned = true;
}
int mapSize = ProtocolUtils.readVarInt(buf);
if (mapSize > MAX_NUM_ARGUMENTS) {
throw LIMITS_VIOLATION;
}
// Mapped as Argument : signature
ImmutableMap.Builder<String, byte[]> entries = ImmutableMap.builderWithExpectedSize(mapSize);
for (int i = 0; i < mapSize; i++) {
entries.put(ProtocolUtils.readString(buf, MAX_LENGTH_ARGUMENTS),
ProtocolUtils.readByteArray(buf, unsigned ? 0 : ProtocolUtils.DEFAULT_MAX_STRING_SIZE));
}
arguments = entries.build();
signedPreview = buf.readBoolean();
if (unsigned && signedPreview) {
throw EncryptionUtils.PREVIEW_SIGNATURE_MISSING;
}
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeString(buf, command);
buf.writeLong(timestamp.toEpochMilli());
buf.writeLong(unsigned ? 0L : salt);
int size = arguments.size();
if (size > MAX_NUM_ARGUMENTS) {
throw LIMITS_VIOLATION;
}
ProtocolUtils.writeVarInt(buf, size);
for (Map.Entry<String, byte[]> entry : arguments.entrySet()) {
// What annoys me is that this isn't "sorted"
ProtocolUtils.writeString(buf, entry.getKey());
ProtocolUtils.writeByteArray(buf, unsigned ? EncryptionUtils.EMPTY : entry.getValue());
}
buf.writeBoolean(signedPreview);
}
/**
* 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(IdentifiedKey signer, UUID sender, boolean mustSign) {
if (unsigned) {
if (mustSign) {
throw EncryptionUtils.INVALID_SIGNATURE;
}
return null;
}
return new SignedChatCommand(command, signer.getSignedPublicKey(), sender, timestamp,
arguments, Longs.toByteArray(salt), signedPreview);
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
}

Datei anzeigen

@ -0,0 +1,65 @@
/*
* 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 net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import org.checkerframework.checker.nullness.qual.Nullable;
public class ServerChatPreview implements MinecraftPacket {
private int id;
private @Nullable Component preview;
public Component getPreview() {
return preview;
}
public int getId() {
return id;
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
id = buf.readInt();
if (buf.readBoolean()) {
preview = GsonComponentSerializer.gson().deserialize(ProtocolUtils.readString(buf));
}
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
buf.writeInt(id);
if (preview != null) {
buf.writeBoolean(true);
ProtocolUtils.writeString(buf, GsonComponentSerializer.gson().serialize(preview));
} else {
buf.writeBoolean(false);
}
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
}

Datei anzeigen

@ -0,0 +1,89 @@
/*
* Copyright (C) 2018 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol.packet.chat;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import java.time.Instant;
import java.util.UUID;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable;
public class ServerPlayerChat implements MinecraftPacket {
private Component component;
private @Nullable Component unsignedComponent;
private int type;
private UUID sender;
private Component senderName;
private @Nullable Component teamName;
private Instant expiry;
public void setType(int type) {
this.type = type;
}
public void setComponent(Component component) {
this.component = component;
}
public int getType() {
return type;
}
public Component getComponent() {
return component;
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
component = ProtocolUtils.getJsonChatSerializer(protocolVersion).deserialize(ProtocolUtils.readString(buf));
if (buf.readBoolean()) {
unsignedComponent = component = ProtocolUtils.getJsonChatSerializer(protocolVersion)
.deserialize(ProtocolUtils.readString(buf));
}
type = ProtocolUtils.readVarInt(buf);
sender = ProtocolUtils.readUuid(buf);
senderName = ProtocolUtils.getJsonChatSerializer(protocolVersion).deserialize(ProtocolUtils.readString(buf));
if (buf.readBoolean()) {
teamName = ProtocolUtils.getJsonChatSerializer(protocolVersion).deserialize(ProtocolUtils.readString(buf));
}
expiry = Instant.ofEpochMilli(buf.readLong());
long salt = buf.readLong();
byte[] signature = ProtocolUtils.readByteArray(buf);
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
// TBD
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
}

Datei anzeigen

@ -0,0 +1,63 @@
/*
* 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 net.kyori.adventure.text.Component;
public class SystemChat implements MinecraftPacket {
public SystemChat() {}
public SystemChat(Component component, int type) {
this.component = component;
this.type = type;
}
private Component component;
private int type;
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));
type = ProtocolUtils.readVarInt(buf);
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeString(buf, ProtocolUtils.getJsonChatSerializer(protocolVersion).serialize(component));
ProtocolUtils.writeVarInt(buf, type);
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
}

Datei anzeigen

@ -18,6 +18,7 @@
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.proxy.crypto.IdentifiedKey;
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;
@ -119,10 +120,16 @@ public class VelocityTabList implements TabList {
return Collections.unmodifiableCollection(this.entries.values()); 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, null);
}
@Override @Override
public TabListEntry buildEntry(GameProfile profile, public TabListEntry buildEntry(GameProfile profile,
net.kyori.adventure.text.@Nullable Component displayName, int latency, int gameMode) { net.kyori.adventure.text.@Nullable Component displayName,
return new VelocityTabListEntry(this, profile, displayName, latency, gameMode); int latency, int gameMode, @Nullable IdentifiedKey key) {
return new VelocityTabListEntry(this, profile, displayName, latency, gameMode, key);
} }
/** /**

Datei anzeigen

@ -17,6 +17,7 @@
package com.velocitypowered.proxy.tablist; package com.velocitypowered.proxy.tablist;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
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;
@ -31,14 +32,17 @@ public class VelocityTabListEntry implements TabListEntry {
private net.kyori.adventure.text.Component displayName; private net.kyori.adventure.text.Component displayName;
private int latency; private int latency;
private int gameMode; private int gameMode;
private @Nullable IdentifiedKey playerKey;
VelocityTabListEntry(VelocityTabList tabList, GameProfile profile, VelocityTabListEntry(VelocityTabList tabList, GameProfile profile,
net.kyori.adventure.text.@Nullable Component displayName, int latency, int gameMode) { net.kyori.adventure.text.@Nullable Component displayName, int latency, int gameMode,
@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;
} }
@Override @Override
@ -98,4 +102,13 @@ public class VelocityTabListEntry implements TabListEntry {
void setGameModeInternal(int gameMode) { void setGameModeInternal(int gameMode) {
this.gameMode = gameMode; this.gameMode = gameMode;
} }
@Override
public IdentifiedKey getIdentifiedKey() {
return playerKey;
}
void setPlayerKeyInternal(IdentifiedKey playerKey) {
this.playerKey = playerKey;
}
} }

Datei anzeigen

@ -26,7 +26,7 @@ public class VelocityTabListEntryLegacy extends VelocityTabListEntry {
VelocityTabListEntryLegacy(VelocityTabListLegacy tabList, GameProfile profile, VelocityTabListEntryLegacy(VelocityTabListLegacy tabList, GameProfile profile,
@Nullable Component displayName, int latency, int gameMode) { @Nullable Component displayName, int latency, int gameMode) {
super(tabList, profile, displayName, latency, gameMode); super(tabList, profile, displayName, latency, gameMode, null);
} }
@Override @Override

Datei anzeigen

@ -1,89 +0,0 @@
/*
* Copyright (C) 2018 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.util;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import javax.crypto.Cipher;
public enum EncryptionUtils {
;
/**
* Generates an RSA key pair.
*
* @param keysize the key size (in bits) for the RSA key pair
* @return the generated key pair
*/
public static KeyPair createRsaKeyPair(final int keysize) {
try {
final KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(keysize);
return generator.generateKeyPair();
} catch (final NoSuchAlgorithmException e) {
throw new RuntimeException("Unable to generate RSA keypair", e);
}
}
/**
* Generates a hex digest in two's complement form for use with the Mojang joinedServer endpoint.
*
* @param digest the bytes to digest
* @return the hex digest
*/
public static String twosComplementHexdigest(byte[] digest) {
return new BigInteger(digest).toString(16);
}
/**
* Decrypts an RSA message.
*
* @param keyPair the key pair to use
* @param bytes the bytes of the encrypted message
* @return the decrypted message
* @throws GeneralSecurityException if the message couldn't be decoded
*/
public static byte[] decryptRsa(KeyPair keyPair, byte[] bytes) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
return cipher.doFinal(bytes);
}
/**
* Generates the server ID for the hasJoined endpoint.
*
* @param sharedSecret the shared secret between the client and the proxy
* @param key the RSA public key
* @return the server ID
*/
public static String generateServerId(byte[] sharedSecret, PublicKey key) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.update(sharedSecret);
digest.update(key.getEncoded());
return twosComplementHexdigest(digest.digest());
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
}
}

Datei anzeigen

@ -29,7 +29,7 @@ velocity.error.modern-forwarding-needs-new-client=This server is only compatible
velocity.error.modern-forwarding-failed=Your server did not send a forwarding request to the proxy. Make sure the server is configured for Velocity forwarding. velocity.error.modern-forwarding-failed=Your server did not send a forwarding request to the proxy. Make sure the server is configured for Velocity forwarding.
velocity.error.moved-to-new-server=You were kicked from {0}: {1} velocity.error.moved-to-new-server=You were kicked from {0}: {1}
velocity.error.no-available-servers=There are no available servers to connect you to. Try again later or contact an admin. velocity.error.no-available-servers=There are no available servers to connect you to. Try again later or contact an admin.
velocity.error.illegal-chat-characters=Illegal characters in chat velocity.error.illegal-legacyChat-characters=Illegal characters in legacyChat
# Commands # Commands
velocity.command.generic-error=An error occurred while running this command. velocity.command.generic-error=An error occurred while running this command.

Datei anzeigen

@ -15,6 +15,9 @@ show-max-players = 500
# Should we authenticate players with Mojang? By default, this is on. # Should we authenticate players with Mojang? By default, this is on.
online-mode = true online-mode = true
# Should the proxy enforce the new public key security standard? By default, this is on.
force-key-authentication = true
# If client's ISP/AS sent from this proxy is different from the one from Mojang's # If client's ISP/AS sent from this proxy is different from the one from Mojang's
# authentication server, the player is kicked. This disallows some VPN and proxy # authentication server, the player is kicked. This disallows some VPN and proxy
# connections but is a weak form of protection. # connections but is a weak form of protection.

Binäre Datei nicht angezeigt.

Datei anzeigen

@ -19,8 +19,10 @@ package com.velocitypowered.proxy.util;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import com.velocitypowered.proxy.crypto.EncryptionUtils;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.MessageDigest; import java.security.MessageDigest;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
class EncryptionUtilsTest { class EncryptionUtilsTest {