From a078053e26eff94b106afcaf594f9fde2897c592 Mon Sep 17 00:00:00 2001 From: Shane Freeder Date: Mon, 24 Aug 2026 17:26:12 +0100 Subject: [PATCH 01/20] velocity 4.1.0 release --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 44cdb5d0..90fbaf0b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ group=com.velocitypowered -version=4.1.0-SNAPSHOT +version=4.1.0 -- 2.39.5 From b2ba2e66bff993e04208a3eb4093f6385c137aa7 Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Wed, 26 Aug 2026 12:09:07 +0200 Subject: [PATCH 02/20] Snapshot 4.1.1-SNAPSHOT --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 90fbaf0b..b68ca355 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ group=com.velocitypowered -version=4.1.0 +version=4.1.1-SNAPSHOT -- 2.39.5 From 133f0e36b1247faed11502b13de1de80667ad370 Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Wed, 26 Aug 2026 12:09:46 +0200 Subject: [PATCH 03/20] Switch to JLine's jni backend FFM is the canonical choice for Java 22+, but it has some issues (mainly crashing the JVM on window resize), see jline/jline3#2139 and jline/jline3#2141 Downgrading to 4.0.x isn't an option as it re-introduces CVE-2026-77420..77423 Once the referenced PR gets merged and released, jline should be bumped to jline-terminal-ffm's latest version again --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9c12ac2d..0a3b79af 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,7 +32,7 @@ disruptor = "com.lmax:disruptor:4.0.0" fastutil = "it.unimi.dsi:fastutil:8.5.19" flare-core = { module = "space.vectrix.flare:flare", version.ref = "flare" } flare-fastutil = { module = "space.vectrix.flare:flare-fastutil", version.ref = "flare" } -jline = "org.jline:jline-terminal-ffm:4.3.1" +jline = "org.jline:jline-terminal-jni:4.3.1" jopt = "net.sf.jopt-simple:jopt-simple:5.0.4" junit = "org.junit.jupiter:junit-jupiter:6.1.2" jspecify = "org.jspecify:jspecify:1.0.1" -- 2.39.5 From db0a17eea61f18994963da0ae936d3560aade74e Mon Sep 17 00:00:00 2001 From: Shane Freeder Date: Wed, 26 Aug 2026 20:29:34 +0100 Subject: [PATCH 04/20] Velocity 4.1.1 release --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index b68ca355..48599a5d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ group=com.velocitypowered -version=4.1.1-SNAPSHOT +version=4.1.1 -- 2.39.5 From ddea763c8ba064975905e4b2ee638152b87d7ef2 Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Tue, 1 Sep 2026 13:10:58 +0200 Subject: [PATCH 05/20] Snapshot 4.1.2-SNAPSHOT --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 48599a5d..ed875a01 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ group=com.velocitypowered -version=4.1.1 +version=4.1.2-SNAPSHOT -- 2.39.5 From 940f395e3e2b4df1f16d3457c9930f1c87bf1e18 Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Tue, 1 Sep 2026 13:19:46 +0200 Subject: [PATCH 06/20] Use correct minimum ClientSettings packet length for older protocol versions For correctness's sake: in practice this bug never got triggered because `locale` is almost always a string of length 5+ ("en_us"). --- .../protocol/packet/ClientSettingsPacket.java | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientSettingsPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientSettingsPacket.java index b8b60a9f..7e6600cc 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientSettingsPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientSettingsPacket.java @@ -213,7 +213,34 @@ public class ClientSettingsPacket implements MinecraftPacket { @Override public int decodeExpectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - return 1 + 0 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1; + int minLength = 1 // locale + + 1 // viewDistance + + 1 // chatVisibility + + 1; // chatColors + + if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6)) { + minLength += 1; // difficulty + } + + minLength += 1; // skinParts + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_9)) { + minLength += 1; // mainHand + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_17)) { + minLength += 1; // textFilteringEnabled + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_18)) { + minLength += 1; // clientListingAllowed + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) { + minLength += 1; // particleStatus + } + } + } + } + + return minLength; } @Override -- 2.39.5 From e84b48a71654b0195674e9d1ca5de9a3ddd49993 Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Tue, 1 Sep 2026 13:55:10 +0200 Subject: [PATCH 07/20] Clean up Title packets and enforce action types (#1803) --- .../connection/client/ConnectedPlayer.java | 5 +-- .../packet/title/GenericTitlePacket.java | 29 +++++------------ .../packet/title/LegacyTitlePacket.java | 31 +++++++++++++------ .../packet/title/TitleActionbarPacket.java | 6 ++-- .../packet/title/TitleClearPacket.java | 23 +++++++++----- .../packet/title/TitleSubtitlePacket.java | 6 ++-- .../packet/title/TitleTextPacket.java | 6 ++-- .../packet/title/TitleTimesPacket.java | 6 ++-- 8 files changed, 63 insertions(+), 49 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index e28b7c94..f5898ace 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -135,6 +135,7 @@ import net.kyori.adventure.text.logger.slf4j.ComponentLogger; import net.kyori.adventure.text.minimessage.translation.Argument; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import net.kyori.adventure.title.Title; import net.kyori.adventure.title.Title.Times; import net.kyori.adventure.title.TitlePart; import net.kyori.adventure.translation.GlobalTranslator; @@ -486,11 +487,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, } @Override - public void showTitle(net.kyori.adventure.title.@NonNull Title title) { + public void showTitle(@NonNull Title title) { if (this.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_8)) { GenericTitlePacket timesPkt = GenericTitlePacket.constructTitlePacket( GenericTitlePacket.ActionType.SET_TIMES, this.getProtocolVersion()); - net.kyori.adventure.title.Title.Times times = title.times(); + Title.Times times = title.times(); if (times != null) { timesPkt.setFadeIn((int) DurationUtils.toTicks(times.fadeIn())); timesPkt.setStay((int) DurationUtils.toTicks(times.stay())); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/GenericTitlePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/GenericTitlePacket.java index 8641173e..411dcc91 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/GenericTitlePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/GenericTitlePacket.java @@ -22,10 +22,12 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import io.netty.buffer.ByteBuf; +import org.jetbrains.annotations.NotNull; public abstract class GenericTitlePacket implements MinecraftPacket { public enum ActionType { + SET_TITLE(0), SET_SUBTITLE(1), SET_ACTION_BAR(2), @@ -45,16 +47,7 @@ public abstract class GenericTitlePacket implements MinecraftPacket { } } - - private ActionType action; - - protected void setAction(ActionType action) { - this.action = action; - } - - public final ActionType getAction() { - return action; - } + public abstract @NotNull ActionType getAction(); public ComponentHolder getComponent() { throw new UnsupportedOperationException("Invalid function for this TitlePacket ActionType"); @@ -88,11 +81,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) { - throw new UnsupportedOperationException(); // encode only + public final void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { + throw new UnsupportedOperationException("Decode is not implemented"); } /** @@ -103,21 +94,17 @@ 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; if (version.noLessThan(ProtocolVersion.MINECRAFT_1_17)) { - packet = switch (type) { + return switch (type) { case SET_ACTION_BAR -> new TitleActionbarPacket(); case SET_SUBTITLE -> new TitleSubtitlePacket(); case SET_TIMES -> new TitleTimesPacket(); case SET_TITLE -> new TitleTextPacket(); - case HIDE, RESET -> new TitleClearPacket(); + case HIDE, RESET -> new TitleClearPacket(type); default -> throw new IllegalArgumentException("Invalid ActionType"); }; } else { - packet = new LegacyTitlePacket(); + return new LegacyTitlePacket(type); } - packet.setAction(type); - return packet; } - } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/LegacyTitlePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/LegacyTitlePacket.java index ebae8a9e..8033a90f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/LegacyTitlePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/LegacyTitlePacket.java @@ -17,32 +17,44 @@ package com.velocitypowered.proxy.protocol.packet.title; +import com.google.common.base.Preconditions; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import io.netty.buffer.ByteBuf; -import org.checkerframework.checker.nullness.qual.Nullable; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; public class LegacyTitlePacket extends GenericTitlePacket { + private final ActionType action; + private @Nullable ComponentHolder component; private int fadeIn; private int stay; private int fadeOut; + public LegacyTitlePacket() { + throw new UnsupportedOperationException("Decode is not implemented"); + } + + public LegacyTitlePacket(ActionType action) { + this.action = Preconditions.checkNotNull(action, "action"); + } + @Override public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { if (version.lessThan(ProtocolVersion.MINECRAFT_1_11) - && getAction() == ActionType.SET_ACTION_BAR) { + && this.action == ActionType.SET_ACTION_BAR) { throw new IllegalStateException("Action bars are only supported on 1.11 and newer"); } - ProtocolUtils.writeVarInt(buf, getAction().getAction(version)); + ProtocolUtils.writeVarInt(buf, this.action.getAction(version)); - switch (getAction()) { + switch (this.action) { case SET_TITLE, SET_SUBTITLE, SET_ACTION_BAR -> { if (component == null) { - throw new IllegalStateException("No component found for " + getAction()); + throw new IllegalStateException("No component found for " + this.action); } component.write(buf); } @@ -52,14 +64,13 @@ public class LegacyTitlePacket extends GenericTitlePacket { buf.writeInt(fadeOut); } case HIDE, RESET -> {} - default -> throw new UnsupportedOperationException("Unknown action " + getAction()); + default -> throw new UnsupportedOperationException("Unknown action " + this.action); } - } @Override - public void setAction(ActionType action) { - super.setAction(action); + public @NotNull ActionType getAction() { + return action; } @Override @@ -105,7 +116,7 @@ public class LegacyTitlePacket extends GenericTitlePacket { @Override public String toString() { return "GenericTitlePacket{" - + "action=" + getAction() + + "action=" + action + ", component='" + component + '\'' + ", fadeIn=" + fadeIn + ", stay=" + stay diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleActionbarPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleActionbarPacket.java index 6bab8dd6..b52c6364 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleActionbarPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleActionbarPacket.java @@ -22,13 +22,15 @@ import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import io.netty.buffer.ByteBuf; +import org.jetbrains.annotations.NotNull; public class TitleActionbarPacket extends GenericTitlePacket { private ComponentHolder component; - public TitleActionbarPacket() { - setAction(ActionType.SET_ACTION_BAR); + @Override + public @NotNull ActionType getAction() { + return ActionType.SET_ACTION_BAR; } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleClearPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleClearPacket.java index 1b348969..0019b4a9 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleClearPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleClearPacket.java @@ -21,30 +21,37 @@ import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; +import org.jetbrains.annotations.NotNull; public class TitleClearPacket extends GenericTitlePacket { + private final ActionType action; + public TitleClearPacket() { - setAction(ActionType.HIDE); + this(ActionType.HIDE); + } + + public TitleClearPacket(ActionType action) { + if (action != ActionType.HIDE && action != ActionType.RESET) { + throw new IllegalArgumentException("TitleClearPacket only accepts the HIDE and RESET actions."); + } + this.action = action; } @Override - public void setAction(ActionType action) { - if (action != ActionType.HIDE && action != ActionType.RESET) { - throw new IllegalArgumentException("TitleClearPacket only accepts CLEAR and RESET actions"); - } - super.setAction(action); + public @NotNull ActionType getAction() { + return action; } @Override public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - buf.writeBoolean(getAction() == ActionType.RESET); + buf.writeBoolean(this.action == ActionType.RESET); } @Override public String toString() { return "TitleClearPacket{" - + ", resetTimes=" + (getAction() == ActionType.RESET) + + ", resetTimes=" + (this.action == ActionType.RESET) + '}'; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleSubtitlePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleSubtitlePacket.java index 0f375ae2..a17d5399 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleSubtitlePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleSubtitlePacket.java @@ -22,13 +22,15 @@ import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import io.netty.buffer.ByteBuf; +import org.jetbrains.annotations.NotNull; public class TitleSubtitlePacket extends GenericTitlePacket { private ComponentHolder component; - public TitleSubtitlePacket() { - setAction(ActionType.SET_SUBTITLE); + @Override + public @NotNull ActionType getAction() { + return ActionType.SET_SUBTITLE; } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleTextPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleTextPacket.java index ae75f5d6..ceab8290 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleTextPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleTextPacket.java @@ -22,13 +22,15 @@ import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import io.netty.buffer.ByteBuf; +import org.jetbrains.annotations.NotNull; public class TitleTextPacket extends GenericTitlePacket { private ComponentHolder component; - public TitleTextPacket() { - setAction(ActionType.SET_TITLE); + @Override + public @NotNull ActionType getAction() { + return ActionType.SET_TITLE; } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleTimesPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleTimesPacket.java index 8764a12f..21972991 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleTimesPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/title/TitleTimesPacket.java @@ -21,6 +21,7 @@ import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; +import org.jetbrains.annotations.NotNull; public class TitleTimesPacket extends GenericTitlePacket { @@ -28,8 +29,9 @@ public class TitleTimesPacket extends GenericTitlePacket { private int stay; private int fadeOut; - public TitleTimesPacket() { - setAction(ActionType.SET_TIMES); + @Override + public @NotNull ActionType getAction() { + return ActionType.SET_TIMES; } @Override -- 2.39.5 From 21bbf35d1da1d8a74aad897ecb4e6915f121898d Mon Sep 17 00:00:00 2001 From: Loganius <31364192+TheMiningTeamYT@users.noreply.github.com> Date: Tue, 1 Sep 2026 07:49:24 -0500 Subject: [PATCH 08/20] Adding more options for configuring ping passthrough. (#1870) * Added 'ALLBUTVERSION' option for ping passthrough. * Added more configuration options for ping passthrough. * Updated default velocity.toml * Add support for the legacy ping passthrough. * Removed legacy ping passthrough & added config migration - Bumped config-version to 2.8 - Updated comments and grammar - Removed legacy ping passthrough in the config * Cleaned up code to match code style * Update to latest * Move ping passthrough into its own section * Reorder migrations so they occur in the right order. * Clean up ping passthrough related code. * Update file dates and cleanup/consolidate ping-passthrough code. * Update proxy/src/main/java/com/velocitypowered/proxy/config/PingPassthroughMode.java * Update proxy/src/main/java/com/velocitypowered/proxy/config/PingPassthroughMode.java * Remaining nits - Read a string instead of LegacyPingPassthroughMode - Update `announce-forge`'s comment in `PingPassthroughMigration` - Consume `Config` instead of `CommentedConfig` in `PingPassthroughMode#fromConfig` --------- Co-authored-by: TheMiningTeamYT Co-authored-by: ButterDebugger <34288129+ButterDebugger@users.noreply.github.com> Co-authored-by: Wouter Gritter --- .../proxy/config/PingPassthroughMode.java | 48 +++++++- .../proxy/config/VelocityConfiguration.java | 12 +- .../migration/ConfigurationMigration.java | 3 +- .../migration/PingPassthroughMigration.java | 105 +++++++++++++++++ .../util/ServerListPingHandler.java | 109 ++++++++++-------- .../src/main/resources/default-velocity.toml | 31 +++-- 6 files changed, 228 insertions(+), 80 deletions(-) create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/config/migration/PingPassthroughMigration.java diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/PingPassthroughMode.java b/proxy/src/main/java/com/velocitypowered/proxy/config/PingPassthroughMode.java index 718e57f1..aa73cefd 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/PingPassthroughMode.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/PingPassthroughMode.java @@ -16,13 +16,49 @@ */ package com.velocitypowered.proxy.config; +import com.electronwill.nightconfig.core.Config; /** - * Supported passthrough modes for ping passthrough. + * Object to contain all the things that can be toggled for ping passthrough. + * + * @param version Whether the version should be passed through. + * @param players Whether the player count should be passed through. + * @param description Whether the description should be passed through. + * @param favicon Whether the favicon should be passed through. + * @param modinfo Whether the modinfo should be passed through. */ -public enum PingPassthroughMode { - DISABLED, - MODS, - DESCRIPTION, - ALL +public record PingPassthroughMode(boolean version, boolean players, + boolean description, boolean favicon, boolean modinfo) { + public static final PingPassthroughMode DEFAULT = new PingPassthroughMode(); + + /** + * Creates a default PingPassthroughMode. + */ + private PingPassthroughMode() { + this(false, false, false, false, false); + } + + /** + * Returns a PingPassthroughMode from a config section, or the default if the section is null. + * Based on the code for PacketLimiterConfig. + * + * @param config The configuration object to parse. + * @return The PingPassthroughMode, or the default if {@code config} is null. + */ + public static PingPassthroughMode fromConfig(Config config) { + if (config == null) { + return DEFAULT; + } + return new PingPassthroughMode( + config.getOrElse("version", DEFAULT.version()), + config.getOrElse("players", DEFAULT.players()), + config.getOrElse("description", DEFAULT.description()), + config.getOrElse("favicon", DEFAULT.favicon()), + config.getOrElse("modinfo", DEFAULT.modinfo())); + } + + public boolean enabled() { + return this.version || this.players || this.description || this.favicon + || this.modinfo; + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java index 2c7826e0..fc9ba583 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java @@ -32,6 +32,7 @@ import com.velocitypowered.proxy.config.migration.KeyAuthenticationMigration; import com.velocitypowered.proxy.config.migration.MiniMessageTranslationsMigration; import com.velocitypowered.proxy.config.migration.MotdMigration; import com.velocitypowered.proxy.config.migration.PacketLimiterMigration; +import com.velocitypowered.proxy.config.migration.PingPassthroughMigration; import com.velocitypowered.proxy.config.migration.TransferIntegrationMigration; import com.velocitypowered.proxy.util.AddressUtil; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; @@ -79,7 +80,7 @@ public class VelocityConfiguration implements ProxyConfig { @Expose private boolean onlineModeKickExistingPlayers = false; @Expose - private PingPassthroughMode pingPassthrough = PingPassthroughMode.DISABLED; + private PingPassthroughMode pingPassthrough = PingPassthroughMode.DEFAULT; @Expose private boolean samplePlayersInPing = false; private final Servers servers; @@ -513,7 +514,8 @@ public class VelocityConfiguration implements ProxyConfig { new MotdMigration(), new MiniMessageTranslationsMigration(), new TransferIntegrationMigration(), - new PacketLimiterMigration() + new PacketLimiterMigration(), + new PingPassthroughMigration(), }; for (final ConfigurationMigration migration : migrations) { @@ -555,9 +557,7 @@ public class VelocityConfiguration implements ProxyConfig { final CommentedConfig metricsConfig = config.get("metrics"); final PlayerInfoForwarding forwardingMode = config.getEnumOrElse( "player-info-forwarding-mode", PlayerInfoForwarding.NONE); - final PingPassthroughMode pingPassthroughMode = config.getEnumOrElse("ping-passthrough", - PingPassthroughMode.DISABLED); - + final PingPassthroughMode pingPassthrough = PingPassthroughMode.fromConfig(config.get("ping-passthrough")); final boolean samplePlayersInPing = config.getOrElse("sample-players-in-ping", false); final String bind = config.getOrElse("bind", "0.0.0.0:25565"); @@ -590,7 +590,7 @@ public class VelocityConfiguration implements ProxyConfig { forwardingMode, forwardingSecret, kickExisting, - pingPassthroughMode, + pingPassthrough, samplePlayersInPing, enablePlayerAddressLogging, new Servers(serversConfig), diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/migration/ConfigurationMigration.java b/proxy/src/main/java/com/velocitypowered/proxy/config/migration/ConfigurationMigration.java index 7c00b7bb..27891369 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/migration/ConfigurationMigration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/migration/ConfigurationMigration.java @@ -30,7 +30,8 @@ public sealed interface ConfigurationMigration MotdMigration, MiniMessageTranslationsMigration, TransferIntegrationMigration, - PacketLimiterMigration { + PacketLimiterMigration, + PingPassthroughMigration { boolean shouldMigrate(CommentedFileConfig config); void migrate(CommentedFileConfig config, Logger logger) throws IOException; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/migration/PingPassthroughMigration.java b/proxy/src/main/java/com/velocitypowered/proxy/config/migration/PingPassthroughMigration.java new file mode 100644 index 00000000..702a06cf --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/migration/PingPassthroughMigration.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2024-2026 Velocity Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.velocitypowered.proxy.config.migration; + +import com.electronwill.nightconfig.core.file.CommentedFileConfig; +import java.util.Locale; +import org.apache.logging.log4j.Logger; + +/** + * Migrate the old ping passthrough entry to separate config entries. + */ +public final class PingPassthroughMigration implements ConfigurationMigration { + @Override + public boolean shouldMigrate(final CommentedFileConfig config) { + return configVersion(config) < 2.9; + } + + @Override + public void migrate(final CommentedFileConfig config, final Logger logger) { + // Get legacy ping passthrough value + final String legacyMode = config.getOrElse("ping-passthrough", "DISABLED") + .toUpperCase(Locale.ROOT); + boolean version = false; + boolean players = false; + boolean description = false; + boolean favicon = false; + boolean modinfo = false; + + switch (legacyMode) { + case "ALL": + version = true; + players = true; + description = true; + favicon = true; + modinfo = true; + break; + case "DESCRIPTION": + description = true; + modinfo = true; + break; + case "MODS": + modinfo = true; + break; + default: // DISABLED + break; + } + + config.removeComment("ping-passthrough"); + config.remove("ping-passthrough"); + + config.setComment("announce-forge", + " Announce whether or not your server supports Forge. If you run a modded server, we\n" + + " suggest turning this on.\n" + + "\n" + + " If your network runs one modpack consistently, consider using ping-passthrough.modinfo = true\n" + + " instead for a nicer display in the server list."); + + config.set("ping-passthrough.version", version); + config.setComment( + "ping-passthrough.version", + " Should Velocity pass the version number from the backend server when responding to server list ping requests?" + ); + + config.set("ping-passthrough.players", players); + config.setComment( + "ping-passthrough.players", + " Should Velocity pass the player count from the backend server when responding to server list ping requests?" + ); + + config.set("ping-passthrough.description", description); + config.setComment( + "ping-passthrough.description", + " Should Velocity pass the description from the backend server when responding to server list ping requests?" + ); + + config.set("ping-passthrough.favicon", favicon); + config.setComment( + "ping-passthrough.favicon", + " Should Velocity pass the favicon (also known as the server icon) from the backend server when responding to server list ping requests?" + ); + + config.set("ping-passthrough.modinfo", modinfo); + config.setComment( + "ping-passthrough.modinfo", + " Should Velocity pass the mod list from the backend server when responding to server list ping requests?" + ); + + config.set("config-version", "2.9"); + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java index 5ccb2b11..97582ca2 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java @@ -23,6 +23,7 @@ import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.proxy.server.PingOptions; import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.ServerPing; +import com.velocitypowered.api.util.Favicon; import com.velocitypowered.api.util.ModInfo; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.config.PingPassthroughMode; @@ -100,60 +101,68 @@ public class ServerListPingHandler { CompletableFuture> pingResponses = CompletableFutures.successfulAsList(pings, (ex) -> fallback); - return switch (mode) { - case ALL -> pingResponses.thenApply(responses -> { - // Find the first non-fallback - for (ServerPing response : responses) { - if (response == fallback) { - continue; - } - if (response.getDescriptionComponent() == null) { - return response.asBuilder() - .description(Component.empty()) - .build(); - } + // Return early if ping passthrough is not enabled + if (!mode.enabled()) { + return CompletableFuture.completedFuture(fallback); + } - return response; + return pingResponses.thenApply(responses -> { + // Find the first non-fallback + for (ServerPing response : responses) { + if (response == fallback) { + continue; } - return fallback; - }); - case MODS -> pingResponses.thenApply(responses -> { - // Find the first non-fallback that contains a mod list - for (ServerPing response : responses) { - if (response == fallback) { - continue; - } - Optional modInfo = response.getModinfo(); - if (modInfo.isPresent()) { - return fallback.asBuilder().mods(modInfo.get()).build(); - } - } - return fallback; - }); - case DESCRIPTION -> pingResponses.thenApply(responses -> { - // Find the first non-fallback. If it includes a modlist, add it too. - for (ServerPing response : responses) { - if (response == fallback) { - continue; - } - if (response.getDescriptionComponent() == null) { - continue; - } - return new ServerPing( - fallback.getVersion(), - fallback.getPlayers().orElse(null), - response.getDescriptionComponent(), - fallback.getFavicon().orElse(null), - response.getModinfo().orElse(null) - ); + ServerPing.Version version; + if (mode.version()) { + version = response.getVersion(); + } else { + version = fallback.getVersion(); } - return fallback; - }); - // Not possible, but covered for completeness. - default -> CompletableFuture.completedFuture(fallback); - }; + + ServerPing.Players players; + if (mode.players()) { + players = response.getPlayers().orElse(null); + } else { + players = fallback.getPlayers().orElse(null); + } + + Component description; + if (mode.description()) { + if (response.getDescriptionComponent() != null) { + description = response.getDescriptionComponent(); + } else { + description = Component.empty(); + } + } else { + description = fallback.getDescriptionComponent(); + } + + Favicon favicon; + if (mode.favicon()) { + favicon = response.getFavicon().orElse(null); + } else { + favicon = fallback.getFavicon().orElse(null); + } + + ModInfo modinfo; + if (mode.modinfo()) { + modinfo = response.getModinfo().orElse(null); + } else { + modinfo = fallback.getModinfo().orElse(null); + } + + return new ServerPing( + version, + players, + description, + favicon, + modinfo + ); + } + return fallback; + }); } /** @@ -168,7 +177,7 @@ public class ServerListPingHandler { ? connection.getProtocolVersion() : ProtocolVersion.MAXIMUM_VERSION; PingPassthroughMode passthroughMode = configuration.getPingPassthrough(); - if (passthroughMode == PingPassthroughMode.DISABLED) { + if (!passthroughMode.enabled()) { return CompletableFuture.completedFuture(constructLocalPing(shownVersion)); } else { String virtualHostStr = connection.getVirtualHost().map(InetSocketAddress::getHostString) diff --git a/proxy/src/main/resources/default-velocity.toml b/proxy/src/main/resources/default-velocity.toml index 0eae2734..34a17f6c 100644 --- a/proxy/src/main/resources/default-velocity.toml +++ b/proxy/src/main/resources/default-velocity.toml @@ -1,5 +1,5 @@ # Config version. Do not change this -config-version = "2.8" +config-version = "2.9" # What port should the proxy be bound to? By default, we'll bind to all addresses on port 25565. bind = "0.0.0.0:25565" @@ -43,7 +43,7 @@ forwarding-secret-file = "forwarding.secret" # Announce whether or not your server supports Forge. If you run a modded server, we # suggest turning this on. # -# If your network runs one modpack consistently, consider using ping-passthrough = "mods" +# If your network runs one modpack consistently, consider using ping-passthrough.modinfo = true # instead for a nicer display in the server list. announce-forge = false @@ -51,21 +51,6 @@ announce-forge = false # any existing player who is online if a duplicate connection attempt is made. kick-existing-players = false -# Should Velocity pass server list ping requests to a backend server? -# Available options: -# - "disabled": No pass-through will be done. The velocity.toml and server-icon.png -# will determine the initial server list ping response. -# - "mods": Passes only the mod list from your backend server into the response. -# The first server in your try list (or forced host) with a mod list will be -# used. If no backend servers can be contacted, Velocity won't display any -# mod information. -# - "description": Uses the description and mod list from the backend server. The first -# server in the try (or forced host) list that responds is used for the -# description and mod list. -# - "all": Uses the backend server's response as the proxy response. The Velocity -# configuration is used if no servers could be contacted. -ping-passthrough = "DISABLED" - # If enabled (default is false), then a sample of the online players on the proxy will be visible # when hovering over the player count in the server list. # This doesn't have any effect when ping passthrough is set to either "description" or "all". @@ -74,6 +59,18 @@ sample-players-in-ping = false # If not enabled (default is true) player IP addresses will be replaced by in logs enable-player-address-logging = true +[ping-passthrough] +# Should Velocity pass the version number from the backend server when responding to server list ping requests? +version = false +# Should Velocity pass the player count from the backend server when responding to server list ping requests? +players = false +# Should Velocity pass the description from the backend server when responding to server list ping requests? +description = false +# Should Velocity pass the favicon (also known as the server icon) from the backend server when responding to server list ping requests? +favicon = false +# Should Velocity pass the mod list from the backend server when responding to server list ping requests? +modinfo = false + [packet-limiter] # Size of the moving time window in seconds used to calculate average rates. # A larger window tolerates short bursts while still enforcing the configured limits over time. -- 2.39.5 From 15b6ddd1bf2cd0ac115a810fce88b9edf5dee58b Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Sun, 13 Sep 2026 12:41:56 +0200 Subject: [PATCH 09/20] Various dependency bumps --- gradle/libs.versions.toml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0a3b79af..f00c0aca 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,12 +3,12 @@ configurate3 = "3.7.3" configurate4 = "4.2.0" flare = "2.0.1" log4j = "2.26.1" -netty = "4.2.16.Final" +netty = "4.2.18.Final" [plugins] fill = "io.papermc.fill.gradle:1.0.12" shadow = "com.gradleup.shadow:9.6.1" -spotless = "com.diffplug.spotless:8.9.0" +spotless = "com.diffplug.spotless:8.10.2" [libraries] adventure-bom = "net.kyori:adventure-bom:5.2.0" @@ -19,7 +19,7 @@ auto-service-annotations = "com.google.auto.service:auto-service-annotations:1.1 brigadier = "com.velocitypowered:velocity-brigadier:1.0.0-SNAPSHOT" bstats = "org.bstats:bstats-base:3.2.1" caffeine = "com.github.ben-manes.caffeine:caffeine:3.2.4" -checker-qual = "org.checkerframework:checker-qual:4.2.1" +checker-qual = "org.checkerframework:checker-qual:4.2.3" checkstyle = "com.puppycrawl.tools:checkstyle:10.9.3" completablefutures = "com.spotify:completable-futures:0.3.6" configurate3-hocon = { module = "org.spongepowered:configurate-hocon", version.ref = "configurate3" } @@ -34,10 +34,10 @@ flare-core = { module = "space.vectrix.flare:flare", version.ref = "flare" } flare-fastutil = { module = "space.vectrix.flare:flare-fastutil", version.ref = "flare" } jline = "org.jline:jline-terminal-jni:4.3.1" jopt = "net.sf.jopt-simple:jopt-simple:5.0.4" -junit = "org.junit.jupiter:junit-jupiter:6.1.2" +junit = "org.junit.jupiter:junit-jupiter:6.1.3" jspecify = "org.jspecify:jspecify:1.0.1" kyori-ansi = "net.kyori:ansi:1.1.1" -guava = "com.google.guava:guava:33.6.0-jre" +guava = "com.google.guava:guava:33.7.1-jre" gson = "com.google.code.gson:gson:2.14.0" guice = "com.google.inject:guice:7.0.0" lmbda = "org.lanternpowered:lmbda:3.0.0" @@ -55,9 +55,9 @@ netty-transport-native-epoll = { module = "io.netty:netty-transport-native-epoll netty-transport-native-kqueue = { module = "io.netty:netty-transport-native-kqueue", version.ref = "netty" } netty-transport-native-iouring = { module = "io.netty:netty-transport-native-io_uring", version.ref = "netty" } nightconfig = "com.electronwill.night-config:toml:3.9.0" -slf4j = "org.slf4j:slf4j-api:2.0.18" -snakeyaml = "org.yaml:snakeyaml:2.6" -spotbugs-annotations = "com.github.spotbugs:spotbugs-annotations:4.10.3" +slf4j = "org.slf4j:slf4j-api:2.0.19" +snakeyaml = "org.yaml:snakeyaml:2.7" +spotbugs-annotations = "com.github.spotbugs:spotbugs-annotations:4.10.4" terminalconsoleappender = "net.minecrell:terminalconsoleappender:1.3.0" [bundles] -- 2.39.5 From bc553001230ba01fc41a9a8e0aad1b21b87fabfc Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Sun, 13 Sep 2026 14:01:39 +0200 Subject: [PATCH 10/20] Bump `com.puppycrawl.tools:checkstyle` to `14.1.0` and fix warnings that were false-negatives before [ci skip] Checkstyle fixed a bunch of false-negatives; linting errors in the source that should've been caught according to our settings. Rather than updating `checkstyle.xml` to work around these, this commit also properly fixes these checkstyle errors. --- .../api/event/connection/PreLoginEvent.java | 1 + .../event/player/PlayerClientBrandEvent.java | 6 +- .../PlayerConfigurationEvent.java | 1 + .../PlayerEnterConfigurationEvent.java | 1 + .../PlayerEnteredConfigurationEvent.java | 1 + .../PlayerFinishConfigurationEvent.java | 1 + .../PlayerFinishedConfigurationEvent.java | 1 + .../api/network/ProtocolState.java | 5 ++ .../api/permission/Tristate.java | 1 - .../com/velocitypowered/api/proxy/Player.java | 15 ++--- .../api/proxy/crypto/KeySigned.java | 1 + .../api/proxy/player/TabListEntry.java | 3 + gradle/libs.versions.toml | 2 +- .../natives/util/MoreByteBufUtils.java | 4 +- .../velocitypowered/proxy/VelocityServer.java | 2 +- .../proxy/config/PingPassthroughMode.java | 2 +- .../handler/ModernResourcePackHandler.java | 2 +- .../handler/ResourcePackHandler.java | 1 + .../proxy/event/VelocityEventManager.java | 60 +++++++++---------- .../proxy/protocol/ProtocolUtils.java | 6 +- 20 files changed, 66 insertions(+), 50 deletions(-) diff --git a/api/src/main/java/com/velocitypowered/api/event/connection/PreLoginEvent.java b/api/src/main/java/com/velocitypowered/api/event/connection/PreLoginEvent.java index 952cb091..3407bcdb 100644 --- a/api/src/main/java/com/velocitypowered/api/event/connection/PreLoginEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/connection/PreLoginEvent.java @@ -73,6 +73,7 @@ public final class PreLoginEvent implements ResultedEventThis value is {@code null} on 1.19.2 and lower, * up to 1.20.1 it is optional and from 1.20.2 it will always be available.

* diff --git a/api/src/main/java/com/velocitypowered/api/event/player/PlayerClientBrandEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/PlayerClientBrandEvent.java index 268d5b6a..2a671ac6 100644 --- a/api/src/main/java/com/velocitypowered/api/event/player/PlayerClientBrandEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/player/PlayerClientBrandEvent.java @@ -40,9 +40,9 @@ public final class PlayerClientBrandEvent { @Override public String toString() { return "PlayerClientBrandEvent{" - + "player=" + player - + ", brand='" + brand + '\'' - + '}'; + + "player=" + player + + ", brand='" + brand + '\'' + + '}'; } } diff --git a/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerConfigurationEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerConfigurationEvent.java index 6e042af1..60b172f6 100644 --- a/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerConfigurationEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerConfigurationEvent.java @@ -14,6 +14,7 @@ import org.jetbrains.annotations.NotNull; /** * This event is executed when a player entered the configuration state and can be configured by Velocity. + * *

Velocity will wait for this event before continuing/ending the configuration state.

* * @param player The player who can be configured. diff --git a/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerEnterConfigurationEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerEnterConfigurationEvent.java index 05d6c2af..01782d63 100644 --- a/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerEnterConfigurationEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerEnterConfigurationEvent.java @@ -15,6 +15,7 @@ import org.jetbrains.annotations.NotNull; /** * This event is executed when a player is about to enter the configuration state. * It is not called for the initial configuration of a player after login. + * *

Velocity will wait for this event before asking the client to enter configuration state. * However due to backend server being unable to keep the connection alive during state changes, * Velocity will only wait for a maximum of 5 seconds.

diff --git a/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerEnteredConfigurationEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerEnteredConfigurationEvent.java index c1677706..6560f7bc 100644 --- a/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerEnteredConfigurationEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerEnteredConfigurationEvent.java @@ -14,6 +14,7 @@ import org.jetbrains.annotations.NotNull; /** * This event is executed when a player has entered the configuration state. + * *

From this moment on, until the {@link PlayerFinishedConfigurationEvent} is executed, * the {@linkplain Player#getProtocolState()} method is guaranteed * to return {@link ProtocolState#CONFIGURATION}.

diff --git a/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerFinishConfigurationEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerFinishConfigurationEvent.java index 50df5a8a..ab87974a 100644 --- a/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerFinishConfigurationEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerFinishConfigurationEvent.java @@ -14,6 +14,7 @@ import org.jetbrains.annotations.NotNull; /** * This event is executed when a player is about to finish the configuration state. + * *

Velocity will wait for this event before asking the client to finish the configuration state. * However due to backend server being unable to keep the connection alive during state changes, * Velocity will only wait for a maximum of 5 seconds. If you need to hold a player in configuration diff --git a/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerFinishedConfigurationEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerFinishedConfigurationEvent.java index 517f119c..994a1d41 100644 --- a/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerFinishedConfigurationEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerFinishedConfigurationEvent.java @@ -14,6 +14,7 @@ import org.jetbrains.annotations.NotNull; /** * This event is executed when a player has finished the configuration state. + * *

From this moment on, the {@link Player#getProtocolState()} method * will return {@link ProtocolState#PLAY}.

* diff --git a/api/src/main/java/com/velocitypowered/api/network/ProtocolState.java b/api/src/main/java/com/velocitypowered/api/network/ProtocolState.java index 0ecc8287..cd9b04d5 100644 --- a/api/src/main/java/com/velocitypowered/api/network/ProtocolState.java +++ b/api/src/main/java/com/velocitypowered/api/network/ProtocolState.java @@ -16,6 +16,7 @@ package com.velocitypowered.api.network; public enum ProtocolState { /** * Initial connection State. + * *

This status can be caused by a {@link HandshakeIntent#STATUS}, * {@link HandshakeIntent#LOGIN} or {@link HandshakeIntent#TRANSFER} intent.

* If the intent is LOGIN or TRANSFER, the next state will be {@link #LOGIN}, @@ -24,6 +25,7 @@ public enum ProtocolState { HANDSHAKE, /** * Ping State of a connection. + * *

Connections with the {@link HandshakeIntent#STATUS} intent will pass through this state * and be disconnected after it requests the ping from the server * and the server responds with the respective ping.

@@ -31,11 +33,13 @@ public enum ProtocolState { STATUS, /** * Authentication State of a connection. + * *

At this moment the player is authenticating with the authentication servers.

*/ LOGIN, /** * Configuration State of a connection. + * *

At this point the player allows the server to send information * such as resource packs and plugin messages, at the same time the player * will send his client brand and the respective plugin messages @@ -46,6 +50,7 @@ public enum ProtocolState { CONFIGURATION, /** * Game State of a connection. + * *

In this state is where the whole game runs, the server is able to change * the player's state to {@link #CONFIGURATION} as needed in versions 1.20.2 and higher.

*/ diff --git a/api/src/main/java/com/velocitypowered/api/permission/Tristate.java b/api/src/main/java/com/velocitypowered/api/permission/Tristate.java index ddbe26a8..0d81b1dc 100644 --- a/api/src/main/java/com/velocitypowered/api/permission/Tristate.java +++ b/api/src/main/java/com/velocitypowered/api/permission/Tristate.java @@ -15,7 +15,6 @@ import org.checkerframework.checker.nullness.qual.Nullable; * Represents three different states of a setting. * *

Possible values:

- *

*
    *
  • {@link #TRUE} - a positive setting
  • *
  • {@link #FALSE} - a negative (negated) setting
  • diff --git a/api/src/main/java/com/velocitypowered/api/proxy/Player.java b/api/src/main/java/com/velocitypowered/api/proxy/Player.java index c4b8703f..b209954d 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/Player.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/Player.java @@ -244,7 +244,7 @@ public interface Player extends * Gets the {@link ResourcePackInfo} of the currently applied * resource-pack or null if none. * - *

    Note that since 1.20.3 it is no longer recommended to use + *

    Note that since 1.20.3 it is no longer recommended to use * this method as it will only return the last applied * resource pack. To get all applied resource packs, use * {@link #getAppliedResourcePacks()} instead.

    @@ -260,7 +260,7 @@ public interface Player extends * the user is currently downloading or is currently * prompted to install or null if none. * - *

    Note that since 1.20.3 it is no longer recommended to use + *

    Note that since 1.20.3 it is no longer recommended to use * this method as it will only return the last pending * resource pack. To get all pending resource packs, use * {@link #getPendingResourcePacks()} instead.

    @@ -315,6 +315,7 @@ public interface Player extends /** * {@inheritDoc} + * *

    Note that this method does not send a plugin message to the server the player * is connected to. You should only use this method if you are trying to communicate * with a mod that is installed on the player's client.

    @@ -400,7 +401,7 @@ public interface Player extends * and will not perform any actions. * @see #playSound(Sound, Sound.Emitter) * @see - * Unsupported Adventure Operations + * Unsupported Adventure Operations */ @Override default void playSound(@NotNull Sound sound) { @@ -413,7 +414,7 @@ public interface Player extends * and will not perform any actions. * @see #playSound(Sound, Sound.Emitter) * @see - * Unsupported Adventure Operations + * Unsupported Adventure Operations */ @Override default void playSound(@NotNull Sound sound, double x, double y, double z) { @@ -456,7 +457,7 @@ public interface Player extends * and will not perform any actions. * * @see - * Unsupported Adventure Operations + * Unsupported Adventure Operations */ @Override default void openBook(@NotNull Book book) { @@ -469,7 +470,7 @@ public interface Player extends * and will not perform any actions. * * @see - * Unsupported Adventure Operations + * Unsupported Adventure Operations */ @Override default void showDialog(@NotNull DialogLike dialog) { @@ -482,7 +483,7 @@ public interface Player extends * and will not perform any actions. * * @see - * Unsupported Adventure Operations + * Unsupported Adventure Operations */ @Override default void closeDialog() { diff --git a/api/src/main/java/com/velocitypowered/api/proxy/crypto/KeySigned.java b/api/src/main/java/com/velocitypowered/api/proxy/crypto/KeySigned.java index 8ad64c18..c653218e 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/crypto/KeySigned.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/crypto/KeySigned.java @@ -56,6 +56,7 @@ public interface KeySigned { * Validates the signature, expiry temporal and key against the * signer public key. Note: This will **not** check for * expiry. You can check for expiry with {@link KeySigned#hasExpired()}. + * *

    DOES NOT WORK YET FOR MESSAGES AND COMMANDS!

    * Addendum: Does not work for 1.19.1 until the user has authenticated. * diff --git a/api/src/main/java/com/velocitypowered/api/proxy/player/TabListEntry.java b/api/src/main/java/com/velocitypowered/api/proxy/player/TabListEntry.java index aea45287..57a42392 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/player/TabListEntry.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/player/TabListEntry.java @@ -237,8 +237,11 @@ public interface TabListEntry extends KeyIdentifiable { /** * Sets the {@link IdentifiedKey} of the {@link TabListEntry}. + * *

    This only works for players currently not connected to this proxy.

    + * *

    For any player currently connected to this proxy this will be filled automatically.

    + * *

    Will ignore mismatching key revisions data.

    * * @param chatSession session to set diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f00c0aca..30325a81 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -20,7 +20,7 @@ brigadier = "com.velocitypowered:velocity-brigadier:1.0.0-SNAPSHOT" bstats = "org.bstats:bstats-base:3.2.1" caffeine = "com.github.ben-manes.caffeine:caffeine:3.2.4" checker-qual = "org.checkerframework:checker-qual:4.2.3" -checkstyle = "com.puppycrawl.tools:checkstyle:10.9.3" +checkstyle = "com.puppycrawl.tools:checkstyle:14.1.0" completablefutures = "com.spotify:completable-futures:0.3.6" configurate3-hocon = { module = "org.spongepowered:configurate-hocon", version.ref = "configurate3" } configurate3-yaml = { module = "org.spongepowered:configurate-yaml", version.ref = "configurate3" } diff --git a/native/src/main/java/com/velocitypowered/natives/util/MoreByteBufUtils.java b/native/src/main/java/com/velocitypowered/natives/util/MoreByteBufUtils.java index 13f4f0c5..886f2081 100644 --- a/native/src/main/java/com/velocitypowered/natives/util/MoreByteBufUtils.java +++ b/native/src/main/java/com/velocitypowered/natives/util/MoreByteBufUtils.java @@ -54,8 +54,8 @@ public class MoreByteBufUtils { BufferPreference preferred = nativeStuff.preferredBufferType(); return switch (preferred) { case DIRECT_PREFERRED, HEAP_PREFERRED -> - // The native prefers this type, but doesn't strictly require we provide it. - true; + // The native prefers this type, but doesn't strictly require we provide it. + true; case DIRECT_REQUIRED -> buf.hasMemoryAddress(); case HEAP_REQUIRED -> buf.hasArray(); }; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index 7c027dd6..4fbeabb9 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -653,7 +653,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 */ diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/PingPassthroughMode.java b/proxy/src/main/java/com/velocitypowered/proxy/config/PingPassthroughMode.java index aa73cefd..623e3d07 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/PingPassthroughMode.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/PingPassthroughMode.java @@ -59,6 +59,6 @@ public record PingPassthroughMode(boolean version, boolean players, public boolean enabled() { return this.version || this.players || this.description || this.favicon - || this.modinfo; + || this.modinfo; } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/ModernResourcePackHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/ModernResourcePackHandler.java index f0fd5e08..3bef8a4b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/ModernResourcePackHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/ModernResourcePackHandler.java @@ -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 outstandingResourcePacks = diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/ResourcePackHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/ResourcePackHandler.java index b3843885..dc04b912 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/ResourcePackHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/resourcepack/handler/ResourcePackHandler.java @@ -118,6 +118,7 @@ public abstract sealed class ResourcePackHandler /** * Processes a client response to a sent resource-pack. + * *

    Cases in which no action will be taken:

    *
      * diff --git a/proxy/src/main/java/com/velocitypowered/proxy/event/VelocityEventManager.java b/proxy/src/main/java/com/velocitypowered/proxy/event/VelocityEventManager.java index b56b907c..8f12dca0 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/event/VelocityEventManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/event/VelocityEventManager.java @@ -545,6 +545,36 @@ public class VelocityEventManager implements EventManager { } } + private void fire(final @Nullable CompletableFuture 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 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,36 +699,6 @@ public class VelocityEventManager implements EventManager { } } - private void fire(final @Nullable CompletableFuture 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 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(); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java index 3cb64382..328252ea 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java @@ -644,7 +644,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 @@ -685,7 +685,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 @@ -709,7 +709,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 -- 2.39.5 From 1b48f4053422e39b556e323a6525e2db28022cfc Mon Sep 17 00:00:00 2001 From: Braydon Date: Mon, 14 Sep 2026 12:34:50 -0400 Subject: [PATCH 11/20] Fix config reload not removing deleted servers (#1882) * Fix config reload not removing deleted servers When reloading velocity.toml, unregister servers that were removed from the config and evacuate any connected players to fallback servers. * Requested PR fixes * Make Expected ConnectedPlayer exceptions more detailed --- .../velocitypowered/proxy/VelocityServer.java | 41 +++++++++++++++---- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index 4fbeabb9..b3059f9e 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -488,23 +488,46 @@ public class VelocityServer implements ProxyServer, ForwardingAudience { return false; } - // Re-register servers. If a server is being replaced, make sure to note what players need to - // move back to a fallback server. + // Re-register servers. If a server is being replaced or removed, make sure to note what + // players need to move back to a fallback server. Collection evacuate = new ArrayList<>(); + for (Map.Entry entry : configuration.getServers().entrySet()) { + if (newConfiguration.getServers().keySet().stream() + .anyMatch(name -> name.equalsIgnoreCase(entry.getKey()))) { + continue; + } + + RegisteredServer rs = servers.getServer(entry.getKey()).orElse(null); + if (rs == null) { + continue; + } + + for (Player player : rs.getPlayersConnected()) { + if (!(player instanceof ConnectedPlayer)) { + throw new IllegalStateException("Expected ConnectedPlayer for player " + player + + " on server " + rs.getServerInfo().getName() + ", got " + + player.getClass().getName()); + } + evacuate.add((ConnectedPlayer) player); + } + servers.unregister(rs.getServerInfo()); + } + for (Map.Entry entry : newConfiguration.getServers().entrySet()) { ServerInfo newInfo = new ServerInfo(entry.getKey(), AddressUtil.parseAddress(entry.getValue())); - Optional rs = servers.getServer(entry.getKey()); - if (rs.isEmpty()) { + RegisteredServer rs = servers.getServer(entry.getKey()).orElse(null); + if (rs == null) { servers.register(newInfo); - } else if (!rs.get().getServerInfo().equals(newInfo)) { - for (Player player : rs.get().getPlayersConnected()) { + } else if (!rs.getServerInfo().equals(newInfo)) { + for (Player player : rs.getPlayersConnected()) { if (!(player instanceof ConnectedPlayer)) { - throw new IllegalStateException("ConnectedPlayer not found for player " + player - + " in server " + rs.get().getServerInfo().getName()); + throw new IllegalStateException("Expected ConnectedPlayer for player " + player + + " on server " + rs.getServerInfo().getName() + ", got " + + player.getClass().getName()); } evacuate.add((ConnectedPlayer) player); } - servers.unregister(rs.get().getServerInfo()); + servers.unregister(rs.getServerInfo()); servers.register(newInfo); } } -- 2.39.5 From dff94da2b3d79bff1874ecdf7320e178a8480625 Mon Sep 17 00:00:00 2001 From: Aaron <71191102+RealBauHD@users.noreply.github.com> Date: Mon, 14 Sep 2026 18:34:59 +0200 Subject: [PATCH 12/20] Minecraft 26.3 (#1867) * 26.3-snapshot-7 * 26.3-snapshot-8 * 26.3-snapshot-9 * 26.3-snapshot-10 * 26.3-pre-1 * 26.3-pre-2 * feat: add post effects packet * chore: write game modes as var int * 26.3-pre-3 * 26.3-rc-1 * 26.3 * chore: add key array cap * fix: add a zero --- .../api/network/ProtocolVersion.java | 3 +- .../connection/MinecraftSessionHandler.java | 5 + .../proxy/protocol/ProtocolUtils.java | 8 +- .../proxy/protocol/StateRegistry.java | 104 ++++++++++++------ .../packet/ClientboundPostEffectsPacket.java | 53 +++++++++ .../proxy/protocol/packet/JoinGamePacket.java | 30 +++-- .../proxy/protocol/packet/RespawnPacket.java | 40 +++++-- .../brigadier/ArgumentPropertyRegistry.java | 12 +- 8 files changed, 196 insertions(+), 59 deletions(-) create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientboundPostEffectsPacket.java diff --git a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java index 7f6912a6..6653ff9c 100644 --- a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java +++ b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java @@ -96,7 +96,8 @@ public enum ProtocolVersion implements Ordered { MINECRAFT_1_21_9(773, "1.21.9", "1.21.10"), MINECRAFT_1_21_11(774, "1.21.11"), MINECRAFT_26_1(775, "26.1", "26.1.1", "26.1.2"), - MINECRAFT_26_2(776, "26.2"); + MINECRAFT_26_2(776, "26.2"), + MINECRAFT_26_3(777, "26.3"); private static final int SNAPSHOT_BIT = 30; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java index 00783dbf..9df71c3f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java @@ -23,6 +23,7 @@ import com.velocitypowered.proxy.protocol.packet.BossBarPacket; import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket; import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket; import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket; +import com.velocitypowered.proxy.protocol.packet.ClientboundPostEffectsPacket; import com.velocitypowered.proxy.protocol.packet.ClientboundSoundEntityPacket; import com.velocitypowered.proxy.protocol.packet.ClientboundStopSoundPacket; import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket; @@ -404,4 +405,8 @@ public interface MinecraftSessionHandler { default boolean handle(ClientboundStopSoundPacket packet) { return false; } + + default boolean handle(ClientboundPostEffectsPacket packet) { + return false; + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java index 328252ea..f5ed5d3e 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java @@ -143,6 +143,7 @@ public enum ProtocolUtils { .build(); public static final int DEFAULT_MAX_STRING_SIZE = 65536; // 64KiB + private static final int DEFAULT_MAX_KEY_ARRAY_SIZE = 1024; private static final int MAXIMUM_VARINT_SIZE = 5; private static final BinaryTagType[] BINARY_TAG_TYPES = new BinaryTagType[] { BinaryTagTypes.END, BinaryTagTypes.BYTE, BinaryTagTypes.SHORT, BinaryTagTypes.INT, @@ -349,15 +350,20 @@ public enum ProtocolUtils { writeString(buf, key.asMinimalString()); } + public static Key[] readKeyArray(ByteBuf buf) { + return readKeyArray(buf, DEFAULT_MAX_KEY_ARRAY_SIZE); + } + /** * Reads a standard Mojang Text namespaced:key array from the buffer. * * @param buf the buffer to read from * @return the decoded key array */ - public static Key[] readKeyArray(ByteBuf buf) { + public static Key[] readKeyArray(ByteBuf buf, int cap) { int length = readVarInt(buf); checkFrame(length >= 0, "Got a negative-length array (%s)", length); + checkFrame(length <= cap, "Bad key array size (got %s, maximum is %s)", length, cap); checkFrame(buf.isReadable(length), "Trying to read an array that is too long (wanted %s, only have %s)", length, buf.readableBytes()); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java index b35bf1c7..1da9c424 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -47,6 +47,7 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9_4; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_26_1; +import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_26_3; import static com.velocitypowered.api.network.ProtocolVersion.MINIMUM_VERSION; import static com.velocitypowered.api.network.ProtocolVersion.SUPPORTED_VERSIONS; import static com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; @@ -60,6 +61,7 @@ import com.velocitypowered.proxy.protocol.packet.BossBarPacket; import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket; import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket; import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket; +import com.velocitypowered.proxy.protocol.packet.ClientboundPostEffectsPacket; import com.velocitypowered.proxy.protocol.packet.ClientboundSoundEntityPacket; import com.velocitypowered.proxy.protocol.packet.ClientboundStopSoundPacket; import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket; @@ -234,31 +236,44 @@ public enum StateRegistry { map(0x06, MINECRAFT_1_20_2, false), map(0x07, MINECRAFT_1_20_3, false), map(0x09, MINECRAFT_1_20_5, false)); + clientbound.register( + ClientboundPostEffectsPacket.class, ClientboundPostEffectsPacket::new, + map(0x0A, MINECRAFT_26_3, false)); clientbound.register( ClientboundStoreCookiePacket.class, ClientboundStoreCookiePacket::new, - map(0x0A, MINECRAFT_1_20_5, false)); + map(0x0A, MINECRAFT_1_20_5, false), + map(0x0B, MINECRAFT_26_3, false)); clientbound.register(TransferPacket.class, TransferPacket::new, - map(0x0B, MINECRAFT_1_20_5, false)); + map(0x0B, MINECRAFT_1_20_5, false), + map(0x0C, MINECRAFT_26_3, false)); clientbound.register(ActiveFeaturesPacket.class, ActiveFeaturesPacket::new, map(0x07, MINECRAFT_1_20_2, false), map(0x08, MINECRAFT_1_20_3, false), - map(0x0C, MINECRAFT_1_20_5, false)); + map(0x0C, MINECRAFT_1_20_5, false), + map(0x0D, MINECRAFT_26_3, false)); clientbound.register(TagsUpdatePacket.class, TagsUpdatePacket::new, map(0x08, MINECRAFT_1_20_2, false), map(0x09, MINECRAFT_1_20_3, false), - map(0x0D, MINECRAFT_1_20_5, false)); + map(0x0D, MINECRAFT_1_20_5, false), + map(0x0E, MINECRAFT_26_3, false)); clientbound.register(KnownPacksPacket.class, KnownPacksPacket::new, - map(0x0E, MINECRAFT_1_20_5, false)); + map(0x0E, MINECRAFT_1_20_5, false), + map(0x0F, MINECRAFT_26_3, false)); clientbound.register(ClientboundCustomReportDetailsPacket.class, ClientboundCustomReportDetailsPacket::new, - map(0x0F, MINECRAFT_1_21, false)); + map(0x0F, MINECRAFT_1_21, false), + map(0x10, MINECRAFT_26_3, false)); clientbound.register(ClientboundServerLinksPacket.class, ClientboundServerLinksPacket::new, - map(0x10, MINECRAFT_1_21, false)); + map(0x10, MINECRAFT_1_21, false), + map(0x11, MINECRAFT_26_3, false)); clientbound.register(DialogClearPacket.class, () -> DialogClearPacket.INSTANCE, - map(0x11, MINECRAFT_1_21_6, false)); + map(0x11, MINECRAFT_1_21_6, false), + map(0x12, MINECRAFT_26_3, false)); clientbound.register(DialogShowPacket.class, () -> new DialogShowPacket(this), - map(0x12, MINECRAFT_1_21_6, false)); + map(0x12, MINECRAFT_1_21_6, false), + map(0x13, MINECRAFT_26_3, false)); clientbound.register(CodeOfConductPacket.class, CodeOfConductPacket::new, - map(0x13, MINECRAFT_1_21_9, false)); + map(0x13, MINECRAFT_1_21_9, false), + map(0x14, MINECRAFT_26_3, false)); } }, PLAY { @@ -410,7 +425,8 @@ public enum StateRegistry { map(0x2D, MINECRAFT_1_21_2, false), map(0x2F, MINECRAFT_1_21_4, false), map(0x30, MINECRAFT_1_21_6, false), - map(0x31, MINECRAFT_26_1, false)); + map(0x31, MINECRAFT_26_1, false), + map(0x32, MINECRAFT_26_3, false)); serverbound.register( FinishedUpdatePacket.class, () -> FinishedUpdatePacket.INSTANCE, map(0x0B, MINECRAFT_1_20_2, false), @@ -469,6 +485,9 @@ public enum StateRegistry { ClientboundCookieRequestPacket.class, ClientboundCookieRequestPacket::new, map(0x16, MINECRAFT_1_20_5, false), map(0x15, MINECRAFT_1_21_5, false)); + clientbound.register( + ClientboundPostEffectsPacket.class, ClientboundPostEffectsPacket::new, + map(0x53, MINECRAFT_26_3, false)); clientbound.register( ClientboundSoundEntityPacket.class, ClientboundSoundEntityPacket::new, map(0x5D, MINECRAFT_1_19_3, true), @@ -479,7 +498,8 @@ public enum StateRegistry { map(0x6E, MINECRAFT_1_21_2, true), map(0x6D, MINECRAFT_1_21_5, true), map(0x72, MINECRAFT_1_21_9, true), - map(0x74, MINECRAFT_26_1, true)); + map(0x74, MINECRAFT_26_1, true), + map(0x76, MINECRAFT_26_3, true)); clientbound.register( ClientboundStopSoundPacket.class, ClientboundStopSoundPacket::new, map(0x5F, MINECRAFT_1_19_3, true), @@ -490,7 +510,8 @@ public enum StateRegistry { map(0x71, MINECRAFT_1_21_2, true), map(0x70, MINECRAFT_1_21_5, true), map(0x75, MINECRAFT_1_21_9, true), - map(0x77, MINECRAFT_26_1, true)); + map(0x77, MINECRAFT_26_1, true), + map(0x79, MINECRAFT_26_3, true)); clientbound.register( PluginMessagePacket.class, PluginMessagePacket::new, @@ -548,7 +569,8 @@ public enum StateRegistry { map(0x27, MINECRAFT_1_21_2, false), map(0x26, MINECRAFT_1_21_5, false), map(0x2B, MINECRAFT_1_21_9, false), - map(0x2C, MINECRAFT_26_1, false)); + map(0x2C, MINECRAFT_26_1, false), + map(0x2D, MINECRAFT_26_3, false)); clientbound.register( JoinGamePacket.class, JoinGamePacket::new, @@ -569,7 +591,8 @@ public enum StateRegistry { map(0x2C, MINECRAFT_1_21_2, false), map(0x2B, MINECRAFT_1_21_5, false), map(0x30, MINECRAFT_1_21_9, false), - map(0x31, MINECRAFT_26_1, false)); + map(0x31, MINECRAFT_26_1, false), + map(0x32, MINECRAFT_26_3, false)); clientbound.register( RespawnPacket.class, RespawnPacket::new, @@ -593,7 +616,8 @@ public enum StateRegistry { map(0x4C, MINECRAFT_1_21_2, true), map(0x4B, MINECRAFT_1_21_5, true), map(0x50, MINECRAFT_1_21_9, true), - map(0x52, MINECRAFT_26_1, true)); + map(0x52, MINECRAFT_26_1, true), + map(0x54, MINECRAFT_26_3, true)); clientbound.register( RemoveResourcePackPacket.class, RemoveResourcePackPacket::new, @@ -602,7 +626,8 @@ public enum StateRegistry { map(0x4A, MINECRAFT_1_21_2, false), map(0x49, MINECRAFT_1_21_5, false), map(0x4E, MINECRAFT_1_21_9, false), - map(0x50, MINECRAFT_26_1, false)); + map(0x50, MINECRAFT_26_1, false), + map(0x51, MINECRAFT_26_3, false)); clientbound.register( ResourcePackRequestPacket.class, ResourcePackRequestPacket::new, @@ -626,7 +651,8 @@ public enum StateRegistry { map(0x4B, MINECRAFT_1_21_2, false), map(0x4A, MINECRAFT_1_21_5, false), map(0x4F, MINECRAFT_1_21_9, false), - map(0x51, MINECRAFT_26_1, false)); + map(0x51, MINECRAFT_26_1, false), + map(0x52, MINECRAFT_26_3, false)); clientbound.register( HeaderAndFooterPacket.class, HeaderAndFooterPacket::new, @@ -651,7 +677,8 @@ public enum StateRegistry { map(0x74, MINECRAFT_1_21_2, true), map(0x73, MINECRAFT_1_21_5, true), map(0x78, MINECRAFT_1_21_9, true), - map(0x7A, MINECRAFT_26_1, true)); + map(0x7A, MINECRAFT_26_1, true), + map(0x7D, MINECRAFT_26_3, true)); clientbound.register( LegacyTitlePacket.class, LegacyTitlePacket::new, @@ -675,7 +702,8 @@ public enum StateRegistry { map(0x6A, MINECRAFT_1_21_2, true), map(0x69, MINECRAFT_1_21_5, true), map(0x6E, MINECRAFT_1_21_9, true), - map(0x70, MINECRAFT_26_1, true)); + map(0x70, MINECRAFT_26_1, true), + map(0x72, MINECRAFT_26_3, true)); clientbound.register( TitleTextPacket.class, TitleTextPacket::new, @@ -690,7 +718,8 @@ public enum StateRegistry { map(0x6C, MINECRAFT_1_21_2, true), map(0x6B, MINECRAFT_1_21_5, true), map(0x70, MINECRAFT_1_21_9, true), - map(0x72, MINECRAFT_26_1, true)); + map(0x72, MINECRAFT_26_1, true), + map(0x74, MINECRAFT_26_3, true)); clientbound.register( TitleActionbarPacket.class, TitleActionbarPacket::new, @@ -705,7 +734,8 @@ public enum StateRegistry { map(0x51, MINECRAFT_1_21_2, true), map(0x50, MINECRAFT_1_21_5, true), map(0x55, MINECRAFT_1_21_9, true), - map(0x57, MINECRAFT_26_1, true)); + map(0x57, MINECRAFT_26_1, true), + map(0x59, MINECRAFT_26_3, true)); clientbound.register( TitleTimesPacket.class, TitleTimesPacket::new, @@ -720,7 +750,8 @@ public enum StateRegistry { map(0x6D, MINECRAFT_1_21_2, true), map(0x6C, MINECRAFT_1_21_5, true), map(0x71, MINECRAFT_1_21_9, true), - map(0x73, MINECRAFT_26_1, true)); + map(0x73, MINECRAFT_26_1, true), + map(0x75, MINECRAFT_26_3, true)); clientbound.register( TitleClearPacket.class, TitleClearPacket::new, @@ -752,7 +783,8 @@ public enum StateRegistry { map(0x3F, MINECRAFT_1_21_2, false), map(0x3E, MINECRAFT_1_21_5, false), map(0x43, MINECRAFT_1_21_9, false), - map(0x45, MINECRAFT_26_1, false)); + map(0x45, MINECRAFT_26_1, false), + map(0x46, MINECRAFT_26_3, false)); clientbound.register( UpsertPlayerInfoPacket.class, UpsertPlayerInfoPacket::new, @@ -763,14 +795,16 @@ public enum StateRegistry { map(0x40, MINECRAFT_1_21_2, false), map(0x3F, MINECRAFT_1_21_5, false), map(0x44, MINECRAFT_1_21_9, false), - map(0x46, MINECRAFT_26_1, false)); + map(0x46, MINECRAFT_26_1, false), + map(0x47, MINECRAFT_26_3, false)); clientbound.register( ClientboundStoreCookiePacket.class, ClientboundStoreCookiePacket::new, map(0x6B, MINECRAFT_1_20_5, false), map(0x72, MINECRAFT_1_21_2, false), map(0x71, MINECRAFT_1_21_5, false), map(0x76, MINECRAFT_1_21_9, false), - map(0x78, MINECRAFT_26_1, false)); + map(0x78, MINECRAFT_26_1, false), + map(0x7A, MINECRAFT_26_3, false)); clientbound.register( SystemChatPacket.class, SystemChatPacket::new, @@ -784,7 +818,8 @@ public enum StateRegistry { map(0x73, MINECRAFT_1_21_2, true), map(0x72, MINECRAFT_1_21_5, true), map(0x77, MINECRAFT_1_21_9, true), - map(0x79, MINECRAFT_26_1, true)); + map(0x79, MINECRAFT_26_1, true), + map(0x7C, MINECRAFT_26_3, true)); clientbound.register( PlayerChatCompletionPacket.class, PlayerChatCompletionPacket::new, @@ -807,7 +842,8 @@ public enum StateRegistry { map(0x50, MINECRAFT_1_21_2, false), map(0x4F, MINECRAFT_1_21_5, false), map(0x54, MINECRAFT_1_21_9, false), - map(0x56, MINECRAFT_26_1, false)); + map(0x56, MINECRAFT_26_1, false), + map(0x58, MINECRAFT_26_3, false)); clientbound.register( StartUpdatePacket.class, () -> StartUpdatePacket.INSTANCE, @@ -817,7 +853,8 @@ public enum StateRegistry { map(0x70, MINECRAFT_1_21_2, false), map(0x6F, MINECRAFT_1_21_5, false), map(0x74, MINECRAFT_1_21_9, false), - map(0x76, MINECRAFT_26_1, false)); + map(0x76, MINECRAFT_26_1, false), + map(0x78, MINECRAFT_26_3, false)); clientbound.register( BundleDelimiterPacket.class, () -> BundleDelimiterPacket.INSTANCE, @@ -828,21 +865,24 @@ public enum StateRegistry { map(0x73, MINECRAFT_1_20_5, false), map(0x7A, MINECRAFT_1_21_2, false), map(0x7F, MINECRAFT_1_21_9, false), - map(0x81, MINECRAFT_26_1, false)); + map(0x81, MINECRAFT_26_1, false), + map(0x84, MINECRAFT_26_3, false)); clientbound.register( ClientboundCustomReportDetailsPacket.class, ClientboundCustomReportDetailsPacket::new, map(0x7A, MINECRAFT_1_21, false), map(0x81, MINECRAFT_1_21_2, false), map(0x86, MINECRAFT_1_21_9, false), - map(0x88, MINECRAFT_26_1, false)); + map(0x88, MINECRAFT_26_1, false), + map(0x8B, MINECRAFT_26_3, false)); clientbound.register( ClientboundServerLinksPacket.class, ClientboundServerLinksPacket::new, map(0x7B, MINECRAFT_1_21, false), map(0x82, MINECRAFT_1_21_2, false), map(0x87, MINECRAFT_1_21_9, false), - map(0x89, MINECRAFT_26_1, false)); + map(0x89, MINECRAFT_26_1, false), + map(0x8C, MINECRAFT_26_3, false)); } }, LOGIN { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientboundPostEffectsPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientboundPostEffectsPacket.java new file mode 100644 index 00000000..19d4f403 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ClientboundPostEffectsPacket.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2018-2026 Velocity Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.velocitypowered.proxy.protocol.packet; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; +import io.netty.buffer.ByteBuf; +import net.kyori.adventure.key.Key; + +public final class ClientboundPostEffectsPacket implements MinecraftPacket { + + private Key[] postEffects; + + public ClientboundPostEffectsPacket() { + } + + public ClientboundPostEffectsPacket(Key[] postEffects) { + this.postEffects = postEffects; + } + + @Override + public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { + this.postEffects = ProtocolUtils.readKeyArray(buf); + } + + @Override + public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { + ProtocolUtils.writeKeyArray(buf, this.postEffects); + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGamePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGamePacket.java index 645dc7b6..ca777a27 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGamePacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGamePacket.java @@ -32,7 +32,7 @@ public class JoinGamePacket implements MinecraftPacket { private static final BinaryTagIO.Reader JOINGAME_READER = BinaryTagIO.reader(4 * 1024 * 1024); private int entityId; - private short gamemode; + private int gamemode; private int dimension; private long partialHashedSeed; // 1.15+ private short difficulty; @@ -47,7 +47,7 @@ public class JoinGamePacket implements MinecraftPacket { private CompoundBinaryTag registry; // 1.16+ private DimensionInfo dimensionInfo; // 1.16+ private CompoundBinaryTag currentDimensionData; // 1.16.2+ - private short previousGamemode; // 1.16+ + private int previousGamemode; // 1.16+ private int simulationDistance; // 1.18+ private @Nullable Pair lastDeathPosition; // 1.19+ private int portalCooldown; // 1.20+ @@ -63,11 +63,11 @@ public class JoinGamePacket implements MinecraftPacket { this.entityId = entityId; } - public short getGamemode() { + public int getGamemode() { return gamemode; } - public void setGamemode(short gamemode) { + public void setGamemode(int gamemode) { this.gamemode = gamemode; } @@ -131,11 +131,11 @@ public class JoinGamePacket implements MinecraftPacket { this.dimensionInfo = dimensionInfo; } - public short getPreviousGamemode() { + public int getPreviousGamemode() { return previousGamemode; } - public void setPreviousGamemode(short previousGamemode) { + public void setPreviousGamemode(int previousGamemode) { this.previousGamemode = previousGamemode; } @@ -345,8 +345,13 @@ public class JoinGamePacket implements MinecraftPacket { String levelName = ProtocolUtils.readString(buf); this.partialHashedSeed = buf.readLong(); - this.gamemode = buf.readByte(); - this.previousGamemode = buf.readByte(); + if (version.noLessThan(ProtocolVersion.MINECRAFT_26_3)) { + this.gamemode = ProtocolUtils.readVarInt(buf); + this.previousGamemode = ProtocolUtils.readVarInt(buf); // game mode + 1 or 0 + } else { + this.gamemode = buf.readByte(); + this.previousGamemode = buf.readByte(); // game mode or -1 + } boolean isDebug = buf.readBoolean(); boolean isFlat = buf.readBoolean(); @@ -498,8 +503,13 @@ public class JoinGamePacket implements MinecraftPacket { ProtocolUtils.writeString(buf, dimensionInfo.getLevelName()); buf.writeLong(partialHashedSeed); - buf.writeByte(gamemode); - buf.writeByte(previousGamemode); + if (version.noLessThan(ProtocolVersion.MINECRAFT_26_3)) { + ProtocolUtils.writeVarInt(buf, this.gamemode); + ProtocolUtils.writeVarInt(buf, this.previousGamemode); + } else { + buf.writeByte(this.gamemode); + buf.writeByte(this.previousGamemode); + } buf.writeBoolean(dimensionInfo.isDebugType()); buf.writeBoolean(dimensionInfo.isFlat()); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/RespawnPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/RespawnPacket.java index fd9c8ca7..d20c253f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/RespawnPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/RespawnPacket.java @@ -33,11 +33,11 @@ public class RespawnPacket implements MinecraftPacket { private int dimension; private long partialHashedSeed; private short difficulty; - private short gamemode; + private int gamemode; private String levelType = ""; private byte dataToKeep; // 1.16+ private DimensionInfo dimensionInfo; // 1.16-1.16.1 - private short previousGamemode; // 1.16+ + private int previousGamemode; // 1.16+ private CompoundBinaryTag currentDimensionData; // 1.16.2+ private @Nullable Pair lastDeathPosition; // 1.19+ private int portalCooldown; // 1.20+ @@ -46,9 +46,9 @@ public class RespawnPacket implements MinecraftPacket { public RespawnPacket() { } - public RespawnPacket(int dimension, long partialHashedSeed, short difficulty, short gamemode, + public RespawnPacket(int dimension, long partialHashedSeed, short difficulty, int gamemode, String levelType, byte dataToKeep, DimensionInfo dimensionInfo, - short previousGamemode, CompoundBinaryTag currentDimensionData, + int previousGamemode, CompoundBinaryTag currentDimensionData, @Nullable Pair lastDeathPosition, int portalCooldown, int seaLevel) { this.dimension = dimension; @@ -97,11 +97,11 @@ public class RespawnPacket implements MinecraftPacket { this.difficulty = difficulty; } - public short getGamemode() { + public int getGamemode() { return gamemode; } - public void setGamemode(short gamemode) { + public void setGamemode(int gamemode) { this.gamemode = gamemode; } @@ -121,11 +121,11 @@ public class RespawnPacket implements MinecraftPacket { this.dataToKeep = dataToKeep; } - public short getPreviousGamemode() { + public int getPreviousGamemode() { return previousGamemode; } - public void setPreviousGamemode(short previousGamemode) { + public void setPreviousGamemode(int previousGamemode) { this.previousGamemode = previousGamemode; } @@ -197,9 +197,17 @@ public class RespawnPacket implements MinecraftPacket { if (version.noLessThan(ProtocolVersion.MINECRAFT_1_15)) { this.partialHashedSeed = buf.readLong(); } - this.gamemode = buf.readByte(); + if (version.noLessThan(ProtocolVersion.MINECRAFT_26_3)) { + this.gamemode = ProtocolUtils.readVarInt(buf); + } else { + this.gamemode = buf.readByte(); + } if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) { - this.previousGamemode = buf.readByte(); + if (version.noLessThan(ProtocolVersion.MINECRAFT_26_3)) { + this.previousGamemode = ProtocolUtils.readVarInt(buf); + } else { + this.previousGamemode = buf.readByte(); + } boolean isDebug = buf.readBoolean(); boolean isFlat = buf.readBoolean(); this.dimensionInfo = new DimensionInfo(dimensionKey, levelName, isFlat, isDebug, version); @@ -249,9 +257,17 @@ public class RespawnPacket implements MinecraftPacket { if (version.noLessThan(ProtocolVersion.MINECRAFT_1_15)) { buf.writeLong(partialHashedSeed); } - buf.writeByte(gamemode); + if (version.noLessThan(ProtocolVersion.MINECRAFT_26_3)) { + ProtocolUtils.writeVarInt(buf, this.gamemode); + } else { + buf.writeByte(this.gamemode); + } if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) { - buf.writeByte(previousGamemode); + if (version.noLessThan(ProtocolVersion.MINECRAFT_26_3)) { + ProtocolUtils.writeVarInt(buf, this.previousGamemode); + } else { + buf.writeByte(this.previousGamemode); + } buf.writeBoolean(dimensionInfo.isDebugType()); buf.writeBoolean(dimensionInfo.isFlat()); if (version.lessThan(ProtocolVersion.MINECRAFT_1_19_3)) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java index 3c57cd75..6d735fa8 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentPropertyRegistry.java @@ -25,6 +25,7 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_5; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_5; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_6; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_26_2; +import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_26_3; import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.id; import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.mapSet; import static com.velocitypowered.proxy.protocol.packet.brigadier.DoubleArgumentPropertySerializer.DOUBLE; @@ -273,16 +274,21 @@ 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), - mapSet(MINECRAFT_1_19_4, 48), mapSet(MINECRAFT_1_19, 47))); // added in 1.16 + empty(id("minecraft:uuid", mapSet(MINECRAFT_26_3, 61), 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))); empty(id("minecraft:loot_predicate", mapSet(MINECRAFT_1_21_6, 53), mapSet(MINECRAFT_1_21_5, 52), mapSet(MINECRAFT_1_20_5, 51))); empty(id("minecraft:loot_modifier", mapSet(MINECRAFT_1_21_6, 54), mapSet(MINECRAFT_1_21_5, 53), mapSet(MINECRAFT_1_20_5, 52))); empty(id("minecraft:hex_color", mapSet(MINECRAFT_1_21_6, 17))); // added in 1.21.6 - empty(id("minecraft:dialog", mapSet(MINECRAFT_1_21_6, 55))); // added in 1.21.6 + empty(id("minecraft:dialog", mapSet(MINECRAFT_26_3, 58), mapSet(MINECRAFT_1_21_6, 55))); // added in 1.21.6 empty(id("minecraft:team_color", mapSet(MINECRAFT_26_2, 16))); // renamed from color in 26.2 + empty(id("minecraft:context_float_provider", mapSet(MINECRAFT_26_3, 55))); // added in 26.3 + empty(id("minecraft:context_int_provider", mapSet(MINECRAFT_26_3, 56))); // added in 26.3 + empty(id("minecraft:slot_source", mapSet(MINECRAFT_26_3, 57))); // added in 26.3 + empty(id("minecraft:feature", mapSet(MINECRAFT_26_3, 59))); // added in 26.3 + empty(id("minecraft:swing_animation", mapSet(MINECRAFT_26_3, 60))); // added in 26.3 // Crossstitch support register(id("crossstitch:mod_argument", mapSet(MINECRAFT_1_19, -256)), ModArgumentProperty.class, MOD); -- 2.39.5 From c10b49255447694ad42c3c189d7a3f4ee3e73e5e Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Mon, 14 Sep 2026 18:36:05 +0200 Subject: [PATCH 13/20] Release 4.2.0 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index ed875a01..3313abc0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ group=com.velocitypowered -version=4.1.2-SNAPSHOT +version=4.2.0 -- 2.39.5 From 5000227d11583d121409d01a420ed5d3fe855ebd Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Wed, 16 Sep 2026 13:41:50 +0200 Subject: [PATCH 14/20] Snapshot 4.2.1-SNAPSHOT --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 3313abc0..b6abe9af 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ group=com.velocitypowered -version=4.2.0 +version=4.2.1-SNAPSHOT -- 2.39.5 From a6f9de9581e0cae5d826c8f71ada64cb2a4ce812 Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Wed, 16 Sep 2026 13:41:57 +0200 Subject: [PATCH 15/20] Bound IntervalledCounter size under packet floods The counter stored one entry per addTime call, so a client sending tiny (even empty) packets could grow it without limit while staying under the bytes-per-second limit, eventually OOMing the proxy. Merge data points within 1ms of the newest one so the entry count is bounded by the window size (~7000 for the default 7s window) rather than by packet rate. --- .../proxy/util/IntervalledCounter.java | 40 ++++-- .../proxy/util/IntervalledCounterTest.java | 115 ++++++++++++++++++ 2 files changed, 146 insertions(+), 9 deletions(-) create mode 100644 proxy/src/test/java/com/velocitypowered/proxy/util/IntervalledCounterTest.java diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/IntervalledCounter.java b/proxy/src/main/java/com/velocitypowered/proxy/util/IntervalledCounter.java index 9b986dca..7e0bf4c8 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/IntervalledCounter.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/IntervalledCounter.java @@ -30,40 +30,51 @@ package com.velocitypowered.proxy.util; *

      This class is not thread-safe. If multiple threads access an instance concurrently, * external synchronization is required.

      */ -@SuppressWarnings("checkstyle:WhitespaceAfter") // Not our class public final class IntervalledCounter { private static final int INITIAL_SIZE = 8; + /** + * Data points within this many nanoseconds of the newest one are merged into it, bounding the + * number of stored data points to roughly {@code interval / COALESCE_INTERVAL}. + */ + private static final long COALESCE_INTERVAL = 1_000_000L; // 1ms + /** * Ring buffer holding the timestamp (in nanoseconds) for each data point. */ - protected long[] times; + private long[] times; + /** * Ring buffer holding the count associated with each timestamp. */ - protected long[] counts; + private long[] counts; + /** * The sliding window size in nanoseconds. Only entries with time >= (currentTime - interval) * are considered part of the window. */ - protected final long interval; + private final long interval; + /** * Cached lower bound of the window (in nanoseconds) after the last update. */ - protected long minTime; + private long minTime; + /** * Running sum of all counts currently within the window. */ - protected long sum; + private long sum; + /** * Head index (inclusive) of the ring buffer. */ - protected int head; // inclusive + private int head; // inclusive + /** * Tail index (exclusive) of the ring buffer. */ - protected int tail; // exclusive + private int tail; // exclusive /** * Creates a new counter with the specified interval. @@ -131,6 +142,8 @@ public final class IntervalledCounter { /** * Adds {@code count} units at the specified timestamp, assuming the timestamp is within the * current window. If the timestamp is older than {@code minTime}, the value is ignored. + * If the timestamp is within {@link #COALESCE_INTERVAL} of the newest stored data point, the + * count is merged into that data point instead of creating a new one. * This method does not automatically advance the window; callers should invoke * {@link #updateCurrentTime()} or {@link #updateCurrentTime(long)} beforehand. * @@ -142,6 +155,15 @@ public final class IntervalledCounter { if (currTime - this.minTime < 0) { return; } + if (this.head != this.tail) { + final int last = this.tail == 0 ? this.times.length - 1 : this.tail - 1; + // guard against overflow by using subtraction + if (currTime - this.times[last] < COALESCE_INTERVAL) { + this.counts[last] += count; + this.sum += count; + return; + } + } int nextTail = (this.tail + 1) % this.times.length; if (nextTail == this.head) { this.resize(); @@ -219,7 +241,7 @@ public final class IntervalledCounter { * @return the rate in units per second for the current window */ public double getRate() { - return (double)this.sum / ((double)this.interval * 1.0E-9); + return (double) this.sum / ((double) this.interval * 1.0E-9); } /** diff --git a/proxy/src/test/java/com/velocitypowered/proxy/util/IntervalledCounterTest.java b/proxy/src/test/java/com/velocitypowered/proxy/util/IntervalledCounterTest.java new file mode 100644 index 00000000..f187ff2c --- /dev/null +++ b/proxy/src/test/java/com/velocitypowered/proxy/util/IntervalledCounterTest.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2026 Velocity Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.velocitypowered.proxy.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.Test; + +class IntervalledCounterTest { + + private static final long INTERVAL = TimeUnit.SECONDS.toNanos(7); + + @Test + void sumAndRateTrackAddedCounts() { + IntervalledCounter counter = new IntervalledCounter(INTERVAL); + long now = 0; + + counter.updateAndAdd(700, now); + assertEquals(700, counter.getSum()); + assertEquals(100.0, counter.getRate(), 1e-9); + + now += TimeUnit.SECONDS.toNanos(1); + counter.updateAndAdd(1400, now); + assertEquals(2100, counter.getSum()); + assertEquals(300.0, counter.getRate(), 1e-9); + } + + @Test + void dataPointsOutsideTheWindowAreEvicted() { + IntervalledCounter counter = new IntervalledCounter(INTERVAL); + long now = 0; + + counter.updateAndAdd(10, now); + now += TimeUnit.SECONDS.toNanos(3); + counter.updateAndAdd(20, now); + assertEquals(30, counter.getSum()); + assertEquals(2, counter.totalDataPoints()); + + // 7.5s after the first point: only the second point remains + now = TimeUnit.MILLISECONDS.toNanos(7500); + counter.updateCurrentTime(now); + assertEquals(20, counter.getSum()); + assertEquals(1, counter.totalDataPoints()); + + // 10.5s: everything has expired + now = TimeUnit.MILLISECONDS.toNanos(10500); + counter.updateCurrentTime(now); + assertEquals(0, counter.getSum()); + assertEquals(0, counter.totalDataPoints()); + } + + @Test + void storedDataPointsAreBoundedByTimeNotByCallCount() { + IntervalledCounter counter = new IntervalledCounter(INTERVAL); + + // Simulate a flood of tiny (even zero-sized) packets arriving far faster than one per + // millisecond for the whole window. Before coalescing was introduced, each call stored a + // separate data point, so an attacker could grow the ring buffer without bound while staying + // under a bytes-per-second limit. + final long stepNanos = 100; + final long calls = INTERVAL / stepNanos; + long now = 0; + for (long i = 0; i < calls; i++) { + counter.updateAndAdd(i % 2, now); + now += stepNanos; + } + + long expectedSum = calls / 2; + assertEquals(expectedSum, counter.getSum()); + // 7s window at 1ms coalescing granularity is ~7000 points; leave some slack. + assertTrue(counter.totalDataPoints() <= 7100, + "expected at most ~7000 data points, got " + counter.totalDataPoints()); + assertTrue(counter.totalDataPoints() >= 7000, + "expected at least 7000 data points, got " + counter.totalDataPoints()); + } + + @Test + void coalescedDataPointsExpireTogether() { + IntervalledCounter counter = new IntervalledCounter(INTERVAL); + + long now = 0; + counter.updateAndAdd(5, now); + // Within the same 1ms bucket: merged into the previous point + counter.updateAndAdd(7, now + 500_000L); + assertEquals(12, counter.getSum()); + assertEquals(1, counter.totalDataPoints()); + + // A new bucket starts a new point + counter.updateAndAdd(1, now + 1_000_000L); + assertEquals(13, counter.getSum()); + assertEquals(2, counter.totalDataPoints()); + + // The merged bucket carries the timestamp of its first point and expires with it + counter.updateCurrentTime(now + INTERVAL + 1); + assertEquals(1, counter.getSum()); + assertEquals(1, counter.totalDataPoints()); + } +} -- 2.39.5 From f975230f14f102d9adef65a858a82cc800751dca Mon Sep 17 00:00:00 2001 From: MiniDigger | Martin Date: Sun, 20 Sep 2026 10:23:18 +0200 Subject: [PATCH 16/20] =?UTF-8?q?[ci=20skip]=20migrate=20CI=20to=20namespa?= =?UTF-8?q?ce=20=F0=9F=9A=80=20(#1888)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gradle.yml | 20 +++++++++++++++----- README.md | 2 ++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 865c9390..f3d18540 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -6,18 +6,28 @@ on: [push, pull_request] jobs: build: - runs-on: ubuntu-24.04 + runs-on: namespace-profile-linux-default steps: - name: Checkout Repository - uses: actions/checkout@v6 + uses: namespacelabs/nscloud-checkout-action@v9 with: persist-credentials: false + - name: Set up cache + uses: namespacelabs/nscloud-cache-action@v1 + with: + cache: gradle + - name: Setup gradle build cache + run: | + nsc cache gradle setup --init-gradle /tmp/init.gradle - name: Set up Gradle - uses: gradle/actions/setup-gradle@v5 + uses: gradle/actions/setup-gradle@v6 + with: + cache-disabled: true # namespace handles this - name: Set up JDK 21 - uses: actions/setup-java@v5 + uses: actions/setup-java@v6 with: java-version: 21 distribution: 'zulu' + cache: "" # namespace handles this - name: Build with Gradle - run: ./gradlew build + run: ./gradlew --init-script=/tmp/init.gradle build diff --git a/README.md b/README.md index aa6527f0..fccffeb7 100644 --- a/README.md +++ b/README.md @@ -40,3 +40,5 @@ page. Translations are handled using [Crowdin](https://papermc-io.crowdin.com/velocity). If you want to translate a language not available on Crowdin, you might want to ask in the [Discord](https://discord.gg/papermc) about it. + +CI powered by namespace badge -- 2.39.5 From 5bb1992014e951b8ce757cf27d135eff25a26eb6 Mon Sep 17 00:00:00 2001 From: MiniDigger | Martin Date: Tue, 22 Sep 2026 16:09:28 +0200 Subject: [PATCH 17/20] [ci skip] update namespace badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fccffeb7..7441044f 100644 --- a/README.md +++ b/README.md @@ -41,4 +41,4 @@ Translations are handled using [Crowdin](https://papermc-io.crowdin.com/velocity If you want to translate a language not available on Crowdin, you might want to ask in the [Discord](https://discord.gg/papermc) about it. -CI powered by namespace badge +[![CI powered by namespace badge](https://papermc.io/assets/misc/namespace-oss-badge.svg?project=velocity)](https://namespace.so/github-actions/?utm_source=oss&utm_campaign=papermc) -- 2.39.5 From fb0f21f425a36a2ebe4a7c35d233b0e6771b81f6 Mon Sep 17 00:00:00 2001 From: Rocco Date: Wed, 23 Sep 2026 13:09:07 +0000 Subject: [PATCH 18/20] Update online mode in reconfigure join packet (#1889) --- .../connection/backend/BackendPlaySessionHandler.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java index 5ba2659f..37a61d35 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java @@ -55,6 +55,7 @@ import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket; import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket; import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket; import com.velocitypowered.proxy.protocol.packet.DisconnectPacket; +import com.velocitypowered.proxy.protocol.packet.JoinGamePacket; import com.velocitypowered.proxy.protocol.packet.KeepAlivePacket; import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItemPacket; import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket; @@ -164,6 +165,13 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { return false; // forwards on } + @Override + public boolean handle(JoinGamePacket packet) { + // We receive this if the connection is reconfigured + packet.setOnlineMode(serverConn.getPlayer().isOnlineMode()); + return false; // forward + } + @Override public boolean handle(ClientSettingsPacket packet) { serverConn.ensureConnected().write(packet); -- 2.39.5 From f4eb5c7536323e9e9c0860fdde40ee7b2445a980 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Thu, 24 Sep 2026 17:07:17 +0200 Subject: [PATCH 19/20] Fix UpdateTeamsPacket in StateRegistry --- .../proxy/protocol/StateRegistry.java | 46 ++----------------- 1 file changed, 3 insertions(+), 43 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java index a4a247aa..260738e4 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -25,48 +25,7 @@ import static com.velocitypowered.proxy.protocol.ProtocolUtils.Direction.SERVERB import com.velocitypowered.api.network.ProtocolState; import com.velocitypowered.api.network.ProtocolVersion; -import com.velocitypowered.proxy.protocol.packet.AvailableCommandsPacket; -import com.velocitypowered.proxy.protocol.packet.BossBarPacket; -import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket; -import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket; -import com.velocitypowered.proxy.protocol.packet.ClientboundCookieRequestPacket; -import com.velocitypowered.proxy.protocol.packet.ClientboundSoundEntityPacket; -import com.velocitypowered.proxy.protocol.packet.ClientboundStopSoundPacket; -import com.velocitypowered.proxy.protocol.packet.ClientboundStoreCookiePacket; -import com.velocitypowered.proxy.protocol.packet.DialogClearPacket; -import com.velocitypowered.proxy.protocol.packet.DialogShowPacket; -import com.velocitypowered.proxy.protocol.packet.DisconnectPacket; -import com.velocitypowered.proxy.protocol.packet.EncryptionRequestPacket; -import com.velocitypowered.proxy.protocol.packet.EncryptionResponsePacket; -import com.velocitypowered.proxy.protocol.packet.HandshakePacket; -import com.velocitypowered.proxy.protocol.packet.HeaderAndFooterPacket; -import com.velocitypowered.proxy.protocol.packet.JoinGamePacket; -import com.velocitypowered.proxy.protocol.packet.KeepAlivePacket; -import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItemPacket; -import com.velocitypowered.proxy.protocol.packet.LoginAcknowledgedPacket; -import com.velocitypowered.proxy.protocol.packet.LoginPluginMessagePacket; -import com.velocitypowered.proxy.protocol.packet.LoginPluginResponsePacket; -import com.velocitypowered.proxy.protocol.packet.PingIdentifyPacket; -import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket; -import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfoPacket; -import com.velocitypowered.proxy.protocol.packet.RemoveResourcePackPacket; -import com.velocitypowered.proxy.protocol.packet.ResourcePackRequestPacket; -import com.velocitypowered.proxy.protocol.packet.ResourcePackResponsePacket; -import com.velocitypowered.proxy.protocol.packet.RespawnPacket; -import com.velocitypowered.proxy.protocol.packet.ServerDataPacket; -import com.velocitypowered.proxy.protocol.packet.ServerLoginPacket; -import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccessPacket; -import com.velocitypowered.proxy.protocol.packet.ServerboundCookieResponsePacket; -import com.velocitypowered.proxy.protocol.packet.ServerboundCustomClickActionPacket; -import com.velocitypowered.proxy.protocol.packet.ServerboundPlayerLoadedPacket; -import com.velocitypowered.proxy.protocol.packet.SetCompressionPacket; -import com.velocitypowered.proxy.protocol.packet.StatusPingPacket; -import com.velocitypowered.proxy.protocol.packet.StatusRequestPacket; -import com.velocitypowered.proxy.protocol.packet.StatusResponsePacket; -import com.velocitypowered.proxy.protocol.packet.TabCompleteRequestPacket; -import com.velocitypowered.proxy.protocol.packet.TabCompleteResponsePacket; -import com.velocitypowered.proxy.protocol.packet.TransferPacket; -import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfoPacket; +import com.velocitypowered.proxy.protocol.packet.*; import com.velocitypowered.proxy.protocol.packet.chat.ChatAcknowledgementPacket; import com.velocitypowered.proxy.protocol.packet.chat.PlayerChatCompletionPacket; import com.velocitypowered.proxy.protocol.packet.chat.SystemChatPacket; @@ -872,7 +831,8 @@ public enum StateRegistry { map(0x6B, MINECRAFT_1_21_9, true), map(0x63, MINECRAFT_1_21_11, true), map(0x6D, MINECRAFT_26_1, true), - map(0x6D, MINECRAFT_26_2, true)); + map(0x6D, MINECRAFT_26_2, true), + map(0x6F, MINECRAFT_26_3, true)); } }, LOGIN { -- 2.39.5 From 7c79516567789473fa66164e38b0e939e50f9bc1 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Thu, 24 Sep 2026 17:08:41 +0200 Subject: [PATCH 20/20] Fix compile of StateRegistry --- .../java/com/velocitypowered/proxy/protocol/StateRegistry.java | 1 - 1 file changed, 1 deletion(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java index 260738e4..557eea33 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -810,7 +810,6 @@ public enum StateRegistry { map(0x87, MINECRAFT_1_21_9, false), map(0x89, MINECRAFT_26_1, false), map(0x8C, MINECRAFT_26_3, false)); - map(0x89, MINECRAFT_26_1, false)); clientbound.register( UpdateTeamsPacket.class, UpdateTeamsPacket::new, -- 2.39.5