3
0
Mirror von https://github.com/PaperMC/Velocity.git synchronisiert 2024-09-29 06:30:16 +02: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_1(756, "1.17.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;

Datei anzeigen

@ -7,6 +7,7 @@
package com.velocitypowered.api.proxy;
import com.velocitypowered.api.proxy.crypto.KeyIdentifiable;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
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
* messages.
*/
public interface LoginPhaseConnection extends InboundConnection {
public interface LoginPhaseConnection extends InboundConnection, KeyIdentifiable {
void sendLoginPluginMessage(ChannelIdentifier identifier, byte[] contents,
MessageConsumer consumer);

Datei anzeigen

@ -9,6 +9,7 @@ package com.velocitypowered.api.proxy;
import com.velocitypowered.api.command.CommandSource;
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.ChannelMessageSink;
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.
*/
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.

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;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
import com.velocitypowered.api.util.GameProfile;
import java.util.Collection;
import java.util.Optional;
@ -80,4 +81,19 @@ public interface TabList {
@Deprecated
TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency,
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;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
import com.velocitypowered.api.proxy.crypto.KeyIdentifiable;
import com.velocitypowered.api.util.GameProfile;
import java.util.Optional;
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}.
*/
public interface TabListEntry {
public interface TabListEntry extends KeyIdentifiable {
/**
* Returns the parent {@link TabList} of this {@code this} {@link TabListEntry}.
@ -125,6 +127,8 @@ public interface TabListEntry {
private int latency = 0;
private int gameMode = 0;
private @Nullable IdentifiedKey playerKey;
private Builder() {
}
@ -152,6 +156,18 @@ public interface TabListEntry {
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}.
*
@ -200,7 +216,7 @@ public interface TabListEntry {
if (profile == null) {
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.player.VelocityResourcePackInfo;
import com.velocitypowered.proxy.console.VelocityConsole;
import com.velocitypowered.proxy.crypto.EncryptionUtils;
import com.velocitypowered.proxy.event.VelocityEventManager;
import com.velocitypowered.proxy.network.ConnectionManager;
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.util.AddressUtil;
import com.velocitypowered.proxy.util.ClosestLocaleMatcher;
import com.velocitypowered.proxy.util.EncryptionUtils;
import com.velocitypowered.proxy.util.FileSystemUtils;
import com.velocitypowered.proxy.util.VelocityChannelRegistrar;
import com.velocitypowered.proxy.util.bossbar.AdventureBossBarManager;
@ -80,7 +80,6 @@ import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
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.translation.GlobalTranslator;
import net.kyori.adventure.translation.TranslationRegistry;
import net.kyori.adventure.util.UTF8ResourceBundleControl;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.asynchttpclient.AsyncHttpClient;

Datei anzeigen

@ -75,6 +75,7 @@ public class VelocityConfiguration implements ProxyConfig {
@Expose private boolean enablePlayerAddressLogging = true;
private net.kyori.adventure.text.@MonotonicNonNull Component motdAsComponent;
private @Nullable Favicon favicon;
@Expose private boolean forceKeyAuthentication = true; // Added in 1.19
private VelocityConfiguration(Servers servers, ForcedHosts forcedHosts, Advanced advanced,
Query query, Metrics metrics) {
@ -90,7 +91,7 @@ public class VelocityConfiguration implements ProxyConfig {
PlayerInfoForwarding playerInfoForwardingMode, byte[] forwardingSecret,
boolean onlineModeKickExistingPlayers, PingPassthroughMode pingPassthrough,
boolean enablePlayerAddressLogging, Servers servers,ForcedHosts forcedHosts,
Advanced advanced, Query query, Metrics metrics) {
Advanced advanced, Query query, Metrics metrics, boolean forceKeyAuthentication) {
this.bind = bind;
this.motd = motd;
this.showMaxPlayers = showMaxPlayers;
@ -107,6 +108,7 @@ public class VelocityConfiguration implements ProxyConfig {
this.advanced = advanced;
this.query = query;
this.metrics = metrics;
this.forceKeyAuthentication = forceKeyAuthentication;
}
/**
@ -381,6 +383,10 @@ public class VelocityConfiguration implements ProxyConfig {
return advanced.isLogPlayerConnections();
}
public boolean isForceKeyAuthentication() {
return forceKeyAuthentication;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
@ -397,6 +403,7 @@ public class VelocityConfiguration implements ProxyConfig {
.add("query", query)
.add("favicon", favicon)
.add("enablePlayerAddressLogging", enablePlayerAddressLogging)
.add("forceKeyAuthentication", forceKeyAuthentication)
.toString();
}
@ -466,6 +473,7 @@ public class VelocityConfiguration implements ProxyConfig {
String motd = config.getOrElse("motd", "&#09add3A Velocity Server");
int maxPlayers = config.getIntOrElse("show-max-players", 500);
Boolean onlineMode = config.getOrElse("online-mode", true);
Boolean forceKeyAuthentication = config.getOrElse("force-key-authentication", true);
Boolean announceForge = config.getOrElse("announce-forge", true);
Boolean preventClientProxyConnections = config.getOrElse("prevent-client-proxy-connections",
true);
@ -488,7 +496,8 @@ public class VelocityConfiguration implements ProxyConfig {
new ForcedHosts(forcedHostsConfig),
new Advanced(advancedConfig),
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.packet.AvailableCommands;
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.Disconnect;
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.TabCompleteRequest;
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse;
import com.velocitypowered.proxy.protocol.packet.chat.LegacyChat;
import com.velocitypowered.proxy.protocol.packet.chat.PlayerChat;
import com.velocitypowered.proxy.protocol.packet.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.TitleActionbarPacket;
import com.velocitypowered.proxy.protocol.packet.title.TitleClearPacket;
@ -104,7 +110,7 @@ public interface MinecraftSessionHandler {
return false;
}
default boolean handle(Chat packet) {
default boolean handle(LegacyChat packet) {
return false;
}
@ -231,4 +237,28 @@ public interface MinecraftSessionHandler {
default boolean handle(ResourcePackResponse packet) {
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 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];
}

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -85,7 +85,7 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
// Initiate a regular connection and move over to it.
ConnectedPlayer player = new ConnectedPlayer(server, profileEvent.getGameProfile(),
mcConnection, inbound.getVirtualHost().orElse(null), onlineMode);
mcConnection, inbound.getVirtualHost().orElse(null), onlineMode, inbound.getIdentifiedKey());
this.connectedPlayer = player;
if (!server.canRegisterConnection(player)) {
player.disconnect0(Component.translatable("velocity.error.already-connected-proxy",
@ -133,6 +133,7 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
}
ServerLoginSuccess success = new ServerLoginSuccess();
success.setUsername(player.getUsername());
success.setProperties(player.getGameProfileProperties());
success.setUuid(playerUniqueId);
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.BungeeCordMessageResponder;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.crypto.SignedChatCommand;
import com.velocitypowered.proxy.crypto.SignedChatMessage;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.BossBar;
import com.velocitypowered.proxy.protocol.packet.Chat;
import com.velocitypowered.proxy.protocol.packet.ClientSettings;
import com.velocitypowered.proxy.protocol.packet.JoinGame;
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.TabCompleteResponse;
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.util.PluginMessageUtil;
import com.velocitypowered.proxy.util.CharacterUtil;
@ -62,6 +67,8 @@ import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.util.ReferenceCountUtil;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@ -90,9 +97,11 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
private final Queue<PluginMessage> loginPluginMessages = new ConcurrentLinkedQueue<>();
private final VelocityServer server;
private @Nullable TabCompleteRequest outstandingTabComplete;
private @Nullable Instant lastChatMessage; // Added in 1.19
/**
* Constructs a client play session handler.
*
* @param server the Velocity server instance
* @param player the player
*/
@ -101,6 +110,83 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
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
public void activated() {
Collection<String> channels = server.getChannelRegistrar().getChannelsForProtocol(player
@ -142,58 +228,57 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
}
@Override
public boolean handle(Chat packet) {
VelocityServerConnection serverConnection = player.getConnectedServer();
if (serverConnection == null) {
public boolean handle(PlayerCommand packet) {
if (!validateChat(packet.getCommand())) {
return true;
}
MinecraftConnection smc = serverConnection.getConnection();
if (smc == null) {
if (!packet.isUnsigned()) {
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;
}
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();
if (CharacterUtil.containsIllegalCharacters(msg)) {
player.disconnect(Component.translatable("velocity.error.illegal-chat-characters",
NamedTextColor.RED));
if (!validateChat(msg)) {
return true;
}
if (msg.startsWith("/")) {
String originalCommand = msg.substring(1);
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;
});
processCommandMessage(msg.substring(1), null, packet);
} else {
PlayerChatEvent event = new PlayerChatEvent(player, msg);
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;
});
processPlayerChat(msg, null, packet);
}
return true;
}
@ -229,7 +314,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
}
}
server.getEventManager().fireAndForget(new PlayerChannelRegisterEvent(player,
ImmutableList.copyOf(channelIdentifiers)));
ImmutableList.copyOf(channelIdentifiers)));
backendConn.write(packet.retain());
} else if (PluginMessageUtil.isUnregister(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
* 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
*/
public void handleBackendJoinGame(JoinGame joinGame, VelocityServerConnection destination) {
@ -452,7 +538,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
new Respawn(sentOldDim, joinGame.getPartialHashedSeed(),
joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(),
false, joinGame.getDimensionInfo(), joinGame.getPreviousGamemode(),
joinGame.getCurrentDimensionData()));
joinGame.getCurrentDimensionData(), joinGame.getLastDeathPosition()));
}
private void doSafeClientServerSwitch(JoinGame joinGame) {
@ -469,14 +555,14 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
new Respawn(tempDim, joinGame.getPartialHashedSeed(), joinGame.getDifficulty(),
joinGame.getGamemode(), joinGame.getLevelType(),
false, joinGame.getDimensionInfo(), joinGame.getPreviousGamemode(),
joinGame.getCurrentDimensionData()));
joinGame.getCurrentDimensionData(), joinGame.getLastDeathPosition()));
// Now send a respawn packet in the correct dimension.
player.getConnection().delayedWrite(
new Respawn(joinGame.getDimension(), joinGame.getPartialHashedSeed(),
joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(),
false, joinGame.getDimensionInfo(), joinGame.getPreviousGamemode(),
joinGame.getCurrentDimensionData()));
joinGame.getCurrentDimensionData(), joinGame.getLastDeathPosition()));
}
public List<UUID> getServerBossBars() {
@ -619,8 +705,10 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
});
}
private CompletableFuture<Void> processCommandExecuteResult(String originalCommand,
CommandResult result) {
CommandResult result,
@Nullable SignedChatCommand signedCommand) {
if (result == CommandResult.denied()) {
return CompletableFuture.completedFuture(null);
}
@ -628,13 +716,30 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
MinecraftConnection smc = player.ensureAndGetCurrentServer().ensureConnected();
String commandToRun = result.getCommand().orElse(originalCommand);
if (result.isForwardToServer()) {
return CompletableFuture.runAsync(() -> smc.write(Chat.createServerbound("/"
+ commandToRun)), smc.eventLoop());
ChatBuilder write = ChatBuilder
.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 {
return server.getCommandManager().executeImmediatelyAsync(player, commandToRun)
.thenAcceptAsync(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());
}

Datei anzeigen

@ -41,6 +41,8 @@ import com.velocitypowered.api.permission.Tristate;
import com.velocitypowered.api.proxy.ConnectionRequestBuilder;
import com.velocitypowered.api.proxy.Player;
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.player.PlayerSettings;
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.protocol.ProtocolUtils;
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.Disconnect;
import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter;
import com.velocitypowered.proxy.protocol.packet.KeepAlive;
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest;
import com.velocitypowered.proxy.protocol.packet.chat.ChatBuilder;
import com.velocitypowered.proxy.protocol.packet.chat.LegacyChat;
import com.velocitypowered.proxy.protocol.packet.title.GenericTitlePacket;
import com.velocitypowered.proxy.server.VelocityRegisteredServer;
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.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 PlainTextComponentSerializer PASS_THRU_TRANSLATE = PlainTextComponentSerializer.builder()
@ -159,9 +162,10 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
.build();
private @Nullable String clientBrand;
private @Nullable Locale effectiveLocale;
private @Nullable IdentifiedKey playerKey;
ConnectedPlayer(VelocityServer server, GameProfile profile, MinecraftConnection connection,
@Nullable InetSocketAddress virtualHost, boolean onlineMode) {
@Nullable InetSocketAddress virtualHost, boolean onlineMode, @Nullable IdentifiedKey playerKey) {
this.server = server;
this.profile = profile;
this.connection = connection;
@ -176,6 +180,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
} else {
this.tabList = new VelocityTabListLegacy(this);
}
this.playerKey = playerKey;
}
@Override
@ -311,7 +316,9 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
@Override
public void sendMessage(@NonNull Identity identity, @NonNull Component 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
@ -321,9 +328,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
Preconditions.checkNotNull(type, "type");
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(packet);
connection.write(ChatBuilder.builder(this.getProtocolVersion())
.component(translated).forIdentity(identity)
.setType(type == MessageType.CHAT ? ChatBuilder.ChatType.CHAT : ChatBuilder.ChatType.SYSTEM)
.toClient());
}
@Override
@ -344,10 +353,10 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
JsonObject object = new JsonObject();
object.addProperty("text", LegacyComponentSerializer.legacySection()
.serialize(translated));
Chat chat = new Chat();
chat.setMessage(object.toString());
chat.setType(Chat.GAME_INFO_TYPE);
connection.write(chat);
LegacyChat legacyChat = new LegacyChat();
legacyChat.setMessage(object.toString());
legacyChat.setType(LegacyChat.GAME_INFO_TYPE);
connection.write(legacyChat);
}
}
@ -883,10 +892,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
@Override
public void spoofChatInput(String input) {
Preconditions.checkArgument(input.length() <= Chat.MAX_SERVERBOUND_MESSAGE_LENGTH,
"input cannot be greater than " + Chat.MAX_SERVERBOUND_MESSAGE_LENGTH
Preconditions.checkArgument(input.length() <= LegacyChat.MAX_SERVERBOUND_MESSAGE_LENGTH,
"input cannot be greater than " + LegacyChat.MAX_SERVERBOUND_MESSAGE_LENGTH
+ " characters in length");
ensureBackendConnection().write(Chat.createServerbound(input));
ensureBackendConnection().write(ChatBuilder.builder(getProtocolVersion())
.asPlayer(this).message(input).toServer());
}
@Override
@ -1055,6 +1065,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
return knownChannels;
}
@Override
public IdentifiedKey getIdentifiedKey() {
return playerKey;
}
private class IdentityImpl implements Identity {
@Override
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.velocitypowered.proxy.VelocityServer.GENERAL_GSON;
import static com.velocitypowered.proxy.connection.VelocityConstants.EMPTY_BYTE_ARRAY;
import static com.velocitypowered.proxy.util.EncryptionUtils.decryptRsa;
import static com.velocitypowered.proxy.util.EncryptionUtils.generateServerId;
import static com.velocitypowered.proxy.crypto.EncryptionUtils.decryptRsa;
import static com.velocitypowered.proxy.crypto.EncryptionUtils.generateServerId;
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.PreLoginComponentResult;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection;
@ -77,6 +80,23 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
public boolean handle(ServerLogin packet) {
assertState(LoginState.LOGIN_PACKET_EXPECTED);
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;
PreLoginEvent event = new PreLoginEvent(inbound, login.getUsername());
@ -135,7 +155,6 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
public boolean handle(EncryptionResponse packet) {
assertState(LoginState.ENCRYPTION_REQUEST_SENT);
this.currentState = LoginState.ENCRYPTION_RESPONSE_RECEIVED;
ServerLogin login = this.login;
if (login == null) {
throw new IllegalStateException("No ServerLogin packet received yet.");
@ -147,9 +166,16 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
try {
KeyPair serverKeyPair = server.getServerKeyPair();
byte[] decryptedVerifyToken = decryptRsa(serverKeyPair, packet.getVerifyToken());
if (!MessageDigest.isEqual(verify, decryptedVerifyToken)) {
throw new IllegalStateException("Unable to successfully decrypt the verification token.");
if (inbound.getIdentifiedKey() != null) {
IdentifiedKey playerKey = inbound.getIdentifiedKey();
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());

Datei anzeigen

@ -19,6 +19,8 @@ package com.velocitypowered.proxy.connection.client;
import com.velocitypowered.api.network.ProtocolVersion;
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.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage;
@ -32,9 +34,10 @@ import java.util.Optional;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import space.vectrix.flare.fastutil.Int2ObjectSyncMap;
public class LoginInboundConnection implements LoginPhaseConnection {
public class LoginInboundConnection implements LoginPhaseConnection, KeyIdentifiable {
private static final AtomicIntegerFieldUpdater<LoginInboundConnection> SEQUENCE_UPDATER =
AtomicIntegerFieldUpdater.newUpdater(LoginInboundConnection.class, "sequenceCounter");
@ -45,6 +48,7 @@ public class LoginInboundConnection implements LoginPhaseConnection {
private final Queue<LoginPluginMessage> loginMessagesToSend;
private volatile Runnable onAllMessagesHandled;
private volatile boolean loginEventFired;
private @MonotonicNonNull IdentifiedKey playerKey;
LoginInboundConnection(
InitialInboundConnection delegate) {
@ -149,4 +153,13 @@ public class LoginInboundConnection implements LoginPhaseConnection {
MinecraftConnection delegatedConnection() {
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 Integer minY; // 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.
* @param registryIdentifier the identifier for the dimension from the registry.
* @param dimensionId the dimension ID contained in the registry (the "id" tag)
* @param isNatural indicates if the dimension use natural world generation (e.g. overworld)
* @param ambientLight the light level the client sees without external lighting
* @param isShrunk indicates if the world is shrunk, aka not the full 256 blocks (e.g. nether)
* @param isUltrawarm internal dimension warmth flag
* @param hasCeiling indicates if the dimension has a ceiling layer
* @param hasSkylight indicates if the dimension should display the sun
* @param isPiglinSafe indicates if piglins should naturally zombify in this dimension
* @param doBedsWork indicates if players should be able to sleep in beds in this dimension
* @param doRespawnAnchorsWork indicates if player respawn points can be used in this dimension
* @param hasRaids indicates if raids can be spawned in the dimension
* @param logicalHeight the natural max height for the given dimension
* @param burningBehaviourIdentifier the identifier for how burning blocks work in the dimension
* @param fixedTime optional. If set to any game daytime value will deactivate time cycle
* @param createDragonFight optional. Internal flag used in the end dimension
* @param coordinateScale optional, unknown purpose
* @param effects optional, unknown purpose
* @param minY the world effective lowest build-level
* @param height the world height above zero
*
* @param registryIdentifier the identifier for the dimension from the registry.
* @param dimensionId the dimension ID contained in the registry (the "id" tag)
* @param isNatural indicates if the dimension use natural world generation (e.g. overworld)
* @param ambientLight the light level the client sees without external lighting
* @param isShrunk indicates if the world is shrunk, aka not the full 256 blocks (e.g. nether)
* @param isUltrawarm internal dimension warmth flag
* @param hasCeiling indicates if the dimension has a ceiling layer
* @param hasSkylight indicates if the dimension should display the sun
* @param isPiglinSafe indicates if piglins should naturally zombify in this dimension
* @param doBedsWork indicates if players should be able to sleep in beds in this dimension
* @param doRespawnAnchorsWork indicates if player respawn points can be used in this dimension
* @param hasRaids indicates if raids can be spawned in the dimension
* @param logicalHeight the natural max height for the given dimension
* @param burningBehaviourIdentifier the identifier for how burning blocks work in the dimension
* @param fixedTime optional. If set to any game daytime value will deactivate time cycle
* @param createDragonFight optional. Internal flag used in the end dimension
* @param coordinateScale optional, unknown purpose
* @param effects optional, unknown purpose
* @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,
@Nullable Integer dimensionId,
boolean isNatural,
float ambientLight, boolean isShrunk, boolean isUltrawarm,
boolean hasCeiling, boolean hasSkylight,
boolean isPiglinSafe, boolean doBedsWork,
boolean doRespawnAnchorsWork, boolean hasRaids,
int logicalHeight, String burningBehaviourIdentifier,
@Nullable Long fixedTime, @Nullable Boolean createDragonFight,
@Nullable Double coordinateScale,
@Nullable String effects,
@Nullable Integer minY, @Nullable Integer height) {
@Nullable Integer dimensionId,
boolean isNatural,
float ambientLight, boolean isShrunk, boolean isUltrawarm,
boolean hasCeiling, boolean hasSkylight,
boolean isPiglinSafe, boolean doBedsWork,
boolean doRespawnAnchorsWork, boolean hasRaids,
int logicalHeight, String burningBehaviourIdentifier,
@Nullable Long fixedTime, @Nullable Boolean createDragonFight,
@Nullable Double coordinateScale,
@Nullable String effects,
@Nullable Integer minY, @Nullable Integer height, @Nullable Integer monsterSpawnBlockLightLimit,
@Nullable Integer monsterSpawnLightLevel) {
this.monsterSpawnBlockLightLimit = monsterSpawnBlockLightLimit;
this.monsterSpawnLightLevel = monsterSpawnLightLevel;
Preconditions.checkNotNull(
registryIdentifier, "registryIdentifier cannot be null");
Preconditions.checkArgument(registryIdentifier.length() > 0,
@ -193,15 +202,15 @@ public final class DimensionData {
* and {@code dimensionId}.
*
* @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}
*/
public DimensionData annotateWith(String registryIdentifier,
@Nullable Integer dimensionId) {
@Nullable Integer dimensionId) {
return new DimensionData(registryIdentifier, dimensionId, isNatural, ambientLight, isShrunk,
isUltrawarm, hasCeiling, hasSkylight, isPiglinSafe, doBedsWork, doRespawnAnchorsWork,
hasRaids, logicalHeight, burningBehaviourIdentifier, fixedTime, createDragonFight,
coordinateScale, effects, minY, height);
coordinateScale, effects, minY, height, monsterSpawnBlockLightLimit, monsterSpawnLightLevel);
}
public boolean isUnannotated() {
@ -217,7 +226,7 @@ public final class DimensionData {
* @return game dimension data
*/
public static DimensionData decodeBaseCompoundTag(CompoundBinaryTag details,
ProtocolVersion version) {
ProtocolVersion version) {
boolean isNatural = details.getBoolean("natural");
float ambientLight = details.getFloat("ambient_light");
boolean isShrunk = details.getBoolean("shrunk");
@ -240,28 +249,40 @@ public final class DimensionData {
: null;
Integer minY = details.keySet().contains("min_y") ? details.getInt("min_y") : 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) {
Preconditions.checkNotNull(height,
"DimensionData requires 'height' to be present for this version");
"DimensionData requires 'height' to be present for this version");
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(
UNKNOWN_DIMENSION_ID, null, isNatural, ambientLight, isShrunk,
isUltrawarm, hasCeiling, hasSkylight, isPiglinSafe, doBedsWork, doRespawnAnchorsWork,
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
* 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
* @return game dimension data
*/
public static DimensionData decodeRegistryEntry(CompoundBinaryTag dimTag,
ProtocolVersion version) {
ProtocolVersion version) {
String registryIdentifier = dimTag.getString("name");
CompoundBinaryTag details;
Integer dimensionId = null;
@ -278,6 +299,7 @@ public final class DimensionData {
/**
* Encodes the Dimension data as CompoundTag.
*
* @param version the version to serialize as
* @return compound containing the dimension data
*/
@ -301,6 +323,7 @@ public final class DimensionData {
/**
* Serializes details of this dimension.
*
* @return serialized details of this dimension
*/
public CompoundBinaryTag serializeDimensionDetails() {
@ -335,6 +358,12 @@ public final class DimensionData {
if (height != null) {
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();
}
}

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 com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.crypto.IdentifiedKeyImpl;
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.util.VelocityLegacyHoverEventSerializer;
import com.velocitypowered.proxy.util.except.QuietDecoderException;
@ -59,7 +61,7 @@ public enum ProtocolUtils {
.legacyHoverEventSerializer(VelocityLegacyHoverEventSerializer.INSTANCE)
.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 =
new QuietDecoderException("Bad VarInt decoded");
private static final int[] VARINT_EXACT_BYTE_LENGTHS = new int[33];
@ -537,6 +539,31 @@ public enum ProtocolUtils {
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 {
SERVERBOUND,
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_17;
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_8;
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.proxy.protocol.packet.AvailableCommands;
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.Disconnect;
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.TabCompleteRequest;
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse;
import com.velocitypowered.proxy.protocol.packet.chat.LegacyChat;
import com.velocitypowered.proxy.protocol.packet.chat.PlayerChat;
import com.velocitypowered.proxy.protocol.packet.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.TitleActionbarPacket;
import com.velocitypowered.proxy.protocol.packet.title.TitleClearPacket;
@ -114,19 +119,25 @@ public enum StateRegistry {
map(0x02, MINECRAFT_1_12, false),
map(0x01, MINECRAFT_1_12_1, false),
map(0x05, MINECRAFT_1_13, false),
map(0x06, MINECRAFT_1_14, false));
serverbound.register(Chat.class, Chat::new,
map(0x06, MINECRAFT_1_14, false),
map(0x08, MINECRAFT_1_19, false));
serverbound.register(LegacyChat.class, LegacyChat::new,
map(0x01, MINECRAFT_1_7_2, false),
map(0x02, MINECRAFT_1_9, false),
map(0x03, MINECRAFT_1_12, false),
map(0x02, MINECRAFT_1_12_1, false),
map(0x03, MINECRAFT_1_14, 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,
map(0x15, MINECRAFT_1_7_2, false),
map(0x04, MINECRAFT_1_9, false),
map(0x05, MINECRAFT_1_12, 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,
map(0x17, MINECRAFT_1_7_2, false),
map(0x09, MINECRAFT_1_9, false),
@ -134,7 +145,8 @@ public enum StateRegistry {
map(0x09, MINECRAFT_1_12_1, false),
map(0x0A, MINECRAFT_1_13, 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,
map(0x00, MINECRAFT_1_7_2, false),
map(0x0B, MINECRAFT_1_9, false),
@ -143,7 +155,8 @@ public enum StateRegistry {
map(0x0E, MINECRAFT_1_13, false),
map(0x0F, MINECRAFT_1_14, 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,
map(0x19, MINECRAFT_1_8, false),
map(0x16, MINECRAFT_1_9, false),
@ -151,20 +164,22 @@ public enum StateRegistry {
map(0x1D, MINECRAFT_1_13, false),
map(0x1F, MINECRAFT_1_14, 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,
map(0x0C, MINECRAFT_1_9, false),
map(0x0D, MINECRAFT_1_15, false),
map(0x0C, MINECRAFT_1_16, false),
map(0x0D, MINECRAFT_1_17, false));
clientbound.register(Chat.class, Chat::new,
map(0x0D, MINECRAFT_1_17, false),
map(0x0A, MINECRAFT_1_19, false));
clientbound.register(LegacyChat.class, LegacyChat::new,
map(0x02, MINECRAFT_1_7_2, true),
map(0x0F, MINECRAFT_1_9, true),
map(0x0E, MINECRAFT_1_13, true),
map(0x0F, MINECRAFT_1_15, 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,
map(0x3A, MINECRAFT_1_7_2, false),
map(0x0E, MINECRAFT_1_9, false),
@ -172,13 +187,15 @@ public enum StateRegistry {
map(0x11, MINECRAFT_1_15, false),
map(0x10, MINECRAFT_1_16, 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,
map(0x11, MINECRAFT_1_13, false),
map(0x12, MINECRAFT_1_15, false),
map(0x11, MINECRAFT_1_16, false),
map(0x10, MINECRAFT_1_16_2, false),
map(0x12, MINECRAFT_1_17, false));
map(0x12, MINECRAFT_1_17, false),
map(0x0F, MINECRAFT_1_19, false));
clientbound.register(PluginMessage.class, PluginMessage::new,
map(0x3F, MINECRAFT_1_7_2, false),
map(0x18, MINECRAFT_1_9, false),
@ -187,7 +204,8 @@ public enum StateRegistry {
map(0x19, MINECRAFT_1_15, false),
map(0x18, MINECRAFT_1_16, 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,
map(0x40, MINECRAFT_1_7_2, false),
map(0x1A, MINECRAFT_1_9, false),
@ -196,7 +214,8 @@ public enum StateRegistry {
map(0x1B, MINECRAFT_1_15, false),
map(0x1A, MINECRAFT_1_16, 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,
map(0x00, MINECRAFT_1_7_2, false),
map(0x1F, MINECRAFT_1_9, false),
@ -205,7 +224,8 @@ public enum StateRegistry {
map(0x21, MINECRAFT_1_15, false),
map(0x20, MINECRAFT_1_16, 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,
map(0x01, MINECRAFT_1_7_2, false),
map(0x23, MINECRAFT_1_9, false),
@ -214,7 +234,8 @@ public enum StateRegistry {
map(0x26, MINECRAFT_1_15, false),
map(0x25, MINECRAFT_1_16, 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,
map(0x07, MINECRAFT_1_7_2, true),
map(0x33, MINECRAFT_1_9, true),
@ -225,7 +246,8 @@ public enum StateRegistry {
map(0x3B, MINECRAFT_1_15, true),
map(0x3A, MINECRAFT_1_16, 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,
map(0x48, MINECRAFT_1_8, false),
map(0x32, MINECRAFT_1_9, false),
@ -236,7 +258,8 @@ public enum StateRegistry {
map(0x3A, MINECRAFT_1_15, false),
map(0x39, MINECRAFT_1_16, 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,
map(0x47, MINECRAFT_1_8, true),
map(0x48, MINECRAFT_1_9, true),
@ -248,7 +271,8 @@ public enum StateRegistry {
map(0x54, MINECRAFT_1_15, true),
map(0x53, MINECRAFT_1_16, 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,
map(0x45, MINECRAFT_1_8, true),
map(0x45, MINECRAFT_1_9, true),
@ -265,12 +289,14 @@ public enum StateRegistry {
map(0x59, MINECRAFT_1_17, true),
map(0x5A, MINECRAFT_1_18, true));
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,
map(0x5A, MINECRAFT_1_17, true),
map(0x5B, MINECRAFT_1_18, true));
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,
map(0x38, MINECRAFT_1_7_2, false),
map(0x2D, MINECRAFT_1_9, false),
@ -280,7 +306,10 @@ public enum StateRegistry {
map(0x34, MINECRAFT_1_15, false),
map(0x33, MINECRAFT_1_16, 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 {

Datei anzeigen

@ -83,7 +83,7 @@ public class AvailableCommands implements MinecraftPacket {
int commands = ProtocolUtils.readVarInt(buf);
WireNode[] wireNodes = new WireNode[commands];
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
@ -130,13 +130,13 @@ public class AvailableCommands implements MinecraftPacket {
// Now serialize the children.
ProtocolUtils.writeVarInt(buf, idMappings.size());
for (CommandNode<CommandSource> child : idMappings.keySet()) {
serializeNode(child, buf, idMappings);
serializeNode(child, buf, idMappings, protocolVersion);
}
ProtocolUtils.writeVarInt(buf, idMappings.getInt(rootNode));
}
private static void serializeNode(CommandNode<CommandSource> node, ByteBuf buf,
Object2IntMap<CommandNode<CommandSource>> idMappings) {
Object2IntMap<CommandNode<CommandSource>> idMappings, ProtocolVersion protocolVersion) {
byte flags = 0;
if (node.getRedirect() != null) {
flags |= FLAG_IS_REDIRECT;
@ -168,7 +168,7 @@ public class AvailableCommands implements MinecraftPacket {
if (node instanceof ArgumentCommandNode<?, ?>) {
ProtocolUtils.writeString(buf, node.getName());
ArgumentPropertyRegistry.serialize(buf,
((ArgumentCommandNode<CommandSource, ?>) node).getType());
((ArgumentCommandNode<CommandSource, ?>) node).getType(), protocolVersion);
if (((ArgumentCommandNode<CommandSource, ?>) node).getCustomSuggestions() != null) {
SuggestionProvider<CommandSource> provider = ((ArgumentCommandNode<CommandSource, ?>) node)
@ -189,7 +189,7 @@ public class AvailableCommands implements MinecraftPacket {
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();
int[] children = ProtocolUtils.readIntegerArray(buf);
int redirectTo = -1;
@ -205,14 +205,13 @@ public class AvailableCommands implements MinecraftPacket {
.literal(ProtocolUtils.readString(buf)));
case NODE_TYPE_ARGUMENT:
String name = ProtocolUtils.readString(buf);
ArgumentType<?> argumentType = ArgumentPropertyRegistry.deserialize(buf);
ArgumentType<?> argumentType = ArgumentPropertyRegistry.deserialize(buf, version);
RequiredArgumentBuilder<CommandSource, ?> argumentBuilder = RequiredArgumentBuilder
.argument(name, argumentType);
if ((flags & FLAG_HAS_SUGGESTIONS) != 0) {
argumentBuilder.suggests(new ProtocolSuggestionProvider(ProtocolUtils.readString(buf)));
}
return new WireNode(idx, flags, children, redirectTo, argumentBuilder);
default:
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.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import com.velocitypowered.proxy.util.except.QuietDecoderException;
import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Arrays;
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[] verifyToken = EMPTY_BYTE_ARRAY;
private @Nullable Long salt;
public byte[] getSharedSecret() {
return sharedSecret.clone();
@ -40,6 +46,13 @@ public class EncryptionResponse implements MinecraftPacket {
return verifyToken.clone();
}
public long getSalt() {
if (salt == null) {
throw NO_SALT;
}
return salt;
}
@Override
public String toString() {
return "EncryptionResponse{"
@ -52,7 +65,14 @@ public class EncryptionResponse implements MinecraftPacket {
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
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 {
this.sharedSecret = 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) {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
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);
} else {
ProtocolUtils.writeByteArray17(sharedSecret, buf, false);
@ -79,11 +107,22 @@ public class EncryptionResponse implements MinecraftPacket {
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.
// 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
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.protocol.*;
import io.netty.buffer.ByteBuf;
import it.unimi.dsi.fastutil.Pair;
import net.kyori.adventure.nbt.BinaryTagIO;
import net.kyori.adventure.nbt.BinaryTagTypes;
import net.kyori.adventure.nbt.CompoundBinaryTag;
@ -33,7 +34,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
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 short gamemode;
private int dimension;
@ -51,6 +52,8 @@ public class JoinGame implements MinecraftPacket {
private short previousGamemode; // 1.16+
private CompoundBinaryTag biomeRegistry; // 1.16.2+
private int simulationDistance; // 1.18+
private @Nullable Pair<String, Long> lastDeathPosition;
private CompoundBinaryTag chatTypeRegistry; // placeholder, 1.19+
public int getEntityId() {
return entityId;
@ -172,6 +175,22 @@ public class JoinGame implements MinecraftPacket {
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
public String toString() {
return "JoinGame{"
@ -188,6 +207,7 @@ public class JoinGame implements MinecraftPacket {
+ ", dimensionInfo='" + dimensionInfo + '\''
+ ", previousGamemode=" + previousGamemode
+ ", simulationDistance=" + simulationDistance
+ ", lastDeathPosition='" + lastDeathPosition + '\''
+ '}';
}
@ -253,6 +273,7 @@ public class JoinGame implements MinecraftPacket {
dimensionRegistryContainer = registryContainer.getCompound("minecraft:dimension_type")
.getList("value", BinaryTagTypes.COMPOUND);
this.biomeRegistry = registryContainer.getCompound("minecraft:worldgen/biome");
this.chatTypeRegistry = registryContainer.getCompound("minecraft:chat_type");
} else {
dimensionRegistryContainer = registryContainer.getList("dimension",
BinaryTagTypes.COMPOUND);
@ -263,7 +284,8 @@ public class JoinGame implements MinecraftPacket {
String dimensionIdentifier;
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);
dimensionIdentifier = ProtocolUtils.readString(buf);
this.currentDimensionData = DimensionData.decodeBaseCompoundTag(currentDimDataTag, version)
@ -290,6 +312,10 @@ public class JoinGame implements MinecraftPacket {
boolean isDebug = buf.readBoolean();
boolean isFlat = buf.readBoolean();
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
@ -356,11 +382,13 @@ public class JoinGame implements MinecraftPacket {
dimensionRegistryEntry.put("value", encodedDimensionRegistry);
registryContainer.put("minecraft:dimension_type", dimensionRegistryEntry.build());
registryContainer.put("minecraft:worldgen/biome", biomeRegistry);
registryContainer.put("minecraft:chat_type", chatTypeRegistry);
} else {
registryContainer.put("dimension", encodedDimensionRegistry);
}
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.writeString(buf, dimensionInfo.getRegistryIdentifier());
} else {
@ -382,6 +410,17 @@ public class JoinGame implements MinecraftPacket {
buf.writeBoolean(showRespawnScreen);
buf.writeBoolean(dimensionInfo.isDebugType());
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

Datei anzeigen

@ -19,6 +19,7 @@ package com.velocitypowered.proxy.protocol.packet;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
import com.velocitypowered.api.proxy.player.TabListEntry;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
@ -74,6 +75,12 @@ public class PlayerListItem implements MinecraftPacket {
item.setGameMode(ProtocolUtils.readVarInt(buf));
item.setLatency(ProtocolUtils.readVarInt(buf));
item.setDisplayName(readOptionalComponent(buf, version));
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
if (buf.readBoolean()) {
item.setPlayerKey(ProtocolUtils.readPlayerKey(buf));
}
}
break;
case UPDATE_GAMEMODE:
item.setGameMode(ProtocolUtils.readVarInt(buf));
@ -124,8 +131,15 @@ public class PlayerListItem implements MinecraftPacket {
ProtocolUtils.writeProperties(buf, item.getProperties());
ProtocolUtils.writeVarInt(buf, item.getGameMode());
ProtocolUtils.writeVarInt(buf, item.getLatency());
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;
case UPDATE_GAMEMODE:
ProtocolUtils.writeVarInt(buf, item.getGameMode());
@ -181,6 +195,7 @@ public class PlayerListItem implements MinecraftPacket {
private int gameMode;
private int latency;
private @Nullable Component displayName;
private @Nullable IdentifiedKey playerKey;
public Item() {
uuid = null;
@ -196,6 +211,7 @@ public class PlayerListItem implements MinecraftPacket {
.setProperties(entry.getProfile().getProperties())
.setLatency(entry.getLatency())
.setGameMode(entry.getGameMode())
.setPlayerKey(entry.getIdentifiedKey())
.setDisplayName(entry.getDisplayNameComponent().orElse(null));
}
@ -247,5 +263,14 @@ public class PlayerListItem implements MinecraftPacket {
this.displayName = displayName;
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.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import it.unimi.dsi.fastutil.Pair;
import net.kyori.adventure.nbt.BinaryTagIO;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import org.checkerframework.checker.nullness.qual.Nullable;
public class Respawn implements MinecraftPacket {
@ -38,13 +40,14 @@ public class Respawn implements MinecraftPacket {
private DimensionInfo dimensionInfo; // 1.16-1.16.1
private short previousGamemode; // 1.16+
private DimensionData currentDimensionData; // 1.16.2+
private @Nullable Pair<String, Long> lastDeathPosition; // 1.19+
public Respawn() {
}
public Respawn(int dimension, long partialHashedSeed, short difficulty, short gamemode,
String levelType, boolean shouldKeepPlayerData, DimensionInfo dimensionInfo,
short previousGamemode, DimensionData currentDimensionData) {
short previousGamemode, DimensionData currentDimensionData, @Nullable Pair<String, Long> lastDeathPosition) {
this.dimension = dimension;
this.partialHashedSeed = partialHashedSeed;
this.difficulty = difficulty;
@ -54,6 +57,7 @@ public class Respawn implements MinecraftPacket {
this.dimensionInfo = dimensionInfo;
this.previousGamemode = previousGamemode;
this.currentDimensionData = currentDimensionData;
this.lastDeathPosition = lastDeathPosition;
}
public int getDimension() {
@ -112,6 +116,14 @@ public class Respawn implements MinecraftPacket {
this.previousGamemode = previousGamemode;
}
public void setLastDeathPosition(Pair<String, Long> lastDeathPosition) {
this.lastDeathPosition = lastDeathPosition;
}
public Pair<String, Long> getLastDeathPosition() {
return lastDeathPosition;
}
@Override
public String toString() {
return "Respawn{"
@ -133,7 +145,8 @@ public class Respawn implements MinecraftPacket {
String dimensionIdentifier = null;
String levelName = null;
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());
dimensionIdentifier = ProtocolUtils.readString(buf);
this.currentDimensionData = DimensionData.decodeBaseCompoundTag(dimDataTag, version)
@ -161,12 +174,16 @@ public class Respawn implements MinecraftPacket {
} else {
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
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0
&& version.compareTo(ProtocolVersion.MINECRAFT_1_19) < 0) {
ProtocolUtils.writeCompoundTag(buf, currentDimensionData.serializeDimensionDetails());
ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier());
} else {
@ -191,6 +208,17 @@ public class Respawn implements MinecraftPacket {
} else {
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

Datei anzeigen

@ -19,6 +19,7 @@ package com.velocitypowered.proxy.protocol.packet;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
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 @Nullable String username;
private @Nullable IdentifiedKey playerKey; // Introduced in 1.19
public ServerLogin() {
}
public ServerLogin(String username) {
public ServerLogin(String username, @Nullable IdentifiedKey playerKey) {
this.username = Preconditions.checkNotNull(username, "username");
}
@ -47,10 +49,19 @@ public class ServerLogin implements MinecraftPacket {
return username;
}
public IdentifiedKey getPlayerKey() {
return playerKey;
}
public void setPlayerKey(IdentifiedKey playerKey) {
this.playerKey = playerKey;
}
@Override
public String toString() {
return "ServerLogin{"
+ "username='" + username + '\''
+ "playerKey='" + playerKey + '\''
+ '}';
}
@ -60,6 +71,12 @@ public class ServerLogin implements MinecraftPacket {
if (username.isEmpty()) {
throw EMPTY_USERNAME;
}
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
if (buf.readBoolean()) {
playerKey = ProtocolUtils.readPlayerKey(buf);
}
}
}
@Override
@ -68,13 +85,27 @@ public class ServerLogin implements MinecraftPacket {
throw new IllegalStateException("No username found!");
}
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
public int expectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) {
// Accommodate the rare (but likely malicious) use of UTF-8 usernames, since it is technically
// 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

Datei anzeigen

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

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;
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.EmptyArgumentPropertySerializer.EMPTY;
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.StringArgumentPropertySerializer.STRING;
import com.google.common.base.Preconditions;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.arguments.BoolArgumentType;
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.LongArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import java.util.HashMap;
import java.util.Map;
import org.checkerframework.checker.nullness.qual.Nullable;
public class ArgumentPropertyRegistry {
private ArgumentPropertyRegistry() {
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>,
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,
ArgumentPropertySerializer<T> serializer) {
byId.put(identifier, serializer);
private static <T extends ArgumentType<?>> void register(ArgumentIdentifier identifier,
Class<T> klazz, ArgumentPropertySerializer<T> serializer) {
byIdentifier.put(identifier, serializer);
byClass.put(klazz, serializer);
classToId.put(klazz, identifier);
}
private static <T> void empty(String identifier) {
private static <T> void empty(ArgumentIdentifier identifier) {
empty(identifier, EMPTY);
}
private static <T> void empty(String identifier, ArgumentPropertySerializer<T> serializer) {
byId.put(identifier, serializer);
private static <T> void empty(ArgumentIdentifier identifier,
ArgumentPropertySerializer<T> serializer) {
byIdentifier.put(identifier, serializer);
}
/**
@ -68,13 +75,14 @@ public class ArgumentPropertyRegistry {
* @param buf the buffer to deserialize
* @return the deserialized {@link ArgumentType}
*/
public static ArgumentType<?> deserialize(ByteBuf buf) {
String identifier = ProtocolUtils.readString(buf);
ArgumentPropertySerializer<?> serializer = byId.get(identifier);
public static ArgumentType<?> deserialize(ByteBuf buf, ProtocolVersion protocolVersion) {
ArgumentIdentifier identifier = readIdentifier(buf, protocolVersion);
ArgumentPropertySerializer<?> serializer = byIdentifier.get(identifier);
if (serializer == null) {
throw new IllegalArgumentException("Argument type identifier " + identifier + " unknown.");
}
Object result = serializer.deserialize(buf);
Object result = serializer.deserialize(buf, protocolVersion);
if (result instanceof ArgumentType) {
return (ArgumentType<?>) result;
@ -88,95 +96,144 @@ public class ArgumentPropertyRegistry {
* @param buf the buffer to serialize into
* @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) {
PassthroughProperty property = (PassthroughProperty) type;
ProtocolUtils.writeString(buf, property.getIdentifier());
writeIdentifier(buf, property.getIdentifier(), protocolVersion);
if (property.getResult() != null) {
property.getSerializer().serialize(property.getResult(), buf);
property.getSerializer().serialize(property.getResult(), buf, protocolVersion);
}
} else if (type instanceof ModArgumentProperty) {
ModArgumentProperty property = (ModArgumentProperty) type;
ProtocolUtils.writeString(buf, property.getIdentifier());
writeIdentifier(buf, property.getIdentifier(), protocolVersion);
buf.writeBytes(property.getData());
} else {
ArgumentPropertySerializer serializer = byClass.get(type.getClass());
String id = classToId.get(type.getClass());
ArgumentIdentifier id = classToId.get(type.getClass());
if (serializer == null || id == null) {
throw new IllegalArgumentException("Don't know how to serialize "
+ type.getClass().getName());
}
ProtocolUtils.writeString(buf, id);
serializer.serialize(type, buf);
writeIdentifier(buf, id, protocolVersion);
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 {
// Base Brigadier argument types
register("brigadier:string", StringArgumentType.class, STRING);
register("brigadier:integer", IntegerArgumentType.class, INTEGER);
register("brigadier:float", FloatArgumentType.class, FLOAT);
register("brigadier:double", DoubleArgumentType.class, DOUBLE);
register("brigadier:bool", BoolArgumentType.class,
register(id("brigadier:bool", mapSet(MINECRAFT_1_19, 0)), BoolArgumentType.class,
new ArgumentPropertySerializer<>() {
@Override
public BoolArgumentType deserialize(ByteBuf buf) {
public BoolArgumentType deserialize(ByteBuf buf, ProtocolVersion protocolVersion) {
return BoolArgumentType.bool();
}
@Override
public void serialize(BoolArgumentType object, ByteBuf buf) {
public void serialize(BoolArgumentType object, ByteBuf buf,
ProtocolVersion protocolVersion) {
}
});
register("brigadier:long", LongArgumentType.class, LONG);
register("minecraft:resource", RegistryKeyArgument.class, RegistryKeyArgumentSerializer.REGISTRY);
register("minecraft:resource_or_tag", RegistryKeyArgument.class, RegistryKeyArgumentSerializer.REGISTRY);
register(id("brigadier:float", mapSet(MINECRAFT_1_19, 1)), FloatArgumentType.class, FLOAT);
register(id("brigadier:double", mapSet(MINECRAFT_1_19, 2)), DoubleArgumentType.class, DOUBLE);
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
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("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
empty(id("minecraft:nbt")); // No longer in 1.19+
}
}

Datei anzeigen

@ -17,11 +17,12 @@
package com.velocitypowered.proxy.protocol.packet.brigadier;
import com.velocitypowered.api.network.ProtocolVersion;
import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.Nullable;
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;
import com.velocitypowered.api.network.ProtocolVersion;
import io.netty.buffer.ByteBuf;
class ByteArgumentPropertySerializer implements ArgumentPropertySerializer<Byte> {
@ -28,12 +29,12 @@ class ByteArgumentPropertySerializer implements ArgumentPropertySerializer<Byte>
}
@Override
public Byte deserialize(ByteBuf buf) {
public Byte deserialize(ByteBuf buf, ProtocolVersion protocolVersion) {
return buf.readByte();
}
@Override
public void serialize(Byte object, ByteBuf buf) {
public void serialize(Byte object, ByteBuf buf, ProtocolVersion protocolVersion) {
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 com.mojang.brigadier.arguments.DoubleArgumentType;
import com.velocitypowered.api.network.ProtocolVersion;
import io.netty.buffer.ByteBuf;
class DoubleArgumentPropertySerializer implements ArgumentPropertySerializer<DoubleArgumentType> {
@ -32,7 +33,7 @@ class DoubleArgumentPropertySerializer implements ArgumentPropertySerializer<Dou
}
@Override
public DoubleArgumentType deserialize(ByteBuf buf) {
public DoubleArgumentType deserialize(ByteBuf buf, ProtocolVersion protocolVersion) {
byte flags = buf.readByte();
double minimum = (flags & HAS_MINIMUM) != 0 ? buf.readDouble() : Double.MIN_VALUE;
double maximum = (flags & HAS_MAXIMUM) != 0 ? buf.readDouble() : Double.MAX_VALUE;
@ -40,7 +41,7 @@ class DoubleArgumentPropertySerializer implements ArgumentPropertySerializer<Dou
}
@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 hasMaximum = Double.compare(object.getMaximum(), Double.MAX_VALUE) != 0;
byte flag = getFlags(hasMinimum, hasMaximum);

Datei anzeigen

@ -17,6 +17,7 @@
package com.velocitypowered.proxy.protocol.packet.brigadier;
import com.velocitypowered.api.network.ProtocolVersion;
import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.Nullable;
@ -32,12 +33,12 @@ class EmptyArgumentPropertySerializer implements ArgumentPropertySerializer<Void
}
@Override
public @Nullable Void deserialize(ByteBuf buf) {
public @Nullable Void deserialize(ByteBuf buf, ProtocolVersion protocolVersion) {
return null;
}
@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 com.mojang.brigadier.arguments.FloatArgumentType;
import com.velocitypowered.api.network.ProtocolVersion;
import io.netty.buffer.ByteBuf;
class FloatArgumentPropertySerializer implements ArgumentPropertySerializer<FloatArgumentType> {
@ -33,7 +34,7 @@ class FloatArgumentPropertySerializer implements ArgumentPropertySerializer<Floa
}
@Override
public FloatArgumentType deserialize(ByteBuf buf) {
public FloatArgumentType deserialize(ByteBuf buf, ProtocolVersion protocolVersion) {
byte flags = buf.readByte();
float minimum = (flags & HAS_MINIMUM) != 0 ? buf.readFloat() : Float.MIN_VALUE;
float maximum = (flags & HAS_MAXIMUM) != 0 ? buf.readFloat() : Float.MAX_VALUE;
@ -41,7 +42,7 @@ class FloatArgumentPropertySerializer implements ArgumentPropertySerializer<Floa
}
@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 hasMaximum = Float.compare(object.getMaximum(), Float.MAX_VALUE) != 0;
byte flag = getFlags(hasMinimum, hasMaximum);

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -17,6 +17,7 @@
package com.velocitypowered.proxy.protocol.packet.brigadier;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
@ -30,14 +31,22 @@ class ModArgumentPropertySerializer implements ArgumentPropertySerializer<ModArg
}
@Override
public @Nullable ModArgumentProperty deserialize(ByteBuf buf) {
String identifier = ProtocolUtils.readString(buf);
public @Nullable ModArgumentProperty deserialize(ByteBuf buf, ProtocolVersion version) {
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);
return new ModArgumentProperty(identifier, Unpooled.wrappedBuffer(extraData));
}
@Override
public void serialize(ModArgumentProperty object, ByteBuf buf) {
public void serialize(ModArgumentProperty object, ByteBuf buf, ProtocolVersion version) {
// This is special-cased by ArgumentPropertyRegistry
throw new UnsupportedOperationException();
}

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -18,6 +18,7 @@
package com.velocitypowered.proxy.protocol.packet.brigadier;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
@ -34,7 +35,7 @@ class StringArgumentPropertySerializer implements ArgumentPropertySerializer<Str
}
@Override
public StringArgumentType deserialize(ByteBuf buf) {
public StringArgumentType deserialize(ByteBuf buf, ProtocolVersion protocolVersion) {
int type = ProtocolUtils.readVarInt(buf);
switch (type) {
case 0:
@ -49,7 +50,7 @@ class StringArgumentPropertySerializer implements ArgumentPropertySerializer<Str
}
@Override
public void serialize(StringArgumentType object, ByteBuf buf) {
public void serialize(StringArgumentType object, ByteBuf buf, ProtocolVersion protocolVersion) {
switch (object.getType()) {
case SINGLE_WORD:
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/>.
*/
package com.velocitypowered.proxy.protocol.packet;
package com.velocitypowered.proxy.protocol.packet.chat;
import com.google.common.base.Preconditions;
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.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import java.util.UUID;
import net.kyori.adventure.identity.Identity;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.UUID;
public class Chat implements MinecraftPacket {
public class LegacyChat implements MinecraftPacket {
public static final byte CHAT_TYPE = (byte) 0;
public static final byte SYSTEM_TYPE = (byte) 1;
@ -41,15 +40,21 @@ public class Chat implements MinecraftPacket {
private byte type;
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.type = type;
this.sender = sender;
}
/**
* Retrieves the Chat message.
*/
public String getMessage() {
if (message == null) {
throw new IllegalStateException("Message is not specified");
@ -115,20 +120,4 @@ public class Chat implements MinecraftPacket {
public boolean handle(MinecraftSessionHandler handler) {
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;
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.TabListEntry;
import com.velocitypowered.api.util.GameProfile;
@ -119,10 +120,16 @@ public class VelocityTabList implements TabList {
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
public TabListEntry buildEntry(GameProfile profile,
net.kyori.adventure.text.@Nullable Component displayName, int latency, int gameMode) {
return new VelocityTabListEntry(this, profile, displayName, latency, gameMode);
net.kyori.adventure.text.@Nullable Component displayName,
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;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
import com.velocitypowered.api.proxy.player.TabList;
import com.velocitypowered.api.proxy.player.TabListEntry;
import com.velocitypowered.api.util.GameProfile;
@ -31,14 +32,17 @@ public class VelocityTabListEntry implements TabListEntry {
private net.kyori.adventure.text.Component displayName;
private int latency;
private int gameMode;
private @Nullable IdentifiedKey playerKey;
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.profile = profile;
this.displayName = displayName;
this.latency = latency;
this.gameMode = gameMode;
this.playerKey = playerKey;
}
@Override
@ -98,4 +102,13 @@ public class VelocityTabListEntry implements TabListEntry {
void setGameModeInternal(int 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,
@Nullable Component displayName, int latency, int gameMode) {
super(tabList, profile, displayName, latency, gameMode);
super(tabList, profile, displayName, latency, gameMode, null);
}
@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.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.illegal-chat-characters=Illegal characters in chat
velocity.error.illegal-legacyChat-characters=Illegal characters in legacyChat
# Commands
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.
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
# authentication server, the player is kicked. This disallows some VPN and proxy
# 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 com.velocitypowered.proxy.crypto.EncryptionUtils;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import org.junit.jupiter.api.Test;
class EncryptionUtilsTest {