Add various missing jd, bump remaining deps (#1718)

This commit is contained in:
R00tB33rMan
2026-01-29 08:31:50 -05:00
committed by GitHub
parent 5320aae5d9
commit 7d0c002f89
185 changed files with 2990 additions and 502 deletions

View File

@@ -14,10 +14,6 @@ application {
}
tasks {
withType<Checkstyle> {
exclude("**/com/velocitypowered/proxy/protocol/packet/**")
}
jar {
manifest {
attributes["Implementation-Title"] = "Velocity"
@@ -33,7 +29,7 @@ tasks {
transform(Log4j2PluginsCacheFileTransformer::class.java)
// Exclude all the collection types we don"t intend to use
// Exclude all the collection types we don't intend to use
exclude("it/unimi/dsi/fastutil/booleans/**")
exclude("it/unimi/dsi/fastutil/bytes/**")
exclude("it/unimi/dsi/fastutil/chars/**")
@@ -42,7 +38,7 @@ tasks {
exclude("it/unimi/dsi/fastutil/longs/**")
exclude("it/unimi/dsi/fastutil/shorts/**")
// Exclude the fastutil IO utilities - we don"t use them.
// Exclude the fastutil IO utilities - we don't use them.
exclude("it/unimi/dsi/fastutil/io/**")
// Exclude most of the int types - Object2IntMap have a values() method that returns an

View File

@@ -650,7 +650,7 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
}
/**
* Calls {@link #shutdown(boolean, Component)} with the default reason "Proxy shutting down."
* Calls {@link #shutdown(boolean, Component)} with the default reason "Proxy shutting down.".
*
* @param explicitExit whether the user explicitly shut down the proxy
*/

View File

@@ -37,7 +37,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Modern (Minecraft 1.20.3+) ResourcePackHandler
* Modern (Minecraft 1.20.3+) ResourcePackHandler.
*/
public final class ModernResourcePackHandler extends ResourcePackHandler {
private final ListMultimap<UUID, ResourcePackInfo> outstandingResourcePacks =

View File

@@ -118,6 +118,7 @@ public abstract sealed class ResourcePackHandler
/**
* Processes a client response to a sent resource-pack.
*
* <p>Cases in which no action will be taken:</p>
* <ul>
*

View File

@@ -545,6 +545,36 @@ public class VelocityEventManager implements EventManager {
}
}
private <E> void fire(final @Nullable CompletableFuture<E> future, final E event,
final int offset, final boolean currentlyAsync, final HandlerRegistration[] registrations) {
for (int i = offset; i < registrations.length; i++) {
final HandlerRegistration registration = registrations[i];
try {
final EventTask eventTask = registration.handler.executeAsync(event);
if (eventTask == null) {
continue;
}
final ContinuationTask<E> continuationTask = new ContinuationTask<>(eventTask,
registrations, future, event, i, currentlyAsync);
if (currentlyAsync || !eventTask.requiresAsync()) {
if (continuationTask.execute()) {
continue;
}
} else {
registration.plugin.getExecutorService().execute(continuationTask);
}
// fire will continue in another thread once the async task is
// executed and the continuation is resumed
return;
} catch (final Throwable t) {
logHandlerException(registration, t);
}
}
if (future != null) {
future.complete(event);
}
}
private static final int TASK_STATE_DEFAULT = 0;
private static final int TASK_STATE_EXECUTING = 1;
private static final int TASK_STATE_CONTINUE_IMMEDIATELY = 2;
@@ -669,40 +699,10 @@ public class VelocityEventManager implements EventManager {
}
}
private <E> void fire(final @Nullable CompletableFuture<E> future, final E event,
final int offset, final boolean currentlyAsync, final HandlerRegistration[] registrations) {
for (int i = offset; i < registrations.length; i++) {
final HandlerRegistration registration = registrations[i];
try {
final EventTask eventTask = registration.handler.executeAsync(event);
if (eventTask == null) {
continue;
}
final ContinuationTask<E> continuationTask = new ContinuationTask<>(eventTask,
registrations, future, event, i, currentlyAsync);
if (currentlyAsync || !eventTask.requiresAsync()) {
if (continuationTask.execute()) {
continue;
}
} else {
registration.plugin.getExecutorService().execute(continuationTask);
}
// fire will continue in another thread once the async task is
// executed and the continuation is resumed
return;
} catch (final Throwable t) {
logHandlerException(registration, t);
}
}
if (future != null) {
future.complete(event);
}
}
private static void logHandlerException(
final HandlerRegistration registration, final Throwable t) {
final PluginDescription pluginDescription = registration.plugin.getDescription();
logger.error("Couldn't pass {} to {} {}", registration.eventType.getSimpleName(),
pluginDescription.getId(), pluginDescription.getVersion().orElse(""), t);
}
}
}

View File

@@ -633,7 +633,7 @@ public enum ProtocolUtils {
private static final int FORGE_MAX_ARRAY_LENGTH = Integer.MAX_VALUE & 0x1FFF9A;
/**
* Reads an byte array for legacy version 1.7 from the specified {@code buf}
* Reads an byte array for legacy version 1.7 from the specified {@code buf}.
*
* @param buf the buffer to read from
* @return the read byte array
@@ -671,7 +671,7 @@ public enum ProtocolUtils {
}
/**
* Writes an byte array for legacy version 1.7 to the specified {@code buf}
* Writes an byte array for legacy version 1.7 to the specified {@code buf}.
*
* @param b array
* @param buf buf
@@ -695,7 +695,7 @@ public enum ProtocolUtils {
}
/**
* Writes an {@link ByteBuf} for legacy version 1.7 to the specified {@code buf}
* Writes an {@link ByteBuf} for legacy version 1.7 to the specified {@code buf}.
*
* @param b array
* @param buf buf

View File

@@ -53,6 +53,13 @@ import java.util.function.Predicate;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents a packet that contains the list of available commands, implementing {@link MinecraftPacket}.
*
* <p>The {@code AvailableCommandsPacket} is responsible for transmitting the set of commands
* that a player can execute. It provides the necessary information about available commands
* within the current session or game state.</p>
*/
public class AvailableCommandsPacket implements MinecraftPacket {
private static final Command<CommandSource> PLACEHOLDER_COMMAND = source -> 0;

View File

@@ -29,6 +29,10 @@ import java.util.UUID;
import net.kyori.adventure.bossbar.BossBar;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents a packet used to manage boss bars.
* This packet can add, remove, or update a boss bar.
*/
public class BossBarPacket implements MinecraftPacket {
private static final Enum2IntMap<BossBar.Color> COLORS_TO_PROTOCOL =
@@ -70,6 +74,14 @@ public class BossBarPacket implements MinecraftPacket {
private int overlay;
private short flags;
/**
* Creates a packet to add a new boss bar.
*
* @param id the UUID of the boss bar
* @param bar the {@link BossBar} instance
* @param name the {@link ComponentHolder} containing the boss bar's name
* @return a {@link BossBarPacket} to add a boss bar
*/
public static BossBarPacket createAddPacket(
final UUID id,
final BossBar bar,
@@ -86,6 +98,13 @@ public class BossBarPacket implements MinecraftPacket {
return packet;
}
/**
* Creates a packet to remove an existing boss bar.
*
* @param id the UUID of the boss bar to remove
* @param bar the {@link BossBar} instance
* @return a {@link BossBarPacket} to remove a boss bar
*/
public static BossBarPacket createRemovePacket(final UUID id, final BossBar bar) {
final BossBarPacket packet = new BossBarPacket();
packet.setUuid(id);
@@ -93,6 +112,13 @@ public class BossBarPacket implements MinecraftPacket {
return packet;
}
/**
* Creates a packet to update the progress (percentage) of the boss bar.
*
* @param id the UUID of the boss bar
* @param bar the {@link BossBar} instance
* @return a {@link BossBarPacket} to update the boss bar's progress
*/
public static BossBarPacket createUpdateProgressPacket(final UUID id, final BossBar bar) {
final BossBarPacket packet = new BossBarPacket();
packet.setUuid(id);
@@ -101,6 +127,14 @@ public class BossBarPacket implements MinecraftPacket {
return packet;
}
/**
* Creates a packet to update the name of the boss bar.
*
* @param id the UUID of the boss bar
* @param bar the {@link BossBar} instance
* @param name the {@link ComponentHolder} containing the boss bar's new name
* @return a {@link BossBarPacket} to update the boss bar's name
*/
public static BossBarPacket createUpdateNamePacket(
final UUID id,
final BossBar bar,
@@ -113,6 +147,13 @@ public class BossBarPacket implements MinecraftPacket {
return packet;
}
/**
* Creates a packet to update the style (color and overlay) of the boss bar.
*
* @param id the UUID of the boss bar
* @param bar the {@link BossBar} instance
* @return a {@link BossBarPacket} to update the boss bar's style
*/
public static BossBarPacket createUpdateStylePacket(final UUID id, final BossBar bar) {
final BossBarPacket packet = new BossBarPacket();
packet.setUuid(id);
@@ -122,6 +163,13 @@ public class BossBarPacket implements MinecraftPacket {
return packet;
}
/**
* Creates a packet to update the properties of the boss bar.
*
* @param id the UUID of the boss bar
* @param bar the {@link BossBar} instance
* @return a {@link BossBarPacket} to update the boss bar's properties
*/
public static BossBarPacket createUpdatePropertiesPacket(final UUID id, final BossBar bar) {
final BossBarPacket packet = new BossBarPacket();
packet.setUuid(id);
@@ -130,6 +178,12 @@ public class BossBarPacket implements MinecraftPacket {
return packet;
}
/**
* Retrieves the UUID of the boss bar.
*
* @return the UUID of the boss bar
* @throws IllegalStateException if the UUID has not been set
*/
public UUID getUuid() {
if (uuid == null) {
throw new IllegalStateException("No boss bar UUID specified");
@@ -214,7 +268,8 @@ public class BossBarPacket implements MinecraftPacket {
this.overlay = ProtocolUtils.readVarInt(buf);
this.flags = buf.readUnsignedByte();
}
case REMOVE -> {}
case REMOVE -> {
}
case UPDATE_PERCENT -> this.percent = buf.readFloat();
case UPDATE_NAME -> this.name = ComponentHolder.read(buf, version);
case UPDATE_STYLE -> {
@@ -235,22 +290,23 @@ public class BossBarPacket implements MinecraftPacket {
ProtocolUtils.writeVarInt(buf, action);
switch (action) {
case ADD -> {
if (name == null) {
throw new IllegalStateException("No name specified!");
}
name.write(buf);
buf.writeFloat(percent);
ProtocolUtils.writeVarInt(buf, color);
ProtocolUtils.writeVarInt(buf, overlay);
buf.writeByte(flags);
if (name == null) {
throw new IllegalStateException("No name specified!");
}
name.write(buf);
buf.writeFloat(percent);
ProtocolUtils.writeVarInt(buf, color);
ProtocolUtils.writeVarInt(buf, overlay);
buf.writeByte(flags);
}
case REMOVE -> {
}
case REMOVE -> {}
case UPDATE_PERCENT -> buf.writeFloat(percent);
case UPDATE_NAME -> {
if (name == null) {
throw new IllegalStateException("No name specified!");
}
name.write(buf);
if (name == null) {
throw new IllegalStateException("No name specified!");
}
name.write(buf);
}
case UPDATE_STYLE -> {
ProtocolUtils.writeVarInt(buf, color);
@@ -264,7 +320,7 @@ public class BossBarPacket implements MinecraftPacket {
private static byte serializeFlags(Set<BossBar.Flag> flags) {
byte val = 0x0;
for (BossBar.Flag flag : flags) {
val |= FLAG_BITS_TO_PROTOCOL.get(flag);
val |= (byte) FLAG_BITS_TO_PROTOCOL.get(flag);
}
return val;
}
@@ -273,4 +329,4 @@ public class BossBarPacket implements MinecraftPacket {
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
}
}

View File

@@ -23,6 +23,14 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
/**
* Represents a packet used as a delimiter for bundling multiple packets together.
* The {@code BundleDelimiterPacket} marks the beginning or end of a packet bundle,
* allowing the server and client to process groups of packets as a single logical unit.
*
* <p>This packet is typically used to signal the start or end of a packet sequence that
* are sent together, enabling efficient transmission and processing of related data.</p>
*/
public final class BundleDelimiterPacket implements MinecraftPacket {
public static final BundleDelimiterPacket INSTANCE = new BundleDelimiterPacket();

View File

@@ -23,9 +23,13 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import java.util.Objects;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents the client settings packet in Minecraft, which is sent by the client
* to the server to communicate its settings such as locale, view distance, chat preferences,
* skin customization, and other client-side configurations.
*/
public class ClientSettingsPacket implements MinecraftPacket {
private @Nullable String locale;
private byte viewDistance;
@@ -41,6 +45,19 @@ public class ClientSettingsPacket implements MinecraftPacket {
public ClientSettingsPacket() {
}
/**
* Constructs a new {@code ClientSettingsPacket} with the specified settings.
*
* @param locale the client's locale setting
* @param viewDistance the view distance
* @param chatVisibility the client's chat visibility setting
* @param chatColors whether chat colors are enabled
* @param skinParts the customization for skin parts
* @param mainHand the client's main hand preference
* @param textFilteringEnabled whether text filtering is enabled
* @param clientListingAllowed whether the client allows listing
* @param particleStatus whether particles are enabled
*/
public ClientSettingsPacket(String locale, byte viewDistance, int chatVisibility, boolean chatColors,
short skinParts, int mainHand, boolean textFilteringEnabled, boolean clientListingAllowed,
int particleStatus) {
@@ -55,6 +72,12 @@ public class ClientSettingsPacket implements MinecraftPacket {
this.particleStatus = particleStatus;
}
/**
* Gets the client's locale.
*
* @return the locale
* @throws IllegalStateException if no locale is specified
*/
public String getLocale() {
if (locale == null) {
throw new IllegalStateException("No locale specified");
@@ -132,10 +155,10 @@ public class ClientSettingsPacket implements MinecraftPacket {
@Override
public String toString() {
return "ClientSettings{" + "locale='" + locale + '\'' + ", viewDistance=" + viewDistance +
", chatVisibility=" + chatVisibility + ", chatColors=" + chatColors + ", skinParts=" +
skinParts + ", mainHand=" + mainHand + ", chatFilteringEnabled=" + textFilteringEnabled +
", clientListingAllowed=" + clientListingAllowed + ", particleStatus=" + particleStatus + '}';
return "ClientSettings{" + "locale='" + locale + '\'' + ", viewDistance=" + viewDistance
+ ", chatVisibility=" + chatVisibility + ", chatColors=" + chatColors + ", skinParts="
+ skinParts + ", mainHand=" + mainHand + ", chatFilteringEnabled=" + textFilteringEnabled
+ ", clientListingAllowed=" + clientListingAllowed + ", particleStatus=" + particleStatus + '}';
}
@Override

View File

@@ -25,6 +25,11 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import io.netty.buffer.ByteBuf;
import net.kyori.adventure.key.Key;
/**
* Represents a packet sent from the server to the client to request cookies.
* This packet can be used to initiate a request for cookie-related data from the client,
* typically for authentication or tracking purposes.
*/
public class ClientboundCookieRequestPacket implements MinecraftPacket {
private Key key;

View File

@@ -22,11 +22,16 @@ 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.Random;
import net.kyori.adventure.sound.Sound;
import org.jetbrains.annotations.Nullable;
import java.util.Random;
/**
* A clientbound packet that instructs the client to play a sound tied to an entity.
*
* <p>This is sent by the server when a sound should be played at the location of a
* specific entity, with optional fixed range and seed.</p>
*/
public class ClientboundSoundEntityPacket implements MinecraftPacket {
private static final Random SEEDS_RANDOM = new Random();
@@ -35,8 +40,16 @@ public class ClientboundSoundEntityPacket implements MinecraftPacket {
private @Nullable Float fixedRange;
private int emitterEntityId;
public ClientboundSoundEntityPacket() {}
public ClientboundSoundEntityPacket() {
}
/**
* Constructs a new sound entity packet.
*
* @param sound the sound to play
* @param fixedRange the fixed attenuation range, or {@code null} to use the default
* @param emitterEntityId the entity ID of the sound emitter
*/
public ClientboundSoundEntityPacket(Sound sound, @Nullable Float fixedRange, int emitterEntityId) {
this.sound = sound;
this.fixedRange = fixedRange;
@@ -55,8 +68,9 @@ public class ClientboundSoundEntityPacket implements MinecraftPacket {
ProtocolUtils.writeMinimalKey(buf, sound.name());
buf.writeBoolean(fixedRange != null);
if (fixedRange != null)
if (fixedRange != null) {
buf.writeFloat(fixedRange);
}
ProtocolUtils.writeSoundSource(buf, protocolVersion, sound.source());

View File

@@ -22,18 +22,24 @@ import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import javax.annotation.Nullable;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.sound.Sound;
import net.kyori.adventure.sound.SoundStop;
import javax.annotation.Nullable;
/**
* A clientbound packet instructing the client to stop one or more sounds.
*
* <p>This packet supports specifying a {@link Sound.Source}, a {@link Key} sound identifier,
* or both together. If neither is specified, the client will stop all currently playing sounds.</p>
*/
public class ClientboundStopSoundPacket implements MinecraftPacket {
private @Nullable Sound.Source source;
private @Nullable Key soundName;
public ClientboundStopSoundPacket() {}
public ClientboundStopSoundPacket() {
}
public ClientboundStopSoundPacket(SoundStop soundStop) {
this(soundStop.source(), soundStop.sound());

View File

@@ -25,6 +25,11 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import io.netty.buffer.ByteBuf;
import net.kyori.adventure.key.Key;
/**
* Represents a packet sent from the server to the client to store a cookie.
* This packet can be used to send cookie-related data from the server to be stored or processed
* by the client.
*/
public class ClientboundStoreCookiePacket implements MinecraftPacket {
private Key key;

View File

@@ -23,6 +23,13 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import io.netty.buffer.ByteBuf;
/**
* Represents the packet sent by the server to the client to clear any
* currently displayed configuration dialog.
*
* <p>This packet is used during the configuration phase (1.21.6+) to
* instruct the client to dismiss an active dialog window.</p>
*/
public class DialogClearPacket implements MinecraftPacket {
public static final DialogClearPacket INSTANCE = new DialogClearPacket();

View File

@@ -27,6 +27,13 @@ import io.netty.buffer.ByteBuf;
import net.kyori.adventure.nbt.BinaryTag;
import net.kyori.adventure.nbt.BinaryTagIO;
/**
* Represents the packet sent by the server to the client to display a configuration dialog
* during the configuration phase in Minecraft 1.21.6+.
*
* <p>This packet is only relevant in the CONFIG and PLAY states. If the ID is {@code 0},
* a dialog is to be shown and the accompanying {@link BinaryTag} contains its data.</p>
*/
public class DialogShowPacket implements MinecraftPacket {
private final StateRegistry state;

View File

@@ -28,6 +28,12 @@ import io.netty.buffer.ByteBuf;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents a packet sent by the server to disconnect the client. This packet contains
* a reason for the disconnection, which is sent to the client and displayed to the player.
* The packet can be sent in different states (e.g., login, play), which affects how the
* reason is processed.
*/
public class DisconnectPacket implements MinecraftPacket {
private @Nullable ComponentHolder reason;
@@ -42,6 +48,12 @@ public class DisconnectPacket implements MinecraftPacket {
this.reason = Preconditions.checkNotNull(reason, "reason");
}
/**
* Retrieves the reason for the disconnection, which will be sent to the client.
*
* @return the reason for the disconnection as a {@link ComponentHolder}
* @throws IllegalStateException if no reason is specified
*/
public ComponentHolder getReason() {
if (reason == null) {
throw new IllegalStateException("No reason specified");
@@ -62,8 +74,8 @@ public class DisconnectPacket implements MinecraftPacket {
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
reason = ComponentHolder.read(buf, state == StateRegistry.LOGIN
? ProtocolVersion.MINECRAFT_1_20_2 : version);
reason = ComponentHolder.read(buf, state == StateRegistry.LOGIN
? ProtocolVersion.MINECRAFT_1_20_2 : version);
}
@Override
@@ -76,9 +88,17 @@ public class DisconnectPacket implements MinecraftPacket {
return handler.handle(this);
}
/**
* Creates a new {@code DisconnectPacket} with the specified reason and version.
*
* @param component the component explaining the disconnection reason
* @param version the protocol version in use
* @param state the state in which the disconnection occurs
* @return the created {@code DisconnectPacket}
*/
public static DisconnectPacket create(Component component, ProtocolVersion version, StateRegistry state) {
Preconditions.checkNotNull(component, "component");
return new DisconnectPacket(state, new ComponentHolder(state == StateRegistry.LOGIN
? ProtocolVersion.MINECRAFT_1_20_2 : version, component));
}
}
}

View File

@@ -26,6 +26,12 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import java.util.Arrays;
/**
* Represents the encryption request packet in Minecraft, which is sent by the server
* during the encryption handshake process. This packet is used to initiate secure
* communication by providing the client with the server's public key and a verified token.
* The client must respond with the encrypted shared secret and verify token.
*/
public class EncryptionRequestPacket implements MinecraftPacket {
private String serverId = "";

View File

@@ -26,10 +26,18 @@ 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 java.util.Arrays;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Arrays;
/**
* Represents the encryption response packet in Minecraft, which is sent by the client
* during the encryption handshake process. This packet contains the shared secret
* and verifies the token used to establish secure communication between the client
* and the server.
*
* <p>The packet structure varies depending on the Minecraft protocol version, with additional
* fields such as a salt being present in versions 1.19 and above.</p>
*/
public class EncryptionResponsePacket implements MinecraftPacket {
private static final QuietDecoderException NO_SALT = new QuietDecoderException(
@@ -47,6 +55,13 @@ public class EncryptionResponsePacket implements MinecraftPacket {
return verifyToken.clone();
}
/**
* Retrieves the salt used in the encryption response. The salt is introduced in
* Minecraft version 1.19 and is optional in certain protocol versions.
*
* @return the salt used in the encryption response
* @throws QuietDecoderException if the salt is not present
*/
public long getSalt() {
if (salt == null) {
throw NO_SALT;

View File

@@ -27,6 +27,12 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import io.netty.buffer.ByteBuf;
/**
* Represents a handshake packet in Minecraft, which is used during the initial connection process.
* This packet contains information such as the protocol version, server address, port, and the intent
* of the handshake (e.g., login or status request). This packet is crucial for establishing a connection
* between the client and the server.
*/
public class HandshakePacket implements MinecraftPacket {
// This size was chosen to ensure Forge clients can still connect even with very long hostnames.
@@ -110,13 +116,13 @@ public class HandshakePacket implements MinecraftPacket {
@Override
public int decodeExpectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
ProtocolVersion version) {
return 7;
}
@Override
public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
ProtocolVersion version) {
return 9 + (MAXIMUM_HOSTNAME_LENGTH * 3);
}

View File

@@ -26,6 +26,10 @@ import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
import io.netty.buffer.ByteBuf;
import net.kyori.adventure.text.Component;
/**
* Represents a packet that contains both the header and footer for the player list screen (tab list) in Minecraft.
* This packet allows the server to set or update the header and footer text that is displayed on the client's tab list.
*/
public class HeaderAndFooterPacket implements MinecraftPacket {
private final ComponentHolder header;
@@ -67,11 +71,11 @@ public class HeaderAndFooterPacket implements MinecraftPacket {
public static HeaderAndFooterPacket create(Component header,
Component footer, ProtocolVersion protocolVersion) {
return new HeaderAndFooterPacket(new ComponentHolder(protocolVersion, header),
new ComponentHolder(protocolVersion, footer));
new ComponentHolder(protocolVersion, footer));
}
public static HeaderAndFooterPacket reset(ProtocolVersion version) {
ComponentHolder empty = new ComponentHolder(version, Component.empty());
return new HeaderAndFooterPacket(empty, empty);
}
}
}

View File

@@ -21,13 +21,19 @@ import com.google.common.collect.ImmutableSet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.registry.DimensionInfo;
import com.velocitypowered.proxy.protocol.*;
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;
/**
* Represents a packet sent to the client when they successfully join a game in Minecraft.
* This packet contains all the necessary information to initialize the client state,
* including the player's entity ID, game mode, dimension, world settings, and more.
*/
public class JoinGamePacket implements MinecraftPacket {
private static final BinaryTagIO.Reader JOINGAME_READER = BinaryTagIO.reader(4 * 1024 * 1024);
@@ -204,17 +210,17 @@ public class JoinGamePacket implements MinecraftPacket {
@Override
public String toString() {
return "JoinGame{" + "entityId=" + entityId + ", gamemode=" + gamemode + ", dimension=" +
dimension + ", partialHashedSeed=" + partialHashedSeed + ", difficulty=" + difficulty +
", isHardcore=" + isHardcore + ", maxPlayers=" + maxPlayers + ", levelType='" + levelType +
'\'' + ", viewDistance=" + viewDistance + ", reducedDebugInfo=" + reducedDebugInfo +
", showRespawnScreen=" + showRespawnScreen + ", doLimitedCrafting=" + doLimitedCrafting +
", levelNames=" + levelNames + ", registry='" + registry + '\'' + ", dimensionInfo='" +
dimensionInfo + '\'' + ", currentDimensionData='" + currentDimensionData + '\'' +
", previousGamemode=" + previousGamemode + ", simulationDistance=" + simulationDistance +
", lastDeathPosition='" + lastDeathPosition + '\'' + ", portalCooldown=" + portalCooldown +
", seaLevel=" + seaLevel +
'}';
return "JoinGame{" + "entityId=" + entityId + ", gamemode=" + gamemode + ", dimension="
+ dimension + ", partialHashedSeed=" + partialHashedSeed + ", difficulty=" + difficulty
+ ", isHardcore=" + isHardcore + ", maxPlayers=" + maxPlayers + ", levelType='" + levelType
+ '\'' + ", viewDistance=" + viewDistance + ", reducedDebugInfo=" + reducedDebugInfo
+ ", showRespawnScreen=" + showRespawnScreen + ", doLimitedCrafting=" + doLimitedCrafting
+ ", levelNames=" + levelNames + ", registry='" + registry + '\'' + ", dimensionInfo='"
+ dimensionInfo + '\'' + ", currentDimensionData='" + currentDimensionData + '\''
+ ", previousGamemode=" + previousGamemode + ", simulationDistance=" + simulationDistance
+ ", lastDeathPosition='" + lastDeathPosition + '\'' + ", portalCooldown=" + portalCooldown
+ ", seaLevel=" + seaLevel
+ '}';
}
@Override

View File

@@ -23,6 +23,11 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
/**
* Represents a KeepAlive packet in Minecraft. This packet is used to ensure that the connection
* between the client and the server shall still be active by sending a randomly generated ID that
* the client must respond to.
*/
public class KeepAlivePacket implements MinecraftPacket {
private long randomId;

View File

@@ -25,7 +25,13 @@ import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
@SuppressWarnings("checkstyle:MissingJavadocType")
/**
* Represents a legacy disconnect packet that contains a reason for disconnection.
* This class is used to convert modern server ping responses into the legacy format,
* which is compatible with older Minecraft versions.
*
* @param reason the string reason for disconnection
*/
public record LegacyDisconnect(String reason) {
private static final ServerPing.Players FAKE_PLAYERS = new ServerPing.Players(0, 0,
@@ -46,17 +52,17 @@ public record LegacyDisconnect(String reason) {
return switch (version) {
case MINECRAFT_1_3 ->
// Minecraft 1.3 and below use the section symbol as a delimiter. Accordingly, we must
// remove all section symbols, along with fetching just the first line of an (unformatted)
// MOTD.
new LegacyDisconnect(String.join(LEGACY_COLOR_CODE,
// Minecraft 1.3 and below use the section symbol as a delimiter. Accordingly, we must
// remove all section symbols, along with fetching just the first line of an (unformatted)
// MOTD.
new LegacyDisconnect(String.join(LEGACY_COLOR_CODE,
cleanSectionSymbol(getFirstLine(PlainTextComponentSerializer.plainText().serialize(
response.getDescriptionComponent()))),
Integer.toString(players.getOnline()),
Integer.toString(players.getMax())));
case MINECRAFT_1_4, MINECRAFT_1_6 ->
// Minecraft 1.4-1.6 provide support for more fields, and additionally support color codes.
new LegacyDisconnect(String.join("\0",
// Minecraft 1.4-1.6 provide support for more fields, and additionally support color codes.
new LegacyDisconnect(String.join("\0",
LEGACY_COLOR_CODE + "1",
Integer.toString(response.getVersion().getProtocol()),
response.getVersion().getName(),

View File

@@ -23,6 +23,11 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
/**
* Represents a legacy handshake packet in Minecraft, which is typically used
* during the initial connection process for older versions of the Minecraft protocol.
* This class currently does not support decoding of the handshake packet.
*/
public class LegacyHandshakePacket implements MinecraftPacket {
@Override

View File

@@ -26,6 +26,11 @@ import io.netty.buffer.ByteBuf;
import java.net.InetSocketAddress;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents a legacy ping packet in Minecraft, commonly used in the server list ping process.
* This packet handles compatibility with older Minecraft versions and contains information
* such as the ping protocol version and optionally a virtual host address.
*/
public class LegacyPingPacket implements MinecraftPacket {
private final LegacyMinecraftPingVersion version;

View File

@@ -33,6 +33,10 @@ import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents a legacy player list item packet, which is used to modify the player list in a Minecraft client.
* The packet can add, remove, or update player entries (e.g., updating gamemode, latency, or display names).
*/
public class LegacyPlayerListItemPacket implements MinecraftPacket {
public static final int ADD_PLAYER = 0;
@@ -76,16 +80,16 @@ public class LegacyPlayerListItemPacket implements MinecraftPacket {
item.setLatency(ProtocolUtils.readVarInt(buf));
item.setDisplayName(readOptionalComponent(buf, version));
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
if (buf.readBoolean()) {
item.setPlayerKey(ProtocolUtils.readPlayerKey(version, buf));
}
if (buf.readBoolean()) {
item.setPlayerKey(ProtocolUtils.readPlayerKey(version, buf));
}
}
}
case UPDATE_GAMEMODE -> item.setGameMode(ProtocolUtils.readVarInt(buf));
case UPDATE_LATENCY -> item.setLatency(ProtocolUtils.readVarInt(buf));
case UPDATE_DISPLAY_NAME -> item.setDisplayName(readOptionalComponent(buf, version));
case REMOVE_PLAYER -> {
//Do nothing, all that is needed is the uuid
// Do nothing, all that is needed is the uuid
}
default -> throw new UnsupportedOperationException("Unknown action " + action);
}
@@ -107,6 +111,17 @@ public class LegacyPlayerListItemPacket implements MinecraftPacket {
return null;
}
/**
* Encodes this packet's contents into the given {@link ByteBuf}.
*
* <p>This method serializes the packet data based on the current protocol version.
* Subclasses overriding this method should preserve compatibility with legacy
* and modern formats as needed.</p>
*
* @param buf the buffer to write to
* @param direction the direction of the packet (clientbound or serverbound)
* @param version the Minecraft protocol version
*/
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
@@ -125,12 +140,12 @@ public class LegacyPlayerListItemPacket implements MinecraftPacket {
ProtocolUtils.writeVarInt(buf, item.getLatency());
writeDisplayName(buf, item.getDisplayName(), version);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
if (item.getPlayerKey() != null) {
buf.writeBoolean(true);
ProtocolUtils.writePlayerKey(buf, item.getPlayerKey());
} else {
buf.writeBoolean(false);
}
if (item.getPlayerKey() != null) {
buf.writeBoolean(true);
ProtocolUtils.writePlayerKey(buf, item.getPlayerKey());
} else {
buf.writeBoolean(false);
}
}
}
case UPDATE_GAMEMODE -> ProtocolUtils.writeVarInt(buf, item.getGameMode());
@@ -172,6 +187,10 @@ public class LegacyPlayerListItemPacket implements MinecraftPacket {
}
}
/**
* Represents an individual item in the player list, containing the player's details such as UUID, name,
* game mode, latency, and optionally a display name and player key.
*/
public static class Item {
private final UUID uuid;
@@ -190,6 +209,15 @@ public class LegacyPlayerListItemPacket implements MinecraftPacket {
this.uuid = uuid;
}
/**
* Creates an {@link Item} instance from a {@link TabListEntry}.
* This method extracts relevant data from the {@link TabListEntry} such as
* the player's profile ID, name, properties, latency, game mode, player key,
* and display name, and uses them to populate a new {@code Item}.
*
* @param entry the {@link TabListEntry} from which to extract data
* @return an {@link Item} populated with data from the {@link TabListEntry}
*/
public static Item from(TabListEntry entry) {
return new Item(entry.getProfile().getId())
.setName(entry.getProfile().getName())

View File

@@ -23,6 +23,13 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
/**
* Represents a packet that acknowledges a successful login, implementing {@link MinecraftPacket}.
*
* <p>The {@code LoginAcknowledgedPacket} is sent by the server to confirm that the player's login
* process has been successfully completed. It signals the transition from the login phase to the
* game or session phase.</p>
*/
public class LoginAcknowledgedPacket implements MinecraftPacket {
@Override
@@ -37,7 +44,7 @@ public class LoginAcknowledgedPacket implements MinecraftPacket {
@Override
public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
ProtocolVersion version) {
return 0;
}

View File

@@ -27,6 +27,10 @@ import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents a login plugin message packet sent during the login phase. This packet allows custom
* plugin messages to be sent from the server to the client before login is complete.
*/
public class LoginPluginMessagePacket extends DeferredByteBufHolder implements MinecraftPacket {
private int id;
@@ -36,6 +40,13 @@ public class LoginPluginMessagePacket extends DeferredByteBufHolder implements M
super(null);
}
/**
* Constructs a new {@code LoginPluginMessagePacket} with the specified ID, channel, and data buffer.
*
* @param id the plugin message ID
* @param channel the channel name, or {@code null} if not specified
* @param data the data buffer
*/
public LoginPluginMessagePacket(int id, @Nullable String channel, ByteBuf data) {
super(data);
this.id = id;
@@ -46,6 +57,12 @@ public class LoginPluginMessagePacket extends DeferredByteBufHolder implements M
return id;
}
/**
* Gets the plugin message channel.
*
* @return the channel name
* @throws IllegalStateException if the channel is not specified
*/
public String getChannel() {
if (channel == null) {
throw new IllegalStateException("Channel is not specified!");

View File

@@ -27,6 +27,10 @@ import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* Represents the response packet to a plugin message sent during the login phase.
* The packet contains the plugin message ID, a success flag, and any additional data.
*/
public class LoginPluginResponsePacket extends DeferredByteBufHolder implements MinecraftPacket {
private int id;
@@ -36,6 +40,13 @@ public class LoginPluginResponsePacket extends DeferredByteBufHolder implements
super(Unpooled.EMPTY_BUFFER);
}
/**
* Constructs a new {@code LoginPluginResponsePacket} with the specified ID, success status, and data buffer.
*
* @param id the plugin message ID
* @param success {@code true} if the plugin message was successful, {@code false} otherwise
* @param buf the data buffer
*/
public LoginPluginResponsePacket(int id, boolean success, @MonotonicNonNull ByteBuf buf) {
super(buf);
this.id = id;

View File

@@ -23,6 +23,9 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
/**
* Represents a packet used for ping identification with a unique ID.
*/
public class PingIdentifyPacket implements MinecraftPacket {
private int id;

View File

@@ -29,6 +29,10 @@ import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents a plugin message packet, which allows for custom communication between
* a Minecraft server and a client via custom channels.
*/
public class PluginMessagePacket extends DeferredByteBufHolder implements MinecraftPacket {
private @Nullable String channel;
@@ -43,6 +47,12 @@ public class PluginMessagePacket extends DeferredByteBufHolder implements Minecr
this.channel = channel;
}
/**
* Gets the channel for this plugin message.
*
* @return the channel name
* @throws IllegalStateException if the channel is not set
*/
public String getChannel() {
if (channel == null) {
throw new IllegalStateException("Channel is not specified.");
@@ -73,7 +83,6 @@ public class PluginMessagePacket extends DeferredByteBufHolder implements Minecr
} else {
this.replace(ProtocolUtils.readRetainedByteBufSlice17(buf));
}
}
@Override
@@ -97,7 +106,6 @@ public class PluginMessagePacket extends DeferredByteBufHolder implements Minecr
} else {
ProtocolUtils.writeByteBuf17(content(), buf, true); // True for Forge support
}
}
@Override

View File

@@ -27,6 +27,10 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.UUID;
/**
* Represents a packet sent to remove player information from the player list.
* The packet contains a collection of {@link UUID}s representing the profiles to be removed.
*/
public class RemovePlayerInfoPacket implements MinecraftPacket {
private Collection<UUID> profilesToRemove;

View File

@@ -25,6 +25,10 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import io.netty.buffer.ByteBuf;
import java.util.UUID;
/**
* Represents a packet sent to remove a previously applied resource pack from the client.
* The packet contains an optional UUID that identifies the resource pack to be removed.
*/
public class RemoveResourcePackPacket implements MinecraftPacket {
private UUID id;
@@ -60,4 +64,4 @@ public class RemoveResourcePackPacket implements MinecraftPacket {
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
}
}

View File

@@ -33,6 +33,10 @@ import java.util.regex.Pattern;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents a resource pack request packet sent by the server to prompt the client to download a resource pack.
* The packet includes the resource pack URL, SHA1 hash, and optional prompt.
*/
public class ResourcePackRequestPacket implements MinecraftPacket {
private @MonotonicNonNull UUID id; // 1.20.3+
@@ -124,6 +128,12 @@ public class ResourcePackRequestPacket implements MinecraftPacket {
}
}
/**
* Converts this packet into a {@link VelocityResourcePackInfo} object, which contains the information
* about the resource pack being requested.
*
* @return a {@code VelocityResourcePackInfo} representing the resource pack information
*/
public VelocityResourcePackInfo toServerPromptedPack() {
final ResourcePackInfo.Builder builder =
new VelocityResourcePackInfo.BuilderImpl(Preconditions.checkNotNull(url))
@@ -145,12 +155,12 @@ public class ResourcePackRequestPacket implements MinecraftPacket {
@Override
public String toString() {
return "ResourcePackRequestPacket{" +
"id=" + id +
", url='" + url + '\'' +
", hash='" + hash + '\'' +
", isRequired=" + isRequired +
", prompt=" + prompt +
'}';
return "ResourcePackRequestPacket{"
+ "id=" + id
+ ", url='" + url + '\''
+ ", hash='" + hash + '\''
+ ", isRequired=" + isRequired
+ ", prompt=" + prompt
+ '}';
}
}
}

View File

@@ -24,10 +24,13 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import io.netty.buffer.ByteBuf;
import java.util.UUID;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import java.util.UUID;
/**
* Represents the response packet sent by the client after receiving a resource pack request from the server.
* The packet contains information about the client's response, including the resource pack status.
*/
public class ResourcePackResponsePacket implements MinecraftPacket {
private UUID id;
@@ -37,12 +40,25 @@ public class ResourcePackResponsePacket implements MinecraftPacket {
public ResourcePackResponsePacket() {
}
/**
* Constructs a new {@code ResourcePackResponsePacket} with the specified parameters.
*
* @param id the unique identifier for the response
* @param hash the hash of the resource pack
* @param status the status of the resource pack
*/
public ResourcePackResponsePacket(UUID id, String hash, @MonotonicNonNull Status status) {
this.id = id;
this.hash = hash;
this.status = status;
}
/**
* Gets the status of the resource pack response.
*
* @return the status of the response
* @throws IllegalStateException if the packet has not been deserialized yet
*/
public Status getStatus() {
if (status == null) {
throw new IllegalStateException("Packet not yet deserialized");
@@ -87,10 +103,10 @@ public class ResourcePackResponsePacket implements MinecraftPacket {
@Override
public String toString() {
return "ResourcePackResponsePacket{" +
"id=" + id +
", hash='" + hash + '\'' +
", status=" + status +
'}';
return "ResourcePackResponsePacket{"
+ "id=" + id
+ ", hash='" + hash + '\''
+ ", status=" + status
+ '}';
}
}
}

View File

@@ -28,6 +28,10 @@ import net.kyori.adventure.nbt.BinaryTagIO;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents a respawn packet sent by the server when the player changes dimensions or respawns.
* The packet contains information about the new dimension, difficulty, gamemode, and more.
*/
public class RespawnPacket implements MinecraftPacket {
private int dimension;
@@ -46,6 +50,22 @@ public class RespawnPacket implements MinecraftPacket {
public RespawnPacket() {
}
/**
* Constructs a new {@code RespawnPacket} with the specified parameters.
*
* @param dimension the dimension the player is respawning or teleporting to
* @param partialHashedSeed the partial hashed seed
* @param difficulty the difficulty of the server
* @param gamemode the player's current gamemode
* @param levelType the type of level (e.g., "default", "flat")
* @param dataToKeep a byte flag indicating whether certain data should be kept
* @param dimensionInfo additional information about the dimension (for 1.16-1.16.1)
* @param previousGamemode the player's previous gamemode
* @param currentDimensionData data about the current dimension (for 1.16.2+)
* @param lastDeathPosition optional last death position (for 1.19+)
* @param portalCooldown the cooldown for portal usage (for 1.20+)
* @param seaLevel a determinable spawn point for a user (for 1.21.2+)
*/
public RespawnPacket(int dimension, long partialHashedSeed, short difficulty, short gamemode,
String levelType, byte dataToKeep, DimensionInfo dimensionInfo,
short previousGamemode, CompoundBinaryTag currentDimensionData,
@@ -65,6 +85,12 @@ public class RespawnPacket implements MinecraftPacket {
this.seaLevel = seaLevel;
}
/**
* Creates a new {@code RespawnPacket} from a {@link JoinGamePacket}.
*
* @param joinGame the {@code JoinGamePacket} to use
* @return a new {@code RespawnPacket} based on the provided {@code JoinGamePacket}
*/
public static RespawnPacket fromJoinGame(JoinGamePacket joinGame) {
return new RespawnPacket(joinGame.getDimension(), joinGame.getPartialHashedSeed(),
joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(),

View File

@@ -25,10 +25,14 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
import io.netty.buffer.ByteBuf;
import org.jetbrains.annotations.Nullable;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import org.jetbrains.annotations.Nullable;
/**
* Represents the server data packet sent from the server to the client, which contains information
* such as the server description, favicon, and secure chat enforcement status.
*/
public class ServerDataPacket implements MinecraftPacket {
private @Nullable ComponentHolder description;
@@ -38,6 +42,13 @@ public class ServerDataPacket implements MinecraftPacket {
public ServerDataPacket() {
}
/**
* Constructs a new {@code ServerDataPacket} with the given server description, favicon, and secure chat enforcement status.
*
* @param description the server description (maybe null)
* @param favicon the server favicon (maybe null)
* @param secureChatEnforced whether secure chat is enforced (for versions 1.19.1 to 1.20.5)
*/
public ServerDataPacket(@Nullable ComponentHolder description, @Nullable Favicon favicon,
boolean secureChatEnforced) {
this.description = description;
@@ -127,4 +138,4 @@ public class ServerDataPacket implements MinecraftPacket {
public int encodeSizeHint(Direction direction, ProtocolVersion version) {
return 8 * 1024;
}
}
}

View File

@@ -29,6 +29,11 @@ import io.netty.buffer.ByteBuf;
import java.util.UUID;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents the packet sent from the client to the server during the login phase.
* This packet contains the player's username, optionally a cryptographic key for
* authentication, and the holder UUID depending on the Minecraft protocol version.
*/
public class ServerLoginPacket implements MinecraftPacket {
private static final QuietDecoderException EMPTY_USERNAME = new QuietDecoderException(
@@ -41,17 +46,35 @@ public class ServerLoginPacket implements MinecraftPacket {
public ServerLoginPacket() {
}
/**
* Constructs a {@code ServerLoginPacket} with a username and optional player key.
*
* @param username the player's username
* @param playerKey the player's cryptographic key, or {@code null} if not present
*/
public ServerLoginPacket(String username, @Nullable IdentifiedKey playerKey) {
this.username = Preconditions.checkNotNull(username, "username");
this.playerKey = playerKey;
}
/**
* Constructs a new {@code ServerLoginPacket} with the specified username and holder UUID.
*
* @param username the player's username
* @param holderUuid the holder UUID (optional)
*/
public ServerLoginPacket(String username, @Nullable UUID holderUuid) {
this.username = Preconditions.checkNotNull(username, "username");
this.holderUuid = holderUuid;
this.playerKey = null;
}
/**
* Gets the player's username from the login packet.
*
* @return the player's username
* @throws IllegalStateException if the username is not specified
*/
public String getUsername() {
if (username == null) {
throw new IllegalStateException("No username found!");
@@ -74,10 +97,10 @@ public class ServerLoginPacket implements MinecraftPacket {
@Override
public String toString() {
return "ServerLogin{"
+ "username='" + username + '\''
+ "playerKey='" + playerKey + '\''
+ "holderUUID='" + holderUuid + '\''
+ '}';
+ "username='" + username + '\''
+ "playerKey='" + playerKey + '\''
+ "holderUUID='" + holderUuid + '\''
+ '}';
}
@Override

View File

@@ -30,6 +30,10 @@ import java.util.List;
import java.util.UUID;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents the packet sent from the server to the client to indicate successful login.
* This packet contains the player's UUID, username, and properties associated with their profile.
*/
public class ServerLoginSuccessPacket implements MinecraftPacket {
private @Nullable UUID uuid;
@@ -38,6 +42,12 @@ public class ServerLoginSuccessPacket implements MinecraftPacket {
private static final boolean strictErrorHandling = VelocityProperties
.readBoolean("velocity.strictErrorHandling", true);
/**
* Gets the player's UUID from the login success packet.
*
* @return the player's UUID
* @throws IllegalStateException if the UUID is not specified
*/
public UUID getUuid() {
if (uuid == null) {
throw new IllegalStateException("No UUID specified!");
@@ -49,6 +59,12 @@ public class ServerLoginSuccessPacket implements MinecraftPacket {
this.uuid = uuid;
}
/**
* Gets the player's username from the login success packet.
*
* @return the player's username
* @throws IllegalStateException if the username is not specified
*/
public String getUsername() {
if (username == null) {
throw new IllegalStateException("No username specified!");

View File

@@ -26,6 +26,11 @@ import io.netty.buffer.ByteBuf;
import net.kyori.adventure.key.Key;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents a server-bound packet sent by the client containing a key and an optional payload.
* This packet is typically used for exchanging metadata or other information between the client
* and server.
*/
public class ServerboundCookieResponsePacket implements MinecraftPacket {
private Key key;

View File

@@ -25,6 +25,12 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder;
import io.netty.buffer.ByteBuf;
/**
* Represents a serverbound packet carrying an opaque custom click action payload.
*
* <p>The payload is retained as-is and forwarded to the session handler without
* interpretation by the proxy.</p>
*/
public class ServerboundCustomClickActionPacket extends DeferredByteBufHolder implements MinecraftPacket {
public ServerboundCustomClickActionPacket() {

View File

@@ -23,6 +23,10 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
/**
* Represents a packet that sets the compression threshold for network communication.
* When the size of a packet exceeds the threshold, the packet will be compressed.
*/
public class SetCompressionPacket implements MinecraftPacket {
private int threshold;

View File

@@ -24,6 +24,10 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import io.netty.buffer.ByteBuf;
/**
* Represents a status ping packet sent by the client to the server, which is used to measure the latency
* between the client and server.
*/
public class StatusPingPacket implements MinecraftPacket {
private long randomId;

View File

@@ -24,12 +24,14 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import io.netty.buffer.ByteBuf;
/**
* Represents a status request packet sent by the client to the server to request the server's status.
*/
public class StatusRequestPacket implements MinecraftPacket {
public static final StatusRequestPacket INSTANCE = new StatusRequestPacket();
private StatusRequestPacket() {
}
@Override

View File

@@ -25,6 +25,9 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents a status response packet sent from the server to the client.
*/
public class StatusResponsePacket implements MinecraftPacket {
private @Nullable CharSequence status;
@@ -36,6 +39,12 @@ public class StatusResponsePacket implements MinecraftPacket {
this.status = status;
}
/**
* Gets the status message from the packet.
*
* @return the status message as a {@link String}
* @throws IllegalStateException if the status is not specified
*/
public String getStatus() {
if (status == null) {
throw new IllegalStateException("Status is not specified");

View File

@@ -29,6 +29,9 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents a packet sent by the client when a tab-completion request is initiated.
*/
public class TabCompleteRequestPacket implements MinecraftPacket {
private static final int VANILLA_MAX_TAB_COMPLETE_LEN = 2048;
@@ -39,6 +42,12 @@ public class TabCompleteRequestPacket implements MinecraftPacket {
private boolean hasPosition;
private long position;
/**
* Gets the command string to be completed.
*
* @return the command string
* @throws IllegalStateException if the command is not set
*/
public String getCommand() {
if (command == null) {
throw new IllegalStateException("Command is not specified");

View File

@@ -30,6 +30,9 @@ import java.util.ArrayList;
import java.util.List;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents the packet used to send tab-completion suggestions to the client.
*/
public class TabCompleteResponsePacket implements MinecraftPacket {
private int transactionId;
@@ -122,6 +125,9 @@ public class TabCompleteResponsePacket implements MinecraftPacket {
return handler.handle(this);
}
/**
* Represents an individual tab-completion suggestion (offer) sent to the client.
*/
public static class Offer implements Comparable<Offer> {
private final String text;

View File

@@ -25,6 +25,9 @@ import io.netty.buffer.ByteBuf;
import java.net.InetSocketAddress;
import org.jetbrains.annotations.Nullable;
/**
* Represents a packet used to transfer a player to another server.
*/
public class TransferPacket implements MinecraftPacket {
private String host;
private int port;
@@ -32,11 +35,22 @@ public class TransferPacket implements MinecraftPacket {
public TransferPacket() {
}
/**
* Constructs a {@code TransferPacket} with the specified host and port.
*
* @param host the hostname of the destination server
* @param port the port of the destination server
*/
public TransferPacket(final String host, final int port) {
this.host = host;
this.port = port;
}
/**
* Gets the {@link InetSocketAddress} representing the transfer address.
*
* @return the {@code InetSocketAddress}, or {@code null} if the host is not set
*/
@Nullable
public InetSocketAddress address() {
if (host == null) {

View File

@@ -34,6 +34,9 @@ import java.util.List;
import java.util.UUID;
import org.jetbrains.annotations.Nullable;
/**
* Represents the packet for updating or inserting player information.
*/
public class UpsertPlayerInfoPacket implements MinecraftPacket {
private static final Action[] ALL_ACTIONS = Action.class.getEnumConstants();
@@ -133,6 +136,9 @@ public class UpsertPlayerInfoPacket implements MinecraftPacket {
return handler.handle(this);
}
/**
* Represents the possible actions in the player info packet.
*/
public enum Action {
ADD_PLAYER((ignored, buf, info) -> { // read
info.profile = new GameProfile(
@@ -213,6 +219,9 @@ public class UpsertPlayerInfoPacket implements MinecraftPacket {
}
}
/**
* Represents an entry in the player info packet.
*/
public static class Entry {
private final UUID profileId;
@@ -303,16 +312,16 @@ public class UpsertPlayerInfoPacket implements MinecraftPacket {
@Override
public String toString() {
return "Entry{" +
"profileId=" + profileId +
", profile=" + profile +
", listed=" + listed +
", latency=" + latency +
", gameMode=" + gameMode +
", displayName=" + displayName +
", listOrder=" + listOrder +
", chatSession=" + chatSession +
'}';
return "Entry{"
+ "profileId=" + profileId
+ ", profile=" + profile
+ ", listed=" + listed
+ ", latency=" + latency
+ ", gameMode=" + gameMode
+ ", displayName=" + displayName
+ ", listOrder=" + listOrder
+ ", chatSession=" + chatSession
+ '}';
}
}
}
}

View File

@@ -24,6 +24,14 @@ import java.util.HashMap;
import java.util.Map;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents an identifier for a Brigadier command argument, mapping the argument to
* different protocol versions.
*
* <p>The {@code ArgumentIdentifier} is responsible for holding an identifier string for
* an argument and a map that associates protocol versions with their respective IDs.
* It ensures that the protocol version is compatible with the Minecraft 1.19 protocol or later.</p>
*/
public class ArgumentIdentifier {
private final String identifier;
@@ -37,8 +45,8 @@ public class ArgumentIdentifier {
Map<ProtocolVersion, Integer> temp = new HashMap<>();
ProtocolVersion previous = null;
for (int i = 0; i < versions.length; i++) {
VersionSet current = Preconditions.checkNotNull(versions[i]);
for (VersionSet version : versions) {
VersionSet current = Preconditions.checkNotNull(version);
Preconditions.checkArgument(
current.getVersion().noLessThan(ProtocolVersion.MINECRAFT_1_19),
@@ -60,9 +68,9 @@ public class ArgumentIdentifier {
@Override
public String toString() {
return "ArgumentIdentifier{" +
"identifier='" + identifier + '\'' +
'}';
return "ArgumentIdentifier{"
+ "identifier='" + identifier + '\''
+ '}';
}
public String getIdentifier() {
@@ -101,7 +109,6 @@ public class ArgumentIdentifier {
public ProtocolVersion getVersion() {
return version;
}
}
}

View File

@@ -45,11 +45,18 @@ import com.mojang.brigadier.arguments.StringArgumentType;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
/**
* The {@code ArgumentPropertyRegistry} is responsible for managing the registration and
* retrieval of argument properties used in command parsing and execution.
*
* <p>This class functions as a registry, allowing different argument properties to be registered
* and later retrieved or used when processing commands within the system. The properties
* might be tied to argument types, validation rules, or transformations.</p>
*/
public class ArgumentPropertyRegistry {
private ArgumentPropertyRegistry() {
@@ -145,7 +152,6 @@ public class ArgumentPropertyRegistry {
} else {
ProtocolUtils.writeString(buf, identifier.getIdentifier());
}
}
/**
@@ -272,7 +278,7 @@ public class ArgumentPropertyRegistry {
empty(id("minecraft:heightmap", mapSet(MINECRAFT_1_21_6, 51), mapSet(MINECRAFT_1_21_5, 50), mapSet(MINECRAFT_1_20_3, 49),
mapSet(MINECRAFT_1_19_4, 47))); // 1.19.4
empty(id("minecraft:uuid", mapSet(MINECRAFT_1_21_6, 56), mapSet(MINECRAFT_1_21_5, 54),mapSet(MINECRAFT_1_20_5, 53), mapSet(MINECRAFT_1_20_3, 48),
empty(id("minecraft:uuid", mapSet(MINECRAFT_1_21_6, 56), mapSet(MINECRAFT_1_21_5, 54), mapSet(MINECRAFT_1_20_5, 53), mapSet(MINECRAFT_1_20_3, 48),
mapSet(MINECRAFT_1_19_4, 48), mapSet(MINECRAFT_1_19, 47))); // added in 1.16
empty(id("minecraft:loot_table", mapSet(MINECRAFT_1_21_6, 52), mapSet(MINECRAFT_1_21_5, 51), mapSet(MINECRAFT_1_20_5, 50)));
@@ -287,4 +293,4 @@ public class ArgumentPropertyRegistry {
empty(id("minecraft:nbt")); // No longer in 1.19+
}
}
}

View File

@@ -21,6 +21,16 @@ import com.velocitypowered.api.network.ProtocolVersion;
import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* The {@code ArgumentPropertySerializer} interface defines a contract for serializing and
* deserializing argument properties to and from a specific format.
*
* <p>This interface allows implementations to convert argument properties into a serialized form,
* which can later be deserialized and restored to their original form. This is particularly useful
* for persisting command argument configurations or sending them across a network.</p>
*
* @param <T> the type of the argument property being serialized
*/
public interface ArgumentPropertySerializer<T> {
@Nullable T deserialize(ByteBuf buf, ProtocolVersion protocolVersion);

View File

@@ -28,6 +28,17 @@ import io.netty.buffer.Unpooled;
import java.util.Collection;
import java.util.concurrent.CompletableFuture;
/**
* Represents a mod-specific argument type with custom binary data attached.
*
* <p>This class allows external mods or extensions to define their own command argument
* types, identified by a namespaced {@link ArgumentIdentifier} and accompanied by
* serialized {@link ByteBuf} data.</p>
*
* <p>Note: This type is not parseable or suggestible through Brigadier and exists primarily
* to preserve compatibility with extended command metadata during serialization and
* deserialization.</p>
*/
public class ModArgumentProperty implements ArgumentType<ByteBuf> {
private final ArgumentIdentifier identifier;

View File

@@ -21,6 +21,16 @@ import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
/**
* The {@code RegistryIdArgumentSerializer} handles serialization and deserialization
* of integer-based registry ID arguments.
*
* <p>This serializer is used for command arguments that refer to elements in Minecraft
* registries (e.g., items, entities, dimensions) by their numerical registry ID.</p>
*
* <p>Values are encoded as variable-length integers using {@link ProtocolUtils}
* for compact transmission.</p>
*/
public class RegistryIdArgumentSerializer implements ArgumentPropertySerializer<Integer> {
static final RegistryIdArgumentSerializer REGISTRY_ID = new RegistryIdArgumentSerializer();

View File

@@ -28,6 +28,15 @@ import java.util.Collection;
import java.util.List;
import java.util.concurrent.CompletableFuture;
/**
* Represents a Brigadier {@link ArgumentType} for registry keys, which are typically
* namespaced resource locations (e.g., {@code minecraft:diamond_sword}).
*
* <p>This argument type reads an unquoted string from input and treats it as a raw registry
* key. It does not validate the format or resolve the key against a known registry.</p>
*
* <p>Examples include simple strings, namespaced keys, or numeric-like identifiers.</p>
*/
public class RegistryKeyArgument implements ArgumentType<String> {
private static final List<String> EXAMPLES = Arrays.asList("foo", "foo:bar", "012");

View File

@@ -21,14 +21,25 @@ import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
/**
* Represents a list of {@link RegistryKeyArgument} objects.
*
* <p>Used to manage and store multiple registry key arguments.</p>
*/
public final class RegistryKeyArgumentList {
/**
* Represents a registry key argument that can either be a resource or a tag.
*/
public static class ResourceOrTag extends RegistryKeyArgument {
public ResourceOrTag(String identifier) {
super(identifier);
}
/**
* Serializer for {@link ResourceOrTag}.
*/
public static class Serializer implements ArgumentPropertySerializer<ResourceOrTag> {
static final ResourceOrTag.Serializer REGISTRY = new ResourceOrTag.Serializer();
@@ -45,12 +56,18 @@ public final class RegistryKeyArgumentList {
}
}
/**
* Represents a registry key argument specifically for a resource or tag key.
*/
public static class ResourceOrTagKey extends RegistryKeyArgument {
public ResourceOrTagKey(String identifier) {
super(identifier);
}
/**
* Serializer for {@link ResourceOrTagKey}.
*/
public static class Serializer implements ArgumentPropertySerializer<ResourceOrTagKey> {
static final ResourceOrTagKey.Serializer REGISTRY = new ResourceOrTagKey.Serializer();
@@ -67,12 +84,18 @@ public final class RegistryKeyArgumentList {
}
}
/**
* Represents a registry key argument for a resource.
*/
public static class ResourceSelector extends RegistryKeyArgument {
public ResourceSelector(String identifier) {
super(identifier);
}
/**
* Serializer for {@link ResourceSelector}.
*/
public static class Serializer implements ArgumentPropertySerializer<ResourceSelector> {
static final ResourceSelector.Serializer REGISTRY = new ResourceSelector.Serializer();
@@ -89,12 +112,18 @@ public final class RegistryKeyArgumentList {
}
}
/**
* Represents a registry key argument for a resource key.
*/
public static class ResourceKey extends RegistryKeyArgument {
public ResourceKey(String identifier) {
super(identifier);
}
/**
* Serializer for {@link ResourceKey}.
*/
public static class Serializer implements ArgumentPropertySerializer<ResourceKey> {
static final ResourceKey.Serializer REGISTRY = new ResourceKey.Serializer();

View File

@@ -21,6 +21,12 @@ import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
/**
* Serializer for {@link RegistryKeyArgument} objects.
*
* <p>This class handles the serialization and deserialization of {@code RegistryKeyArgument}
* objects to and from a {@link ByteBuf} using the specified {@link ProtocolVersion}.</p>
*/
public class RegistryKeyArgumentSerializer implements
ArgumentPropertySerializer<RegistryKeyArgument> {

View File

@@ -20,6 +20,12 @@ package com.velocitypowered.proxy.protocol.packet.brigadier;
import com.velocitypowered.api.network.ProtocolVersion;
import io.netty.buffer.ByteBuf;
/**
* Serializer for time-based arguments represented as {@link Integer}.
*
* <p>This class handles the serialization and deserialization of time-related arguments,
* converting them to and from an {@link Integer} format.</p>
*/
public class TimeArgumentSerializer implements ArgumentPropertySerializer<Integer> {
static final TimeArgumentSerializer TIME = new TimeArgumentSerializer();

View File

@@ -23,39 +23,44 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
/**
* Represents a packet sent to acknowledge the receipt of a chat message.
* This packet is used to confirm that a player or client has received and processed
* a chat message from the server.
*/
public class ChatAcknowledgementPacket implements MinecraftPacket {
int offset;
int offset;
public ChatAcknowledgementPacket(int offset) {
this.offset = offset;
}
public ChatAcknowledgementPacket(int offset) {
this.offset = offset;
}
public ChatAcknowledgementPacket() {
}
public ChatAcknowledgementPacket() {
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
offset = ProtocolUtils.readVarInt(buf);
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
offset = ProtocolUtils.readVarInt(buf);
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeVarInt(buf, offset);
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeVarInt(buf, offset);
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
@Override
public String toString() {
return "ChatAcknowledgement{" +
"offset=" + offset +
'}';
}
@Override
public String toString() {
return "ChatAcknowledgement{"
+ "offset=" + offset
+ '}';
}
public int offset() {
return offset;
}
public int offset() {
return offset;
}
}

View File

@@ -19,12 +19,28 @@ package com.velocitypowered.proxy.protocol.packet.chat;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
/**
* Represents a handler for processing chat-related packets in the game.
* This interface is generic and can handle different types of Minecraft packets that
* extend {@link MinecraftPacket}.
*
* @param <T> the type of packet that this chat handler processes, which must
* extend {@link MinecraftPacket}
*/
public interface ChatHandler<T extends MinecraftPacket> {
Class<T> packetClass();
void handlePlayerChatInternal(T packet);
/**
* Handles a player chat event represented by the given {@link MinecraftPacket}.
* This default method provides a basic mechanism for processing chat-related packets that
* involve player messages.
*
* @param packet the {@link MinecraftPacket} representing the player chat event to handle
* @return {@code true} if the chat event was successfully handled, {@code false} otherwise
*/
default boolean handlePlayerChat(MinecraftPacket packet) {
if (packetClass().isInstance(packet)) {
handlePlayerChatInternal(packetClass().cast(packet));

View File

@@ -21,12 +21,12 @@ import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import io.netty.channel.ChannelFuture;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.time.Instant;
import java.util.BitSet;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* A precisely ordered queue which allows for outside entries into the ordered queue through
@@ -79,7 +79,8 @@ public class ChatQueue implements AutoCloseable {
* @param timestamp the new {@link Instant} timestamp of this packet to update the internal chat state.
* @param lastSeenMessages the new {@link LastSeenMessages} last seen messages to update the internal chat state.
*/
public void queuePacket(Function<LastSeenMessages, CompletableFuture<MinecraftPacket>> nextPacket, @Nullable Instant timestamp, @Nullable LastSeenMessages lastSeenMessages) {
public void queuePacket(Function<LastSeenMessages, CompletableFuture<MinecraftPacket>> nextPacket, @Nullable Instant timestamp,
@Nullable LastSeenMessages lastSeenMessages) {
queueTask((chatState, smc) -> {
LastSeenMessages newLastSeenMessages = chatState.updateFromMessage(timestamp, lastSeenMessages);
return nextPacket.apply(newLastSeenMessages).thenCompose(packet -> writePacket(packet, smc));
@@ -100,6 +101,12 @@ public class ChatQueue implements AutoCloseable {
});
}
/**
* Handles the acknowledgement of a chat message or event by processing the given offset.
* This method is typically called when a chat message or command is acknowledged by the client or server.
*
* @param offset the offset representing the specific message or event being acknowledged
*/
public void handleAcknowledgement(int offset) {
queueTask((chatState, smc) -> {
int ackCountToForward = chatState.accumulateAckCount(offset);
@@ -138,16 +145,16 @@ public class ChatQueue implements AutoCloseable {
* <li>If we last forwarded a chat or command packet from the client, we have a known 'last seen' that we can
* reuse.</li>
* <li>If we last forwarded a {@link ChatAcknowledgementPacket}, the previous 'last seen' cannot be reused. We
* cannot predict an up-to-date 'last seen', as we do not know which messages the client actually saw.</li>
* cannot predict an up to date 'last seen', as we do not know which messages the client actually saw.</li>
* <li>Therefore, we need to hold back any acknowledgement packets so that we can continue to reuse the last valid
* 'last seen' state.</li>
* <li>However, there is a limit to the number of messages that can remain unacknowledged on the server.</li>
* <li>To address this, we know that if the client has moved its 'last seen' window far enough, we can fill in the
* gap with dummy 'last seen', and it will never be checked.</li>
* gap with stub 'last seen', and it will never be checked.</li>
* </ul>
*
* Note that this is effectively unused for 1.20.5+ clients, as commands without any signature do not send 'last seen'
* updates.
* <p>Note that this is effectively unused for 1.20.5+ clients, as commands without any signature do not send 'last seen'
* updates.</p>
*/
public static class ChatState {
private static final int MINIMUM_DELAYED_ACK_COUNT = LastSeenMessages.WINDOW_SIZE;
@@ -160,6 +167,17 @@ public class ChatQueue implements AutoCloseable {
private ChatState() {
}
/**
* Updates the state of the {@link LastSeenMessages} and the timestamp based on a new message or event.
* This method processes the given timestamp and last seen messages to ensure the internal state is up to date.
* - If the provided {@link Instant} is not null, it updates the last known timestamp.
* - If the provided {@link LastSeenMessages} is not null, it flushes any delayed acknowledgements and updates the
* internal acknowledged messages, returning an adjusted {@link LastSeenMessages} with the offset applied.
*
* @param timestamp the optional {@link Instant} representing the new timestamp for the message or event
* @param lastSeenMessages the optional {@link LastSeenMessages} representing the last seen messages by the player
* @return the updated {@link LastSeenMessages} with the applied offset, or {@code null} if no updates were made
*/
@Nullable
public LastSeenMessages updateFromMessage(@Nullable Instant timestamp, @Nullable LastSeenMessages lastSeenMessages) {
if (timestamp != null) {
@@ -174,6 +192,16 @@ public class ChatQueue implements AutoCloseable {
return null;
}
/**
* Accumulates the given acknowledgement count and determines if enough acknowledgements have been gathered to forward.
* - Adds the provided `ackCount` to the current delayed acknowledgement count.
* - If the accumulated acknowledgements exceed the {@link LastSeenMessages#WINDOW_SIZE}, the method resets the delayed
* acknowledgement count and returns the number of acknowledgements that should be forwarded.
* - If the threshold is not met, the method returns 0, indicating that no acknowledgements need to be forwarded yet.
*
* @param ackCount the number of acknowledgements to add to the accumulated count
* @return the number of acknowledgements that should be forwarded, or 0 if the threshold has not been reached
*/
public int accumulateAckCount(int ackCount) {
int delayedAckCount = this.delayedAckCount.addAndGet(ackCount);
int ackCountToForward = delayedAckCount - MINIMUM_DELAYED_ACK_COUNT;
@@ -186,6 +214,11 @@ public class ChatQueue implements AutoCloseable {
return 0;
}
/**
* Creates a snapshot of the current {@link LastSeenMessages} state.
*
* @return a new {@link LastSeenMessages} representing the current view
*/
public LastSeenMessages createLastSeen() {
return new LastSeenMessages(0, lastSeenMessages, (byte) 0);
}

View File

@@ -19,6 +19,11 @@ package com.velocitypowered.proxy.protocol.packet.chat;
import java.time.Instant;
/**
* Manages the timing and duration of chat messages within the game.
* The {@code ChatTimeKeeper} class tracks when chat messages are sent and provides mechanisms
* to determine how long a message has been displayed or to manage message expiration.
*/
public class ChatTimeKeeper {
private Instant lastTimestamp;
@@ -27,6 +32,18 @@ public class ChatTimeKeeper {
this.lastTimestamp = Instant.MIN;
}
/**
* Updates the internal timestamp of the chat message or session.
* This method checks if the provided {@link Instant} is before the current stored timestamp.
* If it is, the internal timestamp is updated, and the method returns {@code false} to
* indicate that the update was not successful.
* If the provided {@link Instant} is valid, the timestamp is updated,
* and the method may return {@code true}.
*
* @param instant the {@link Instant} representing the new timestamp to update
* @return {@code true} if the timestamp was successfully updated, {@code false}
* if the provided instant is before the current timestamp
*/
public boolean update(Instant instant) {
if (instant.isBefore(this.lastTimestamp)) {
this.lastTimestamp = instant;

View File

@@ -17,6 +17,12 @@
package com.velocitypowered.proxy.protocol.packet.chat;
/**
* Represents different types of chat messages in the game, defining how a message should be
* handled and displayed.
* This enum categorizes various chat message types such as system messages, player chat,
* or game info.
*/
public enum ChatType {
CHAT((byte) 0),
SYSTEM((byte) 1),
@@ -31,4 +37,4 @@ public enum ChatType {
public byte getId() {
return raw;
}
}
}

View File

@@ -31,6 +31,14 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents a handler for processing commands associated with specific types of Minecraft packets.
* This interface is generic and allows for handling different packet types that extend
* {@link MinecraftPacket}.
*
* @param <T> the type of packet that this handler is responsible for, which
* must extend {@link MinecraftPacket}
*/
public interface CommandHandler<T extends MinecraftPacket> {
Logger logger = LogManager.getLogger(CommandHandler.class);
@@ -39,6 +47,14 @@ public interface CommandHandler<T extends MinecraftPacket> {
void handlePlayerCommandInternal(T packet);
/**
* Handles a player command associated with a given {@link MinecraftPacket}.
* This default method provides a mechanism for processing commands related to player
* actions within the game.
*
* @param packet the {@link MinecraftPacket} representing the player command to be handled
* @return {@code true} if the command was successfully handled, {@code false} otherwise
*/
default boolean handlePlayerCommand(MinecraftPacket packet) {
if (packetClass().isInstance(packet)) {
handlePlayerCommandInternal(packetClass().cast(packet));
@@ -54,25 +70,45 @@ public interface CommandHandler<T extends MinecraftPacket> {
.thenApply(hasRunPacketFunction);
}
/**
* Queues the result of a player command for execution, managing the future result of the command.
* This method is designed to interact with the {@link VelocityServer}
* and {@link ConnectedPlayer} to
* handle the process of command execution, queuing, and response handling.
*
* @param server the {@link VelocityServer} instance responsible for managing the
* command execution
* @param player the {@link ConnectedPlayer} who initiated the command
* @param futurePacketCreator a {@link BiFunction} that creates a future packet based on
* the {@link CommandExecuteEvent}
* and {@link LastSeenMessages}, which will be used for sending
* a command result
* @param message the command message that the player sent
* @param timestamp the {@link Instant} when the command was executed
* @param lastSeenMessages the {@link LastSeenMessages} object containing the messages last
* seen by the player,
* or {@code null} if not applicable
* @param invocationInfo signing metadata for the event dispatch
*/
default void queueCommandResult(VelocityServer server, ConnectedPlayer player,
BiFunction<CommandExecuteEvent, LastSeenMessages, CompletableFuture<MinecraftPacket>> futurePacketCreator,
String message, Instant timestamp, @Nullable LastSeenMessages lastSeenMessages,
CommandExecuteEvent.InvocationInfo invocationInfo) {
CompletableFuture<CommandExecuteEvent> eventFuture = server.getCommandManager().callCommandEvent(player, message,
invocationInfo);
player.getChatQueue().queuePacket(
CompletableFuture<CommandExecuteEvent> eventFuture = server.getCommandManager().callCommandEvent(player, message,
invocationInfo);
player.getChatQueue().queuePacket(
newLastSeenMessages -> eventFuture
.thenComposeAsync(event -> futurePacketCreator.apply(event, newLastSeenMessages))
.thenApply(pkt -> {
if (server.getConfiguration().isLogCommandExecutions()) {
logger.info("{} -> executed command /{}", player, message);
}
return pkt;
}).exceptionally(e -> {
logger.info("Exception occurred while running command for {}", player.getUsername(), e);
player.sendMessage(
Component.translatable("velocity.command.generic-error", NamedTextColor.RED));
return null;
}), timestamp, lastSeenMessages);
.thenComposeAsync(event -> futurePacketCreator.apply(event, newLastSeenMessages))
.thenApply(pkt -> {
if (server.getConfiguration().isLogCommandExecutions()) {
logger.info("{} -> executed command /{}", player, message);
}
return pkt;
}).exceptionally(e -> {
logger.info("Exception occurred while running command for {}", player.getUsername(), e);
player.sendMessage(
Component.translatable("velocity.command.generic-error", NamedTextColor.RED));
return null;
}), timestamp, lastSeenMessages);
}
}

View File

@@ -25,6 +25,9 @@ import com.google.gson.internal.LazilyParsedNumber;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import net.kyori.adventure.nbt.BinaryTag;
import net.kyori.adventure.nbt.BinaryTagIO;
import net.kyori.adventure.nbt.BinaryTagType;
@@ -47,10 +50,12 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Represents a holder for components used in chat or other text-based data in the Minecraft
* protocol.
* This class supports various formats including JSON and NBT (Named Binary Tag) for storing and
* transmitting text components.
*/
public class ComponentHolder {
private static final Logger logger = LogManager.getLogger(ComponentHolder.class);
public static final int DEFAULT_MAX_STRING_SIZE = 262143;
@@ -75,6 +80,14 @@ public class ComponentHolder {
this.binaryTag = binaryTag;
}
/**
* Retrieves the {@link Component} stored in this {@link ComponentHolder}.
* If the component is not yet initialized, it will attempt to deserialize it from either
* the JSON or NBT representation, depending on which is available.
*
* @return the {@link Component} stored in this holder
* @throws IllegalStateException if both the JSON and binary representations fail to deserialize
*/
public Component getComponent() {
if (component == null) {
if (json != null) {
@@ -95,6 +108,13 @@ public class ComponentHolder {
return component;
}
/**
* Retrieves the JSON representation of the {@link Component} stored in this
* {@link ComponentHolder}.
* If the JSON string is not yet initialized, it will serialize the component into a JSON string.
*
* @return the JSON string representing the {@link Component}
*/
public String getJson() {
if (json == null) {
json = ProtocolUtils.getJsonChatSerializer(version).serialize(getComponent());
@@ -102,6 +122,14 @@ public class ComponentHolder {
return json;
}
/**
* Retrieves the NBT (Named Binary Tag) representation of the {@link Component} stored in this
* {@link ComponentHolder}.
* If the NBT tag is not yet initialized, it will serialize the component into an NBT
* representation.
*
* @return the {@link BinaryTag} representing the {@link Component}
*/
public BinaryTag getBinaryTag() {
if (binaryTag == null) {
// TODO: replace this with adventure-text-serializer-nbt
@@ -110,6 +138,16 @@ public class ComponentHolder {
return binaryTag;
}
/**
* Serializes a {@link JsonElement} into a {@link BinaryTag} format.
* This method converts JSON primitives (numbers, strings, booleans) and complex structures
* (arrays, objects)
* into their corresponding NBT representations.
*
* @param json the {@link JsonElement} to be serialized into a {@link BinaryTag}
* @return the {@link BinaryTag} representing the serialized JSON element
* @throws IllegalArgumentException if the JSON element is of an unsupported or unknown type
*/
public static BinaryTag serialize(JsonElement json) {
if (json instanceof JsonPrimitive jsonPrimitive) {
if (jsonPrimitive.isNumber()) {
@@ -162,36 +200,40 @@ public class ComponentHolder {
}
switch (listType.id()) {
case 1://BinaryTagTypes.BYTE:
case 1 -> { // BinaryTagTypes.BYTE:
byte[] bytes = new byte[jsonArray.size()];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = jsonArray.get(i).getAsNumber().byteValue();
}
return ByteArrayBinaryTag.byteArrayBinaryTag(bytes);
case 3://BinaryTagTypes.INT:
}
case 3 -> { // BinaryTagTypes.INT:
int[] ints = new int[jsonArray.size()];
for (int i = 0; i < ints.length; i++) {
ints[i] = jsonArray.get(i).getAsNumber().intValue();
}
return IntArrayBinaryTag.intArrayBinaryTag(ints);
case 4://BinaryTagTypes.LONG:
}
case 4 -> { // BinaryTagTypes.LONG:
long[] longs = new long[jsonArray.size()];
for (int i = 0; i < longs.length; i++) {
longs[i] = jsonArray.get(i).getAsNumber().longValue();
}
return LongArrayBinaryTag.longArrayBinaryTag(longs);
case 10://BinaryTagTypes.COMPOUND:
tagItems.replaceAll(tag -> {
if (tag.type() == BinaryTagTypes.COMPOUND) {
return tag;
} else {
return CompoundBinaryTag.builder().put("", tag).build();
}
});
break;
}
case 10 -> // BinaryTagTypes.COMPOUND:
tagItems.replaceAll(tag -> {
if (tag.type() == BinaryTagTypes.COMPOUND) {
return tag;
} else {
return CompoundBinaryTag.builder().put("", tag).build();
}
});
default -> {
}
}
return ListBinaryTag.listBinaryTag(listType, tagItems);
@@ -200,21 +242,30 @@ public class ComponentHolder {
return EndBinaryTag.endBinaryTag();
}
/**
* Deserializes a {@link BinaryTag} into a {@link JsonElement}.
* This method converts NBT (Named Binary Tag) data into its corresponding JSON representation,
* including handling of primitive types, arrays, and compound structures.
*
* @param tag the {@link BinaryTag} to be deserialized into a {@link JsonElement}
* @return the {@link JsonElement} representing the deserialized NBT data
* @throws IllegalArgumentException if the NBT tag type is unsupported or unknown
*/
public static JsonElement deserialize(BinaryTag tag) {
return switch (tag.type().id()) {
//BinaryTagTypes.BYTE
// BinaryTagTypes.BYTE
case 1 -> new JsonPrimitive(((ByteBinaryTag) tag).value());
//BinaryTagTypes.SHORT
// BinaryTagTypes.SHORT
case 2 -> new JsonPrimitive(((ShortBinaryTag) tag).value());
//BinaryTagTypes.INT:
// BinaryTagTypes.INT:
case 3 -> new JsonPrimitive(((IntBinaryTag) tag).value());
//BinaryTagTypes.LONG:
// BinaryTagTypes.LONG:
case 4 -> new JsonPrimitive(((LongBinaryTag) tag).value());
//BinaryTagTypes.FLOAT:
// BinaryTagTypes.FLOAT:
case 5 -> new JsonPrimitive(((FloatBinaryTag) tag).value());
//BinaryTagTypes.DOUBLE:
// BinaryTagTypes.DOUBLE:
case 6 -> new JsonPrimitive(((DoubleBinaryTag) tag).value());
//BinaryTagTypes.BYTE_ARRAY:
// BinaryTagTypes.BYTE_ARRAY:
case 7 -> {
byte[] byteArray = ((ByteArrayBinaryTag) tag).value();
@@ -225,9 +276,9 @@ public class ComponentHolder {
yield jsonByteArray;
}
//BinaryTagTypes.STRING:
// BinaryTagTypes.STRING:
case 8 -> new JsonPrimitive(((StringBinaryTag) tag).value());
//BinaryTagTypes.LIST:
// BinaryTagTypes.LIST:
case 9 -> {
ListBinaryTag items = (ListBinaryTag) tag;
JsonArray jsonList = new JsonArray(items.size());
@@ -238,7 +289,7 @@ public class ComponentHolder {
yield jsonList;
}
//BinaryTagTypes.COMPOUND:
// BinaryTagTypes.COMPOUND:
case 10 -> {
CompoundBinaryTag compound = (CompoundBinaryTag) tag;
JsonObject jsonObject = new JsonObject();
@@ -254,7 +305,7 @@ public class ComponentHolder {
yield jsonObject;
}
//BinaryTagTypes.INT_ARRAY:
// BinaryTagTypes.INT_ARRAY:
case 11 -> {
int[] intArray = ((IntArrayBinaryTag) tag).value();
@@ -265,7 +316,7 @@ public class ComponentHolder {
yield jsonIntArray;
}
//BinaryTagTypes.LONG_ARRAY:
// BinaryTagTypes.LONG_ARRAY:
case 12 -> {
long[] longArray = ((LongArrayBinaryTag) tag).value();
@@ -280,6 +331,19 @@ public class ComponentHolder {
};
}
/**
* Reads a {@link ComponentHolder} from the provided {@link ByteBuf} using the specified
* {@link ProtocolVersion}.
* This method deserializes a component from either its binary (NBT) or JSON representation,
* depending on the protocol version.
* - For Minecraft versions 1.20.3 and later, it reads a binary tag.
* - For Minecraft versions 1.13 and later, it reads a JSON string with a size limit.
* - For earlier versions, it reads a standard JSON string.
*
* @param buf the {@link ByteBuf} containing the serialized component data
* @param version the {@link ProtocolVersion} indicating how the component should be deserialized
* @return a {@link ComponentHolder} containing the deserialized component
*/
public static ComponentHolder read(ByteBuf buf, ProtocolVersion version) {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) {
return new ComponentHolder(version,
@@ -291,6 +355,15 @@ public class ComponentHolder {
}
}
/**
* Writes the {@link ComponentHolder}'s data to the provided {@link ByteBuf}.
* This method serializes the component into either its binary (NBT) or JSON representation
* based on the protocol version.
* - For Minecraft versions 1.20.3 and later, it writes the component as a binary tag (NBT).
* - For earlier versions, it writes the component as a JSON string.
*
* @param buf the {@link ByteBuf} where the component data will be written
*/
public void write(ByteBuf buf) {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) {
ProtocolUtils.writeBinaryTag(buf, version, getBinaryTag());
@@ -298,4 +371,4 @@ public class ComponentHolder {
ProtocolUtils.writeString(buf, getJson());
}
}
}
}

View File

@@ -23,6 +23,10 @@ import io.netty.buffer.ByteBuf;
import java.util.Arrays;
import java.util.BitSet;
/**
* Represents a collection of the last seen messages by a player or client.
* This class tracks the recent chat messages that the player has viewed.
*/
public class LastSeenMessages {
public static final int WINDOW_SIZE = 20;
@@ -35,12 +39,26 @@ public class LastSeenMessages {
this(0, new BitSet(), (byte) 0);
}
/**
* Creates a new {@link LastSeenMessages} instance with the specified offset, acknowledged messages, and checksum.
*
* @param offset the starting index of the message window
* @param acknowledged a BitSet representing which messages have been acknowledged
* @param checksum the checksum for the message window data
*/
public LastSeenMessages(int offset, BitSet acknowledged, byte checksum) {
this.offset = offset;
this.acknowledged = acknowledged;
this.checksum = checksum;
}
/**
* Constructs a new {@link LastSeenMessages} instance by decoding data from the provided
* {@link ByteBuf}.
*
* @param buf the buffer containing the serialized last seen messages data
* @param protocolVersion the protocol version (determines if checksum is written)
*/
public LastSeenMessages(ByteBuf buf, ProtocolVersion protocolVersion) {
this.offset = ProtocolUtils.readVarInt(buf);
@@ -53,6 +71,12 @@ public class LastSeenMessages {
}
}
/**
* Encodes this {@link LastSeenMessages} instance into the provided {@link ByteBuf}.
*
* @param buf the buffer to write the data to
* @param protocolVersion the protocol version used for encoding
*/
public void encode(ByteBuf buf, ProtocolVersion protocolVersion) {
ProtocolUtils.writeVarInt(buf, offset);
buf.writeBytes(Arrays.copyOf(acknowledged.toByteArray(), DIV_FLOOR));
@@ -75,10 +99,10 @@ public class LastSeenMessages {
@Override
public String toString() {
return "LastSeenMessages{" +
"offset=" + offset +
", acknowledged=" + acknowledged +
", checksum=" + checksum +
'}';
return "LastSeenMessages{"
+ "offset=" + offset
+ ", acknowledged=" + acknowledged
+ ", checksum=" + checksum
+ '}';
}
}

View File

@@ -23,6 +23,11 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
/**
* Represents a packet sent between the server and client to handle chat completion suggestions.
* This packet allows the server to send chat message completions or suggestions to the client,
* helping users complete commands or chat messages.
*/
public class PlayerChatCompletionPacket implements MinecraftPacket {
private String[] completions;
@@ -71,6 +76,9 @@ public class PlayerChatCompletionPacket implements MinecraftPacket {
return handler.handle(this);
}
/**
* Represents the different actions that can be taken with chat completions.
*/
public enum Action {
ADD,
REMOVE,

View File

@@ -22,37 +22,46 @@ import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import net.kyori.adventure.text.Component;
/**
* Abstract base class for handling rate-limited player command packets.
*
* <p>Subclasses should implement {@link #handlePlayerCommandInternal(MinecraftPacket)} to define
* how individual command packets are processed. Rate limiting is enforced to prevent abuse.</p>
*
* @param <T> the type of {@link MinecraftPacket} this handler processes
*/
public abstract class RateLimitedCommandHandler<T extends MinecraftPacket> implements CommandHandler<T> {
private final Player player;
private final VelocityServer velocityServer;
private final Player player;
private final VelocityServer velocityServer;
private int failedAttempts;
private int failedAttempts;
protected RateLimitedCommandHandler(Player player, VelocityServer velocityServer) {
this.player = player;
this.velocityServer = velocityServer;
}
protected RateLimitedCommandHandler(Player player, VelocityServer velocityServer) {
this.player = player;
this.velocityServer = velocityServer;
}
@Override
public boolean handlePlayerCommand(MinecraftPacket packet) {
if (packetClass().isInstance(packet)) {
if (!velocityServer.getCommandRateLimiter().attempt(player.getUniqueId())) {
if (velocityServer.getConfiguration().isKickOnCommandRateLimit() && failedAttempts++ >= velocityServer.getConfiguration().getKickAfterRateLimitedCommands()) {
player.disconnect(Component.translatable("velocity.kick.command-rate-limit"));
}
if (velocityServer.getConfiguration().isForwardCommandsIfRateLimited()) {
return false; // Send the packet to the server
}
} else {
failedAttempts = 0;
}
handlePlayerCommandInternal(packetClass().cast(packet));
return true;
@Override
public boolean handlePlayerCommand(MinecraftPacket packet) {
if (packetClass().isInstance(packet)) {
if (!velocityServer.getCommandRateLimiter().attempt(player.getUniqueId())) {
if (velocityServer.getConfiguration().isKickOnCommandRateLimit()
&& failedAttempts++ >= velocityServer.getConfiguration().getKickAfterRateLimitedCommands()) {
player.disconnect(Component.translatable("velocity.kick.command-rate-limit"));
}
return false;
if (velocityServer.getConfiguration().isForwardCommandsIfRateLimited()) {
return false; // Send the packet to the server
}
} else {
failedAttempts = 0;
}
handlePlayerCommandInternal(packetClass().cast(packet));
return true;
}
return false;
}
}

View File

@@ -26,6 +26,11 @@ import java.util.Objects;
import java.util.UUID;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents a remote chat session that implements the {@link ChatSession} interface.
* This session is used for handling chat interactions that occur remotely, typically between
* a client and server, allowing for communication and session tracking.
*/
public class RemoteChatSession implements ChatSession {
private final @Nullable UUID sessionId;

View File

@@ -23,6 +23,11 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
/**
* Represents a packet sent from the server to the client to display system chat messages.
* This packet handles the communication of messages that are not player-generated, but instead
* come from the system or server itself.
*/
public class SystemChatPacket implements MinecraftPacket {
public SystemChatPacket() {
@@ -47,7 +52,7 @@ public class SystemChatPacket implements MinecraftPacket {
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
component = ComponentHolder.read(buf, version);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)){
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) {
type = buf.readBoolean() ? ChatType.GAME_INFO : ChatType.SYSTEM;
} else {
type = ChatType.values()[ProtocolUtils.readVarInt(buf)];

View File

@@ -23,11 +23,22 @@ import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyChatBuilder;
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionChatBuilder;
import java.util.function.Function;
/**
* Factory class for creating instances of chat builders.
*
* <p>The {@code ChatBuilderFactory} is responsible for providing various builder instances
* used to construct chat-related components, such as messages, chat formats, or text components.</p>
*/
public class ChatBuilderFactory {
private final ProtocolVersion version;
private final Function<ProtocolVersion, ChatBuilderV2> builderFunction;
/**
* Creates a new {@code ChatBuilderFactory} for the specified protocol version.
*
* @param version the protocol version to be used by the chat builder factory
*/
public ChatBuilderFactory(ProtocolVersion version) {
this.version = version;
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_3)) {

View File

@@ -28,6 +28,13 @@ import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* An abstract class for building chat components in version 2 of the chat system.
*
* <p>The {@code ChatBuilderV2} class provides the foundation for creating and formatting
* chat components, allowing subclasses to implement specific behaviors for constructing
* chat messages or text components.</p>
*/
public abstract class ChatBuilderV2 {
protected final ProtocolVersion version;

View File

@@ -26,6 +26,12 @@ import com.velocitypowered.proxy.protocol.packet.chat.SystemChatPacket;
import com.velocitypowered.proxy.protocol.packet.chat.builder.ChatBuilderV2;
import net.kyori.adventure.text.Component;
/**
* A concrete implementation of {@link ChatBuilderV2} that uses keys to build chat components.
*
* <p>The {@code KeyedChatBuilder} class extends the functionality of {@link ChatBuilderV2} by allowing
* chat components to be built using specific keys, enabling dynamic message construction.</p>
*/
public class KeyedChatBuilder extends ChatBuilderV2 {
public KeyedChatBuilder(ProtocolVersion version) {

View File

@@ -30,6 +30,13 @@ import net.kyori.adventure.text.Component;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* A handler for processing chat components based on specific keys.
*
* <p>The {@code KeyedChatHandler} class is responsible for managing chat interactions or
* messages that keys identify. It implements the required interface or class
* to handle key-based chat processing.</p>
*/
public class KeyedChatHandler implements
com.velocitypowered.proxy.protocol.packet.chat.ChatHandler<KeyedPlayerChatPacket> {
@@ -48,6 +55,15 @@ public class KeyedChatHandler implements
return KeyedPlayerChatPacket.class;
}
/**
* Logs an error and disconnects the player when a plugin attempts to cancel a signed chat message.
*
* <p>This method handles the invalid behavior of canceling signed chat messages, which is no longer allowed
* starting from Minecraft version 1.19.1.</p>
*
* @param logger the logger used to log the error
* @param player the player to disconnect due to the illegal action
*/
public static void invalidCancel(Logger logger, ConnectedPlayer player) {
logger.fatal("A plugin tried to cancel a signed chat message."
+ " This is no longer possible in 1.19.1 and newer. "
@@ -56,6 +72,15 @@ public class KeyedChatHandler implements
+ "Contact your network administrator."));
}
/**
* Logs an error and disconnects the player when a plugin attempts to modify a signed chat message.
*
* <p>This method handles the invalid behavior of modifying signed chat messages, which is no longer allowed
* starting from Minecraft version 1.19.1.</p>
*
* @param logger the logger used to log the error
* @param player the player to disconnect due to the illegal action
*/
public static void invalidChange(Logger logger, ConnectedPlayer player) {
logger.fatal("A plugin tried to change a signed chat message. "
+ "This is no longer possible in 1.19.1 and newer. "

View File

@@ -26,11 +26,24 @@ import com.velocitypowered.proxy.protocol.packet.chat.builder.ChatBuilderV2;
import java.util.concurrent.CompletableFuture;
import net.kyori.adventure.text.Component;
/**
* Handles keyed player commands by implementing {@link RateLimitedCommandHandler}.
*
* <p>The {@code KeyedCommandHandler} processes commands that are sent using
* {@link KeyedPlayerCommandPacket}. It provides the necessary logic for handling
* and executing commands associated with specific keys.</p>
*/
public class KeyedCommandHandler extends RateLimitedCommandHandler<KeyedPlayerCommandPacket> {
private final ConnectedPlayer player;
private final VelocityServer server;
/**
* Constructs a new {@code KeyedCommandHandler}.
*
* @param player the player sending the command
* @param server the proxy server instance
*/
public KeyedCommandHandler(ConnectedPlayer player, VelocityServer server) {
super(player, server);
this.player = player;
@@ -112,6 +125,7 @@ public class KeyedCommandHandler extends RateLimitedCommandHandler<KeyedPlayerCo
}
return null;
});
}, packet.getCommand(), packet.getTimestamp(), null, new CommandExecuteEvent.InvocationInfo(CommandExecuteEvent.SignedState.UNSUPPORTED, CommandExecuteEvent.Source.PLAYER));
}, packet.getCommand(), packet.getTimestamp(), null, new CommandExecuteEvent.InvocationInfo(CommandExecuteEvent.SignedState.UNSUPPORTED,
CommandExecuteEvent.Source.PLAYER));
}
}

View File

@@ -29,6 +29,13 @@ import io.netty.buffer.ByteBuf;
import java.time.Instant;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents a player chat packet with support for message signing and preview.
*
* <p>The {@code KeyedPlayerChatPacket} handles player chat messages, supporting signed previews,
* message signatures, and previous message validation. It includes fields for tracking message
* signatures and handling expired messages.</p>
*/
public class KeyedPlayerChatPacket implements MinecraftPacket {
private String message;

View File

@@ -35,6 +35,13 @@ import java.util.List;
import java.util.Map;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents a player command packet with support for keyed commands.
*
* <p>The {@code KeyedPlayerCommandPacket} handles player commands sent to the server,
* allowing for command execution based on specific keys. This packet can include additional
* information such as arguments and key-based identifiers for the command.</p>
*/
public class KeyedPlayerCommandPacket implements MinecraftPacket {
private static final int MAX_NUM_ARGUMENTS = 8;

View File

@@ -25,6 +25,13 @@ import java.util.UUID;
import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.text.Component;
/**
* A concrete implementation of {@link ChatBuilderV2} for handling legacy chat formats.
*
* <p>The {@code LegacyChatBuilder} is designed to support and build chat components
* using legacy chat formatting, such as the formats used in earlier versions of Minecraft.
* It extends the functionality of {@link ChatBuilderV2} to cater to older chat systems.</p>
*/
public class LegacyChatBuilder extends ChatBuilderV2 {
public LegacyChatBuilder(ProtocolVersion version) {

View File

@@ -23,6 +23,13 @@ import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.packet.chat.ChatHandler;
/**
* A handler for processing legacy chat packets, implementing {@link ChatHandler}.
*
* <p>The {@code LegacyChatHandler} is responsible for handling and processing chat messages
* sent using {@link LegacyChatPacket}. This class provides the necessary logic for
* processing chat data using legacy Minecraft chat formats.</p>
*/
public class LegacyChatHandler implements ChatHandler<LegacyChatPacket> {
private final VelocityServer server;

View File

@@ -25,6 +25,13 @@ import io.netty.buffer.ByteBuf;
import java.util.UUID;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents a legacy chat packet used in older versions of Minecraft.
*
* <p>The {@code LegacyChatPacket} is responsible for holding and transmitting chat messages
* in the format used by legacy versions of Minecraft. It implements {@link MinecraftPacket}
* to ensure compatibility with the packet-handling system.</p>
*/
public class LegacyChatPacket implements MinecraftPacket {
public static final byte CHAT_TYPE = (byte) 0;
@@ -45,6 +52,7 @@ public class LegacyChatPacket implements MinecraftPacket {
try {
return Integer.parseInt(value.trim());
} catch (final NumberFormatException e) {
// Exception has been handled
}
}
return 100;

View File

@@ -21,15 +21,27 @@ import com.velocitypowered.api.event.command.CommandExecuteEvent;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.packet.chat.RateLimitedCommandHandler;
import java.time.Instant;
import java.util.concurrent.CompletableFuture;
/**
* A handler for processing legacy commands, implementing {@link RateLimitedCommandHandler}.
*
* <p>The {@code LegacyCommandHandler} processes and handles command packets that are sent
* using {@link LegacyChatPacket}. It provides the necessary logic to support legacy
* command formats and ensure compatibility with older Minecraft versions.</p>
*/
public class LegacyCommandHandler extends RateLimitedCommandHandler<LegacyChatPacket> {
private final ConnectedPlayer player;
private final VelocityServer server;
/**
* Constructs a new {@code LegacyCommandHandler} for handling legacy command packets.
*
* @param player the connected player issuing the command
* @param server the Velocity server instance
*/
public LegacyCommandHandler(ConnectedPlayer player, VelocityServer server) {
super(player, server);
this.player = player;
@@ -64,6 +76,7 @@ public class LegacyCommandHandler extends RateLimitedCommandHandler<LegacyChatPa
}
return null;
});
}, command, Instant.now(), null, new CommandExecuteEvent.InvocationInfo(CommandExecuteEvent.SignedState.UNSUPPORTED, CommandExecuteEvent.Source.PLAYER));
}, command, Instant.now(), null, new CommandExecuteEvent.InvocationInfo(CommandExecuteEvent.SignedState.UNSUPPORTED,
CommandExecuteEvent.Source.PLAYER));
}
}

View File

@@ -26,6 +26,13 @@ import com.velocitypowered.proxy.protocol.packet.chat.SystemChatPacket;
import com.velocitypowered.proxy.protocol.packet.chat.builder.ChatBuilderV2;
import net.kyori.adventure.text.Component;
/**
* A concrete implementation of {@link ChatBuilderV2} for handling session-based chat messages.
*
* <p>The {@code SessionChatBuilder} is designed to build chat components that are specific to
* a player's session, allowing customization and context-specific formatting of chat messages
* within the current session.</p>
*/
public class SessionChatBuilder extends ChatBuilderV2 {
public SessionChatBuilder(ProtocolVersion version) {

View File

@@ -26,11 +26,17 @@ import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.packet.chat.ChatHandler;
import com.velocitypowered.proxy.protocol.packet.chat.ChatQueue;
import java.util.concurrent.CompletableFuture;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.concurrent.CompletableFuture;
/**
* A handler for processing session-based chat packets, implementing {@link ChatHandler}.
*
* <p>The {@code SessionChatHandler} processes and handles chat messages sent during a player's
* session using {@link SessionPlayerChatPacket}. It provides the logic for handling session-specific
* chat messages, ensuring the correct context and formatting within the session.</p>
*/
public class SessionChatHandler implements ChatHandler<SessionPlayerChatPacket> {
private static final Logger logger = LogManager.getLogger(SessionChatHandler.class);

View File

@@ -22,17 +22,29 @@ import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.packet.chat.ChatAcknowledgementPacket;
import java.util.concurrent.CompletableFuture;
import com.velocitypowered.proxy.protocol.packet.chat.RateLimitedCommandHandler;
import java.util.concurrent.CompletableFuture;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* A handler for processing session-based commands, implementing {@link RateLimitedCommandHandler}.
*
* <p>The {@code SessionCommandHandler} is responsible for handling commands that are specific
* to a player's session, using {@link SessionPlayerCommandPacket}. It provides logic to
* process commands that are tied to the context of the current session.</p>
*/
public class SessionCommandHandler extends RateLimitedCommandHandler<SessionPlayerCommandPacket> {
private final ConnectedPlayer player;
private final VelocityServer server;
/**
* Constructs a new {@link SessionCommandHandler} for the specified player and server.
*
* @param player the connected player associated with this handler
* @param server the Velocity server instance
*/
public SessionCommandHandler(ConnectedPlayer player, VelocityServer server) {
super(player, server);
this.player = player;

View File

@@ -25,6 +25,13 @@ import com.velocitypowered.proxy.protocol.packet.chat.LastSeenMessages;
import io.netty.buffer.ByteBuf;
import java.time.Instant;
/**
* Represents a player chat packet specific to a session, implementing {@link MinecraftPacket}.
*
* <p>The {@code SessionPlayerChatPacket} handles chat messages sent by a player during a session,
* and may include session-specific context, such as timestamps, message formatting, or other
* relevant session data.</p>
*/
public class SessionPlayerChatPacket implements MinecraftPacket {
protected String message;
@@ -100,6 +107,15 @@ public class SessionPlayerChatPacket implements MinecraftPacket {
return signature;
}
/**
* Creates a new {@code SessionPlayerChatPacket} with the specified last-seen messages.
*
* <p>This method constructs a new {@code SessionPlayerChatPacket} instance that retains the
* current packet's properties, while updating the last seen messages.</p>
*
* @param lastSeenMessages the last seen messages to associate with the new packet
* @return a new {@code SessionPlayerChatPacket} with the updated last seen messages
*/
public SessionPlayerChatPacket withLastSeenMessages(LastSeenMessages lastSeenMessages) {
SessionPlayerChatPacket packet = new SessionPlayerChatPacket();
packet.message = message;

View File

@@ -26,11 +26,17 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.packet.chat.LastSeenMessages;
import com.velocitypowered.proxy.util.except.QuietDecoderException;
import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.time.Instant;
import java.util.List;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents a packet that handles player commands in a Minecraft session.
*
* <p>This packet contains information about the player's command, timestamp,
* salt, argument signatures, and last seen messages for verification and
* processing.</p>
*/
public class SessionPlayerCommandPacket implements MinecraftPacket {
protected String command;
@@ -71,7 +77,8 @@ public class SessionPlayerCommandPacket implements MinecraftPacket {
}
public CommandExecuteEvent.SignedState getEventSignedState() {
return !this.argumentSignatures.isEmpty() ? CommandExecuteEvent.SignedState.SIGNED_WITH_ARGS : CommandExecuteEvent.SignedState.SIGNED_WITHOUT_ARGS;
return !this.argumentSignatures.isEmpty() ? CommandExecuteEvent.SignedState.SIGNED_WITH_ARGS
: CommandExecuteEvent.SignedState.SIGNED_WITHOUT_ARGS;
}
@Override
@@ -81,15 +88,26 @@ public class SessionPlayerCommandPacket implements MinecraftPacket {
@Override
public String toString() {
return "SessionPlayerCommand{" +
"command='" + command + '\'' +
", timeStamp=" + timeStamp +
", salt=" + salt +
", argumentSignatures=" + argumentSignatures +
", lastSeenMessages=" + lastSeenMessages +
'}';
return "SessionPlayerCommand{"
+ "command='" + command + '\''
+ ", timeStamp=" + timeStamp
+ ", salt=" + salt
+ ", argumentSignatures=" + argumentSignatures
+ ", lastSeenMessages=" + lastSeenMessages
+ '}';
}
/**
* Returns a new instance of {@code SessionPlayerCommandPacket} with the specified
* {@code LastSeenMessages}.
*
* <p>If {@code lastSeenMessages} is null, it creates an {@code UnsignedPlayerCommandPacket}
* instead. Otherwise, it creates a new {@code SessionPlayerCommandPacket} with the
* provided {@code lastSeenMessages}.</p>
*
* @param lastSeenMessages the last seen messages to include in the packet, may be {@code null}
* @return a new instance of {@code SessionPlayerCommandPacket} or {@code UnsignedPlayerCommandPacket}
*/
public SessionPlayerCommandPacket withLastSeenMessages(@Nullable LastSeenMessages lastSeenMessages) {
if (lastSeenMessages == null) {
UnsignedPlayerCommandPacket packet = new UnsignedPlayerCommandPacket();
@@ -105,6 +123,12 @@ public class SessionPlayerCommandPacket implements MinecraftPacket {
return packet;
}
/**
* Represents a collection of argument signatures for commands.
*
* <p>This class is responsible for handling the encoding and decoding of
* argument signatures associated with a player command in a Minecraft session.</p>
*/
public static class ArgumentSignatures {
private final List<ArgumentSignature> entries;
@@ -113,6 +137,16 @@ public class SessionPlayerCommandPacket implements MinecraftPacket {
this.entries = List.of();
}
/**
* Constructs an {@code ArgumentSignatures} instance by decoding the signatures
* from the provided {@code ByteBuf}.
*
* <p>This constructor reads the argument signatures from the buffer and ensures
* that the number of signatures does not exceed the allowed limit.</p>
*
* @param buf the {@code ByteBuf} to decode the argument signatures from
* @throws QuietDecoderException if the number of argument signatures exceeds the allowed limit
*/
public ArgumentSignatures(ByteBuf buf) {
int size = ProtocolUtils.readVarInt(buf);
if (size > 8) {
@@ -130,20 +164,44 @@ public class SessionPlayerCommandPacket implements MinecraftPacket {
return this.entries.isEmpty();
}
/**
* Encodes the argument signatures into the provided {@code ByteBuf}.
*
* <p>This method writes the number of argument signatures and each signature's
* details into the buffer for transmission.</p>
*
* @param buf the {@code ByteBuf} to encode the argument signatures into
*/
public void encode(ByteBuf buf) {
ProtocolUtils.writeVarInt(buf, entries.size());
for (ArgumentSignature entry : entries) {
entry.encode(buf);
}
}
/**
* Returns a string representation of this {@code ArgumentSignatures} instance.
*
* <p>The output includes a list of individual {@link ArgumentSignature} entries
* attached to this command packet.</p>
*
* @return a human-readable string describing the argument signatures
*/
@Override
public String toString() {
return "ArgumentSignatures{" +
"entries=" + entries +
'}';
return "ArgumentSignatures{"
+ "entries=" + entries
+ '}';
}
}
/**
* Represents a single argument signature associated with a command.
*
* <p>This class is responsible for handling the encoding and decoding of
* individual argument signatures, which consist of a name and a signature
* (byte array).</p>
*/
public static class ArgumentSignature {
private final String name;
@@ -161,9 +219,9 @@ public class SessionPlayerCommandPacket implements MinecraftPacket {
@Override
public String toString() {
return "ArgumentSignature{" +
"name='" + name + '\'' +
'}';
return "ArgumentSignature{"
+ "name='" + name + '\''
+ '}';
}
}
}

View File

@@ -24,6 +24,13 @@ import com.velocitypowered.proxy.protocol.packet.chat.LastSeenMessages;
import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents an unsigned player command packet, extending {@link SessionPlayerCommandPacket}.
*
* <p>The {@code UnsignedPlayerCommandPacket} is used to handle player commands that are not
* signed. It inherits session-specific behavior from {@link SessionPlayerCommandPacket}
* while indicating that the command is unsigned.</p>
*/
public class UnsignedPlayerCommandPacket extends SessionPlayerCommandPacket {
@Override
@@ -52,8 +59,8 @@ public class UnsignedPlayerCommandPacket extends SessionPlayerCommandPacket {
@Override
public String toString() {
return "UnsignedPlayerCommandPacket{" +
"command='" + command + '\'' +
'}';
return "UnsignedPlayerCommandPacket{"
+ "command='" + command + '\''
+ '}';
}
}

View File

@@ -24,6 +24,13 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import net.kyori.adventure.key.Key;
/**
* The {@code ActiveFeaturesPacket} class represents a packet that communicates the currently
* active features between the client and server in the Minecraft protocol.
*
* <p>This packet is used to inform the client about which features are enabled or active,
* potentially based on server configurations or gameplay states.</p>
*/
public class ActiveFeaturesPacket implements MinecraftPacket {
private Key[] activeFeatures;

View File

@@ -25,43 +25,47 @@ import io.netty.buffer.ByteBuf;
import java.util.HashMap;
import java.util.Map;
/**
* Represents a packet sent from the server to the client, containing custom report details.
* This packet carries a map of key-value pairs, where each key and value are strings.
*/
public class ClientboundCustomReportDetailsPacket implements MinecraftPacket {
private Map<String, String> details;
private Map<String, String> details;
public ClientboundCustomReportDetailsPacket() {
public ClientboundCustomReportDetailsPacket() {
}
public ClientboundCustomReportDetailsPacket(Map<String, String> details) {
this.details = details;
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
int detailsCount = ProtocolUtils.readVarInt(buf);
this.details = new HashMap<>(detailsCount);
for (int i = 0; i < detailsCount; i++) {
details.put(ProtocolUtils.readString(buf), ProtocolUtils.readString(buf));
}
}
public ClientboundCustomReportDetailsPacket(Map<String, String> details) {
this.details = details;
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeVarInt(buf, details.size());
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
int detailsCount = ProtocolUtils.readVarInt(buf);
details.forEach((key, detail) -> {
ProtocolUtils.writeString(buf, key);
ProtocolUtils.writeString(buf, detail);
});
}
this.details = new HashMap<>(detailsCount);
for (int i = 0; i < detailsCount; i++) {
details.put(ProtocolUtils.readString(buf), ProtocolUtils.readString(buf));
}
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeVarInt(buf, details.size());
details.forEach((key, detail) -> {
ProtocolUtils.writeString(buf, key);
ProtocolUtils.writeString(buf, detail);
});
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public Map<String, String> getDetails() {
return details;
}
public Map<String, String> getDetails() {
return details;
}
}

View File

@@ -18,7 +18,6 @@
package com.velocitypowered.proxy.protocol.packet.config;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.util.ServerLink;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
@@ -27,64 +26,78 @@ import io.netty.buffer.ByteBuf;
import java.util.ArrayList;
import java.util.List;
/**
* Represents a packet sent from the server to the client, containing server-related links.
* This packet carries a list of links (e.g., URLs or other resources) associated with the server.
*/
public class ClientboundServerLinksPacket implements MinecraftPacket {
private List<ServerLink> serverLinks;
private List<ServerLink> serverLinks;
public ClientboundServerLinksPacket() {
public ClientboundServerLinksPacket() {
}
public ClientboundServerLinksPacket(List<ServerLink> serverLinks) {
this.serverLinks = serverLinks;
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
int linksCount = ProtocolUtils.readVarInt(buf);
this.serverLinks = new ArrayList<>(linksCount);
for (int i = 0; i < linksCount; i++) {
serverLinks.add(ServerLink.read(buf, version));
}
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeVarInt(buf, serverLinks.size());
for (ServerLink serverLink : serverLinks) {
serverLink.write(buf);
}
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public List<ServerLink> getServerLinks() {
return serverLinks;
}
/**
* Represents a link to a server with an ID, display name, and URL.
*
* <p>This record holds the server's identification number, a display name
* encapsulated in a {@code ComponentHolder}, and the server's URL as a string.</p>
*
* @param id the unique identifier for the server
* @param displayName the display name of the server, represented by a {@code ComponentHolder}
* @param url the URL of the server
*/
public record ServerLink(int id, ComponentHolder displayName, String url) {
private static ServerLink read(ByteBuf buf, ProtocolVersion version) {
if (buf.readBoolean()) {
return new ServerLink(ProtocolUtils.readVarInt(buf), null, ProtocolUtils.readString(buf));
} else {
return new ServerLink(-1, ComponentHolder.read(buf, version), ProtocolUtils.readString(buf));
}
}
public ClientboundServerLinksPacket(List<ServerLink> serverLinks) {
this.serverLinks = serverLinks;
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
int linksCount = ProtocolUtils.readVarInt(buf);
this.serverLinks = new ArrayList<>(linksCount);
for (int i = 0; i < linksCount; i++) {
serverLinks.add(ServerLink.read(buf, version));
}
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeVarInt(buf, serverLinks.size());
for (ServerLink serverLink : serverLinks) {
serverLink.write(buf);
}
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public List<ServerLink> getServerLinks() {
return serverLinks;
}
public record ServerLink(int id, ComponentHolder displayName, String url) {
private static ServerLink read(ByteBuf buf, ProtocolVersion version) {
if (buf.readBoolean()) {
return new ServerLink(ProtocolUtils.readVarInt(buf), null, ProtocolUtils.readString(buf));
} else {
return new ServerLink(-1, ComponentHolder.read(buf, version), ProtocolUtils.readString(buf));
}
}
private void write(ByteBuf buf) {
if (id >= 0) {
buf.writeBoolean(true);
ProtocolUtils.writeVarInt(buf, id);
} else {
buf.writeBoolean(false);
displayName.write(buf);
}
ProtocolUtils.writeString(buf, url);
}
private void write(ByteBuf buf) {
if (id >= 0) {
buf.writeBoolean(true);
ProtocolUtils.writeVarInt(buf, id);
} else {
buf.writeBoolean(false);
displayName.write(buf);
}
ProtocolUtils.writeString(buf, url);
}
}
}

View File

@@ -23,6 +23,12 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import io.netty.buffer.ByteBuf;
/**
* A client-to-server packet indicating the player has accepted the server's
* code of conduct during the configuration stage.
*
* <p>This packet has no payload and is represented as a singleton.</p>
*/
public class CodeOfConductAcceptPacket implements MinecraftPacket {
public static final CodeOfConductAcceptPacket INSTANCE = new CodeOfConductAcceptPacket();

View File

@@ -24,6 +24,14 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder;
import io.netty.buffer.ByteBuf;
/**
* A server-to-client packet containing the server's code of conduct.
*
* <p>This packet is sent during the configuration stage to present the
* server-defined conduct rules to the client. The client may later
* respond with a {@link CodeOfConductAcceptPacket} to indicate
* acceptance.</p>
*/
public class CodeOfConductPacket extends DeferredByteBufHolder implements MinecraftPacket {
public CodeOfConductPacket() {

View File

@@ -23,6 +23,13 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
/**
* The {@code FinishedUpdatePacket} class represents a packet that signals the completion
* of an update process between the client and server in the Minecraft protocol.
*
* <p>This packet is used to indicate that the client has finished receiving and processing
* an update, ensuring that further operations can proceed.</p>
*/
public class FinishedUpdatePacket implements MinecraftPacket {
public static final FinishedUpdatePacket INSTANCE = new FinishedUpdatePacket();
@@ -41,7 +48,7 @@ public class FinishedUpdatePacket implements MinecraftPacket {
@Override
public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
ProtocolVersion version) {
return 0;
}

View File

@@ -24,55 +24,74 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.util.except.QuietDecoderException;
import io.netty.buffer.ByteBuf;
/**
* The {@code KnownPacksPacket} class represents a packet that handles the synchronization
* of known resource packs between the client and server in the Minecraft protocol.
*
* <p>This packet contains a list of {@link KnownPack} instances, each representing a resource
* pack with a namespace, identifier, and version. It allows the server to inform the client
* about available resource packs.</p>
*/
public class KnownPacksPacket implements MinecraftPacket {
private static final int MAX_LENGTH_PACKS = Integer.getInteger("velocity.max-known-packs", 64);
private static final QuietDecoderException TOO_MANY_PACKS =
new QuietDecoderException("too many known packs");
private static final int MAX_LENGTH_PACKS = Integer.getInteger("velocity.max-known-packs", 64);
private static final QuietDecoderException TOO_MANY_PACKS =
new QuietDecoderException("too many known packs");
private KnownPack[] packs;
private KnownPack[] packs;
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
final int packCount = ProtocolUtils.readVarInt(buf);
if (direction == ProtocolUtils.Direction.SERVERBOUND && packCount > MAX_LENGTH_PACKS) {
throw TOO_MANY_PACKS;
}
final KnownPack[] packs = new KnownPack[packCount];
for (int i = 0; i < packCount; i++) {
packs[i] = KnownPack.read(buf);
}
this.packs = packs;
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
final int packCount = ProtocolUtils.readVarInt(buf);
if (direction == ProtocolUtils.Direction.SERVERBOUND && packCount > MAX_LENGTH_PACKS) {
throw TOO_MANY_PACKS;
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
ProtocolUtils.writeVarInt(buf, packs.length);
final KnownPack[] packs = new KnownPack[packCount];
for (KnownPack pack : packs) {
pack.write(buf);
}
for (int i = 0; i < packCount; i++) {
packs[i] = KnownPack.read(buf);
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
this.packs = packs;
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
ProtocolUtils.writeVarInt(buf, packs.length);
for (KnownPack pack : packs) {
pack.write(buf);
}
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
/**
* The {@code KnownPack} record represents a known resource pack with a namespace,
* identifier, and version in the Minecraft protocol.
*
* <p>It encapsulates the information needed to identify a resource pack, typically used
* for managing or synchronizing resource packs between the client and server.</p>
*
* @param namespace the namespace of the resource pack (e.g., "minecraft" or a mod name)
* @param id the unique identifier of the resource pack within the namespace
* @param version the version of the resource pack
*/
public record KnownPack(String namespace, String id, String version) {
private static KnownPack read(ByteBuf buf) {
return new KnownPack(ProtocolUtils.readString(buf), ProtocolUtils.readString(buf), ProtocolUtils.readString(buf));
}
public record KnownPack(String namespace, String id, String version) {
private static KnownPack read(ByteBuf buf) {
return new KnownPack(ProtocolUtils.readString(buf), ProtocolUtils.readString(buf), ProtocolUtils.readString(buf));
}
private void write(ByteBuf buf) {
ProtocolUtils.writeString(buf, namespace);
ProtocolUtils.writeString(buf, id);
ProtocolUtils.writeString(buf, version);
}
private void write(ByteBuf buf) {
ProtocolUtils.writeString(buf, namespace);
ProtocolUtils.writeString(buf, id);
ProtocolUtils.writeString(buf, version);
}
}
}

View File

@@ -25,6 +25,18 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder;
import io.netty.buffer.ByteBuf;
/**
* The {@code RegistrySyncPacket} class is responsible for synchronizing registry data
* between the server and client in Minecraft.
*
* <p>This packet is used to ensure that the client has the same registry information as
* the server, covering aspects like blocks, items, entities, and other game elements
* that are part of Minecraft's internal registries.</p>
*
* <p>It extends the {@link DeferredByteBufHolder} class to handle deferred buffering
* operations for potentially large sets of registry data, which may include
* complex serialization processes.</p>
*/
public class RegistrySyncPacket extends DeferredByteBufHolder implements MinecraftPacket {
public RegistrySyncPacket() {

View File

@@ -23,6 +23,16 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
/**
* The {@code StartUpdatePacket} class represents a packet that signals the
* start of an update process in the Minecraft protocol.
*
* <p>This packet may be used to notify the client or server that a certain update
* process, such as data synchronization or gameplay changes, is about to begin.</p>
*
* <p>Its specific use depends on the version and context of the update,
* typically handled in the Minecraft networking layer.</p>
*/
public class StartUpdatePacket implements MinecraftPacket {
public static final StartUpdatePacket INSTANCE = new StartUpdatePacket();
@@ -41,7 +51,7 @@ public class StartUpdatePacket implements MinecraftPacket {
@Override
public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
ProtocolVersion version) {
return 0;
}

View File

@@ -24,9 +24,17 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import io.netty.buffer.ByteBuf;
import java.util.Map;
/**
* The {@code TagsUpdatePacket} class represents a packet sent to update the tags
* used by the Minecraft client. Tags are used in various parts of the game to group
* blocks, items, entities, and other objects under common categories.
*
* <p>This packet is typically sent to clients when they join a server or when
* the server needs to update the list of tags for the client, ensuring that
* the client has the most up-to-date tag information.</p>
*/
public class TagsUpdatePacket implements MinecraftPacket {
private Map<String, Map<String, int[]>> tags;

View File

@@ -17,6 +17,16 @@
package com.velocitypowered.proxy.protocol.packet.legacyping;
/**
* The {@code LegacyMinecraftPingVersion} enum represents the various protocol versions
* used by older Minecraft clients during the server ping process.
*
* <p>This enum is used to distinguish between the different legacy versions of Minecraft
* that have unique ping formats, ensuring compatibility with those older clients.</p>
*
* <p>Each constant in this enum corresponds to a specific version of Minecraft that
* requires a legacy server ping format.</p>
*/
public enum LegacyMinecraftPingVersion {
MINECRAFT_1_3,
MINECRAFT_1_4,

View File

@@ -23,8 +23,21 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
import io.netty.buffer.ByteBuf;
/**
* The {@code GenericTitlePacket} class serves as the base class for all title-related packets
* in Minecraft. This class provides common functionality and properties for handling title, subtitle,
* action bar, and timing-related packets.
*
* <p>Subclasses of {@code GenericTitlePacket} implement specific behavior for different types of title
* packets, such as titles, subtitles, and action bars.</p>
*/
public abstract class GenericTitlePacket implements MinecraftPacket {
/**
* The {@code ActionType} enum represents the different actions that can be performed with a title packet.
* Each action corresponds to a specific type of title operation, such as setting a title or subtitle,
* updating timing information, or resetting and hiding titles.
*/
public enum ActionType {
SET_TITLE(0),
SET_SUBTITLE(1),
@@ -45,7 +58,6 @@ public abstract class GenericTitlePacket implements MinecraftPacket {
}
}
private ActionType action;
protected void setAction(ActionType action) {
@@ -88,10 +100,9 @@ public abstract class GenericTitlePacket implements MinecraftPacket {
throw new UnsupportedOperationException("Invalid function for this TitlePacket ActionType");
}
@Override
public final void decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
ProtocolVersion version) {
throw new UnsupportedOperationException(); // encode only
}
@@ -103,7 +114,7 @@ public abstract class GenericTitlePacket implements MinecraftPacket {
* @return GenericTitlePacket instance that follows the invoker type/version
*/
public static GenericTitlePacket constructTitlePacket(ActionType type, ProtocolVersion version) {
GenericTitlePacket packet = null;
GenericTitlePacket packet;
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_17)) {
packet = switch (type) {
case SET_ACTION_BAR -> new TitleActionbarPacket();
@@ -111,7 +122,6 @@ public abstract class GenericTitlePacket implements MinecraftPacket {
case SET_TIMES -> new TitleTimesPacket();
case SET_TITLE -> new TitleTextPacket();
case HIDE, RESET -> new TitleClearPacket();
default -> throw new IllegalArgumentException("Invalid ActionType");
};
} else {
packet = new LegacyTitlePacket();

Some files were not shown because too many files have changed in this diff Show More