Compare commits

..
3 Commits
Author SHA1 Message Date
Chaoscaot 47f36e3ff9 Merge remote-tracking branch 'upstream/dev/3.0.0' into updatev2
SteamWarCI Build successful
2025-06-26 22:52:31 +02:00
Chaoscaot 75bb48d00e Merge remote-tracking branch 'upstream/dev/3.0.0' into updatev2
SteamWarCI Build successful
2025-06-03 23:12:58 +02:00
Chaoscaot 11834de220 Revert "Disable io_uring transport by default"
SteamWarCI Build successful
This reverts commit ae312339a3.
2025-05-02 20:49:11 +02:00
180 changed files with 1605 additions and 4206 deletions
+7 -7
View File
@@ -6,18 +6,18 @@ on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-24.04
runs-on: ubuntu-22.04
steps:
- name: Checkout Repository
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v5
- name: Set up JDK 21
uses: actions/setup-java@v5
uses: gradle/actions/setup-gradle@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: 21
distribution: 'zulu'
java-version: 17
distribution: 'temurin'
- name: Build with Gradle
run: ./gradlew build
-6
View File
@@ -34,9 +34,3 @@ and you can configure it from there.
Alternatively, you can get the proxy JAR from the [downloads](https://papermc.io/downloads/velocity)
page.
# Localisation
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.
+7 -5
View File
@@ -59,16 +59,18 @@ tasks {
val o = options as StandardJavadocDocletOptions
o.encoding = "UTF-8"
o.source = "25"
o.source = "17"
o.use()
o.links(
"https://www.javadocs.dev/org.slf4j/slf4j-api/${libs.slf4j.get().version}/",
"https://www.slf4j.org/apidocs/",
"https://guava.dev/releases/${libs.guava.get().version}/api/docs/",
"https://google.github.io/guice/api-docs/${libs.guice.get().version}/javadoc/",
"https://docs.oracle.com/en/java/javase/25/docs/api/",
"https://jd.papermc.io/adventure/${libs.adventure.bom.get().version}/",
"https://www.javadocs.dev/com.github.ben-manes.caffeine/caffeine/${libs.caffeine.get().version}/",
"https://docs.oracle.com/en/java/javase/17/docs/api/",
"https://jd.advntr.dev/api/${libs.adventure.bom.get().version}/",
"https://jd.advntr.dev/text-minimessage/${libs.adventure.bom.get().version}/",
"https://jd.advntr.dev/key/${libs.adventure.bom.get().version}/",
"https://javadoc.io/doc/com.github.ben-manes.caffeine/caffeine/${libs.caffeine.get().version}/",
)
o.tags(
@@ -14,6 +14,7 @@ import com.velocitypowered.api.plugin.Plugin;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.Objects;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
@@ -67,8 +68,8 @@ public class PluginAnnotationProcessor extends AbstractProcessor {
Name qualifiedName = ((TypeElement) element).getQualifiedName();
if (pluginClassFound != null) {
if (!pluginClassFound.equals(qualifiedName.toString()) && !warnedAboutMultiplePlugins) {
if (Objects.equals(pluginClassFound, qualifiedName.toString())) {
if (!warnedAboutMultiplePlugins) {
environment.getMessager()
.printMessage(Diagnostic.Kind.WARNING, "Velocity does not yet currently support "
+ "multiple plugins. We are using " + pluginClassFound
@@ -24,8 +24,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
*/
public final class SerializedPluginDescription {
public static final String ID_PATTERN_STRING = "[a-z][a-z0-9-_]{0,63}";
public static final Pattern ID_PATTERN = Pattern.compile(ID_PATTERN_STRING);
public static final Pattern ID_PATTERN = Pattern.compile("[a-z][a-z0-9-_]{0,63}");
// @Nullable is used here to make GSON skip these in the serialized file
private final String id;
@@ -23,7 +23,7 @@ public interface CommandSource extends Audience, PermissionSubject {
* Sends a message with the MiniMessage format to this source.
*
* @param message MiniMessage content
* @see <a href="https://docs.papermc.io/adventure/minimessage/format/">MiniMessage docs</a>
* @see <a href="https://docs.advntr.dev/minimessage/format.html">MiniMessage docs</a>
* for more information on the format.
**/
default void sendRichMessage(final @NotNull String message) {
@@ -31,14 +31,14 @@ public interface CommandSource extends Audience, PermissionSubject {
}
/**
* Sends a message with the MiniMessage format to this source.
*
* @param message MiniMessage content
* @param resolvers resolvers to use
* @see <a href="https://docs.papermc.io/adventure/minimessage/">MiniMessage docs</a>
* and <a href="https://docs.papermc.io/adventure/minimessage/dynamic-replacements">MiniMessage Placeholders docs</a>
* for more information on the format.
*/
* Sends a message with the MiniMessage format to this source.
*
* @param message MiniMessage content
* @param resolvers resolvers to use
* @see <a href="https://docs.advntr.dev/minimessage/">MiniMessage docs</a>
* and <a href="https://docs.advntr.dev/minimessage/dynamic-replacements">MiniMessage Placeholders docs</a>
* for more information on the format.
**/
default void sendRichMessage(
final @NotNull String message,
final @NotNull TagResolver @NotNull... resolvers
@@ -60,8 +60,7 @@ public interface EventManager {
*
* @param plugin the plugin to associate with the handler
* @param eventClass the class for the event handler to register
* @param postOrder the relative order in which events should be posted to the handler. The higher
* the priority, the earlier the event handler will be called
* @param postOrder the relative order in which events should be posted to the handler
* @param handler the handler to register
* @param <E> the event type to handle
*/
@@ -1,11 +0,0 @@
/*
* Copyright (C) 2018 Velocity Contributors
*
* The Velocity API is licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in the api top-level directory.
*/
/**
* Provides events for handling command execution.
*/
package com.velocitypowered.api.event.command;
@@ -11,7 +11,6 @@ import com.google.common.base.Preconditions;
import com.velocitypowered.api.event.ResultedEvent;
import com.velocitypowered.api.event.annotation.AwaitingEvent;
import com.velocitypowered.api.proxy.Player;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* This event is fired once the player has been authenticated, but before they connect to a server.
@@ -23,24 +22,10 @@ import org.checkerframework.checker.nullness.qual.Nullable;
public final class LoginEvent implements ResultedEvent<ResultedEvent.ComponentResult> {
private final Player player;
private final String serverIdHash;
private ComponentResult result;
@Deprecated(forRemoval = true)
public LoginEvent(Player player) {
this(player, null);
}
/**
* Constructs a new {@link LoginEvent}.
*
* @param player the player who has completed authentication
* @param serverIdHash the server ID hash sent to Mojang for authentication,
* or {@code null} if the connection is in offline-mode
*/
public LoginEvent(Player player, @Nullable String serverIdHash) {
this.player = Preconditions.checkNotNull(player, "player");
this.serverIdHash = serverIdHash;
this.result = ComponentResult.allowed();
}
@@ -48,16 +33,6 @@ public final class LoginEvent implements ResultedEvent<ResultedEvent.ComponentRe
return player;
}
/**
* Returns the server ID hash that was sent to Mojang to authenticate the player.
* If the connection was in offline-mode, this returns {@code null}.
*
* @return the server ID hash that was sent to Mojang to authenticate the player
*/
public @Nullable String getServerIdHash() {
return serverIdHash;
}
@Override
public ComponentResult getResult() {
return result;
@@ -1,44 +0,0 @@
/*
* Copyright (C) 2025 Velocity Contributors
*
* The Velocity API is licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in the api top-level directory.
*/
package com.velocitypowered.api.event.player;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import java.util.List;
/**
* This event is fired when a client ({@link Player}) sends a plugin message through the
* unregister channel. Velocity will not wait on this event to finish firing.
*/
public final class PlayerChannelUnregisterEvent {
private final Player player;
private final List<ChannelIdentifier> channels;
public PlayerChannelUnregisterEvent(Player player, List<ChannelIdentifier> channels) {
this.player = Preconditions.checkNotNull(player, "player");
this.channels = Preconditions.checkNotNull(channels, "channels");
}
public Player getPlayer() {
return player;
}
public List<ChannelIdentifier> getChannels() {
return channels;
}
@Override
public String toString() {
return "PlayerChannelUnregisterEvent{"
+ "player=" + player
+ ", channels=" + channels
+ '}';
}
}
@@ -143,7 +143,7 @@ public final class ServerPreConnectEvent implements
* is used, then {@link ConnectionRequestBuilder#connect()}'s result will have the status
* {@link Status#CONNECTION_CANCELLED}.
*
* @return a result to deny connections
* @return a result to deny conneections
*/
public static ServerResult denied() {
return DENIED;
@@ -1,11 +0,0 @@
/*
* Copyright (C) 2018 Velocity Contributors
*
* The Velocity API is licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in the api top-level directory.
*/
/**
* Provides events for handling the player configuration phase.
*/
package com.velocitypowered.api.event.player.configuration;
@@ -1,33 +0,0 @@
/*
* Copyright (C) 2018-2025 Velocity Contributors
*
* The Velocity API is licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in the api top-level directory.
*/
package com.velocitypowered.api.event.proxy;
import com.google.common.annotations.Beta;
import com.velocitypowered.api.event.annotation.AwaitingEvent;
/**
* This event is fired by the proxy after it has stopped accepting new connections,
* but before players are disconnected.
* This is the last point at which you can interact with currently connected players,
* for example to transfer them to another proxy or perform other cleanup tasks.
*
* @implNote Velocity will wait for all event listeners to complete before disconnecting players,
* but note that the event will time out after the configured value of the
* <code>velocity.pre-shutdown-timeout</code> system property, default 10 seconds,
* in seconds to prevent shutdown from hanging indefinitely
* @since 3.4.0
*/
@Beta
@AwaitingEvent
public final class ProxyPreShutdownEvent {
@Override
public String toString() {
return "ProxyPreShutdownEvent";
}
}
@@ -1,11 +0,0 @@
/*
* Copyright (C) 2018 Velocity Contributors
*
* The Velocity API is licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in the api top-level directory.
*/
/**
* Provides events for handling registration of servers on the proxy.
*/
package com.velocitypowered.api.event.proxy.server;
@@ -91,12 +91,7 @@ public enum ProtocolVersion implements Ordered<ProtocolVersion> {
MINECRAFT_1_21_2(768, "1.21.2", "1.21.3"),
MINECRAFT_1_21_4(769, "1.21.4"),
MINECRAFT_1_21_5(770, "1.21.5"),
MINECRAFT_1_21_6(771, "1.21.6"),
MINECRAFT_1_21_7(772, "1.21.7", "1.21.8"),
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_1_21_6(771, "1.21.6");
private static final int SNAPSHOT_BIT = 30;
@@ -7,11 +7,9 @@
package com.velocitypowered.api.plugin;
import com.velocitypowered.api.plugin.ap.SerializedPluginDescription;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.intellij.lang.annotations.Pattern;
/**
* Indicates that the {@link Plugin} depends on another plugin in order to enable.
@@ -26,7 +24,6 @@ public @interface Dependency {
* @return The dependency plugin ID
* @see Plugin#id()
*/
@Pattern(SerializedPluginDescription.ID_PATTERN_STRING)
String id();
/**
@@ -7,12 +7,10 @@
package com.velocitypowered.api.plugin;
import com.velocitypowered.api.plugin.ap.SerializedPluginDescription;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.intellij.lang.annotations.Pattern;
/**
* Annotation used to describe a Velocity plugin.
@@ -28,7 +26,6 @@ public @interface Plugin {
*
* @return the ID for this plugin
*/
@Pattern(SerializedPluginDescription.ID_PATTERN_STRING)
String id();
/**
@@ -29,7 +29,6 @@ import java.util.Locale;
import java.util.Optional;
import java.util.UUID;
import java.util.function.UnaryOperator;
import net.kyori.adventure.dialog.DialogLike;
import net.kyori.adventure.identity.Identified;
import net.kyori.adventure.inventory.Book;
import net.kyori.adventure.key.Key;
@@ -39,7 +38,6 @@ import net.kyori.adventure.sound.SoundStop;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.event.HoverEventSource;
import net.kyori.adventure.text.object.PlayerHeadObjectContents;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.jetbrains.annotations.NotNull;
@@ -50,8 +48,7 @@ public interface Player extends
/* Fundamental Velocity interfaces */
CommandSource, InboundConnection, ChannelMessageSource, ChannelMessageSink,
/* Adventure-specific interfaces */
Identified, HoverEventSource<HoverEvent.ShowEntity>, Keyed, KeyIdentifiable, Sound.Emitter,
PlayerHeadObjectContents.SkinSource {
Identified, HoverEventSource<HoverEvent.ShowEntity>, Keyed, KeyIdentifiable {
/**
* Returns the player's current username.
@@ -197,7 +194,7 @@ public interface Player extends
*
* @param reason component with the reason
*/
void disconnect(@NotNull Component reason);
void disconnect(Component reason);
/**
* Sends chat input onto the players current server as if they typed it into the client chat box.
@@ -338,15 +335,6 @@ public interface Player extends
Component.text(getUsername()))));
}
@SuppressWarnings("UnstableApiUsage") // permitted implementation
@Override
default void applySkinToPlayerHeadContents(
final PlayerHeadObjectContents.@NotNull Builder builder) {
builder.skin(this.getGameProfile());
if (this.hasSentPlayerSettings()) {
builder.hat(this.getPlayerSettings().getSkinParts().hasHat());
}
}
/**
* Gets the player's client brand.
@@ -395,12 +383,8 @@ public interface Player extends
/**
* {@inheritDoc}
*
*
* @apiNote <b>This method is not currently implemented in Velocity
* and will not perform any actions.</b>
* @see #playSound(Sound, Sound.Emitter)
* @see <a href="https://docs.papermc.io/velocity/dev/pitfalls/#audience-operations-are-not-fully-supported">
* Unsupported Adventure Operations</a>
* <b>This method is not currently implemented in Velocity
* and will not perform any actions.</b>
*/
@Override
default void playSound(@NotNull Sound sound) {
@@ -409,11 +393,8 @@ public interface Player extends
/**
* {@inheritDoc}
*
* @apiNote <b>This method is not currently implemented in Velocity
* and will not perform any actions.</b>
* @see #playSound(Sound, Sound.Emitter)
* @see <a href="https://docs.papermc.io/velocity/dev/pitfalls/#audience-operations-are-not-fully-supported">
* Unsupported Adventure Operations</a>
* <b>This method is not currently implemented in Velocity
* and will not perform any actions.</b>
*/
@Override
default void playSound(@NotNull Sound sound, double x, double y, double z) {
@@ -422,28 +403,18 @@ public interface Player extends
/**
* {@inheritDoc}
*
* <p><b>Note</b>: Due to <a href="https://bugs.mojang.com/browse/MC/issues/MC-146721">MC-146721</a>, stereo sounds are always played globally in 1.14+.
*
* <p><b>Note</b>: Due to <a href="https://bugs.mojang.com/browse/MC/issues/MC-138832">MC-138832</a>, the volume and pitch are ignored when using this method in 1.14 to 1.16.5.
*
* @param sound the sound to play
* @param emitter the emitter of the sound; may be another player of this player's server
* @since 3.4.0
* @sinceMinecraft 1.19.3
* @apiNote This method is currently only implemented for players on 1.19.3+
* and requires a present {@link #getCurrentServer} for the emitting player as well as this player.
* <b>This method is not currently implemented in Velocity
* and will not perform any actions.</b>
*/
@Override
default void playSound(@NotNull Sound sound, @NotNull Sound.Emitter emitter) {
default void playSound(@NotNull Sound sound, Sound.Emitter emitter) {
}
/**
* {@inheritDoc}
*
* @param stop the sound and/or a sound source, to stop
* @since 3.4.0
* @sinceMinecraft 1.19.3
* @apiNote This method is currently only implemented for players on 1.19.3+.
* <b>This method is not currently implemented in Velocity
* and will not perform any actions.</b>
*/
@Override
default void stopSound(@NotNull SoundStop stop) {
@@ -454,40 +425,11 @@ public interface Player extends
*
* <b>This method is not currently implemented in Velocity
* and will not perform any actions.</b>
*
* @see <a href="https://docs.papermc.io/velocity/dev/pitfalls/#audience-operations-are-not-fully-supported">
* Unsupported Adventure Operations</a>
*/
@Override
default void openBook(@NotNull Book book) {
}
/**
* {@inheritDoc}
*
* <b>This method is not currently implemented in Velocity
* and will not perform any actions.</b>
*
* @see <a href="https://docs.papermc.io/velocity/dev/pitfalls/#audience-operations-are-not-fully-supported">
* Unsupported Adventure Operations</a>
*/
@Override
default void showDialog(@NotNull DialogLike dialog) {
}
/**
* {@inheritDoc}
*
* <b>This method is not currently implemented in Velocity
* and will not perform any actions.</b>
*
* @see <a href="https://docs.papermc.io/velocity/dev/pitfalls/#audience-operations-are-not-fully-supported">
* Unsupported Adventure Operations</a>
*/
@Override
default void closeDialog() {
}
/**
* Transfers a Player to a host.
*
@@ -19,9 +19,7 @@ import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import net.kyori.adventure.text.Component;
import org.jspecify.annotations.Nullable;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents a 1.7 and above server list ping response. This class is immutable.
@@ -30,7 +28,7 @@ public final class ServerPing {
private final Version version;
private final @Nullable Players players;
private final @Nullable Component description;
private final net.kyori.adventure.text.Component description;
private final @Nullable Favicon favicon;
private final @Nullable ModInfo modinfo;
@@ -49,8 +47,8 @@ public final class ServerPing {
* @param modinfo the mods this server runs
*/
public ServerPing(Version version, @Nullable Players players,
Component description, @Nullable Favicon favicon,
@Nullable ModInfo modinfo) {
net.kyori.adventure.text.Component description, @Nullable Favicon favicon,
@Nullable ModInfo modinfo) {
this.version = Preconditions.checkNotNull(version, "version");
this.players = players;
this.description = Preconditions.checkNotNull(description, "description");
@@ -66,8 +64,7 @@ public final class ServerPing {
return Optional.ofNullable(players);
}
@Nullable
public Component getDescriptionComponent() {
public net.kyori.adventure.text.Component getDescriptionComponent() {
return description;
}
@@ -154,7 +151,7 @@ public final class ServerPing {
private final List<SamplePlayer> samplePlayers = new ArrayList<>();
private String modType = "FML";
private final List<ModInfo.Mod> mods = new ArrayList<>();
private Component description;
private net.kyori.adventure.text.Component description;
private @Nullable Favicon favicon;
private boolean nullOutPlayers;
private boolean nullOutModinfo;
@@ -302,7 +299,7 @@ public final class ServerPing {
* @param description Component to use as the description.
* @return this builder, for chaining
*/
public Builder description(Component description) {
public Builder description(net.kyori.adventure.text.Component description) {
this.description = Preconditions.checkNotNull(description, "description");
return this;
}
@@ -362,7 +359,7 @@ public final class ServerPing {
return samplePlayers;
}
public Optional<Component> getDescriptionComponent() {
public Optional<net.kyori.adventure.text.Component> getDescriptionComponent() {
return Optional.ofNullable(description);
}
@@ -11,14 +11,11 @@ import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import net.kyori.adventure.text.object.PlayerHeadObjectContents;
import org.jetbrains.annotations.NotNull;
/**
* Represents a Mojang game profile. This class is immutable.
*/
public final class GameProfile implements PlayerHeadObjectContents.SkinSource {
public final class GameProfile {
private final UUID id;
private final String undashedId;
@@ -172,23 +169,6 @@ public final class GameProfile implements PlayerHeadObjectContents.SkinSource {
ImmutableList.of());
}
@SuppressWarnings("UnstableApiUsage") // permitted implementation
@Override
public void applySkinToPlayerHeadContents(
final PlayerHeadObjectContents.@NotNull Builder builder) {
if (this.properties.isEmpty()) {
builder.id(this.id);
return;
}
builder.id(this.id)
.name(this.name)
.profileProperties(this.properties.stream()
.map(property -> PlayerHeadObjectContents.property(property.getName(),
property.getValue(), property.getSignature()))
.collect(Collectors.toList()));
}
@Override
public String toString() {
return "GameProfile{"
@@ -2,15 +2,8 @@ import org.gradle.jvm.tasks.Jar
import org.gradle.kotlin.dsl.withType
import java.io.ByteArrayOutputStream
// This interface is needed as a workaround to get an instance of ExecOperations
interface Injected {
@get:Inject
val execOps: ExecOperations
}
val currentShortRevision = ByteArrayOutputStream().use {
val execOps = objects.newInstance<Injected>().execOps
execOps.exec {
exec {
executable = "git"
args = listOf("rev-parse", "HEAD")
standardOutput = it
@@ -8,10 +8,10 @@ extensions.configure<PublishingExtension> {
maven {
credentials(PasswordCredentials::class.java)
name = if (version.toString().endsWith("SNAPSHOT")) "paperSnapshots" else "paper" // "paper" is seemingly not defined
val base = "https://artifactory.papermc.io/artifactory"
val releasesRepoUrl = "$base/releases/"
val snapshotsRepoUrl = "$base/snapshots/"
name = "paper"
val base = "https://repo.papermc.io/repository/maven"
val releasesRepoUrl = "$base-releases/"
val snapshotsRepoUrl = "$base-snapshots/"
setUrl(if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl)
}
}
+6 -6
View File
@@ -12,7 +12,7 @@ subprojects {
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(25))
languageVersion.set(JavaLanguageVersion.of(17))
}
}
@@ -20,11 +20,11 @@ subprojects {
testImplementation(rootProject.libs.junit)
}
testing.suites.named<JvmTestSuite>("test") {
useJUnitJupiter()
targets.all {
testTask.configure {
reports.junitXml.required = true
tasks {
test {
useJUnitPlatform()
reports {
junitXml.required.set(true)
}
}
}
+1 -1
View File
@@ -1,2 +1,2 @@
group=com.velocitypowered
version=4.0.0-SNAPSHOT
version=3.4.0-SNAPSHOT
+26 -25
View File
@@ -1,25 +1,26 @@
[versions]
configurate3 = "3.7.3"
configurate4 = "4.2.0"
configurate4 = "4.1.2"
flare = "2.0.1"
log4j = "2.26.0"
netty = "4.2.15.Final"
log4j = "2.24.3"
netty = "4.2.1.Final"
[plugins]
fill = "io.papermc.fill.gradle:1.0.12"
shadow = "com.gradleup.shadow:9.5.1"
spotless = "com.diffplug.spotless:8.2.0"
indra-publishing = "net.kyori.indra.publishing:2.0.6"
shadow = "io.github.goooler.shadow:8.1.5"
spotless = "com.diffplug.spotless:6.25.0"
[libraries]
adventure-bom = "net.kyori:adventure-bom:5.2.0"
adventure-text-serializer-json-legacy-impl = "net.kyori:adventure-text-serializer-json-legacy-impl:5.2.0"
asm = "org.ow2.asm:asm:9.9.1"
auto-service = "com.google.auto.service:auto-service:1.1.1"
auto-service-annotations = "com.google.auto.service:auto-service-annotations:1.1.1"
adventure-bom = "net.kyori:adventure-bom:4.21.0"
adventure-text-serializer-json-legacy-impl = "net.kyori:adventure-text-serializer-json-legacy-impl:4.21.0"
adventure-facet = "net.kyori:adventure-platform-facet:4.3.4"
asm = "org.ow2.asm:asm:9.8"
auto-service = "com.google.auto.service:auto-service:1.0.1"
auto-service-annotations = "com.google.auto.service:auto-service-annotations:1.0.1"
brigadier = "com.velocitypowered:velocity-brigadier:1.0.0-SNAPSHOT"
bstats = "org.bstats:bstats-base:3.1.0"
caffeine = "com.github.ben-manes.caffeine:caffeine:3.2.3"
checker-qual = "org.checkerframework:checker-qual:3.53.0"
bstats = "org.bstats:bstats-base:3.0.3"
caffeine = "com.github.ben-manes.caffeine:caffeine:3.1.8"
checker-qual = "org.checkerframework:checker-qual:3.42.0"
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" }
@@ -29,24 +30,24 @@ configurate4-hocon = { module = "org.spongepowered:configurate-hocon", version.r
configurate4-yaml = { module = "org.spongepowered:configurate-yaml", version.ref = "configurate4" }
configurate4-gson = { module = "org.spongepowered:configurate-gson", version.ref = "configurate4" }
disruptor = "com.lmax:disruptor:4.0.0"
fastutil = "it.unimi.dsi:fastutil:8.5.18"
fastutil = "it.unimi.dsi:fastutil:8.5.15"
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-jansi:3.30.6"
jline = "org.jline:jline-terminal-jansi:3.30.2"
jopt = "net.sf.jopt-simple:jopt-simple:5.0.4"
junit = "org.junit.jupiter:junit-jupiter:6.0.3"
jspecify = "org.jspecify:jspecify:1.0.0"
junit = "org.junit.jupiter:junit-jupiter:5.10.2"
jspecify = "org.jspecify:jspecify:0.3.0"
kyori-ansi = "net.kyori:ansi:1.1.1"
guava = "com.google.guava:guava:33.6.0-jre"
gson = "com.google.code.gson:gson:2.14.0"
guice = "com.google.inject:guice:7.0.0"
guava = "com.google.guava:guava:25.1-jre"
gson = "com.google.code.gson:gson:2.10.1"
guice = "com.google.inject:guice:6.0.0"
lmbda = "org.lanternpowered:lmbda:2.0.0"
log4j-api = { module = "org.apache.logging.log4j:log4j-api", version.ref = "log4j" }
log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" }
log4j-slf4j-impl = { module = "org.apache.logging.log4j:log4j-slf4j2-impl", version.ref = "log4j" }
log4j-iostreams = { module = "org.apache.logging.log4j:log4j-iostreams", version.ref = "log4j" }
log4j-jul = { module = "org.apache.logging.log4j:log4j-jul", version.ref = "log4j" }
mockito = "org.mockito:mockito-core:5.22.0"
mockito = "org.mockito:mockito-core:5.10.0"
netty-codec = { module = "io.netty:netty-codec", version.ref = "netty" }
netty-codec-haproxy = { module = "io.netty:netty-codec-haproxy", version.ref = "netty" }
netty-codec-http = { module = "io.netty:netty-codec-http", version.ref = "netty" }
@@ -54,10 +55,10 @@ netty-handler = { module = "io.netty:netty-handler", version.ref = "netty" }
netty-transport-native-epoll = { module = "io.netty:netty-transport-native-epoll", version.ref = "netty" }
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.8.3"
nightconfig = "com.electronwill.night-config:toml:3.6.7"
slf4j = "org.slf4j:slf4j-api:2.0.17"
snakeyaml = "org.yaml:snakeyaml:2.5"
spotbugs-annotations = "com.github.spotbugs:spotbugs-annotations:4.9.8"
snakeyaml = "org.yaml:snakeyaml:1.33"
spotbugs-annotations = "com.github.spotbugs:spotbugs-annotations:4.7.3"
terminalconsoleappender = "net.minecrell:terminalconsoleappender:1.3.0"
[bundles]
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+10 -6
View File
@@ -1,7 +1,7 @@
#!/bin/sh
#
# Copyright © 2015 the original authors.
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -86,7 +86,8 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -114,6 +115,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
@@ -171,6 +173,7 @@ fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
@@ -200,17 +203,18 @@ fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
DEFAULT_JVM_OPTS='-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
@@ -245,4 +249,4 @@ eval "set -- $(
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
exec "$JAVACMD" "$@"
Vendored
+4 -3
View File
@@ -36,7 +36,7 @@ set APP_HOME=%DIRNAME%
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
set DEFAULT_JVM_OPTS=-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
@@ -70,10 +70,11 @@ goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
@@ -90,4 +91,4 @@ exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
:omega
+3 -26
View File
@@ -1,16 +1,14 @@
import com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCacheFileTransformer
import io.papermc.fill.model.BuildChannel
plugins {
application
id("velocity-init-manifest")
alias(libs.plugins.shadow)
alias(libs.plugins.fill)
}
application {
mainClass.set("com.velocitypowered.proxy.Velocity")
applicationDefaultJvmArgs += listOf("-Dvelocity.packet-decode-logging=true")
applicationDefaultJvmArgs += listOf("-Dvelocity.packet-decode-logging=true");
}
tasks {
@@ -27,10 +25,6 @@ tasks {
}
shadowJar {
filesMatching("META-INF/org/apache/logging/log4j/core/config/plugins/**") {
duplicatesStrategy = DuplicatesStrategy.INCLUDE
}
transform(Log4j2PluginsCacheFileTransformer::class.java)
// Exclude all the collection types we don"t intend to use
@@ -114,27 +108,10 @@ tasks {
}
}
val projectVersion = version as String
fill {
project("velocity")
build {
channel = BuildChannel.STABLE
versionFamily("3.0.0")
version(projectVersion)
downloads {
register("server:default") {
file = tasks.shadowJar.flatMap { it.archiveFile }
nameResolver.set { project, _, version, build -> "$project-$version-$build.jar" }
}
}
}
}
dependencies {
implementation(project(":velocity-api"))
implementation(project(":velocity-native"))
implementation(project(":velocity-proxy-log4j2-plugin"))
implementation(libs.bundles.log4j)
implementation(libs.kyori.ansi)
@@ -159,6 +136,7 @@ dependencies {
implementation(libs.fastutil)
implementation(platform(libs.adventure.bom))
implementation(libs.adventure.text.serializer.json.legacy.impl)
implementation(libs.adventure.facet)
implementation(libs.completablefutures)
implementation(libs.nightconfig)
implementation(libs.bstats)
@@ -170,5 +148,4 @@ dependencies {
testImplementation(libs.mockito)
annotationProcessor(libs.auto.service)
annotationProcessor(libs.log4j.core)
}
+4
View File
@@ -0,0 +1,4 @@
dependencies {
implementation(libs.bundles.log4j)
annotationProcessor(libs.log4j.core)
}
@@ -47,6 +47,11 @@ public class Velocity {
System.setProperty("io.netty.native.workdir", System.getProperty("velocity.natives-tmpdir"));
}
// Restore allocator used before Netty 4.2 due to oom issues with the adaptive allocator
if (System.getProperty("io.netty.allocator.type") == null) {
System.setProperty("io.netty.allocator.type", "pooled");
}
// Disable the resource leak detector by default as it reduces performance. Allow the user to
// override this if desired.
if (!VelocityProperties.hasProperty("io.netty.leakDetection.level")) {
@@ -24,7 +24,6 @@ import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.velocitypowered.api.command.BrigadierCommand;
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
import com.velocitypowered.api.event.proxy.ProxyPreShutdownEvent;
import com.velocitypowered.api.event.proxy.ProxyReloadEvent;
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
import com.velocitypowered.api.network.ProtocolVersion;
@@ -82,6 +81,7 @@ import java.net.http.HttpClient;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyPair;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -104,8 +104,8 @@ import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.audience.ForwardingAudience;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.translation.MiniMessageTranslationStore;
import net.kyori.adventure.translation.GlobalTranslator;
import net.kyori.adventure.translation.TranslationStore;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bstats.MetricsBase;
@@ -119,7 +119,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
*/
public class VelocityServer implements ProxyServer, ForwardingAudience {
public static final String VELOCITY_URL = "https://papermc.io/software/velocity";
public static final String VELOCITY_URL = "https://velocitypowered.com";
private static final Logger logger = LogManager.getLogger(VelocityServer.class);
public static final Gson GENERAL_GSON = new GsonBuilder()
@@ -150,8 +150,6 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
)
.registerTypeHierarchyAdapter(Favicon.class, FaviconSerializer.INSTANCE)
.create();
private static final int PRE_SHUTDOWN_TIMEOUT =
Integer.getInteger("velocity.pre-shutdown-timeout", 10);
private final ConnectionManager cm;
private final ProxyOptions options;
@@ -165,8 +163,6 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
private final Map<UUID, ConnectedPlayer> connectionsByUuid = new ConcurrentHashMap<>();
private final Map<String, ConnectedPlayer> connectionsByName = new ConcurrentHashMap<>();
private final Object sessionIdLock = new Object();
private volatile @Nullable UUID sessionId;
private final VelocityConsole console;
private @MonotonicNonNull Ratelimiter<InetAddress> ipAttemptLimiter;
private @MonotonicNonNull Ratelimiter<UUID> commandRateLimiter;
@@ -220,8 +216,7 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
ProxyVersion version = getVersion();
PluginDescription description = new VelocityPluginDescription(
"velocity", version.getName(), version.getVersion(), "The Velocity proxy",
version.getName().equals("Velocity") ? VELOCITY_URL : null,
ImmutableList.of(version.getVendor()), Collections.emptyList(), null);
VELOCITY_URL, ImmutableList.of(version.getVendor()), Collections.emptyList(), null);
VelocityPluginContainer container = new VelocityPluginContainer(description);
container.setInstance(VelocityVirtualPlugin.INSTANCE);
return container;
@@ -243,6 +238,8 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
console.setupStreams();
pluginManager.registerPlugin(this.createVirtualPlugin());
registerTranslations();
// Yes, you're reading that correctly. We're generating a 1024-bit RSA keypair. Sounds
// dangerous, right? We're well within the realm of factoring such a key...
//
@@ -291,8 +288,6 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
this.doStartupConfigLoad();
registerTranslations();
for (ServerInfo cliServer : options.getServers()) {
servers.register(cliServer);
}
@@ -343,8 +338,8 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
}
private void registerTranslations() {
final MiniMessageTranslationStore translationRegistry =
MiniMessageTranslationStore.create(Key.key("velocity", "translations"));
final TranslationStore.StringBased<MessageFormat> translationRegistry =
TranslationStore.messageFormat(Key.key("velocity", "translations"));
translationRegistry.defaultLocale(Locale.US);
try {
ResourceUtils.visitResources(VelocityServer.class, path -> {
@@ -583,20 +578,6 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
// done first to refuse new connections
cm.shutdown();
try {
eventManager.fire(new ProxyPreShutdownEvent())
.toCompletableFuture()
.get(PRE_SHUTDOWN_TIMEOUT, TimeUnit.SECONDS);
} catch (TimeoutException ignored) {
logger.warn("Your plugins took over {} seconds during pre shutdown.",
PRE_SHUTDOWN_TIMEOUT);
} catch (ExecutionException ee) {
logger.error("Exception in ProxyPreShutdownEvent handler; continuing shutdown.", ee);
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
logger.warn("Interrupted while waiting for ProxyPreShutdownEvent; continuing shutdown.");
}
ImmutableList<ConnectedPlayer> players = ImmutableList.copyOf(connectionsByUuid.values());
for (ConnectedPlayer player : players) {
player.disconnect(reason);
@@ -745,36 +726,6 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
connectionsByName.remove(connection.getUsername().toLowerCase(Locale.US), connection);
connectionsByUuid.remove(connection.getUniqueId(), connection);
connection.disconnected();
if (this.sessionId != null && connectionsByUuid.isEmpty()) {
synchronized (this.sessionIdLock) {
if (connectionsByUuid.isEmpty()) {
this.sessionId = null;
}
}
}
}
/**
* Returns the metrics session ID for this proxy, generating one if none is currently active. The
* ID is shared by every player connected during a populated period and is regenerated once the
* proxy empties.
*
* @return the current session ID
*/
public UUID getSessionId() {
UUID uuid = this.sessionId;
if (uuid != null) {
return uuid;
}
synchronized (this.sessionIdLock) {
uuid = this.sessionId;
if (uuid == null) {
uuid = UUID.randomUUID();
this.sessionId = uuid;
}
return uuid;
}
}
@Override
@@ -866,7 +817,7 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
public VelocityChannelRegistrar getChannelRegistrar() {
return channelRegistrar;
}
@Override
public boolean isShuttingDown() {
return shutdownInProgress.get();
@@ -53,23 +53,15 @@ public final class VelocityBossBarImplementation implements BossBar.Listener,
viewer.getProtocolVersion(),
viewer.translateMessage(this.bar.name())
);
viewer.getBossBarManager().writeUpdate(this, BossBarPacket.createAddPacket(this.id, this.bar, name));
viewer.getConnection().write(BossBarPacket.createAddPacket(this.id, this.bar, name));
return true;
}
return false;
}
public void createDirect(final ConnectedPlayer viewer) {
final ComponentHolder name = new ComponentHolder(
viewer.getProtocolVersion(),
viewer.translateMessage(this.bar.name())
);
viewer.getConnection().write(BossBarPacket.createAddPacket(this.id, this.bar, name));
}
public boolean viewerRemove(final ConnectedPlayer viewer) {
if (this.viewers.remove(viewer)) {
viewer.getBossBarManager().remove(this, BossBarPacket.createRemovePacket(this.id, this.bar));
viewer.getConnection().write(BossBarPacket.createRemovePacket(this.id, this.bar));
return true;
}
return false;
@@ -92,7 +84,7 @@ public final class VelocityBossBarImplementation implements BossBar.Listener,
this.bar,
new ComponentHolder(viewer.getProtocolVersion(), translated)
);
viewer.getBossBarManager().writeUpdate(this, packet);
viewer.getConnection().write(packet);
}
}
@@ -104,7 +96,7 @@ public final class VelocityBossBarImplementation implements BossBar.Listener,
) {
final BossBarPacket packet = BossBarPacket.createUpdateProgressPacket(this.id, this.bar);
for (final ConnectedPlayer viewer : this.viewers) {
viewer.getBossBarManager().writeUpdate(this, packet);
viewer.getConnection().write(packet);
}
}
@@ -116,7 +108,7 @@ public final class VelocityBossBarImplementation implements BossBar.Listener,
) {
final BossBarPacket packet = BossBarPacket.createUpdateStylePacket(this.id, this.bar);
for (final ConnectedPlayer viewer : this.viewers) {
viewer.getBossBarManager().writeUpdate(this, packet);
viewer.getConnection().write(packet);
}
}
@@ -128,7 +120,7 @@ public final class VelocityBossBarImplementation implements BossBar.Listener,
) {
final BossBarPacket packet = BossBarPacket.createUpdateStylePacket(this.id, this.bar);
for (final ConnectedPlayer viewer : this.viewers) {
viewer.getBossBarManager().writeUpdate(this, packet);
viewer.getConnection().write(packet);
}
}
@@ -140,7 +132,7 @@ public final class VelocityBossBarImplementation implements BossBar.Listener,
) {
final BossBarPacket packet = BossBarPacket.createUpdatePropertiesPacket(this.id, this.bar);
for (final ConnectedPlayer viewer : this.viewers) {
viewer.getBossBarManager().writeUpdate(this, packet);
viewer.getConnection().write(packet);
}
}
}
@@ -344,7 +344,7 @@ final class SuggestionsProvider<S> {
return 0;
});
}
return potentials.getFirst();
return potentials.get(0);
}
return new ParseResults<>(contextSoFar, originalReader, Collections.emptyMap());
}
@@ -35,6 +35,7 @@ import com.velocitypowered.api.command.CommandManager;
import com.velocitypowered.api.command.CommandMeta;
import com.velocitypowered.api.command.CommandResult;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.command.VelocityBrigadierMessage;
import com.velocitypowered.api.event.command.CommandExecuteEvent;
import com.velocitypowered.api.event.command.PostCommandInvocationEvent;
import com.velocitypowered.api.plugin.PluginManager;
@@ -58,7 +59,6 @@ import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.format.NamedTextColor;
import org.checkerframework.checker.lock.qual.GuardedBy;
import org.checkerframework.checker.nullness.qual.Nullable;
@@ -140,7 +140,7 @@ public class VelocityCommandManager implements CommandManager {
command + " implements multiple registrable Command subinterfaces: "
+ implementedInterfaces);
} else {
this.internalRegister(commandRegistrars.getFirst(), command, meta);
this.internalRegister(commandRegistrars.get(0), command, meta);
}
}
@@ -242,8 +242,8 @@ public class VelocityCommandManager implements CommandManager {
CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownCommand());
if (isSyntaxError) {
final Message message = e.getRawMessage();
if (message instanceof ComponentLike componentLike) {
source.sendMessage(componentLike.asComponent().applyFallbackStyle(NamedTextColor.RED));
if (message instanceof VelocityBrigadierMessage velocityMessage) {
source.sendMessage(velocityMessage.asComponent().applyFallbackStyle(NamedTextColor.RED));
} else {
source.sendMessage(Component.text(e.getMessage(), NamedTextColor.RED));
}
@@ -256,7 +256,7 @@ public class VelocityCommandManager implements CommandManager {
}
} catch (final Throwable e) {
// Ugly, ugly swallowing of everything Throwable, because plugins are naughty.
throw new RuntimeException("Unable to invoke command " + parsed.getReader().getString() + " for " + source, e);
throw new RuntimeException("Unable to invoke command " + parsed.getReader().getString() + "for " + source, e);
} finally {
eventManager.fireAndForget(new PostCommandInvocationEvent(source, parsed.getReader().getString(), result));
}
@@ -400,4 +400,4 @@ public class VelocityCommandManager implements CommandManager {
return MoreExecutors.directExecutor();
}
}
}
}
@@ -70,34 +70,33 @@ public final class VelocityCommands {
maybeCommand = VelocityBrigadierCommandWrapper.wrap(delegate.getCommand(), registrant);
}
return switch (delegate) {
case LiteralCommandNode<CommandSource> lcn -> {
var literalBuilder = shallowCopyAsBuilder(lcn, delegate.getName(), true);
literalBuilder.executes(maybeCommand);
// we also need to wrap any children
for (final CommandNode<CommandSource> child : delegate.getChildren()) {
literalBuilder.then(wrap(child, registrant));
}
if (delegate.getRedirect() != null) {
literalBuilder.redirect(wrap(delegate.getRedirect(), registrant));
}
yield literalBuilder.build();
if (delegate instanceof LiteralCommandNode<CommandSource> lcn) {
var literalBuilder = shallowCopyAsBuilder(lcn, delegate.getName(), true);
literalBuilder.executes(maybeCommand);
// we also need to wrap any children
for (final CommandNode<CommandSource> child : delegate.getChildren()) {
literalBuilder.then(wrap(child, registrant));
}
case VelocityArgumentCommandNode<CommandSource, ?> vacn -> vacn.withCommand(maybeCommand)
.withRedirect(delegate.getRedirect() != null ? wrap(delegate.getRedirect(), registrant) : null);
case ArgumentCommandNode<CommandSource, ?> node -> {
var argBuilder = node.createBuilder().executes(maybeCommand);
// we also need to wrap any children
for (final CommandNode<CommandSource> child : delegate.getChildren()) {
argBuilder.then(wrap(child, registrant));
}
if (delegate.getRedirect() != null) {
argBuilder.redirect(wrap(delegate.getRedirect(), registrant));
}
yield argBuilder.build();
if (delegate.getRedirect() != null) {
literalBuilder.redirect(wrap(delegate.getRedirect(), registrant));
}
default -> throw new IllegalArgumentException("Unsupported node type: " + delegate.getClass());
};
return literalBuilder.build();
} else if (delegate instanceof VelocityArgumentCommandNode<CommandSource, ?> vacn) {
return vacn.withCommand(maybeCommand)
.withRedirect(delegate.getRedirect() != null ? wrap(delegate.getRedirect(), registrant) : null);
} else if (delegate instanceof ArgumentCommandNode) {
var argBuilder = delegate.createBuilder().executes(maybeCommand);
// we also need to wrap any children
for (final CommandNode<CommandSource> child : delegate.getChildren()) {
argBuilder.then(wrap(child, registrant));
}
if (delegate.getRedirect() != null) {
argBuilder.redirect(wrap(delegate.getRedirect(), registrant));
}
return argBuilder.build();
} else {
throw new IllegalArgumentException("Unsupported node type: " + delegate.getClass());
}
}
// Normalization
@@ -134,7 +133,7 @@ public final class VelocityCommands {
if (nodes.isEmpty()) {
throw new IllegalArgumentException("Cannot read alias from empty node list");
}
return nodes.getFirst().getNode().getName();
return nodes.get(0).getNode().getName();
}
public static final String ARGS_NODE_NAME = "arguments";
@@ -118,12 +118,14 @@ public class VelocityArgumentCommandNode<S, T> extends ArgumentCommandNode<S, St
if (this == o) {
return true;
}
if (!(o instanceof VelocityArgumentCommandNode that)) {
if (!(o instanceof VelocityArgumentCommandNode)) {
return false;
}
if (!super.equals(that)) {
if (!super.equals(o)) {
return false;
}
final VelocityArgumentCommandNode<?, ?> that = (VelocityArgumentCommandNode<?, ?>) o;
return this.type.equals(that.type);
}
@@ -38,7 +38,6 @@ import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.TranslatableComponent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.minimessage.translation.Argument;
/**
* Implements the Velocity default {@code /glist} command.
@@ -112,7 +111,7 @@ public class GlistCommand {
if (registeredServer.isEmpty()) {
source.sendMessage(
CommandMessages.SERVER_DOES_NOT_EXIST
.arguments(Argument.string("server", serverName)));
.arguments(Component.text(serverName)));
return -1;
}
sendServerPlayers(source, registeredServer.get(), false);
@@ -127,8 +126,7 @@ public class GlistCommand {
? "velocity.command.glist-player-singular"
: "velocity.command.glist-player-plural"
).color(NamedTextColor.YELLOW)
.arguments(Argument.component(
"players", Component.text(Integer.toString(online), NamedTextColor.GREEN)));
.arguments(Component.text(Integer.toString(online), NamedTextColor.GREEN));
target.sendMessage(msg.build());
}
@@ -35,7 +35,6 @@ import java.util.Objects;
import java.util.Optional;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.minimessage.translation.Argument;
/**
* Implements the Velocity default {@code /send} command.
@@ -122,7 +121,7 @@ public class SendCommand {
if (maybeServer.isEmpty()) {
context.getSource().sendMessage(
CommandMessages.SERVER_DOES_NOT_EXIST.arguments(Argument.string("server", serverName))
CommandMessages.SERVER_DOES_NOT_EXIST.arguments(Component.text(serverName))
);
return 0;
}
@@ -134,7 +133,7 @@ public class SendCommand {
&& !Objects.equals(player, "all")
&& !Objects.equals(player, "current")) {
context.getSource().sendMessage(
CommandMessages.PLAYER_NOT_FOUND.arguments(Argument.string("player", player))
CommandMessages.PLAYER_NOT_FOUND.arguments(Component.text(player))
);
return 0;
}
@@ -37,7 +37,6 @@ import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.TranslatableComponent;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.minimessage.translation.Argument;
/**
* Implements Velocity's {@code /server} command.
@@ -77,7 +76,7 @@ public final class ServerCommand {
final Optional<RegisteredServer> toConnect = server.getServer(serverName);
if (toConnect.isEmpty()) {
player.sendMessage(CommandMessages.SERVER_DOES_NOT_EXIST
.arguments(Argument.string("server", serverName)));
.arguments(Component.text(serverName)));
return -1;
}
@@ -136,7 +135,7 @@ public final class ServerCommand {
} else {
playersTextComponent.key("velocity.command.server-tooltip-players-online");
}
playersTextComponent.arguments(Argument.component("players", Component.text(connectedPlayers)));
playersTextComponent.arguments(Component.text(connectedPlayers));
if (serverInfo.getName().equals(currentPlayerServer)) {
serverTextComponent.color(NamedTextColor.GREEN)
.hoverEvent(
@@ -62,7 +62,6 @@ import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.text.minimessage.translation.Argument;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -166,9 +165,9 @@ public final class VelocityCommand {
.build();
final Component copyright = Component
.translatable("velocity.command.version-copyright",
Argument.string("vendor", version.getVendor()),
Argument.string("name", version.getName()),
Argument.component("year", Component.text(LocalDate.now().getYear())));
Component.text(version.getVendor()),
Component.text(version.getName()),
Component.text(LocalDate.now().getYear()));
source.sendMessage(velocity);
source.sendMessage(copyright);
@@ -177,7 +176,8 @@ public final class VelocityCommand {
.append(Component.text()
.content("PaperMC")
.color(NamedTextColor.GREEN)
.clickEvent(ClickEvent.openUrl(VelocityServer.VELOCITY_URL))
.clickEvent(
ClickEvent.openUrl("https://papermc.io/software/velocity"))
.build())
.append(Component.text(" - "))
.append(Component.text()
@@ -221,7 +221,7 @@ public final class VelocityCommand {
final TranslatableComponent output = Component.translatable()
.key("velocity.command.plugins-list")
.color(NamedTextColor.YELLOW)
.arguments(Argument.component("plugins", listBuilder.build()))
.arguments(listBuilder.build())
.build();
source.sendMessage(output);
return Command.SINGLE_SUCCESS;
@@ -237,17 +237,17 @@ public final class VelocityCommand {
hoverText.append(Component.newline());
hoverText.append(Component.translatable(
"velocity.command.plugin-tooltip-website",
Argument.component("url", Component.text(url))));
Component.text(url)));
});
if (!description.getAuthors().isEmpty()) {
hoverText.append(Component.newline());
if (description.getAuthors().size() == 1) {
hoverText.append(Component.translatable("velocity.command.plugin-tooltip-author",
Component.text(description.getAuthors().getFirst())));
Component.text(description.getAuthors().get(0))));
} else {
hoverText.append(
Component.translatable("velocity.command.plugin-tooltip-author",
Argument.string("authors", String.join(", ", description.getAuthors()))
Component.text(String.join(", ", description.getAuthors()))
)
);
}
@@ -103,17 +103,13 @@ abstract class InvocableCommandRegistrar<T extends InvocableCommand<I>,
.requiresWithContext((context, reader) -> requirement.test(context))
.executes(callback)
.suggests((context, builder) -> {
// Offset the suggestion to the last space seperated word
int lastSpace = builder.getRemaining().lastIndexOf(' ') + 1;
final var offsetBuilder = builder.createOffset(builder.getStart() + lastSpace);
final I invocation = invocationFactory.create(context);
return command.suggestAsync(invocation).thenApply(suggestions -> {
for (String value : suggestions) {
Preconditions.checkNotNull(value, "suggestion");
offsetBuilder.suggest(value);
builder.suggest(value);
}
return offsetBuilder.build();
return builder.build();
});
})
.build();
@@ -29,9 +29,7 @@ import com.velocitypowered.api.util.Favicon;
import com.velocitypowered.proxy.config.migration.ConfigurationMigration;
import com.velocitypowered.proxy.config.migration.ForwardingMigration;
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.TransferIntegrationMigration;
import com.velocitypowered.proxy.util.AddressUtil;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
@@ -95,8 +93,6 @@ public class VelocityConfiguration implements ProxyConfig {
private @Nullable Favicon favicon;
@Expose
private boolean forceKeyAuthentication = true; // Added in 1.19
@Expose
private PacketLimiterConfig packetLimiterConfig = PacketLimiterConfig.DEFAULT;
private VelocityConfiguration(Servers servers, ForcedHosts forcedHosts, Advanced advanced,
Query query, Metrics metrics) {
@@ -113,7 +109,7 @@ public class VelocityConfiguration implements ProxyConfig {
boolean onlineModeKickExistingPlayers, PingPassthroughMode pingPassthrough,
boolean samplePlayersInPing, boolean enablePlayerAddressLogging, Servers servers,
ForcedHosts forcedHosts, Advanced advanced, Query query, Metrics metrics,
boolean forceKeyAuthentication, PacketLimiterConfig packetLimiterConfig) {
boolean forceKeyAuthentication) {
this.bind = bind;
this.motd = motd;
this.showMaxPlayers = showMaxPlayers;
@@ -132,7 +128,6 @@ public class VelocityConfiguration implements ProxyConfig {
this.query = query;
this.metrics = metrics;
this.forceKeyAuthentication = forceKeyAuthentication;
this.packetLimiterConfig = packetLimiterConfig;
}
/**
@@ -161,16 +156,19 @@ public class VelocityConfiguration implements ProxyConfig {
}
switch (playerInfoForwardingMode) {
case NONE -> logger.warn("Player info forwarding is disabled! All players will appear to be connecting "
case NONE:
logger.warn("Player info forwarding is disabled! All players will appear to be connecting "
+ "from the proxy and will have offline-mode UUIDs.");
case MODERN, BUNGEEGUARD -> {
break;
case MODERN:
case BUNGEEGUARD:
if (forwardingSecret == null || forwardingSecret.length == 0) {
logger.error("You don't have a forwarding secret set. This is required for security.");
valid = false;
}
}
default -> {
}
break;
default:
break;
}
if (servers.getServers().isEmpty()) {
@@ -451,10 +449,6 @@ public class VelocityConfiguration implements ProxyConfig {
return advanced.isEnableReusePort();
}
public PacketLimiterConfig getPacketLimiterConfig() {
return packetLimiterConfig;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
@@ -472,7 +466,6 @@ public class VelocityConfiguration implements ProxyConfig {
.add("favicon", favicon)
.add("enablePlayerAddressLogging", enablePlayerAddressLogging)
.add("forceKeyAuthentication", forceKeyAuthentication)
.add("packetLimiterConfig", packetLimiterConfig)
.toString();
}
@@ -511,9 +504,7 @@ public class VelocityConfiguration implements ProxyConfig {
new ForwardingMigration(),
new KeyAuthenticationMigration(),
new MotdMigration(),
new MiniMessageTranslationsMigration(),
new TransferIntegrationMigration(),
new PacketLimiterMigration()
new TransferIntegrationMigration()
};
for (final ConfigurationMigration migration : migrations) {
@@ -524,7 +515,7 @@ public class VelocityConfiguration implements ProxyConfig {
String forwardingSecretString = System.getenv().getOrDefault(
"VELOCITY_FORWARDING_SECRET", "");
if (forwardingSecretString.isBlank()) {
if (forwardingSecretString.isEmpty()) {
final String forwardSecretFile = config.get("forwarding-secret-file");
final Path secretPath = forwardSecretFile == null
? defaultForwardingSecretPath
@@ -537,11 +528,7 @@ public class VelocityConfiguration implements ProxyConfig {
"The file " + forwardSecretFile + " is not a valid file or it is a directory.");
}
} else {
Files.createFile(secretPath);
Files.writeString(secretPath, forwardingSecretString = generateRandomString(12),
StandardCharsets.UTF_8);
logger.info("The forwarding-secret-file does not exist. A new file has been created at {}",
forwardSecretFile);
throw new RuntimeException("The forwarding-secret-file does not exist.");
}
}
final byte[] forwardingSecret = forwardingSecretString.getBytes(StandardCharsets.UTF_8);
@@ -570,7 +557,6 @@ public class VelocityConfiguration implements ProxyConfig {
final boolean kickExisting = config.getOrElse("kick-existing-players", false);
final boolean enablePlayerAddressLogging = config.getOrElse(
"enable-player-address-logging", true);
final PacketLimiterConfig packetLimiterConfig = PacketLimiterConfig.fromConfig(config.get("packet-limiter"));
// Throw an exception if the forwarding-secret file is empty and the proxy is using a
// forwarding mode that requires it.
@@ -598,8 +584,7 @@ public class VelocityConfiguration implements ProxyConfig {
new Advanced(advancedConfig),
new Query(queryConfig),
new Metrics(metricsConfig),
forceKeyAuthentication,
packetLimiterConfig
forceKeyAuthentication
);
}
}
@@ -1002,35 +987,4 @@ public class VelocityConfiguration implements ProxyConfig {
return enabled;
}
}
/**
* Configuration for packet limiting.
*
* @param interval the interval in seconds to measure packets over
* @param pps the maximum number of packets per second allowed
* @param bytes the maximum number of bytes per second allowed
* @param bytesAfterDecompression the maximum number of decompressed bytes per second allowed
*/
public record PacketLimiterConfig(int interval, int pps, int bytes, int bytesAfterDecompression) {
public static PacketLimiterConfig DEFAULT = new PacketLimiterConfig(7, -1, -1, 5242880);
/**
* returns a PacketLimiterConfig from a config section, or the default if the section is null.
*
* @param config the configuration object to parse
* @return the packet limiter config, or the default if {@code config} is null
*/
public static PacketLimiterConfig fromConfig(CommentedConfig config) {
if (config != null) {
return new PacketLimiterConfig(
config.getIntOrElse("interval", DEFAULT.interval()),
config.getIntOrElse("packets-per-second", DEFAULT.pps()),
config.getIntOrElse("bytes-per-second", DEFAULT.bytes()),
config.getIntOrElse("decompressed-bytes-per-second", DEFAULT.bytesAfterDecompression())
);
} else {
return DEFAULT;
}
}
}
}
@@ -28,9 +28,7 @@ public sealed interface ConfigurationMigration
permits ForwardingMigration,
KeyAuthenticationMigration,
MotdMigration,
MiniMessageTranslationsMigration,
TransferIntegrationMigration,
PacketLimiterMigration {
TransferIntegrationMigration {
boolean shouldMigrate(CommentedFileConfig config);
void migrate(CommentedFileConfig config, Logger logger) throws IOException;
@@ -1,65 +0,0 @@
/*
* Copyright (C) 2024 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.config.migration;
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.regex.Pattern;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.apache.logging.log4j.Logger;
/**
* Migration from old to modern language argument format with MiniMessage.
* Also migrates possible use of legacy colors to MiniMessage format.
*/
public final class MiniMessageTranslationsMigration implements ConfigurationMigration {
@Override
public boolean shouldMigrate(final CommentedFileConfig config) {
// Checking whether translations should be migrated would be just as costly as attempting to migrate them directly.
return true;
}
@Override
public void migrate(final CommentedFileConfig config, final Logger logger) throws IOException {
final Path langFolder = Path.of("lang");
if (Files.notExists(langFolder)) {
return;
}
final Pattern oldPlaceholderPattern = Pattern.compile("\\{(\\d+)}");
try (final DirectoryStream<Path> stream
= Files.newDirectoryStream(langFolder, Files::isRegularFile)) {
for (final Path path : stream) {
String content = Files.readString(path, StandardCharsets.UTF_8);
if (content.indexOf('{') == -1) {
continue;
}
// Migrate old arguments
content = oldPlaceholderPattern.matcher(content).replaceAll("<arg:$1>");
// Some setups use legacy color codes, this format is migrated to MiniMessage
content = MiniMessage.miniMessage().serialize(
LegacyComponentSerializer.legacySection().deserialize(content));
Files.writeString(path, content, StandardCharsets.UTF_8);
}
}
}
}
@@ -1,62 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.config.migration;
import static com.velocitypowered.proxy.config.VelocityConfiguration.PacketLimiterConfig.DEFAULT;
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import org.apache.logging.log4j.Logger;
/**
* Configuration migration for the new [packet-limiter] section.
* Config version 2.7 may contain this section with only the `interval`, `packets-per-second`
* and `bytes-per-second` attributes. Config version 2.8 enforces these exist, adds the new
* `decompressed-bytes-per-second` attribute, adjusts the new default, and adds comments.
*/
public final class PacketLimiterMigration implements ConfigurationMigration {
@Override
public boolean shouldMigrate(CommentedFileConfig config) {
return configVersion(config) < 2.8;
}
@Override
public void migrate(CommentedFileConfig config, Logger logger) {
config.set("packet-limiter.interval", DEFAULT.interval());
config.set("packet-limiter.packets-per-second", DEFAULT.pps());
config.set("packet-limiter.bytes-per-second", DEFAULT.bytes());
config.set("packet-limiter.decompressed-bytes-per-second", DEFAULT.bytesAfterDecompression());
config.setComment("packet-limiter.interval", """
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.""");
config.setComment("packet-limiter.packets-per-second", """
Maximum average number of packets per second a client may send. -1 disables this check.""");
config.setComment("packet-limiter.bytes-per-second", """
Maximum average number of compressed (on-wire) bytes per second a client may send. -1 disables this check.""");
config.setComment("packet-limiter.decompressed-bytes-per-second", """
Maximum average number of decompressed bytes per second a client may send.
Protects against compression bomb attacks where small packets expand to excessive sizes after decompression.
-1 disables this check.""");
config.set("config-version", "2.8");
}
}
@@ -33,14 +33,11 @@ import com.velocitypowered.natives.encryption.VelocityCipher;
import com.velocitypowered.natives.encryption.VelocityCipherFactory;
import com.velocitypowered.natives.util.Natives;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.connection.client.HandshakeSessionHandler;
import com.velocitypowered.proxy.connection.client.InitialLoginSessionHandler;
import com.velocitypowered.proxy.connection.client.StatusSessionHandler;
import com.velocitypowered.proxy.network.Connections;
import com.velocitypowered.proxy.network.limiter.SimpleBytesPerSecondLimiter;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.VelocityConnectionEvent;
import com.velocitypowered.proxy.protocol.netty.MinecraftCipherDecoder;
@@ -69,7 +66,7 @@ import io.netty.util.ReferenceCountUtil;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.security.GeneralSecurityException;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@@ -111,7 +108,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
this.server = server;
this.state = StateRegistry.HANDSHAKE;
this.sessionHandlers = new EnumMap<>(StateRegistry.class);
this.sessionHandlers = new HashMap<>();
}
@Override
@@ -156,13 +153,13 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
if (msg instanceof MinecraftPacket pkt) {
if (!pkt.handle(activeSessionHandler)) {
activeSessionHandler.handleGeneric(pkt);
activeSessionHandler.handleGeneric((MinecraftPacket) msg);
}
} else if (msg instanceof HAProxyMessage proxyMessage) {
this.remoteAddress = new InetSocketAddress(proxyMessage.sourceAddress(),
proxyMessage.sourcePort());
} else if (msg instanceof ByteBuf buf) {
activeSessionHandler.handleUnknown(buf);
} else if (msg instanceof ByteBuf) {
activeSessionHandler.handleUnknown((ByteBuf) msg);
}
} finally {
ReferenceCountUtil.release(msg);
@@ -371,7 +368,6 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
public void setState(StateRegistry state) {
ensureInEventLoop();
final StateRegistry previousState = this.state;
this.state = state;
final MinecraftVarintFrameDecoder frameDecoder = this.channel.pipeline()
.get(MinecraftVarintFrameDecoder.class);
@@ -392,13 +388,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
if (state == StateRegistry.CONFIG) {
// Activate the play packet queue
if (previousState == StateRegistry.PLAY
&& this.pendingConfigurationSwitch
&& this.association instanceof ConnectedPlayer) {
addPlayPacketQueueOutboundHandler();
} else {
addPlayPacketQueueHandler();
}
addPlayPacketQueueHandler();
} else {
// Remove the queue
if (this.channel.pipeline().get(Connections.PLAY_PACKET_QUEUE_OUTBOUND) != null) {
@@ -414,23 +404,13 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
* Adds the play packet queue handler.
*/
public void addPlayPacketQueueHandler() {
addPlayPacketQueueOutboundHandler();
if (this.channel.pipeline().get(Connections.PLAY_PACKET_QUEUE_INBOUND) == null) {
this.channel.pipeline().addAfter(Connections.MINECRAFT_DECODER, Connections.PLAY_PACKET_QUEUE_INBOUND,
new PlayPacketQueueInboundHandler(this.protocolVersion,
channel.pipeline().get(MinecraftDecoder.class).getDirection()));
}
}
/**
* Adds only the outbound play packet queue handler.
*/
public void addPlayPacketQueueOutboundHandler() {
if (this.channel.pipeline().get(Connections.PLAY_PACKET_QUEUE_OUTBOUND) == null) {
this.channel.pipeline().addAfter(Connections.MINECRAFT_ENCODER, Connections.PLAY_PACKET_QUEUE_OUTBOUND,
new PlayPacketQueueOutboundHandler(this.protocolVersion,
channel.pipeline().get(MinecraftEncoder.class).getDirection()));
new PlayPacketQueueOutboundHandler(this.protocolVersion, channel.pipeline().get(MinecraftEncoder.class).getDirection()));
}
if (this.channel.pipeline().get(Connections.PLAY_PACKET_QUEUE_INBOUND) == null) {
this.channel.pipeline().addAfter(Connections.MINECRAFT_DECODER, Connections.PLAY_PACKET_QUEUE_INBOUND,
new PlayPacketQueueInboundHandler(this.protocolVersion, channel.pipeline().get(MinecraftDecoder.class).getDirection()));
}
}
@@ -564,23 +544,14 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
} else {
int level = server.getConfiguration().getCompressionLevel();
VelocityCompressor compressor = Natives.compress.get().create(level);
final MinecraftDecoder minecraftDecoder = (MinecraftDecoder) channel.pipeline().get(MINECRAFT_DECODER);
encoder = new MinecraftCompressorAndLengthEncoder(threshold, compressor);
decoder = new MinecraftCompressDecoder(threshold, compressor, minecraftDecoder.getDirection());
decoder = new MinecraftCompressDecoder(threshold, compressor);
channel.pipeline().remove(FRAME_ENCODER);
channel.pipeline().addBefore(MINECRAFT_DECODER, COMPRESSION_DECODER, decoder);
channel.pipeline().addBefore(MINECRAFT_ENCODER, COMPRESSION_ENCODER, encoder);
var packetLimiterConfig = server.getConfiguration().getPacketLimiterConfig();
if (minecraftDecoder.getDirection() == ProtocolUtils.Direction.SERVERBOUND
&& packetLimiterConfig.interval() > 0
&& packetLimiterConfig.bytesAfterDecompression() > 0) {
decoder.setPacketLimiter(new SimpleBytesPerSecondLimiter(
-1, packetLimiterConfig.bytesAfterDecompression(), packetLimiterConfig.interval()));
}
channel.pipeline().fireUserEventTriggered(VelocityConnectionEvent.COMPRESSION_ENABLED);
}
}
@@ -23,11 +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.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;
@@ -52,7 +48,6 @@ 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.SetCompressionPacket;
import com.velocitypowered.proxy.protocol.packet.StatusPingPacket;
import com.velocitypowered.proxy.protocol.packet.StatusRequestPacket;
@@ -72,8 +67,6 @@ import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerComma
import com.velocitypowered.proxy.protocol.packet.config.ActiveFeaturesPacket;
import com.velocitypowered.proxy.protocol.packet.config.ClientboundCustomReportDetailsPacket;
import com.velocitypowered.proxy.protocol.packet.config.ClientboundServerLinksPacket;
import com.velocitypowered.proxy.protocol.packet.config.CodeOfConductAcceptPacket;
import com.velocitypowered.proxy.protocol.packet.config.CodeOfConductPacket;
import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket;
import com.velocitypowered.proxy.protocol.packet.config.KnownPacksPacket;
import com.velocitypowered.proxy.protocol.packet.config.RegistrySyncPacket;
@@ -371,32 +364,4 @@ public interface MinecraftSessionHandler {
default boolean handle(ClientboundServerLinksPacket packet) {
return false;
}
default boolean handle(DialogClearPacket packet) {
return false;
}
default boolean handle(DialogShowPacket packet) {
return false;
}
default boolean handle(ServerboundCustomClickActionPacket packet) {
return false;
}
default boolean handle(CodeOfConductPacket packet) {
return false;
}
default boolean handle(CodeOfConductAcceptPacket packet) {
return false;
}
default boolean handle(ClientboundSoundEntityPacket packet) {
return false;
}
default boolean handle(ClientboundStopSoundPacket packet) {
return false;
}
}
@@ -68,7 +68,6 @@ import com.velocitypowered.proxy.protocol.packet.TransferPacket;
import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfoPacket;
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
import com.velocitypowered.proxy.protocol.packet.config.StartUpdatePacket;
import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder;
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
@@ -92,7 +91,6 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
Boolean.getBoolean("velocity.log-server-backpressure");
private static final int MAXIMUM_PACKETS_TO_FLUSH =
Integer.getInteger("velocity.max-packets-per-flush", 8192);
private static final int LARGE_PACKET_THRESHOLD = 1024 * 128;
private final VelocityServer server;
private final VelocityServerConnection serverConn;
@@ -179,12 +177,10 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
@Override
public boolean handle(BossBarPacket packet) {
if (serverConn.getPlayer().getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
if (packet.getAction() == BossBarPacket.ADD) {
playerSessionHandler.getServerBossBars().add(packet.getUuid());
} else if (packet.getAction() == BossBarPacket.REMOVE) {
playerSessionHandler.getServerBossBars().remove(packet.getUuid());
}
if (packet.getAction() == BossBarPacket.ADD) {
playerSessionHandler.getServerBossBars().add(packet.getUuid());
} else if (packet.getAction() == BossBarPacket.REMOVE) {
playerSessionHandler.getServerBossBars().remove(packet.getUuid());
}
return false; // forward
}
@@ -363,12 +359,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
// Inject commands from the proxy.
final CommandGraphInjector<CommandSource> injector = server.getCommandManager().getInjector();
injector.inject(rootNode, serverConn.getPlayer());
// In 1.21.6 a confirmation prompt was added when executing a command via `run_command` click
// action if the command is unknown. To prevent this prompt we have to send the command.
if (this.playerConnection.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_21_6)) {
rootNode.removeChildByName("velocity:callback");
}
rootNode.removeChildByName("velocity:callback");
}
server.getEventManager().fire(
@@ -454,12 +445,11 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
@Override
public void handleGeneric(MinecraftPacket packet) {
if (packet instanceof PluginMessagePacket pluginMessage) {
pluginMessage.retain();
if (packet instanceof PluginMessagePacket) {
((PluginMessagePacket) packet).retain();
}
boolean huge = packet instanceof DeferredByteBufHolder def && def.content().readableBytes() > LARGE_PACKET_THRESHOLD;
playerConnection.delayedWrite(packet);
if (huge || ++packetsFlushed >= MAXIMUM_PACKETS_TO_FLUSH) {
if (++packetsFlushed >= MAXIMUM_PACKETS_TO_FLUSH) {
playerConnection.flush();
packetsFlushed = 0;
}
@@ -467,9 +457,8 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
@Override
public void handleUnknown(ByteBuf buf) {
boolean huge = buf.readableBytes() > LARGE_PACKET_THRESHOLD;
playerConnection.delayedWrite(buf.retain());
if (huge || ++packetsFlushed >= MAXIMUM_PACKETS_TO_FLUSH) {
if (++packetsFlushed >= MAXIMUM_PACKETS_TO_FLUSH) {
playerConnection.flush();
packetsFlushed = 0;
}
@@ -522,4 +511,4 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
playerConnection.setAutoReading(writable);
}
}
}
@@ -344,30 +344,66 @@ public class BungeeCordMessageResponder {
return false;
}
final ByteBufDataInput in = new ByteBufDataInput(message.content());
final String subChannel = in.readUTF();
ByteBufDataInput in = new ByteBufDataInput(message.content());
String subChannel = in.readUTF();
switch (subChannel) {
case "GetPlayerServer" -> this.processGetPlayerServer(in);
case "ForwardToPlayer" -> this.processForwardToPlayer(in);
case "Forward" -> this.processForwardToServer(in);
case "Connect" -> this.processConnect(in);
case "ConnectOther" -> this.processConnectOther(in);
case "IP" -> this.processIp(in);
case "PlayerCount" -> this.processPlayerCount(in);
case "PlayerList" -> this.processPlayerList(in);
case "GetServers" -> this.processGetServers();
case "Message" -> this.processMessage(in);
case "MessageRaw" -> this.processMessageRaw(in);
case "GetServer" -> this.processGetServer();
case "UUID" -> this.processUuid();
case "UUIDOther" -> this.processUuidOther(in);
case "IPOther" -> this.processIpOther(in);
case "ServerIP" -> this.processServerIp(in);
case "KickPlayer" -> this.processKick(in);
case "KickPlayerRaw" -> this.processKickRaw(in);
default -> {
// Do nothing, unknown command
}
case "GetPlayerServer":
this.processGetPlayerServer(in);
break;
case "ForwardToPlayer":
this.processForwardToPlayer(in);
break;
case "Forward":
this.processForwardToServer(in);
break;
case "Connect":
this.processConnect(in);
break;
case "ConnectOther":
this.processConnectOther(in);
break;
case "IP":
this.processIp(in);
break;
case "PlayerCount":
this.processPlayerCount(in);
break;
case "PlayerList":
this.processPlayerList(in);
break;
case "GetServers":
this.processGetServers();
break;
case "Message":
this.processMessage(in);
break;
case "MessageRaw":
this.processMessageRaw(in);
break;
case "GetServer":
this.processGetServer();
break;
case "UUID":
this.processUuid();
break;
case "UUIDOther":
this.processUuidOther(in);
break;
case "IPOther":
this.processIpOther(in);
break;
case "ServerIP":
this.processServerIp(in);
break;
case "KickPlayer":
this.processKick(in);
break;
case "KickPlayerRaw":
this.processKickRaw(in);
break;
default:
// Do nothing, unknown command
break;
}
return true;
@@ -52,7 +52,6 @@ import com.velocitypowered.proxy.protocol.packet.ResourcePackResponsePacket;
import com.velocitypowered.proxy.protocol.packet.TransferPacket;
import com.velocitypowered.proxy.protocol.packet.config.ClientboundCustomReportDetailsPacket;
import com.velocitypowered.proxy.protocol.packet.config.ClientboundServerLinksPacket;
import com.velocitypowered.proxy.protocol.packet.config.CodeOfConductPacket;
import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket;
import com.velocitypowered.proxy.protocol.packet.config.RegistrySyncPacket;
import com.velocitypowered.proxy.protocol.packet.config.StartUpdatePacket;
@@ -60,7 +59,7 @@ import com.velocitypowered.proxy.protocol.packet.config.TagsUpdatePacket;
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.concurrent.CompletableFuture;
import net.kyori.adventure.key.Key;
@@ -72,9 +71,6 @@ import org.apache.logging.log4j.Logger;
* 1.20.2+ switching. Yes, some of this is exceptionally stupid.
*/
public class ConfigSessionHandler implements MinecraftSessionHandler {
private static final boolean BACKPRESSURE_LOG =
Boolean.getBoolean("velocity.log-server-backpressure");
private static final Logger logger = LogManager.getLogger(ConfigSessionHandler.class);
private final VelocityServer server;
private final VelocityServerConnection serverConn;
@@ -260,13 +256,7 @@ public class ConfigSessionHandler implements MinecraftSessionHandler {
@Override
public boolean handle(DisconnectPacket packet) {
serverConn.disconnect();
// If the player receives a DisconnectPacket without a connection to a server in progress,
// it means that the backend server has kicked the player during reconfiguration
if (serverConn.getPlayer().getConnectionInFlight() != null) {
resultFuture.complete(ConnectionRequestResults.forDisconnect(packet, serverConn.getServer()));
} else {
serverConn.getPlayer().handleConnectionException(serverConn.getServer(), packet, true);
}
resultFuture.complete(ConnectionRequestResults.forDisconnect(packet, serverConn.getServer()));
return true;
}
@@ -368,16 +358,10 @@ public class ConfigSessionHandler implements MinecraftSessionHandler {
return true;
}
@Override
public boolean handle(CodeOfConductPacket packet) {
this.serverConn.getPlayer().getConnection().write(packet.retain());
return true;
}
@Override
public void disconnected() {
resultFuture.complete(ConnectionRequestResults.forDisconnect(
ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR, serverConn.getServer()));
resultFuture.completeExceptionally(
new IOException("Unexpectedly disconnected from remote server"));
}
@Override
@@ -385,22 +369,6 @@ public class ConfigSessionHandler implements MinecraftSessionHandler {
serverConn.getPlayer().getConnection().write(packet);
}
@Override
public void writabilityChanged() {
Channel serverChan = serverConn.ensureConnected().getChannel();
boolean writable = serverChan.isWritable();
if (BACKPRESSURE_LOG) {
if (writable) {
logger.info("{} is writable, will auto-read player connection data", this.serverConn);
} else {
logger.info("{} is not writable, not auto-reading player connection data", this.serverConn);
}
}
serverConn.getPlayer().getConnection().setAutoReading(writable);
}
private void switchFailure(Throwable cause) {
logger.error("Unable to switch to new server {} for {}", serverConn.getServerInfo().getName(),
serverConn.getPlayer().getUsername(), cause);
@@ -414,4 +382,4 @@ public class ConfigSessionHandler implements MinecraftSessionHandler {
public enum State {
START, NEGOTIATING, PLUGIN_MESSAGE_INTERRUPT, RESOURCE_PACK_INTERRUPT, COMPLETE
}
}
}
@@ -165,7 +165,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
}
if (player.getConnection().getActiveSessionHandler() instanceof ClientPlaySessionHandler clientPlaySessionHandler) {
smc.setAutoReading(false);
clientPlaySessionHandler.doSwitch().thenRunAsync(() -> smc.setAutoReading(true), smc.eventLoop());
clientPlaySessionHandler.doSwitch().thenAcceptAsync((unused) -> smc.setAutoReading(true), smc.eventLoop());
} else {
// Initial login - the player is already in configuration state.
server.getEventManager().fireAndForget(new PlayerEnteredConfigurationEvent(player, serverConn));
@@ -38,6 +38,7 @@ 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.PluginMessagePacket;
import java.io.IOException;
import java.util.concurrent.CompletableFuture;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -89,7 +90,7 @@ public class TransitionSessionHandler implements MinecraftSessionHandler {
@Override
public boolean handle(JoinGamePacket packet) {
final MinecraftConnection smc = serverConn.ensureConnected();
MinecraftConnection smc = serverConn.ensureConnected();
final RegisteredServer previousServer = serverConn.getPreviousServer().orElse(null);
final ConnectedPlayer player = serverConn.getPlayer();
final VelocityServerConnection existingConnection = player.getConnectedServer();
@@ -106,9 +107,6 @@ public class TransitionSessionHandler implements MinecraftSessionHandler {
// Reset Tablist header and footer to prevent desync
player.clearPlayerListHeaderAndFooter();
// Override online mode
packet.setOnlineMode(player.isOnlineMode());
// The goods are in hand! We got JoinGame. Let's transition completely to the new state.
smc.setAutoReading(false);
server.getEventManager()
@@ -124,8 +122,9 @@ public class TransitionSessionHandler implements MinecraftSessionHandler {
// Change the client to use the ClientPlaySessionHandler if required.
ClientPlaySessionHandler playHandler;
if (player.getConnection()
.getActiveSessionHandler() instanceof ClientPlaySessionHandler sessionHandler) {
playHandler = sessionHandler;
.getActiveSessionHandler() instanceof ClientPlaySessionHandler) {
playHandler =
(ClientPlaySessionHandler) player.getConnection().getActiveSessionHandler();
} else {
playHandler = new ClientPlaySessionHandler(server, player);
player.getConnection().setActiveSessionHandler(StateRegistry.PLAY, playHandler);
@@ -215,7 +214,7 @@ public class TransitionSessionHandler implements MinecraftSessionHandler {
@Override
public void disconnected() {
resultFuture.complete(ConnectionRequestResults.forDisconnect(
ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR, serverConn.getServer()));
resultFuture
.completeExceptionally(new IOException("Unexpectedly disconnected from remote server"));
}
}
@@ -53,7 +53,6 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.jetbrains.annotations.NotNull;
@@ -71,7 +70,6 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
private boolean gracefulDisconnect = false;
private BackendConnectionPhase connectionPhase = BackendConnectionPhases.UNKNOWN;
private final Map<Long, Long> pendingPings = new HashMap<>();
private @MonotonicNonNull Integer entityId;
/**
* Initializes a new server connection.
@@ -180,8 +178,9 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
handshake.setServerAddress(createBungeeGuardForwardingAddress(secret));
} else if (proxyPlayer.getConnection().getType() == ConnectionTypes.LEGACY_FORGE) {
handshake.setServerAddress(playerVhost + HANDSHAKE_HOSTNAME_TOKEN);
} else if (proxyPlayer.getConnection().getType() instanceof ModernForgeConnectionType forgeConnection) {
handshake.setServerAddress(playerVhost + forgeConnection.getModernToken());
} else if (proxyPlayer.getConnection().getType() instanceof ModernForgeConnectionType) {
handshake.setServerAddress(playerVhost + ((ModernForgeConnectionType) proxyPlayer
.getConnection().getType()).getModernToken());
} else {
handshake.setServerAddress(playerVhost);
}
@@ -325,14 +324,6 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
return pendingPings;
}
public Integer getEntityId() {
return entityId;
}
public void setEntityId(Integer entityId) {
this.entityId = entityId;
}
/**
* Ensures that this server connection remains "active": the connection is established and not
* closed, the player is still connected to the server, and the player still remains online.
@@ -69,16 +69,14 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
private @MonotonicNonNull ConnectedPlayer connectedPlayer;
private final boolean onlineMode;
private State loginState = State.START; // 1.20.2+
private final String serverIdHash;
AuthSessionHandler(VelocityServer server, LoginInboundConnection inbound,
GameProfile profile, boolean onlineMode, String serverIdHash) {
GameProfile profile, boolean onlineMode) {
this.server = Preconditions.checkNotNull(server, "server");
this.inbound = Preconditions.checkNotNull(inbound, "inbound");
this.profile = Preconditions.checkNotNull(profile, "profile");
this.onlineMode = onlineMode;
this.mcConnection = inbound.delegatedConnection();
this.serverIdHash = serverIdHash;
}
@Override
@@ -215,7 +213,7 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
private void completeLoginProtocolPhaseAndInitialize(ConnectedPlayer player) {
mcConnection.setAssociation(player);
server.getEventManager().fire(new LoginEvent(player, serverIdHash)).thenAcceptAsync(event -> {
server.getEventManager().fire(new LoginEvent(player)).thenAcceptAsync(event -> {
if (mcConnection.isClosed()) {
// The player was disconnected
server.getEventManager().fireAndForget(new DisconnectEvent(player,
@@ -236,9 +234,6 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
success.setUsername(player.getUsername());
success.setProperties(player.getGameProfileProperties());
success.setUuid(player.getUniqueId());
if (inbound.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_26_2)) {
success.setSessionId(server.getSessionId());
}
mcConnection.write(success);
loginState = State.SUCCESS_SENT;
@@ -33,7 +33,6 @@ import com.velocitypowered.proxy.connection.player.resourcepack.ResourcePackResp
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket;
import com.velocitypowered.proxy.protocol.packet.KeepAlivePacket;
@@ -41,8 +40,6 @@ import com.velocitypowered.proxy.protocol.packet.PingIdentifyPacket;
import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket;
import com.velocitypowered.proxy.protocol.packet.ResourcePackResponsePacket;
import com.velocitypowered.proxy.protocol.packet.ServerboundCookieResponsePacket;
import com.velocitypowered.proxy.protocol.packet.ServerboundCustomClickActionPacket;
import com.velocitypowered.proxy.protocol.packet.config.CodeOfConductAcceptPacket;
import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket;
import com.velocitypowered.proxy.protocol.packet.config.KnownPacksPacket;
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
@@ -61,8 +58,6 @@ import org.apache.logging.log4j.Logger;
* Handles the client config stage.
*/
public class ClientConfigSessionHandler implements MinecraftSessionHandler {
private static final boolean BACKPRESSURE_LOG =
Boolean.getBoolean("velocity.log-server-backpressure");
private static final Logger logger = LogManager.getLogger(ClientConfigSessionHandler.class);
private final VelocityServer server;
@@ -210,26 +205,6 @@ public class ClientConfigSessionHandler implements MinecraftSessionHandler {
return true;
}
@Override
public boolean handle(ServerboundCustomClickActionPacket packet) {
if (player.getConnectionInFlight() != null) {
player.getConnectionInFlight().ensureConnected().write(packet.retain());
return true;
}
return false;
}
@Override
public boolean handle(CodeOfConductAcceptPacket packet) {
if (this.player.getConnectionInFlight() != null) {
this.player.getConnectionInFlight().ensureConnected().write(packet);
return true;
}
return false;
}
@Override
public void handleGeneric(MinecraftPacket packet) {
VelocityServerConnection serverConnection = player.getConnectedServer();
@@ -269,36 +244,6 @@ public class ClientConfigSessionHandler implements MinecraftSessionHandler {
@Override
public void exception(Throwable throwable) {
player.disconnect(Component.translatable("velocity.error.player-connection-error", NamedTextColor.RED));
if (MinecraftDecoder.DEBUG) {
logger.info("Exception while handling packet for {}", player, throwable);
}
}
@Override
public void writabilityChanged() {
final boolean writable = player.getConnection().getChannel().isWritable();
if (BACKPRESSURE_LOG) {
if (writable) {
logger.info("{} is writable, will auto-read backend connection data", player);
} else {
logger.info("{} is not writable, not auto-reading backend connection data", player);
}
}
if (!writable) {
// Flush pending packets to free up memory. Schedule on a future event loop invocation
// to avoid disabling auto-read while the flush resolves backpressure.
player.getConnection().eventLoop().execute(() -> player.getConnection().flush());
}
final VelocityServerConnection serverConn = player.getConnectionInFlightOrConnectedServer();
if (serverConn != null) {
final MinecraftConnection smc = serverConn.getConnection();
if (smc != null) {
smc.setAutoReading(writable);
}
}
}
/**
@@ -21,10 +21,10 @@ import static com.velocitypowered.proxy.protocol.util.PluginMessageUtil.construc
import com.google.common.collect.ImmutableList;
import com.mojang.brigadier.suggestion.Suggestion;
import com.velocitypowered.api.command.VelocityBrigadierMessage;
import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.event.player.CookieReceiveEvent;
import com.velocitypowered.api.event.player.PlayerChannelRegisterEvent;
import com.velocitypowered.api.event.player.PlayerChannelUnregisterEvent;
import com.velocitypowered.api.event.player.PlayerClientBrandEvent;
import com.velocitypowered.api.event.player.TabCompleteEvent;
import com.velocitypowered.api.event.player.configuration.PlayerEnteredConfigurationEvent;
@@ -41,7 +41,6 @@ import com.velocitypowered.proxy.connection.forge.legacy.LegacyForgeConstants;
import com.velocitypowered.proxy.connection.player.resourcepack.ResourcePackResponseBundle;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.packet.BossBarPacket;
import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket;
import com.velocitypowered.proxy.protocol.packet.JoinGamePacket;
@@ -86,11 +85,8 @@ import java.util.Queue;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.format.NamedTextColor;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -101,16 +97,6 @@ import org.checkerframework.checker.nullness.qual.Nullable;
* center that joins backend servers with players.
*/
public class ClientPlaySessionHandler implements MinecraftSessionHandler {
private static final boolean BACKPRESSURE_LOG =
Boolean.getBoolean("velocity.log-server-backpressure");
// Caps the per-connection queue used while the FML/login phases are not yet "complete". Without
// these caps, a client that never completes its handshake phase can spam plugin messages (each up
// to ~32 KiB serverbound) and grow the queue without bound.
private static final long MAX_QUEUED_LOGIN_PLUGIN_MESSAGE_BYTES =
Long.getLong("velocity.max-queued-login-plugin-message-bytes", 4L * 1024 * 1024);
private static final int MAX_QUEUED_LOGIN_PLUGIN_MESSAGES =
Integer.getInteger("velocity.max-queued-login-plugin-messages", 1024);
private static final Logger logger = LogManager.getLogger(ClientPlaySessionHandler.class);
@@ -118,9 +104,6 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
private boolean spawned = false;
private final List<UUID> serverBossBars = new ArrayList<>();
private final Queue<PluginMessagePacket> loginPluginMessages = new ConcurrentLinkedQueue<>();
private final AtomicLong loginPluginMessagesBytes = new AtomicLong();
private final AtomicInteger loginPluginMessagesCount = new AtomicInteger();
private volatile boolean loginPluginMessagesOverflowed;
private final VelocityServer server;
private @Nullable TabCompleteRequestPacket outstandingTabComplete;
private final ChatHandler<? extends MinecraftPacket> chatHandler;
@@ -189,38 +172,9 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
@Override
public void deactivated() {
player.discardChatQueue();
PluginMessagePacket message;
while ((message = loginPluginMessages.poll()) != null) {
for (PluginMessagePacket message : loginPluginMessages) {
ReferenceCountUtil.release(message);
}
loginPluginMessagesBytes.set(0);
loginPluginMessagesCount.set(0);
}
/**
* Adds a retained plugin message to the queue used while the FML/login phases are still in
* progress, enforcing the per-connection byte and count caps. Returns {@code true} if queued,
* {@code false} if the packet was released (and the player disconnected on overflow).
*/
private boolean enqueueLoginPluginMessage(PluginMessagePacket packet) {
if (loginPluginMessagesOverflowed) {
ReferenceCountUtil.release(packet);
return false;
}
int packetSize = packet.content().readableBytes();
long newBytes = loginPluginMessagesBytes.addAndGet(packetSize);
int newCount = loginPluginMessagesCount.incrementAndGet();
if (newBytes > MAX_QUEUED_LOGIN_PLUGIN_MESSAGE_BYTES
|| newCount > MAX_QUEUED_LOGIN_PLUGIN_MESSAGES) {
loginPluginMessagesOverflowed = true;
ReferenceCountUtil.release(packet);
logger.warn("Disconnecting {}: pre-join plugin-message queue exceeded its limits "
+ "({} messages, {} bytes).", player, newCount, newBytes);
player.disconnect(Component.translatable("velocity.error.plugin-message-overflow"));
return false;
}
loginPluginMessages.add(packet);
return true;
}
@Override
@@ -364,12 +318,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
new PlayerChannelRegisterEvent(player, ImmutableList.copyOf(channels)));
backendConn.write(packet.retain());
} else if (PluginMessageUtil.isUnregister(packet)) {
List<ChannelIdentifier> channels =
PluginMessageUtil.getChannels(0, packet, this.player.getProtocolVersion());
player.getClientsideChannels().removeAll(channels);
server.getEventManager()
.fireAndForget(
new PlayerChannelUnregisterEvent(player, ImmutableList.copyOf(channels)));
player.getClientsideChannels()
.removeAll(PluginMessageUtil.getChannels(0, packet, this.player.getProtocolVersion()));
backendConn.write(packet.retain());
} else if (PluginMessageUtil.isMcBrand(packet)) {
String brand = PluginMessageUtil.readBrandMessage(packet.content());
@@ -401,7 +351,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
//
// We also need to make sure to retain these packets, so they can be flushed
// appropriately.
enqueueLoginPluginMessage(packet.retain());
loginPluginMessages.add(packet.retain());
} else {
// The connection is ready, send the packet now.
backendConn.write(packet.retain());
@@ -416,7 +366,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
if (!player.getPhase().consideredComplete() || !serverConn.getPhase()
.consideredComplete()) {
// We're still processing the connection (see above), enqueue the packet for now.
enqueueLoginPluginMessage(message.retain());
loginPluginMessages.add(message.retain());
} else {
backendConn.write(message);
}
@@ -513,11 +463,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
}
MinecraftConnection smc = serverConnection.getConnection();
final boolean stateAllowsForward = smc != null
&& !smc.isClosed()
&& serverConnection.getPhase().consideredComplete()
&& smc.getState() == StateRegistry.PLAY;
if (stateAllowsForward) {
if (smc != null && serverConnection.getPhase().consideredComplete()) {
if (packet instanceof PluginMessagePacket) {
((PluginMessagePacket) packet).retain();
}
@@ -534,11 +480,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
}
MinecraftConnection smc = serverConnection.getConnection();
final boolean stateAllowsForward = smc != null
&& !smc.isClosed()
&& serverConnection.getPhase().consideredComplete()
&& smc.getState() == StateRegistry.PLAY;
if (stateAllowsForward) {
if (smc != null && !smc.isClosed() && serverConnection.getPhase().consideredComplete()) {
smc.write(buf.retain());
}
}
@@ -550,24 +492,14 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
@Override
public void exception(Throwable throwable) {
player.disconnect(Component.translatable("velocity.error.player-connection-error", NamedTextColor.RED));
if (MinecraftDecoder.DEBUG) {
logger.info("Exception while handling packet for {}", player, throwable);
}
player.disconnect(
Component.translatable("velocity.error.player-connection-error", NamedTextColor.RED));
}
@Override
public void writabilityChanged() {
boolean writable = player.getConnection().getChannel().isWritable();
if (BACKPRESSURE_LOG) {
if (writable) {
logger.info("{} is writable, will auto-read backend connection data", player);
} else {
logger.info("{} is not writable, not auto-reading backend connection data", player);
}
}
if (!writable) {
// We might have packets queued from the server, so flush them now to free up memory. Make
// sure to do it on a future invocation of the event loop, otherwise while the issue will
@@ -603,13 +535,9 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
// Config state clears everything in the client. No need to clear later.
spawned = false;
serverBossBars.clear();
player.clearPlayerListHeaderAndFooterSilent();
player.getTabList().clearAllSilent();
if (player.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
player.getBossBarManager().dropPackets();
} else {
serverBossBars.clear();
}
}
player.switchToConfigState();
@@ -647,20 +575,15 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
}
}
destination.setEntityId(joinGame.getEntityId()); // used for sound api
if (player.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
player.getBossBarManager().sendBossBars();
} else {
// Remove previous boss bars. These don't get cleared when sending JoinGame (up until 1.20.2),
// thus the need to track them.
for (UUID serverBossBar : serverBossBars) {
BossBarPacket deletePacket = new BossBarPacket();
deletePacket.setUuid(serverBossBar);
deletePacket.setAction(BossBarPacket.REMOVE);
player.getConnection().delayedWrite(deletePacket);
}
serverBossBars.clear();
// Remove previous boss bars. These don't get cleared when sending JoinGame, thus the need to
// track them.
for (UUID serverBossBar : serverBossBars) {
BossBarPacket deletePacket = new BossBarPacket();
deletePacket.setUuid(serverBossBar);
deletePacket.setAction(BossBarPacket.REMOVE);
player.getConnection().delayedWrite(deletePacket);
}
serverBossBars.clear();
// Tell the server about the proxy's plugin message channels.
ProtocolVersion serverVersion = serverMc.getProtocolVersion();
@@ -679,8 +602,6 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
while ((pm = loginPluginMessages.poll()) != null) {
serverMc.delayedWrite(pm);
}
loginPluginMessagesBytes.set(0);
loginPluginMessagesCount.set(0);
// Clear any title from the previous server.
if (player.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
@@ -773,35 +694,23 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
return;
}
int startPos = -1;
for (var suggestion : suggestions.getList()) {
if (startPos == -1 || startPos > suggestion.getRange().getStart()) {
startPos = suggestion.getRange().getStart();
List<Offer> offers = new ArrayList<>();
for (Suggestion suggestion : suggestions.getList()) {
String offer = suggestion.getText();
ComponentHolder tooltip = null;
if (suggestion.getTooltip() != null
&& suggestion.getTooltip() instanceof VelocityBrigadierMessage) {
tooltip = new ComponentHolder(player.getProtocolVersion(),
((VelocityBrigadierMessage) suggestion.getTooltip()).asComponent());
}
offers.add(new Offer(offer, tooltip));
}
int startPos = packet.getCommand().lastIndexOf(' ') + 1;
if (startPos > 0) {
List<Offer> offers = new ArrayList<>();
for (Suggestion suggestion : suggestions.getList()) {
String offer;
if (suggestion.getRange().getStart() == startPos) {
offer = suggestion.getText();
} else {
offer = command.substring(startPos, suggestion.getRange().getStart()) + suggestion.getText();
}
ComponentHolder tooltip = null;
if (suggestion.getTooltip() instanceof ComponentLike componentLike) {
tooltip = new ComponentHolder(player.getProtocolVersion(), componentLike.asComponent());
} else if (suggestion.getTooltip() != null) {
tooltip = new ComponentHolder(player.getProtocolVersion(), Component.text(suggestion.getTooltip().getString()));
}
offers.add(new Offer(offer, tooltip));
}
TabCompleteResponsePacket resp = new TabCompleteResponsePacket();
resp.setTransactionId(packet.getTransactionId());
resp.setStart(startPos + 1);
resp.setLength(packet.getCommand().length() - startPos - 1);
resp.setStart(startPos);
resp.setLength(packet.getCommand().length() - startPos);
resp.getOffers().addAll(offers);
player.getConnection().write(resp);
}
@@ -856,10 +765,10 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
offer = offer.substring(command.length());
}
ComponentHolder tooltip = null;
if (suggestion.getTooltip() instanceof ComponentLike componentLike) {
tooltip = new ComponentHolder(player.getProtocolVersion(), componentLike.asComponent());
} else if (suggestion.getTooltip() != null) {
tooltip = new ComponentHolder(player.getProtocolVersion(), Component.text(suggestion.getTooltip().getString()));
if (suggestion.getTooltip() != null
&& suggestion.getTooltip() instanceof VelocityBrigadierMessage) {
tooltip = new ComponentHolder(player.getProtocolVersion(),
((VelocityBrigadierMessage) suggestion.getTooltip()).asComponent());
}
response.getOffers().add(new Offer(offer, tooltip));
}
@@ -913,8 +822,6 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
while ((pm = loginPluginMessages.poll()) != null) {
connection.write(pm);
}
loginPluginMessagesBytes.set(0);
loginPluginMessagesCount.set(0);
}
}
}
@@ -62,7 +62,6 @@ import com.velocitypowered.proxy.adventure.VelocityBossBarImplementation;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.connection.player.bossbar.BossBarManager;
import com.velocitypowered.proxy.connection.player.bundle.BundleDelimiterHandler;
import com.velocitypowered.proxy.connection.player.resourcepack.VelocityResourcePackInfo;
import com.velocitypowered.proxy.connection.player.resourcepack.handler.ResourcePackHandler;
@@ -74,8 +73,6 @@ import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
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.DisconnectPacket;
import com.velocitypowered.proxy.protocol.packet.HeaderAndFooterPacket;
@@ -84,6 +81,7 @@ import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket;
import com.velocitypowered.proxy.protocol.packet.RemoveResourcePackPacket;
import com.velocitypowered.proxy.protocol.packet.TransferPacket;
import com.velocitypowered.proxy.protocol.packet.chat.ChatQueue;
import com.velocitypowered.proxy.protocol.packet.chat.ChatType;
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
import com.velocitypowered.proxy.protocol.packet.chat.PlayerChatCompletionPacket;
import com.velocitypowered.proxy.protocol.packet.chat.builder.ChatBuilderFactory;
@@ -110,7 +108,6 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
@@ -118,21 +115,21 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import net.kyori.adventure.audience.MessageType;
import net.kyori.adventure.bossbar.BossBar;
import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.permission.PermissionChecker;
import net.kyori.adventure.platform.facet.FacetPointers;
import net.kyori.adventure.platform.facet.FacetPointers.Type;
import net.kyori.adventure.pointer.Pointers;
import net.kyori.adventure.pointer.PointersSupplier;
import net.kyori.adventure.resource.ResourcePackInfoLike;
import net.kyori.adventure.resource.ResourcePackRequest;
import net.kyori.adventure.resource.ResourcePackRequestLike;
import net.kyori.adventure.sound.Sound;
import net.kyori.adventure.sound.SoundStop;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
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.Times;
@@ -163,6 +160,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
.resolving(Identity.DISPLAY_NAME, player -> Component.text(player.getUsername()))
.resolving(Identity.LOCALE, Player::getEffectiveLocale)
.resolving(PermissionChecker.POINTER, Player::getPermissionChecker)
.resolving(FacetPointers.TYPE, player -> Type.PLAYER)
.build();
/**
@@ -199,7 +197,6 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
private @Nullable ClientSettingsPacket clientSettingsPacket;
private volatile ChatQueue chatQueue;
private final ChatBuilderFactory chatBuilderFactory;
private final BossBarManager bossBarManager;
ConnectedPlayer(VelocityServer server, GameProfile profile, MinecraftConnection connection,
@Nullable InetSocketAddress virtualHost, @Nullable String rawVirtualHost, boolean onlineMode,
@@ -226,7 +223,6 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
this.chatQueue = new ChatQueue(this);
this.chatBuilderFactory = new ChatBuilderFactory(this.getProtocolVersion());
this.resourcePackHandler = ResourcePackHandler.create(this, server);
this.bossBarManager = new BossBarManager(this);
}
/**
@@ -420,16 +416,29 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
}
@Override
public void sendMessage(final @NonNull Component message) {
Preconditions.checkNotNull(message, "message");
public void sendMessage(@NonNull Identity identity, @NonNull Component message) {
final Component translated = translateMessage(message);
connection.write(getChatBuilderFactory().builder()
.component(translated).toClient());
.component(translated).forIdentity(identity).toClient());
}
@Override
public void sendActionBar(@NonNull Component message) {
public void sendMessage(@NonNull Identity identity, @NonNull Component message,
@NonNull MessageType type) {
Preconditions.checkNotNull(message, "message");
Preconditions.checkNotNull(type, "type");
Component translated = translateMessage(message);
connection.write(getChatBuilderFactory().builder()
.component(translated).forIdentity(identity)
.setType(type == MessageType.CHAT ? ChatType.CHAT : ChatType.SYSTEM)
.toClient());
}
@Override
public void sendActionBar(net.kyori.adventure.text.@NonNull Component message) {
Component translated = translateMessage(message);
ProtocolVersion playerVersion = getProtocolVersion();
@@ -628,8 +637,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
}
@Override
public void disconnect(@NotNull Component reason) {
Objects.requireNonNull(reason, "reason");
public void disconnect(Component reason) {
if (connection.eventLoop().inEventLoop()) {
disconnect0(reason, false);
} else {
@@ -698,12 +706,12 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
Component friendlyError;
if (connectedServer != null && connectedServer.getServerInfo().equals(server.getServerInfo())) {
friendlyError = Component.translatable("velocity.error.connected-server-error",
Argument.string("server", server.getServerInfo().getName()));
Component.text(server.getServerInfo().getName()));
} else {
logger.error("{}: unable to connect to server {}", this, server.getServerInfo().getName(),
wrapped);
friendlyError = Component.translatable("velocity.error.connecting-server-error",
Argument.string("server", server.getServerInfo().getName()));
Component.text(server.getServerInfo().getName()));
}
handleConnectionException(server, null, friendlyError.color(NamedTextColor.RED), safe);
}
@@ -725,22 +733,18 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
Component disconnectReason = disconnect.getReason().getComponent();
String plainTextReason = PASS_THRU_TRANSLATE.serialize(disconnectReason);
if (connectedServer != null && connectedServer.getServerInfo().equals(server.getServerInfo())) {
if (this.server.getConfiguration().isLogPlayerConnections()) {
logger.info("{}: kicked from server {}: {}", this, server.getServerInfo().getName(),
plainTextReason);
}
logger.info("{}: kicked from server {}: {}", this, server.getServerInfo().getName(),
plainTextReason);
handleConnectionException(server, disconnectReason,
Component.translatable("velocity.error.moved-to-new-server", NamedTextColor.RED,
Argument.string("server", server.getServerInfo().getName()),
Component.text(server.getServerInfo().getName()),
disconnectReason), safe);
} else {
if (this.server.getConfiguration().isLogPlayerConnections()) {
logger.error("{}: disconnected while connecting to {}: {}", this,
server.getServerInfo().getName(), plainTextReason);
}
logger.error("{}: disconnected while connecting to {}: {}", this,
server.getServerInfo().getName(), plainTextReason);
handleConnectionException(server, disconnectReason,
Component.translatable("velocity.error.cant-connect", NamedTextColor.RED,
Argument.string("server", server.getServerInfo().getName()),
Component.text(server.getServerInfo().getName()),
disconnectReason), safe);
}
}
@@ -796,56 +800,63 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
return;
}
switch (event.getResult()) {
case DisconnectPlayer res -> disconnect(res.getReasonComponent());
case RedirectPlayer res -> createConnectionRequest(res.getServer(), previousConnection).connect()
.whenCompleteAsync((status, throwable) -> {
if (throwable != null) {
handleConnectionException(res.getServer(), throwable, true);
return;
}
if (event.getResult() instanceof final DisconnectPlayer res) {
disconnect(res.getReasonComponent());
} else if (event.getResult() instanceof final RedirectPlayer res) {
createConnectionRequest(res.getServer(), previousConnection).connect()
.whenCompleteAsync((status, throwable) -> {
if (throwable != null) {
handleConnectionException(
status != null ? status.getAttemptedConnection() : res.getServer(), throwable,
true);
return;
}
switch (status.getStatus()) {
// Impossible/nonsensical cases
case ALREADY_CONNECTED -> logger.error("{}: already connected to {}", this,
status.getAttemptedConnection().getServerInfo().getName());
case CONNECTION_IN_PROGRESS, CONNECTION_CANCELLED -> {
Component fallbackMsg = res.getMessageComponent();
if (fallbackMsg == null) {
fallbackMsg = friendlyReason;
}
disconnect(status.getReasonComponent().orElse(fallbackMsg));
}
case SERVER_DISCONNECTED -> {
Component reason = status.getReasonComponent()
.orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR);
handleConnectionException(res.getServer(),
DisconnectPacket.create(reason, getProtocolVersion(), connection.getState()),
((Impl) status).isSafe());
}
case SUCCESS -> {
Component requestedMessage = res.getMessageComponent();
if (requestedMessage == null) {
requestedMessage = friendlyReason;
}
if (requestedMessage != Component.empty()) {
sendMessage(requestedMessage);
}
}
default -> {
// The only remaining value is successful (no need to do anything!)
}
switch (status.getStatus()) {
// Impossible/nonsensical cases
case ALREADY_CONNECTED:
logger.error("{}: already connected to {}", this,
status.getAttemptedConnection().getServerInfo().getName());
break;
case CONNECTION_IN_PROGRESS:
// Fatal case
case CONNECTION_CANCELLED:
Component fallbackMsg = res.getMessageComponent();
if (fallbackMsg == null) {
fallbackMsg = friendlyReason;
}
}, connection.eventLoop());
case Notify res -> {
if (event.kickedDuringServerConnect() && previousConnection != null) {
sendMessage(res.getMessageComponent());
} else {
disconnect(res.getMessageComponent());
}
disconnect(status.getReasonComponent().orElse(fallbackMsg));
break;
case SERVER_DISCONNECTED:
Component reason = status.getReasonComponent()
.orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR);
handleConnectionException(res.getServer(),
DisconnectPacket.create(reason, getProtocolVersion(), connection.getState()),
((Impl) status).isSafe());
break;
case SUCCESS:
Component requestedMessage = res.getMessageComponent();
if (requestedMessage == null) {
requestedMessage = friendlyReason;
}
if (requestedMessage != Component.empty()) {
sendMessage(requestedMessage);
}
break;
default:
// The only remaining value is successful (no need to do anything!)
break;
}
}, connection.eventLoop());
} else if (event.getResult() instanceof final Notify res) {
if (event.kickedDuringServerConnect() && previousConnection != null) {
sendMessage(res.getMessageComponent());
} else {
disconnect(res.getMessageComponent());
}
} else {
// In case someone gets creative, assume we want to disconnect the player.
default -> disconnect(friendlyReason);
disconnect(friendlyReason);
}
}, connection.eventLoop());
}
@@ -1027,50 +1038,6 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
this.clientBrand = clientBrand;
}
@Override
public void playSound(@NotNull Sound sound, @NotNull Sound.Emitter emitter) {
Preconditions.checkNotNull(sound, "sound");
Preconditions.checkNotNull(emitter, "emitter");
VelocityServerConnection soundTargetServerConn = getConnectedServer();
if (getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_19_3)
|| connection.getState() != StateRegistry.PLAY
|| soundTargetServerConn == null
|| (sound.source() == Sound.Source.UI
&& getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_21_5))) {
return;
}
VelocityServerConnection soundEmitterServerConn;
if (emitter == Sound.Emitter.self()) {
soundEmitterServerConn = soundTargetServerConn;
} else if (emitter instanceof ConnectedPlayer player) {
if ((soundEmitterServerConn = player.getConnectedServer()) == null) {
return;
}
if (!soundEmitterServerConn.getServer().equals(soundTargetServerConn.getServer())) {
return;
}
} else {
return;
}
connection.write(new ClientboundSoundEntityPacket(sound, null, soundEmitterServerConn.getEntityId()));
}
@Override
public void stopSound(@NotNull SoundStop stop) {
Preconditions.checkNotNull(stop, "stop");
if (getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_19_3)
|| connection.getState() != StateRegistry.PLAY
|| (stop.source() == Sound.Source.UI
&& getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_21_5))) {
return;
}
connection.write(new ClientboundStopSoundPacket(stop));
}
@Override
public void transferToHost(final InetSocketAddress address) {
Preconditions.checkNotNull(address);
@@ -1333,17 +1300,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
final Long sentTime = serverConnection.getPendingPings().remove(packet.getRandomId());
if (sentTime != null) {
final MinecraftConnection smc = serverConnection.getConnection();
final StateRegistry clientState = connection.getState();
final boolean stateAllowsForward = smc != null
&& !smc.isClosed()
&& clientState == smc.getState()
&& (clientState == StateRegistry.CONFIG || clientState == StateRegistry.PLAY);
if (stateAllowsForward) {
if (smc != null) {
setPing(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - sentTime));
smc.write(packet);
return true;
}
// We removed this, and so this is ours
return true;
}
}
return false;
@@ -1353,8 +1314,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
* Switches the connection to the client into config state.
*/
public void switchToConfigState() {
final VelocityServerConnection targetServer = getConnectionInFlightOrConnectedServer();
server.getEventManager().fire(new PlayerEnterConfigurationEvent(this, targetServer))
server.getEventManager().fire(new PlayerEnterConfigurationEvent(this, getConnectionInFlightOrConnectedServer()))
.completeOnTimeout(null, 5, TimeUnit.SECONDS).thenRunAsync(() -> {
// if the connection was closed earlier, there is a risk that the player is no longer connected
if (!connection.getChannel().isActive()) {
@@ -1369,7 +1329,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
connection.pendingConfigurationSwitch = true;
connection.getChannel().pipeline().get(MinecraftEncoder.class).setState(StateRegistry.CONFIG);
// Make sure we don't send any play packets to the player after update start
connection.addPlayPacketQueueOutboundHandler();
connection.addPlayPacketQueueHandler();
}, connection.eventLoop()).exceptionally((ex) -> {
logger.error("Error switching player connection to config state", ex);
return null;
@@ -1419,10 +1379,6 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
return handshakeIntent;
}
public BossBarManager getBossBarManager() {
return bossBarManager;
}
private final class ConnectionRequestBuilderImpl implements ConnectionRequestBuilder {
private final RegisteredServer toConnect;
@@ -1482,16 +1438,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
VelocityServerConnection con =
new VelocityServerConnection(vrs, previousServer, ConnectedPlayer.this, server);
connectionInFlight = con;
return con.connect().whenCompleteAsync((result, exception) -> {
if (result != null && !result.isSuccessful() && !result.isSafe()) {
handleConnectionException(result.getAttemptedConnection(),
// The only way for the reason to be null is if the result is safe
DisconnectPacket.create(result.getReasonComponent().orElseThrow(),
getProtocolVersion(), connection.getState()), false);
}
this.resetIfInFlightIs(con);
},
return con.connect().whenCompleteAsync((result, exception) -> this.resetIfInFlightIs(con),
connection.eventLoop());
}, connection.eventLoop());
});
@@ -1505,14 +1452,22 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
@Override
public CompletableFuture<Result> connect() {
return this.internalConnect().thenApply(x -> x);
return this.internalConnect().whenCompleteAsync((status, throwable) -> {
if (status != null && !status.isSuccessful()) {
if (!status.isSafe()) {
handleConnectionException(status.getAttemptedConnection(), throwable, false);
}
}
}, connection.eventLoop()).thenApply(x -> x);
}
@Override
public CompletableFuture<Boolean> connectWithIndication() {
return internalConnect().whenCompleteAsync((status, throwable) -> {
if (throwable != null) {
handleConnectionException(toConnect, throwable, true);
// TODO: The exception handling from this is not very good. Find a better way.
handleConnectionException(status != null ? status.getAttemptedConnection() : toConnect,
throwable, true);
return;
}
@@ -127,10 +127,10 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
if (!handshake.getProtocolVersion().isSupported()) {
// Bump connection into correct protocol state so that we can send the disconnect packet.
connection.setState(StateRegistry.LOGIN);
ic.disconnectQuietly(Component.translatable(
"multiplayer.disconnect.outdated_client",
Component.text(ProtocolVersion.SUPPORTED_VERSION_STRING)
));
ic.disconnectQuietly(Component.translatable()
.key("multiplayer.disconnect.outdated_client")
.arguments(Component.text(ProtocolVersion.SUPPORTED_VERSION_STRING))
.build());
return;
}
@@ -65,7 +65,8 @@ public class InitialConnectSessionHandler implements MinecraftSessionHandler {
}
byte[] copy = ByteBufUtil.getBytes(packet.content());
PluginMessageEvent event = new PluginMessageEvent(player, serverConn, id, copy);
PluginMessageEvent event = new PluginMessageEvent(serverConn, serverConn.getPlayer(), id,
copy);
server.getEventManager().fire(event)
.thenAcceptAsync(pme -> {
if (pme.getResult().isAllowed() && serverConn.isActive()) {
@@ -152,7 +152,7 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
} else {
mcConnection.setActiveSessionHandler(StateRegistry.LOGIN,
new AuthSessionHandler(server, inbound,
GameProfile.forOfflinePlayer(login.getUsername()), false, null));
GameProfile.forOfflinePlayer(login.getUsername()), false));
}
});
});
@@ -214,7 +214,6 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
server.getVersion().getName() + "/" + server.getVersion().getVersion())
.uri(URI.create(url))
.build();
//noinspection resource
final HttpClient httpClient = server.createHttpClient();
httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString())
.whenCompleteAsync((response, throwable) -> {
@@ -255,7 +254,7 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
}
// All went well, initialize the session.
mcConnection.setActiveSessionHandler(StateRegistry.LOGIN,
new AuthSessionHandler(server, inbound, profile, true, serverId));
new AuthSessionHandler(server, inbound, profile, true));
} else if (response.statusCode() == 204) {
// Apparently an offline-mode user logged onto this online-mode proxy.
inbound.disconnect(
@@ -268,8 +267,16 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
inbound.disconnect(Component.translatable("multiplayer.disconnect.authservers_down"));
}
}, mcConnection.eventLoop())
.whenComplete((ignored, throwable) -> {
httpClient.close();
.thenRun(() -> {
if (httpClient instanceof final AutoCloseable closeable) {
try {
closeable.close();
} catch (Exception e) {
// In Java 21, the HttpClient does not throw any Exception
// when trying to clean its resources, so this should not happen
logger.error("An unknown error occurred while trying to close an HttpClient", e);
}
}
});
} catch (GeneralSecurityException e) {
logger.error("Unable to enable encryption", e);
@@ -1,79 +0,0 @@
/*
* Copyright (C) 2019-2023 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.connection.player.bossbar;
import com.velocitypowered.proxy.adventure.VelocityBossBarImplementation;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.packet.BossBarPacket;
import java.util.HashSet;
import java.util.Set;
/**
* Handles dropping and resending boss bar packets on versions 1.20.2 and newer because the client now
* deletes all boss bars during the login phase, and sending update packets would cause the client to be disconnected.
*/
public final class BossBarManager {
private final ConnectedPlayer player;
private final Set<VelocityBossBarImplementation> bossBars = new HashSet<>();
private boolean dropPackets = false;
public BossBarManager(ConnectedPlayer player) {
this.player = player;
}
/**
* Records the specified boss bar to be re-sent when a player changes server, and sends the update packet
* if the client is able to receive it and not be disconnected.
*/
public synchronized void writeUpdate(VelocityBossBarImplementation bar, BossBarPacket packet) {
this.bossBars.add(bar);
if (!this.dropPackets) {
this.player.getConnection().write(packet);
}
}
/**
* Removes the specified boss bar from the player to ensure it is not re-sent.
*/
public synchronized void remove(VelocityBossBarImplementation bar, BossBarPacket packet) {
this.bossBars.remove(bar);
if (!this.dropPackets) {
this.player.getConnection().write(packet);
}
}
/**
* Re-creates the boss bars the player can see with any updates that may have occurred in the meantime,
* and allows update packets for those boss bars to be sent.
*/
public synchronized void sendBossBars() {
for (VelocityBossBarImplementation bossBar : bossBars) {
bossBar.createDirect(player);
}
this.dropPackets = false;
}
/**
* Prevents the player from receiving boss bar update packets while logging in to a new server.
*/
public synchronized void dropPackets() {
this.dropPackets = true;
}
}
@@ -94,7 +94,7 @@ public final class ModernResourcePackHandler extends ResourcePackHandler {
this.outstandingResourcePacks.get(info.getId());
outstandingResourcePacks.add(info);
if (outstandingResourcePacks.size() == 1) {
tickResourcePackQueue(outstandingResourcePacks.getFirst().getId());
tickResourcePackQueue(outstandingResourcePacks.get(0).getId());
}
}
@@ -111,7 +111,7 @@ public final class ModernResourcePackHandler extends ResourcePackHandler {
final List<ResourcePackInfo> outstandingResourcePacks =
this.outstandingResourcePacks.get(uuid);
if (!outstandingResourcePacks.isEmpty()) {
sendResourcePackRequestPacket(outstandingResourcePacks.getFirst());
sendResourcePackRequestPacket(outstandingResourcePacks.get(0));
}
}
@@ -124,7 +124,7 @@ public final class ModernResourcePackHandler extends ResourcePackHandler {
this.outstandingResourcePacks.get(uuid);
final boolean peek = bundle.status().isIntermediate();
final ResourcePackInfo queued = outstandingResourcePacks.isEmpty() ? null :
peek ? outstandingResourcePacks.getFirst() : outstandingResourcePacks.removeFirst();
peek ? outstandingResourcePacks.get(0) : outstandingResourcePacks.remove(0);
server.getEventManager()
.fire(new PlayerResourcePackStatusEvent(this.player, uuid, bundle.status(), queued))
@@ -111,7 +111,7 @@ public abstract sealed class ResourcePackHandler
}
request.setRequired(queued.getShouldForce());
request.setPrompt(queued.getPrompt() == null ? null :
new ComponentHolder(player.getProtocolVersion(), player.translateMessage(queued.getPrompt())));
new ComponentHolder(player.getProtocolVersion(), queued.getPrompt()));
player.getConnection().write(request);
}
@@ -36,7 +36,6 @@ import java.util.Locale;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import net.kyori.adventure.text.Component;
/**
* Common utilities for handling server list ping results.
@@ -57,16 +56,16 @@ public class ServerListPingHandler {
List<ServerPing.SamplePlayer> samplePlayers;
if (configuration.getSamplePlayersInPing()) {
List<ServerPing.SamplePlayer> unshuffledPlayers = server.getAllPlayers().stream()
.map(p -> {
if (p.getPlayerSettings().isClientListingAllowed()) {
return new ServerPing.SamplePlayer(p.getUsername(), p.getUniqueId());
} else {
return ServerPing.SamplePlayer.ANONYMOUS;
}
})
.collect(Collectors.toList());
.map(p -> {
if (p.getPlayerSettings().isClientListingAllowed()) {
return new ServerPing.SamplePlayer(p.getUsername(), p.getUniqueId());
} else {
return ServerPing.SamplePlayer.ANONYMOUS;
}
})
.collect(Collectors.toList());
Collections.shuffle(unshuffledPlayers);
samplePlayers = unshuffledPlayers.subList(0, Math.min(12, unshuffledPlayers.size()));
samplePlayers = unshuffledPlayers.subList(0, Math.min(12, server.getPlayerCount()));
} else {
samplePlayers = ImmutableList.of();
}
@@ -100,60 +99,58 @@ public class ServerListPingHandler {
CompletableFuture<List<ServerPing>> 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;
switch (mode) {
case ALL:
return pingResponses.thenApply(responses -> {
// Find the first non-fallback
for (ServerPing response : responses) {
if (response == fallback) {
continue;
}
return response;
}
return fallback;
});
case MODS:
return pingResponses.thenApply(responses -> {
// Find the first non-fallback that contains a mod list
for (ServerPing response : responses) {
if (response == fallback) {
continue;
}
Optional<ModInfo> modInfo = response.getModinfo();
if (modInfo.isPresent()) {
return fallback.asBuilder().mods(modInfo.get()).build();
}
}
return fallback;
});
case DESCRIPTION:
return 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) {
return response.asBuilder()
.description(Component.empty())
.build();
}
if (response.getDescriptionComponent() == null) {
continue;
}
return response;
}
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;
return new ServerPing(
fallback.getVersion(),
fallback.getPlayers().orElse(null),
response.getDescriptionComponent(),
fallback.getFavicon().orElse(null),
response.getModinfo().orElse(null)
);
}
Optional<ModInfo> 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)
);
}
return fallback;
});
return fallback;
});
// Not possible, but covered for completeness.
default -> CompletableFuture.completedFuture(fallback);
};
default:
return CompletableFuture.completedFuture(fallback);
}
}
/**
@@ -25,13 +25,14 @@ import com.velocitypowered.api.permission.Tristate;
import com.velocitypowered.api.proxy.ConsoleCommandSource;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.util.ClosestLocaleMatcher;
import java.nio.file.Path;
import java.util.List;
import java.util.Locale;
import net.kyori.adventure.audience.MessageType;
import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.permission.PermissionChecker;
import net.kyori.adventure.platform.facet.FacetPointers;
import net.kyori.adventure.platform.facet.FacetPointers.Type;
import net.kyori.adventure.pointer.Pointers;
import net.kyori.adventure.pointer.PointersSupplier;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
@@ -58,10 +59,11 @@ public final class VelocityConsole extends SimpleTerminalConsole implements Cons
private final VelocityServer server;
private PermissionFunction permissionFunction = ALWAYS_TRUE;
private static final @NotNull PointersSupplier<VelocityConsole> POINTERS = PointersSupplier.<VelocityConsole>builder()
.resolving(PermissionChecker.POINTER, VelocityConsole::getPermissionChecker)
.resolving(Identity.LOCALE, (console) -> ClosestLocaleMatcher.INSTANCE
private final @NotNull Pointers pointers = ConsoleCommandSource.super.pointers().toBuilder()
.withDynamic(PermissionChecker.POINTER, this::getPermissionChecker)
.withDynamic(Identity.LOCALE, () -> ClosestLocaleMatcher.INSTANCE
.lookupClosest(Locale.getDefault()))
.withStatic(FacetPointers.TYPE, Type.CONSOLE)
.build();
public VelocityConsole(VelocityServer server) {
@@ -69,7 +71,8 @@ public final class VelocityConsole extends SimpleTerminalConsole implements Cons
}
@Override
public void sendMessage(@NonNull Component message) {
public void sendMessage(@NonNull Identity identity, @NonNull Component message,
@NonNull MessageType messageType) {
componentLogger.info(message);
}
@@ -107,7 +110,6 @@ public final class VelocityConsole extends SimpleTerminalConsole implements Cons
protected LineReader buildReader(LineReaderBuilder builder) {
return super.buildReader(builder
.appName("Velocity")
.variable(LineReader.HISTORY_FILE, Path.of(".console_history"))
.completer((reader, parsedLine, list) -> {
try {
List<String> offers = this.server.getCommandManager()
@@ -134,10 +136,6 @@ public final class VelocityConsole extends SimpleTerminalConsole implements Cons
if (!this.server.getCommandManager().executeAsync(this, command).join()) {
sendMessage(Component.translatable("velocity.command.command-does-not-exist",
NamedTextColor.RED));
return;
}
if (server.getConfiguration().isLogCommandExecutions()) {
logger.info("CONSOLE -> executed command /{}", command);
}
} catch (Exception e) {
logger.error("An error occurred while running this command.", e);
@@ -151,6 +149,6 @@ public final class VelocityConsole extends SimpleTerminalConsole implements Cons
@Override
public @NotNull Pointers pointers() {
return POINTERS.view(this);
return pointers;
}
}
@@ -26,10 +26,8 @@ import static com.velocitypowered.proxy.network.Connections.MINECRAFT_ENCODER;
import static com.velocitypowered.proxy.network.Connections.READ_TIMEOUT;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.config.VelocityConfiguration;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.client.HandshakeSessionHandler;
import com.velocitypowered.proxy.network.limiter.SimpleBytesPerSecondLimiter;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.LegacyPingDecoder;
@@ -74,17 +72,6 @@ public class ServerChannelInitializer extends ChannelInitializer<Channel> {
new HandshakeSessionHandler(connection, this.server));
ch.pipeline().addLast(Connections.HANDLER, connection);
VelocityConfiguration.PacketLimiterConfig packetLimiterConfig =
server.getConfiguration().getPacketLimiterConfig();
int configuredInterval = packetLimiterConfig.interval();
int configuredPacketsPerSecond = packetLimiterConfig.pps();
int configuredBytes = packetLimiterConfig.bytes();
if (configuredInterval > 0 && (configuredBytes > 0 || configuredPacketsPerSecond > 0)) {
ch.pipeline().get(MinecraftVarintFrameDecoder.class).setPacketLimiter(
new SimpleBytesPerSecondLimiter(configuredPacketsPerSecond, configuredBytes, configuredInterval)
);
}
if (this.server.getConfiguration().isProxyProtocol()) {
ch.pipeline().addFirst(new HAProxyMessageDecoder());
}
@@ -116,7 +116,7 @@ public enum TransportType {
return NIO;
}
if (IoUring.isAvailable() && Boolean.getBoolean("velocity.enable-iouring-transport")) {
if (IoUring.isAvailable() && !Boolean.getBoolean("velocity.disable-iouring-transport")) {
return IO_URING;
}
@@ -1,32 +0,0 @@
/*
* Copyright (C) 2025 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.network.limiter;
/**
* PacketLimiter enforces a limit on the number of bytes processed over a time window.
* Implementations should be thread-safe.
*/
public interface PacketLimiter {
/**
* Attempts to record the specified number of bytes within the current window.
*
* @param bytes the number of bytes to record
* @return true if the bytes are allowed and recorded; false if the limit would be exceeded
*/
boolean account(int bytes);
}
@@ -1,77 +0,0 @@
/*
* Copyright (C) 2025 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.network.limiter;
import com.velocitypowered.proxy.util.IntervalledCounter;
import org.jspecify.annotations.Nullable;
/**
* A moving-window limiter over a configurable number of seconds.
* It enforces both packets-per-second and average bytes-per-second limits.
* The effective cap over the full window equals limitPerSecond * windowSeconds.
*/
public final class SimpleBytesPerSecondLimiter implements PacketLimiter {
@Nullable
private final IntervalledCounter bytesCounter;
@Nullable
private final IntervalledCounter packetsCounter;
private final int packetsPerSecond;
private final int bytesPerSecond;
/**
* Creates a new SimpleBytesPerSecondLimiter.
*
* @param packetsPerSecond maximum average packets per second allowed (> 0)
* @param bytesPerSecond maximum average bytes per second allowed (> 0)
* @param windowSeconds number of seconds in the moving window (> 0)
*/
public SimpleBytesPerSecondLimiter(int packetsPerSecond, int bytesPerSecond, int windowSeconds) {
this.packetsPerSecond = packetsPerSecond;
if (windowSeconds <= 0) {
throw new IllegalArgumentException("windowSeconds must be > 0");
}
this.bytesPerSecond = bytesPerSecond;
this.packetsCounter = packetsPerSecond > 0 ? new IntervalledCounter((long) (windowSeconds * 1.0e9)) : null;
this.bytesCounter = bytesPerSecond > 0 ? new IntervalledCounter((long) (windowSeconds * 1.0e9)) : null;
}
/**
* Records the given payload length as one packet and returns whether it is allowed.
*/
@SuppressWarnings("RedundantIfStatement")
@Override
public boolean account(int bytes) {
long currTime = System.nanoTime();
if (packetsCounter != null) {
packetsCounter.updateAndAdd(1, currTime);
if (packetsCounter.getRate() > packetsPerSecond) {
return false;
}
}
if (bytesCounter != null) {
bytesCounter.updateAndAdd(bytes, currTime);
if (bytesCounter.getRate() > bytesPerSecond) {
return false;
}
}
return true;
}
}
@@ -32,20 +32,17 @@ import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* An implementation of {@code InetNameResolver} that performs blocking DNS name lookups
* on a small bounded pool of separate threads, avoiding blocking the Netty threads for an
* extended period of time and without the downsides of Netty's native DNS resolver.
* in a separate thread, avoiding blocking the Netty threads for an extended period of time
* and without the downsides of Netty's native DNS resolver.
*/
public final class SeparatePoolInetNameResolver extends InetNameResolver {
private static final int MAX_RESOLVE_THREADS = 8;
private final ExecutorService resolveExecutor;
private final InetNameResolver delegate;
private final Cache<String, List<InetAddress>> cache;
@@ -59,15 +56,11 @@ public final class SeparatePoolInetNameResolver extends InetNameResolver {
*/
public SeparatePoolInetNameResolver(EventExecutor executor) {
super(executor);
ThreadPoolExecutor resolveExecutor = new ThreadPoolExecutor(
MAX_RESOLVE_THREADS, MAX_RESOLVE_THREADS,
60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(),
this.resolveExecutor = Executors.newSingleThreadExecutor(
new ThreadFactoryBuilder()
.setNameFormat("Velocity DNS Resolver #%d")
.setNameFormat("Velocity DNS Resolver")
.setDaemon(true)
.build());
resolveExecutor.allowCoreThreadTimeOut(true);
this.resolveExecutor = resolveExecutor;
this.delegate = new DefaultNameResolver(executor);
this.cache = Caffeine.newBuilder()
.expireAfterWrite(30, TimeUnit.SECONDS)
@@ -78,7 +71,7 @@ public final class SeparatePoolInetNameResolver extends InetNameResolver {
protected void doResolve(String inetHost, Promise<InetAddress> promise) throws Exception {
List<InetAddress> addresses = cache.getIfPresent(inetHost);
if (addresses != null) {
promise.trySuccess(addresses.getFirst());
promise.trySuccess(addresses.get(0));
return;
}
@@ -83,7 +83,7 @@ public class JavaPluginLoader implements PluginLoader {
@Override
public PluginDescription createPluginFromCandidate(PluginDescription candidate) throws Exception {
if (!(candidate instanceof JavaVelocityPluginDescriptionCandidate candidateInst)) {
if (!(candidate instanceof JavaVelocityPluginDescriptionCandidate)) {
throw new IllegalArgumentException("Description provided isn't of the Java plugin loader");
}
@@ -93,6 +93,8 @@ public class JavaPluginLoader implements PluginLoader {
PluginClassLoader loader = new PluginClassLoader(new URL[]{pluginJarUrl});
loader.addToClassloaders();
JavaVelocityPluginDescriptionCandidate candidateInst =
(JavaVelocityPluginDescriptionCandidate) candidate;
Class<?> mainClass = loader.loadClass(candidateInst.getMainClass());
return createDescription(candidateInst, mainClass);
}
@@ -100,10 +102,11 @@ public class JavaPluginLoader implements PluginLoader {
@Override
public Module createModule(PluginContainer container) {
PluginDescription description = container.getDescription();
if (!(description instanceof JavaVelocityPluginDescription javaDescription)) {
if (!(description instanceof JavaVelocityPluginDescription)) {
throw new IllegalArgumentException("Description provided isn't of the Java plugin loader");
}
JavaVelocityPluginDescription javaDescription = (JavaVelocityPluginDescription) description;
Optional<Path> source = javaDescription.getSource();
if (source.isEmpty()) {
@@ -115,23 +118,24 @@ public class JavaPluginLoader implements PluginLoader {
@Override
public void createPlugin(PluginContainer container, Module... modules) {
if (!(container instanceof VelocityPluginContainer pluginContainer)) {
if (!(container instanceof VelocityPluginContainer)) {
throw new IllegalArgumentException("Container provided isn't of the Java plugin loader");
}
PluginDescription description = pluginContainer.getDescription();
if (!(description instanceof JavaVelocityPluginDescription javaPluginDescription)) {
PluginDescription description = container.getDescription();
if (!(description instanceof JavaVelocityPluginDescription)) {
throw new IllegalArgumentException("Description provided isn't of the Java plugin loader");
}
Injector injector = Guice.createInjector(modules);
Object instance = injector.getInstance(javaPluginDescription.getMainClass());
Object instance = injector
.getInstance(((JavaVelocityPluginDescription) description).getMainClass());
if (instance == null) {
throw new IllegalStateException(
"Got nothing from injector for plugin " + description.getId());
}
pluginContainer.setInstance(instance);
((VelocityPluginContainer) container).setInstance(instance);
}
private Optional<SerializedPluginDescription> getSerializedPluginInfo(Path source)
@@ -141,23 +145,22 @@ public class JavaPluginLoader implements PluginLoader {
new BufferedInputStream(Files.newInputStream(source)))) {
JarEntry entry;
while ((entry = in.getNextJarEntry()) != null) {
switch (entry.getName()) {
case "velocity-plugin.json" -> {
try (Reader pluginInfoReader = new InputStreamReader(in, StandardCharsets.UTF_8)) {
return Optional.of(VelocityServer.GENERAL_GSON.fromJson(pluginInfoReader,
SerializedPluginDescription.class));
}
}
case "paper-plugin.yml", "plugin.yml", "bungee.yml" -> foundBungeeBukkitPluginFile = true;
default -> {
if (entry.getName().equals("velocity-plugin.json")) {
try (Reader pluginInfoReader = new InputStreamReader(in, StandardCharsets.UTF_8)) {
return Optional.of(VelocityServer.GENERAL_GSON.fromJson(pluginInfoReader,
SerializedPluginDescription.class));
}
}
if (entry.getName().equals("plugin.yml") || entry.getName().equals("bungee.yml")) {
foundBungeeBukkitPluginFile = true;
}
}
if (foundBungeeBukkitPluginFile) {
throw new InvalidPluginException("The plugin file " + source.getFileName() + " appears to "
+ "be a Paper, Bukkit or BungeeCord plugin. Velocity does not support plugins from these "
+ "platforms.");
+ "be a Bukkit or BungeeCord plugin. Velocity does not support Bukkit or BungeeCord "
+ "plugins.");
}
return Optional.empty();
@@ -32,18 +32,13 @@ public interface MinecraftPacket {
boolean handle(MinecraftSessionHandler handler);
default int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
default int expectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
return -1;
}
default int decodeExpectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction,
default int expectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
return 0;
}
default int encodeSizeHint(ProtocolUtils.Direction direction,
ProtocolVersion version) {
return -1;
}
}
@@ -36,9 +36,7 @@ import io.netty.handler.codec.EncoderException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.nbt.BinaryTag;
@@ -46,7 +44,6 @@ import net.kyori.adventure.nbt.BinaryTagIO;
import net.kyori.adventure.nbt.BinaryTagType;
import net.kyori.adventure.nbt.BinaryTagTypes;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.sound.Sound;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.json.JSONOptions;
import net.kyori.adventure.text.serializer.json.legacyimpl.NBTLegacyHoverEventSerializer;
@@ -60,11 +57,10 @@ public enum ProtocolUtils {
private static final GsonComponentSerializer PRE_1_16_SERIALIZER =
GsonComponentSerializer.builder()
.downsampleColors()
.legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.get())
.options(
OptionSchema.globalSchema().stateBuilder()
// general options
.value(JSONOptions.EMIT_CLICK_URL_HTTPS, Boolean.TRUE)
// before 1.16
.value(JSONOptions.EMIT_RGB, Boolean.FALSE)
.value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.VALUE_FIELD)
@@ -73,8 +69,6 @@ public enum ProtocolUtils {
.value(JSONOptions.EMIT_COMPACT_TEXT_COMPONENT, Boolean.FALSE)
.value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY, Boolean.FALSE)
.value(JSONOptions.VALIDATE_STRICT_EVENTS, Boolean.FALSE)
// before 1.21.5
.value(JSONOptions.EMIT_CHANGE_PAGE_CLICK_EVENT_PAGE_AS_STRING, Boolean.TRUE)
.build()
)
.build();
@@ -83,8 +77,6 @@ public enum ProtocolUtils {
.legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.get())
.options(
OptionSchema.globalSchema().stateBuilder()
// general options
.value(JSONOptions.EMIT_CLICK_URL_HTTPS, Boolean.TRUE)
// after 1.16
.value(JSONOptions.EMIT_RGB, Boolean.TRUE)
.value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.CAMEL_CASE)
@@ -94,8 +86,6 @@ public enum ProtocolUtils {
.value(JSONOptions.EMIT_COMPACT_TEXT_COMPONENT, Boolean.FALSE)
.value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY, Boolean.FALSE)
.value(JSONOptions.VALIDATE_STRICT_EVENTS, Boolean.FALSE)
// before 1.21.5
.value(JSONOptions.EMIT_CHANGE_PAGE_CLICK_EVENT_PAGE_AS_STRING, Boolean.TRUE)
.build()
)
.build();
@@ -104,8 +94,6 @@ public enum ProtocolUtils {
.legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.get())
.options(
OptionSchema.globalSchema().stateBuilder()
// general options
.value(JSONOptions.EMIT_CLICK_URL_HTTPS, Boolean.TRUE)
// after 1.16
.value(JSONOptions.EMIT_RGB, Boolean.TRUE)
.value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.CAMEL_CASE)
@@ -115,8 +103,6 @@ public enum ProtocolUtils {
.value(JSONOptions.EMIT_COMPACT_TEXT_COMPONENT, Boolean.TRUE)
.value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_ID_AS_INT_ARRAY, Boolean.TRUE)
.value(JSONOptions.VALIDATE_STRICT_EVENTS, Boolean.TRUE)
// before 1.21.5
.value(JSONOptions.EMIT_CHANGE_PAGE_CLICK_EVENT_PAGE_AS_STRING, Boolean.TRUE)
.build()
)
.build();
@@ -125,8 +111,6 @@ public enum ProtocolUtils {
.legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.get())
.options(
OptionSchema.globalSchema().stateBuilder()
// general options
.value(JSONOptions.EMIT_CLICK_URL_HTTPS, Boolean.TRUE)
// after 1.16
.value(JSONOptions.EMIT_RGB, Boolean.TRUE)
.value(JSONOptions.EMIT_HOVER_EVENT_TYPE, JSONOptions.HoverEventValueMode.SNAKE_CASE)
@@ -137,7 +121,6 @@ public enum ProtocolUtils {
// after 1.21.5
.value(JSONOptions.EMIT_HOVER_SHOW_ENTITY_KEY_AS_TYPE_AND_UUID_AS_ID, Boolean.FALSE)
.value(JSONOptions.VALIDATE_STRICT_EVENTS, Boolean.TRUE)
.value(JSONOptions.EMIT_CHANGE_PAGE_CLICK_EVENT_PAGE_AS_STRING, Boolean.FALSE)
.build()
)
.build();
@@ -151,7 +134,7 @@ public enum ProtocolUtils {
BinaryTagTypes.COMPOUND, BinaryTagTypes.INT_ARRAY, BinaryTagTypes.LONG_ARRAY};
private static final QuietDecoderException BAD_VARINT_CACHED =
new QuietDecoderException("Bad VarInt decoded");
private static final int[] VAR_INT_LENGTHS = new int[33];
private static final int[] VAR_INT_LENGTHS = new int[65];
static {
for (int i = 0; i <= 32; ++i) {
@@ -160,9 +143,6 @@ public enum ProtocolUtils {
VAR_INT_LENGTHS[32] = 1; // Special case for the number 0.
}
public static final int DEFAULT_MAX_STRING_BYTES = varIntBytes(ByteBufUtil.utf8MaxBytes(DEFAULT_MAX_STRING_SIZE))
+ ByteBufUtil.utf8MaxBytes(DEFAULT_MAX_STRING_SIZE);
private static DecoderException badVarint() {
return MinecraftDecoder.DEBUG ? new CorruptedFrameException("Bad VarInt decoded")
: BAD_VARINT_CACHED;
@@ -254,15 +234,16 @@ public enum ProtocolUtils {
}
/**
* Directly encodes a 21-bit Minecraft VarInt, ready to be written with {@link ByteBuf#writeMedium(int)}.
* Writes the specified {@code value} as a 21-bit Minecraft VarInt to the specified {@code buf}.
* The upper 11 bits will be discarded.
*
* @param value the value to encode
* @return the encoded value
* @param buf the buffer to read from
* @param value the integer to write
*/
public static int encode21BitVarInt(int value) {
public static void write21BitVarInt(ByteBuf buf, int value) {
// See https://steinborn.me/posts/performance/how-fast-can-you-write-a-varint/
return (value & 0x7F | 0x80) << 16 | ((value >>> 7) & 0x7F | 0x80) << 8 | (value >>> 14);
int w = (value & 0x7F | 0x80) << 16 | ((value >>> 7) & 0x7F | 0x80) << 8 | (value >>> 14);
buf.writeMedium(w);
}
public static String readString(ByteBuf buf) {
@@ -291,22 +272,12 @@ public enum ProtocolUtils {
checkFrame(buf.isReadable(length),
"Trying to read a string that is too long (wanted %s, only have %s)", length,
buf.readableBytes());
String str = buf.readString(length, StandardCharsets.UTF_8);
String str = buf.toString(buf.readerIndex(), length, StandardCharsets.UTF_8);
buf.skipBytes(length);
checkFrame(str.length() <= cap, "Got a too-long string (got %s, max %s)", str.length(), cap);
return str;
}
/**
* Determines the size of the written {@code str} if encoded as a VarInt-prefixed UTF-8 string.
*
* @param str the string to write
* @return the encoded size
*/
public static int stringSizeHint(CharSequence str) {
int size = ByteBufUtil.utf8Bytes(str);
return varIntBytes(size) + size;
}
/**
* Writes the specified {@code str} to the {@code buf} with a VarInt prefix.
*
@@ -339,16 +310,6 @@ public enum ProtocolUtils {
writeString(buf, key.asString());
}
/**
* Writes the key to the buffer, dropping the "minecraft:" namespace when present.
*
* @param buf the buffer to write to
* @param key the key to write
*/
public static void writeMinimalKey(ByteBuf buf, Key key) {
writeString(buf, key.asMinimalString());
}
/**
* Reads a standard Mojang Text namespaced:key array from the buffer.
*
@@ -419,10 +380,7 @@ public enum ProtocolUtils {
*/
public static int[] readIntegerArray(ByteBuf buf) {
int len = readVarInt(buf);
checkFrame(len >= 0, "Got a negative-length integer array (%s)", len);
checkFrame(buf.isReadable(len),
"Trying to read an array that is too long (wanted %s, only have %s)", len,
buf.readableBytes());
checkArgument(len >= 0, "Got a negative-length integer array (%s)", len);
int[] array = new int[len];
for (int i = 0; i < len; i++) {
array[i] = readVarInt(buf);
@@ -542,10 +500,6 @@ public enum ProtocolUtils {
*/
public static String[] readStringArray(ByteBuf buf) {
int length = readVarInt(buf);
checkFrame(length >= 0, "Got a negative-length array (%s)", length);
checkFrame(buf.isReadable(length),
"Trying to read an array that is too long (wanted %s, only have %s)", length,
buf.readableBytes());
String[] ret = new String[length];
for (int i = 0; i < length; i++) {
ret[i] = readString(buf);
@@ -657,9 +611,6 @@ public enum ProtocolUtils {
checkArgument(len <= FORGE_MAX_ARRAY_LENGTH,
"Cannot receive array longer than %s (got %s bytes)", FORGE_MAX_ARRAY_LENGTH, len);
checkFrame(buf.isReadable(len),
"Trying to read an array that is too long (wanted %s, only have %s)", len,
buf.readableBytes());
byte[] ret = new byte[len];
buf.readBytes(ret);
@@ -824,63 +775,6 @@ public enum ProtocolUtils {
return new IdentifiedKeyImpl(revision, key, expiry, signature);
}
/**
* Reads a {@link Sound.Source} from the buffer.
*
* @param buf the buffer
* @param version the protocol version
* @return the sound source
*/
public static Sound.Source readSoundSource(ByteBuf buf, ProtocolVersion version) {
int ordinal = readVarInt(buf);
if (version.lessThan(ProtocolVersion.MINECRAFT_1_21_5)
&& ordinal == Sound.Source.UI.ordinal()) {
throw new UnsupportedOperationException("UI sound-source is only supported in 1.21.5+");
}
return Sound.Source.values()[ordinal];
}
/**
* Writes a {@link Sound.Source} to the buffer.
*
* @param buf the buffer
* @param version the protocol version
* @param source the sound source to write
*/
public static void writeSoundSource(ByteBuf buf, ProtocolVersion version, Sound.Source source) {
if (version.lessThan(ProtocolVersion.MINECRAFT_1_21_5)
&& source == Sound.Source.UI) {
throw new UnsupportedOperationException("UI sound-source is only supported in 1.21.5+");
}
writeVarInt(buf, source.ordinal());
}
/**
* Returns a pre-sized list with a max initial size of {@code Short.MAX_VALUE}.
*
* @param initialCapacity expected initial capacity
* @param <T> entry type
* @return pre-sized list
*/
public static <T> List<T> newList(int initialCapacity) {
return new ArrayList<>(Math.min(initialCapacity, Short.MAX_VALUE));
}
/**
* Returns a pre-sized map with a max initial size of {@code Short.MAX_VALUE}.
*
* @param initialCapacity expected initial capacity
* @param <K> key type
* @param <V> value type
* @return pre-sized map
*/
public static <K, V> Map<K, V> newMap(int initialCapacity) {
return new HashMap<>(Math.min(initialCapacity, Short.MAX_VALUE));
}
/**
* Represents the direction in which a packet flows.
*/
@@ -888,4 +782,4 @@ public enum ProtocolUtils {
SERVERBOUND,
CLIENTBOUND
}
}
}
@@ -41,12 +41,10 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_2;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_4;
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_1_21_9;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_7_2;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9;
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.MINIMUM_VERSION;
import static com.velocitypowered.api.network.ProtocolVersion.SUPPORTED_VERSIONS;
import static com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
@@ -60,11 +58,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.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;
@@ -87,7 +81,6 @@ 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.SetCompressionPacket;
import com.velocitypowered.proxy.protocol.packet.StatusPingPacket;
import com.velocitypowered.proxy.protocol.packet.StatusRequestPacket;
@@ -108,8 +101,6 @@ import com.velocitypowered.proxy.protocol.packet.chat.session.UnsignedPlayerComm
import com.velocitypowered.proxy.protocol.packet.config.ActiveFeaturesPacket;
import com.velocitypowered.proxy.protocol.packet.config.ClientboundCustomReportDetailsPacket;
import com.velocitypowered.proxy.protocol.packet.config.ClientboundServerLinksPacket;
import com.velocitypowered.proxy.protocol.packet.config.CodeOfConductAcceptPacket;
import com.velocitypowered.proxy.protocol.packet.config.CodeOfConductPacket;
import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket;
import com.velocitypowered.proxy.protocol.packet.config.KnownPacksPacket;
import com.velocitypowered.proxy.protocol.packet.config.RegistrySyncPacket;
@@ -192,12 +183,6 @@ public enum StateRegistry {
KnownPacksPacket.class,
KnownPacksPacket::new,
map(0x07, MINECRAFT_1_20_5, false));
serverbound.register(ServerboundCustomClickActionPacket.class, ServerboundCustomClickActionPacket::new,
map(0x08, MINECRAFT_1_21_6, false));
serverbound.register(
CodeOfConductAcceptPacket.class,
() -> CodeOfConductAcceptPacket.INSTANCE,
map(0x09, MINECRAFT_1_21_9, false));
clientbound.register(
ClientboundCookieRequestPacket.class, ClientboundCookieRequestPacket::new,
@@ -252,12 +237,6 @@ public enum StateRegistry {
map(0x0F, MINECRAFT_1_21, false));
clientbound.register(ClientboundServerLinksPacket.class, ClientboundServerLinksPacket::new,
map(0x10, MINECRAFT_1_21, false));
clientbound.register(DialogClearPacket.class, () -> DialogClearPacket.INSTANCE,
map(0x11, MINECRAFT_1_21_6, false));
clientbound.register(DialogShowPacket.class, () -> new DialogShowPacket(this),
map(0x12, MINECRAFT_1_21_6, false));
clientbound.register(CodeOfConductPacket.class, CodeOfConductPacket::new,
map(0x13, MINECRAFT_1_21_9, false));
}
},
PLAY {
@@ -279,8 +258,7 @@ public enum StateRegistry {
map(0x0A, MINECRAFT_1_20_2, false),
map(0x0B, MINECRAFT_1_20_5, false),
map(0x0D, MINECRAFT_1_21_2, false),
map(0x0E, MINECRAFT_1_21_6, false),
map(0x0F, MINECRAFT_26_1, false));
map(0x0E, MINECRAFT_1_21_6, false));
serverbound.register(
LegacyChatPacket.class,
LegacyChatPacket::new,
@@ -294,8 +272,7 @@ public enum StateRegistry {
ChatAcknowledgementPacket::new,
map(0x03, MINECRAFT_1_19_3, false),
map(0x04, MINECRAFT_1_21_2, false),
map(0x05, MINECRAFT_1_21_6, false),
map(0x06, MINECRAFT_26_1, false));
map(0x05, MINECRAFT_1_21_6, false));
serverbound.register(KeyedPlayerCommandPacket.class, KeyedPlayerCommandPacket::new,
map(0x03, MINECRAFT_1_19, false),
map(0x04, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false));
@@ -306,21 +283,18 @@ public enum StateRegistry {
map(0x04, MINECRAFT_1_19_3, false),
map(0x05, MINECRAFT_1_20_5, false),
map(0x06, MINECRAFT_1_21_2, false),
map(0x07, MINECRAFT_1_21_6, false),
map(0x08, MINECRAFT_26_1, false));
map(0x07, MINECRAFT_1_21_6, false));
serverbound.register(UnsignedPlayerCommandPacket.class, UnsignedPlayerCommandPacket::new,
map(0x04, MINECRAFT_1_20_5, false),
map(0x05, MINECRAFT_1_21_2, false),
map(0x06, MINECRAFT_1_21_6, false),
map(0x07, MINECRAFT_26_1, false));
map(0x06, MINECRAFT_1_21_6, false));
serverbound.register(
SessionPlayerChatPacket.class,
SessionPlayerChatPacket::new,
map(0x05, MINECRAFT_1_19_3, false),
map(0x06, MINECRAFT_1_20_5, false),
map(0x07, MINECRAFT_1_21_2, false),
map(0x08, MINECRAFT_1_21_6, false),
map(0x09, MINECRAFT_26_1, false));
map(0x08, MINECRAFT_1_21_6, false));
serverbound.register(
ClientSettingsPacket.class,
ClientSettingsPacket::new,
@@ -336,14 +310,12 @@ public enum StateRegistry {
map(0x09, MINECRAFT_1_20_2, false),
map(0x0A, MINECRAFT_1_20_5, false),
map(0x0C, MINECRAFT_1_21_2, false),
map(0x0D, MINECRAFT_1_21_6, false),
map(0x0E, MINECRAFT_26_1, false));
map(0x0D, MINECRAFT_1_21_6, false));
serverbound.register(
ServerboundCookieResponsePacket.class, ServerboundCookieResponsePacket::new,
map(0x11, MINECRAFT_1_20_5, false),
map(0x13, MINECRAFT_1_21_2, false),
map(0x14, MINECRAFT_1_21_6, false),
map(0x15, MINECRAFT_26_1, false));
map(0x14, MINECRAFT_1_21_6, false));
serverbound.register(
PluginMessagePacket.class,
PluginMessagePacket::new,
@@ -362,8 +334,7 @@ public enum StateRegistry {
map(0x10, MINECRAFT_1_20_3, false),
map(0x12, MINECRAFT_1_20_5, false),
map(0x14, MINECRAFT_1_21_2, false),
map(0x15, MINECRAFT_1_21_6, false),
map(0x16, MINECRAFT_26_1, false));
map(0x15, MINECRAFT_1_21_6, false));
serverbound.register(
KeepAlivePacket.class,
KeepAlivePacket::new,
@@ -383,8 +354,7 @@ public enum StateRegistry {
map(0x15, MINECRAFT_1_20_3, false),
map(0x18, MINECRAFT_1_20_5, false),
map(0x1A, MINECRAFT_1_21_2, false),
map(0x1B, MINECRAFT_1_21_6, false),
map(0x1C, MINECRAFT_26_1, false));
map(0x1B, MINECRAFT_1_21_6, false));
serverbound.register(
ResourcePackResponsePacket.class,
ResourcePackResponsePacket::new,
@@ -402,15 +372,13 @@ public enum StateRegistry {
map(0x2B, MINECRAFT_1_20_5, false),
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(0x30, MINECRAFT_1_21_6, false));
serverbound.register(
FinishedUpdatePacket.class, () -> FinishedUpdatePacket.INSTANCE,
map(0x0B, MINECRAFT_1_20_2, false),
map(0x0C, MINECRAFT_1_20_5, false),
map(0x0E, MINECRAFT_1_21_2, false),
map(0x0F, MINECRAFT_1_21_6, false),
map(0x10, MINECRAFT_26_1, false));
map(0x0F, MINECRAFT_1_21_6, false));
clientbound.register(
BossBarPacket.class,
@@ -462,28 +430,6 @@ public enum StateRegistry {
ClientboundCookieRequestPacket.class, ClientboundCookieRequestPacket::new,
map(0x16, MINECRAFT_1_20_5, false),
map(0x15, MINECRAFT_1_21_5, false));
clientbound.register(
ClientboundSoundEntityPacket.class, ClientboundSoundEntityPacket::new,
map(0x5D, MINECRAFT_1_19_3, true),
map(0x61, MINECRAFT_1_19_4, true),
map(0x63, MINECRAFT_1_20_2, true),
map(0x65, MINECRAFT_1_20_3, true),
map(0x67, MINECRAFT_1_20_5, true),
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));
clientbound.register(
ClientboundStopSoundPacket.class, ClientboundStopSoundPacket::new,
map(0x5F, MINECRAFT_1_19_3, true),
map(0x63, MINECRAFT_1_19_4, true),
map(0x66, MINECRAFT_1_20_2, true),
map(0x68, MINECRAFT_1_20_3, true),
map(0x6A, MINECRAFT_1_20_5, true),
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));
clientbound.register(
PluginMessagePacket.class,
PluginMessagePacket::new,
@@ -519,8 +465,7 @@ public enum StateRegistry {
map(0x1A, MINECRAFT_1_19_4, false),
map(0x1B, MINECRAFT_1_20_2, false),
map(0x1D, MINECRAFT_1_20_5, false),
map(0x1C, MINECRAFT_1_21_5, false),
map(0x20, MINECRAFT_1_21_9, false));
map(0x1C, MINECRAFT_1_21_5, false));
clientbound.register(
KeepAlivePacket.class,
KeepAlivePacket::new,
@@ -539,9 +484,7 @@ public enum StateRegistry {
map(0x24, MINECRAFT_1_20_2, false),
map(0x26, MINECRAFT_1_20_5, false),
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(0x26, MINECRAFT_1_21_5, false));
clientbound.register(
JoinGamePacket.class,
JoinGamePacket::new,
@@ -560,9 +503,7 @@ public enum StateRegistry {
map(0x29, MINECRAFT_1_20_2, false),
map(0x2B, MINECRAFT_1_20_5, false),
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(0x2B, MINECRAFT_1_21_5, false));
clientbound.register(
RespawnPacket.class,
RespawnPacket::new,
@@ -584,18 +525,14 @@ public enum StateRegistry {
map(0x45, MINECRAFT_1_20_3, true),
map(0x47, MINECRAFT_1_20_5, true),
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(0x4B, MINECRAFT_1_21_5, true));
clientbound.register(
RemoveResourcePackPacket.class,
RemoveResourcePackPacket::new,
map(0x43, MINECRAFT_1_20_3, false),
map(0x45, MINECRAFT_1_20_5, false),
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(0x49, MINECRAFT_1_21_5, false));
clientbound.register(
ResourcePackRequestPacket.class,
ResourcePackRequestPacket::new,
@@ -617,9 +554,7 @@ public enum StateRegistry {
map(0x44, MINECRAFT_1_20_3, false),
map(0x46, MINECRAFT_1_20_5, false),
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(0x4A, MINECRAFT_1_21_5, false));
clientbound.register(
HeaderAndFooterPacket.class,
HeaderAndFooterPacket::new,
@@ -642,9 +577,7 @@ public enum StateRegistry {
map(0x6A, MINECRAFT_1_20_3, true),
map(0x6D, MINECRAFT_1_20_5, true),
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(0x73, MINECRAFT_1_21_5, true));
clientbound.register(
LegacyTitlePacket.class,
LegacyTitlePacket::new,
@@ -666,9 +599,7 @@ public enum StateRegistry {
map(0x61, MINECRAFT_1_20_3, true),
map(0x63, MINECRAFT_1_20_5, true),
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(0x69, MINECRAFT_1_21_5, true));
clientbound.register(
TitleTextPacket.class,
TitleTextPacket::new,
@@ -681,9 +612,7 @@ public enum StateRegistry {
map(0x63, MINECRAFT_1_20_3, true),
map(0x65, MINECRAFT_1_20_5, true),
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(0x6B, MINECRAFT_1_21_5, true));
clientbound.register(
TitleActionbarPacket.class,
TitleActionbarPacket::new,
@@ -696,9 +625,7 @@ public enum StateRegistry {
map(0x4A, MINECRAFT_1_20_3, true),
map(0x4C, MINECRAFT_1_20_5, true),
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(0x50, MINECRAFT_1_21_5, true));
clientbound.register(
TitleTimesPacket.class,
TitleTimesPacket::new,
@@ -711,9 +638,7 @@ public enum StateRegistry {
map(0x64, MINECRAFT_1_20_3, true),
map(0x66, MINECRAFT_1_20_5, true),
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(0x6C, MINECRAFT_1_21_5, true));
clientbound.register(
TitleClearPacket.class,
TitleClearPacket::new,
@@ -743,9 +668,7 @@ public enum StateRegistry {
map(0x3B, MINECRAFT_1_20_2, false),
map(0x3D, MINECRAFT_1_20_5, false),
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(0x3E, MINECRAFT_1_21_5, false));
clientbound.register(
UpsertPlayerInfoPacket.class,
UpsertPlayerInfoPacket::new,
@@ -754,16 +677,12 @@ public enum StateRegistry {
map(0x3C, MINECRAFT_1_20_2, false),
map(0x3E, MINECRAFT_1_20_5, false),
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(0x3F, MINECRAFT_1_21_5, 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(0x71, MINECRAFT_1_21_5, false));
clientbound.register(
SystemChatPacket.class,
SystemChatPacket::new,
@@ -775,9 +694,7 @@ public enum StateRegistry {
map(0x69, MINECRAFT_1_20_3, true),
map(0x6C, MINECRAFT_1_20_5, true),
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(0x72, MINECRAFT_1_21_5, true));
clientbound.register(
PlayerChatCompletionPacket.class,
PlayerChatCompletionPacket::new,
@@ -798,9 +715,7 @@ public enum StateRegistry {
map(0x49, MINECRAFT_1_20_3, false),
map(0x4B, MINECRAFT_1_20_5, false),
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(0x4F, MINECRAFT_1_21_5, false));
clientbound.register(
StartUpdatePacket.class,
() -> StartUpdatePacket.INSTANCE,
@@ -808,9 +723,7 @@ public enum StateRegistry {
map(0x67, MINECRAFT_1_20_3, false),
map(0x69, MINECRAFT_1_20_5, false),
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(0x6F, MINECRAFT_1_21_5, false));
clientbound.register(
BundleDelimiterPacket.class,
() -> BundleDelimiterPacket.INSTANCE,
@@ -819,23 +732,17 @@ public enum StateRegistry {
TransferPacket.class,
TransferPacket::new,
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(0x7A, MINECRAFT_1_21_2, 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(0x81, MINECRAFT_1_21_2, 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(0x82, MINECRAFT_1_21_2, false));
}
},
LOGIN {
@@ -864,7 +771,7 @@ public enum StateRegistry {
map(0x01, MINECRAFT_1_7_2, false));
clientbound.register(
ServerLoginSuccessPacket.class, ServerLoginSuccessPacket::new,
map(0x02, MINECRAFT_1_7_2, false));
map(0x02, MINECRAFT_1_7_2, false));
clientbound.register(
SetCompressionPacket.class, SetCompressionPacket::new,
map(0x03, MINECRAFT_1_8, false));
@@ -119,7 +119,7 @@ public class GameSpyQueryHandler extends SimpleChannelInboundHandler<DatagramPac
int sessionId = queryMessage.readInt();
switch (type) {
case QUERY_TYPE_HANDSHAKE -> {
case QUERY_TYPE_HANDSHAKE: {
// Generate new challenge token and put it into the sessions cache
int challengeToken = random.nextInt();
sessions.put(senderAddress, challengeToken);
@@ -132,9 +132,10 @@ public class GameSpyQueryHandler extends SimpleChannelInboundHandler<DatagramPac
DatagramPacket responsePacket = new DatagramPacket(queryResponse, msg.sender());
ctx.writeAndFlush(responsePacket, ctx.voidPromise());
break;
}
case QUERY_TYPE_STAT -> {
case QUERY_TYPE_STAT: {
// Check if query was done with session previously generated using a handshake packet
int challengeToken = queryMessage.readInt();
Integer session = sessions.getIfPresent(senderAddress);
@@ -189,10 +190,10 @@ public class GameSpyQueryHandler extends SimpleChannelInboundHandler<DatagramPac
"Exception while writing GS4 response for query from {}", senderAddress, ex);
return null;
});
break;
}
default -> {
// Invalid query type - just don't respond
}
default:
// Invalid query type - just don't respond
}
}
@@ -22,49 +22,31 @@ import static com.velocitypowered.natives.util.MoreByteBufUtils.preferredBuffer;
import static com.velocitypowered.proxy.protocol.util.NettyPreconditions.checkFrame;
import com.velocitypowered.natives.compression.VelocityCompressor;
import com.velocitypowered.proxy.network.limiter.PacketLimiter;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.util.except.QuietDecoderException;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;
import java.util.List;
import org.jspecify.annotations.Nullable;
/**
* Decompresses a Minecraft packet.
*/
public class MinecraftCompressDecoder extends MessageToMessageDecoder<ByteBuf> {
private static final int SERVERBOUND_MAXIMUM_UNCOMPRESSED_SIZE = 2 * 1024 * 1024; // 2MiB
private static final int VANILLA_MAXIMUM_UNCOMPRESSED_SIZE = 8 * 1024 * 1024; // 8MiB
private static final int HARD_MAXIMUM_UNCOMPRESSED_SIZE = 128 * 1024 * 1024; // 128MiB
private static final int CLIENTBOUND_UNCOMPRESSED_CAP =
private static final int UNCOMPRESSED_CAP =
Boolean.getBoolean("velocity.increased-compression-cap")
? HARD_MAXIMUM_UNCOMPRESSED_SIZE : VANILLA_MAXIMUM_UNCOMPRESSED_SIZE;
private static final int SERVERBOUND_UNCOMPRESSED_CAP =
Boolean.getBoolean("velocity.increased-compression-cap")
? HARD_MAXIMUM_UNCOMPRESSED_SIZE : SERVERBOUND_MAXIMUM_UNCOMPRESSED_SIZE;
private static final boolean SKIP_COMPRESSION_VALIDATION = Boolean.getBoolean("velocity.skip-uncompressed-packet-size-validation");
private final ProtocolUtils.Direction direction;
private int threshold;
private final VelocityCompressor compressor;
@Nullable
private PacketLimiter packetLimiter;
/**
* Creates a new {@code MinecraftCompressDecoder} with the specified compression {@code threshold}.
*
* @param threshold the threshold for compression. Packets with uncompressed size below this threshold will not be compressed.
* @param compressor the compressor instance to use
* @param direction the direction of the packets being decoded
*/
public MinecraftCompressDecoder(int threshold, VelocityCompressor compressor, ProtocolUtils.Direction direction) {
public MinecraftCompressDecoder(int threshold, VelocityCompressor compressor) {
this.threshold = threshold;
this.compressor = compressor;
this.direction = direction;
}
@Override
@@ -77,35 +59,20 @@ public class MinecraftCompressDecoder extends MessageToMessageDecoder<ByteBuf> {
+ " threshold %s", actualUncompressedSize, threshold);
}
// This message is not compressed.
if (packetLimiter != null && !packetLimiter.account(in.readableBytes())) {
throw new QuietDecoderException("Rate limit exceeded while processing packets for %s"
.formatted(ctx.channel().remoteAddress()));
}
out.add(in.retain());
return;
}
checkFrame(claimedUncompressedSize >= threshold, "Uncompressed size %s is less than"
+ " threshold %s", claimedUncompressedSize, threshold);
if (direction == ProtocolUtils.Direction.CLIENTBOUND) {
checkFrame(claimedUncompressedSize <= CLIENTBOUND_UNCOMPRESSED_CAP,
"Uncompressed size %s exceeds hard threshold of %s", claimedUncompressedSize,
CLIENTBOUND_UNCOMPRESSED_CAP);
} else {
checkFrame(claimedUncompressedSize <= SERVERBOUND_UNCOMPRESSED_CAP,
"Uncompressed size %s exceeds hard threshold of %s", claimedUncompressedSize,
SERVERBOUND_UNCOMPRESSED_CAP);
}
checkFrame(claimedUncompressedSize <= UNCOMPRESSED_CAP,
"Uncompressed size %s exceeds hard threshold of %s", claimedUncompressedSize,
UNCOMPRESSED_CAP);
ByteBuf compatibleIn = ensureCompatible(ctx.alloc(), compressor, in);
ByteBuf uncompressed = preferredBuffer(ctx.alloc(), compressor, claimedUncompressedSize);
try {
compressor.inflate(compatibleIn, uncompressed, claimedUncompressedSize);
checkFrame(uncompressed.writerIndex() == claimedUncompressedSize,
"Decompressed size %s does not match claimed uncompressed size %s", uncompressed.writerIndex(), claimedUncompressedSize);
if (packetLimiter != null && !packetLimiter.account(claimedUncompressedSize)) {
throw new QuietDecoderException("Rate limit exceeded while processing packets for %s"
.formatted(ctx.channel().remoteAddress()));
}
out.add(uncompressed);
} catch (Exception e) {
uncompressed.release();
@@ -123,8 +90,4 @@ public class MinecraftCompressDecoder extends MessageToMessageDecoder<ByteBuf> {
public void setThreshold(int threshold) {
this.threshold = threshold;
}
public void setPacketLimiter(@Nullable PacketLimiter packetLimiter) {
this.packetLimiter = packetLimiter;
}
}
@@ -46,7 +46,7 @@ public class MinecraftCompressorAndLengthEncoder extends MessageToByteEncoder<By
if (uncompressed < threshold) {
// Under the threshold, there is nothing to do.
ProtocolUtils.writeVarInt(out, uncompressed + 1);
out.writeByte(0);
ProtocolUtils.writeVarInt(out, 0);
out.writeBytes(msg);
} else {
handleCompressed(ctx, msg, out);
@@ -57,7 +57,7 @@ public class MinecraftCompressorAndLengthEncoder extends MessageToByteEncoder<By
throws DataFormatException {
int uncompressed = msg.readableBytes();
out.writeMedium(0); // Reserve the packet length
ProtocolUtils.write21BitVarInt(out, 0); // Dummy packet length
ProtocolUtils.writeVarInt(out, uncompressed);
ByteBuf compatibleIn = MoreByteBufUtils.ensureCompatible(ctx.alloc(), compressor, msg);
@@ -72,8 +72,11 @@ public class MinecraftCompressorAndLengthEncoder extends MessageToByteEncoder<By
throw new DataFormatException("The server sent a very large (over 2MiB compressed) packet.");
}
int writerIndex = out.writerIndex();
int packetLength = out.readableBytes() - 3;
out.setMedium(0, ProtocolUtils.encode21BitVarInt(packetLength)); // Rewrite packet length
out.writerIndex(0);
ProtocolUtils.write21BitVarInt(out, packetLength); // Rewrite packet length
out.writerIndex(writerIndex);
}
@Override
@@ -57,11 +57,7 @@ public class MinecraftDecoder extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof ByteBuf buf) {
try {
tryDecode(ctx, buf);
} finally {
buf.release();
}
tryDecode(ctx, buf);
} else {
ctx.fireChannelRead(msg);
}
@@ -69,6 +65,7 @@ public class MinecraftDecoder extends ChannelInboundHandlerAdapter {
private void tryDecode(ChannelHandlerContext ctx, ByteBuf buf) throws Exception {
if (!ctx.channel().isActive() || !buf.isReadable()) {
buf.release();
return;
}
@@ -77,34 +74,35 @@ public class MinecraftDecoder extends ChannelInboundHandlerAdapter {
MinecraftPacket packet = this.registry.createPacket(packetId);
if (packet == null) {
buf.readerIndex(originalReaderIndex);
if (this.direction == ProtocolUtils.Direction.SERVERBOUND && this.state != StateRegistry.PLAY) {
throw this.handleInvalidPacketId(packetId);
}
ctx.fireChannelRead(buf.retain());
ctx.fireChannelRead(buf);
} else {
doLengthSanityChecks(buf, packet);
try {
packet.decode(buf, direction, registry.version);
} catch (Exception e) {
throw handleDecodeFailure(e, packet, packetId);
}
doLengthSanityChecks(buf, packet);
if (buf.isReadable()) {
throw handleOverflow(packet, buf.readerIndex(), buf.writerIndex());
try {
packet.decode(buf, direction, registry.version);
} catch (Exception e) {
throw handleDecodeFailure(e, packet, packetId);
}
if (buf.isReadable()) {
throw handleOverflow(packet, buf.readerIndex(), buf.writerIndex());
}
ctx.fireChannelRead(packet);
} finally {
buf.release();
}
ctx.fireChannelRead(packet);
}
}
private void doLengthSanityChecks(ByteBuf buf, MinecraftPacket packet) throws Exception {
int expectedMinLen = packet.decodeExpectedMinLength(buf, direction, registry.version);
int expectedMaxLen = packet.decodeExpectedMaxLength(buf, direction, registry.version);
int expectedMinLen = packet.expectedMinLength(buf, direction, registry.version);
int expectedMaxLen = packet.expectedMaxLength(buf, direction, registry.version);
if (expectedMaxLen != -1 && buf.readableBytes() > expectedMaxLen) {
throw handleOverflow(packet, expectedMaxLen, buf.readableBytes());
}
if (buf.readableBytes() < expectedMinLen) {
throw handleUnderflow(packet, expectedMinLen, buf.readableBytes());
throw handleUnderflow(packet, expectedMaxLen, buf.readableBytes());
}
}
@@ -135,14 +133,6 @@ public class MinecraftDecoder extends ChannelInboundHandlerAdapter {
}
}
private Exception handleInvalidPacketId(int packetId) {
if (DEBUG) {
return new CorruptedFrameException("Invalid packet " + getExtraConnectionDetail(packetId));
} else {
return DECODE_FAILED;
}
}
private String getExtraConnectionDetail(int packetId) {
return "Direction " + direction + " Protocol " + registry.version + " State " + state
+ " ID 0x" + Integer.toHexString(packetId);
@@ -54,19 +54,6 @@ public class MinecraftEncoder extends MessageToByteEncoder<MinecraftPacket> {
msg.encode(out, direction, registry.version);
}
@Override
protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, MinecraftPacket msg,
boolean preferDirect) throws Exception {
int hint = msg.encodeSizeHint(direction, registry.version);
if (hint < 0) {
return super.allocateBuffer(ctx, msg, preferDirect);
}
int packetId = this.registry.getPacketId(msg);
int totalHint = ProtocolUtils.varIntBytes(packetId) + hint;
return preferDirect ? ctx.alloc().ioBuffer(totalHint) : ctx.alloc().heapBuffer(totalHint);
}
public void setProtocolVersion(final ProtocolVersion protocolVersion) {
this.registry = state.getProtocolRegistry(direction, protocolVersion);
}
@@ -20,7 +20,6 @@ package com.velocitypowered.proxy.protocol.netty;
import static io.netty.util.ByteProcessor.FIND_NON_NUL;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.network.limiter.PacketLimiter;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry;
@@ -33,7 +32,6 @@ import io.netty.handler.codec.CorruptedFrameException;
import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jspecify.annotations.Nullable;
/**
* Frames Minecraft server packets which are prefixed by a 21-bit VarInt encoding.
@@ -46,8 +44,6 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
+ "Velocity with -Dvelocity.packet-decode-logging=true to see more.");
private static final QuietDecoderException BAD_PACKET_LENGTH =
new QuietDecoderException("Bad packet length");
private static final QuietDecoderException INVALID_PREAMBLE =
new QuietDecoderException("Invalid packet preamble");
private static final QuietDecoderException VARINT_TOO_BIG =
new QuietDecoderException("VarInt too big");
private static final QuietDecoderException UNKNOWN_PACKET =
@@ -56,8 +52,6 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
private final ProtocolUtils.Direction direction;
private final StateRegistry.PacketRegistry.ProtocolRegistry registry;
private StateRegistry state;
@Nullable
private PacketLimiter packetLimiter;
/**
* Creates a new {@code MinecraftVarintFrameDecoder} decoding packets from the specified {@code Direction}.
@@ -80,93 +74,69 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
}
// skip any runs of 0x00 we might find
int wlen = in.readableBytes();
int packetStart = in.forEachByte(FIND_NON_NUL);
if (packetStart == -1) {
in.clear();
// Apply a more strict check in serverbound direction, we really shouldn't be seeing this many 0x00s
// even from the server, the only reason we even allow these is due to bugged servers
if (direction == ProtocolUtils.Direction.SERVERBOUND && wlen > 16) {
throw INVALID_PREAMBLE;
}
return;
}
in.readerIndex(packetStart);
// try to read the length of the packet
try {
int length = readRawVarInt21(in);
if (packetStart == in.readerIndex()) {
return;
}
if (length < 0) {
throw BAD_PACKET_LENGTH;
}
in.markReaderIndex();
int preIndex = in.readerIndex();
int length = readRawVarInt21(in);
if (preIndex == in.readerIndex()) {
return;
}
if (length < 0) {
throw BAD_PACKET_LENGTH;
}
if (length > 0) {
if (state == StateRegistry.HANDSHAKE && direction == ProtocolUtils.Direction.SERVERBOUND) {
if (validateServerboundHandshakePacket(in, length)) {
in.readerIndex(packetStart);
return;
}
if (length > 0) {
if (state == StateRegistry.HANDSHAKE && direction == ProtocolUtils.Direction.SERVERBOUND) {
StateRegistry.PacketRegistry.ProtocolRegistry registry =
state.getProtocolRegistry(direction, ProtocolVersion.MINIMUM_VERSION);
final int index = in.readerIndex();
final int packetId = readRawVarInt21(in);
// Index hasn't changed, we've read nothing
if (index == in.readerIndex()) {
in.resetReaderIndex();
return;
}
}
final int payloadLength = length - ProtocolUtils.varIntBytes(packetId);
// note that zero-length packets are ignored
if (length > 0) {
if (in.readableBytes() < length) {
in.readerIndex(packetStart);
} else {
// If enabled, rate-limit serverbound payload bytes based on frame length
if (packetLimiter != null) {
if (!packetLimiter.account(length)) {
throw new QuietDecoderException(
"Rate limit exceeded while processing packets for %s".formatted(
ctx.channel().remoteAddress()));
}
}
out.add(in.readRetainedSlice(length));
MinecraftPacket packet = registry.createPacket(packetId);
// We handle every packet in this phase, if you said something we don't know, something is really wrong
if (packet == null) {
throw UNKNOWN_PACKET;
}
// We 'technically' have the incoming bytes of a payload here, and so, these can actually parse
// the packet if needed, so, we'll take advantage of the existing methods
int expectedMinLen = packet.expectedMinLength(in, direction, registry.version);
int expectedMaxLen = packet.expectedMaxLength(in, direction, registry.version);
if (expectedMaxLen != -1 && payloadLength > expectedMaxLen) {
throw handleOverflow(packet, expectedMaxLen, in.readableBytes());
}
if (payloadLength < expectedMinLen) {
throw handleUnderflow(packet, expectedMaxLen, in.readableBytes());
}
in.readerIndex(index);
}
} catch (Exception e) {
// Reset buffer to consistent state before propagating exception to prevent memory leaks
in.readerIndex(packetStart);
throw e;
}
}
private boolean validateServerboundHandshakePacket(ByteBuf in, int length) throws Exception {
StateRegistry.PacketRegistry.ProtocolRegistry registry =
state.getProtocolRegistry(direction, ProtocolVersion.MINIMUM_VERSION);
final int index = in.readerIndex();
final int packetId = readRawVarInt21(in);
// Index hasn't changed, we've read nothing
if (index == in.readerIndex()) {
return true;
}
final int payloadLength = length - ProtocolUtils.varIntBytes(packetId);
MinecraftPacket packet = registry.createPacket(packetId);
// We handle every packet in this phase, if you said something we don't know, something is really wrong
if (packet == null) {
throw UNKNOWN_PACKET;
}
// We 'technically' have the incoming bytes of a payload here, and so, these can actually parse
// the packet if needed, so, we'll take advantage of the existing methods
int expectedMinLen = packet.decodeExpectedMinLength(in, direction, registry.version);
int expectedMaxLen = packet.decodeExpectedMaxLength(in, direction, registry.version);
if (expectedMaxLen != -1 && payloadLength > expectedMaxLen) {
throw handleOverflow(packet, expectedMaxLen, payloadLength);
// note that zero-length packets are ignored
if (length > 0) {
if (in.readableBytes() < length) {
in.resetReaderIndex();
} else {
out.add(in.readRetainedSlice(length));
}
}
if (payloadLength < expectedMinLen) {
throw handleUnderflow(packet, expectedMinLen, payloadLength);
}
in.readerIndex(index);
return false;
}
@Override
@@ -270,8 +240,4 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
public void setState(StateRegistry stateRegistry) {
this.state = stateRegistry;
}
public void setPacketLimiter(@Nullable PacketLimiter packetLimiter) {
this.packetLimiter = packetLimiter;
}
}
@@ -21,9 +21,6 @@ import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.util.except.QuietDecoderException;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufHolder;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.ReferenceCountUtil;
@@ -44,13 +41,8 @@ import org.jetbrains.annotations.NotNull;
*/
public class PlayPacketQueueInboundHandler extends ChannelDuplexHandler {
private static final int MAXIMUM_SIZE = Integer.getInteger("velocity.maximum-play-queue-size", 128 * 1024 * 1024); // 128MiB by default
private static final QuietDecoderException QUEUE_LIMIT_FAILED = new QuietDecoderException(
"Queue too big (greater than " + MAXIMUM_SIZE + " bytes)");
private final StateRegistry.PacketRegistry.ProtocolRegistry registry;
private final Queue<Object> queue = new ArrayDeque<>();
private int queueSize = 0;
/**
* Provides registries for client &amp; server bound packets.
@@ -72,20 +64,6 @@ public class PlayPacketQueueInboundHandler extends ChannelDuplexHandler {
}
}
int length = 0;
if (msg instanceof ByteBuf) {
// keep track of raw packets
length = ((ByteBuf) msg).readableBytes();
} else if (msg instanceof ByteBufHolder) {
// keep track of bytebufs wrapped inside packets
length = ((ByteBufHolder) msg).content().readableBytes();
}
if (this.queueSize + length > MAXIMUM_SIZE) {
ReferenceCountUtil.release(msg);
throw QUEUE_LIMIT_FAILED;
}
this.queueSize += length;
// Otherwise, queue the packet
this.queue.offer(msg);
}
@@ -112,6 +90,5 @@ public class PlayPacketQueueInboundHandler extends ChannelDuplexHandler {
ReferenceCountUtil.release(msg);
}
}
this.queueSize = 0;
}
}
@@ -25,6 +25,7 @@ import com.mojang.brigadier.builder.ArgumentBuilder;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.SuggestionProvider;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
@@ -44,9 +45,9 @@ import io.netty.buffer.ByteBuf;
import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenCustomHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
@@ -85,14 +86,14 @@ public class AvailableCommandsPacket implements MinecraftPacket {
@Override
public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
int commands = ProtocolUtils.readVarInt(buf);
List<WireNode> wireNodes = ProtocolUtils.newList(commands);
WireNode[] wireNodes = new WireNode[commands];
for (int i = 0; i < commands; i++) {
wireNodes.add(deserializeNode(buf, i, protocolVersion));
wireNodes[i] = deserializeNode(buf, i, protocolVersion);
}
// Iterate over the deserialized nodes and attempt to form a graph. We also resolve any cycles
// that exist.
Queue<WireNode> nodeQueue = new ArrayDeque<>(wireNodes);
Queue<WireNode> nodeQueue = new ArrayDeque<>(Arrays.asList(wireNodes));
while (!nodeQueue.isEmpty()) {
boolean cycling = false;
@@ -111,7 +112,7 @@ public class AvailableCommandsPacket implements MinecraftPacket {
}
int rootIdx = ProtocolUtils.readVarInt(buf);
rootNode = (RootCommandNode<CommandSource>) wireNodes.get(rootIdx).built;
rootNode = (RootCommandNode<CommandSource>) wireNodes[rootIdx].built;
}
@Override
@@ -245,17 +246,17 @@ public class AvailableCommandsPacket implements MinecraftPacket {
this.validated = false;
}
void validate(List<WireNode> wireNodes) {
void validate(WireNode[] wireNodes) {
// Ensure all children exist. Note that we delay checking if the node has been built yet;
// that needs to come after this node is built.
for (int child : children) {
if (child < 0 || child >= wireNodes.size()) {
if (child < 0 || child >= wireNodes.length) {
throw new IllegalStateException("Node points to non-existent index " + child);
}
}
if (redirectTo != -1) {
if (redirectTo < 0 || redirectTo >= wireNodes.size()) {
if (redirectTo < 0 || redirectTo >= wireNodes.length) {
throw new IllegalStateException("Redirect node points to non-existent index "
+ redirectTo);
}
@@ -264,7 +265,7 @@ public class AvailableCommandsPacket implements MinecraftPacket {
this.validated = true;
}
boolean toNode(List<WireNode> wireNodes) {
boolean toNode(WireNode[] wireNodes) {
if (!this.validated) {
this.validate(wireNodes);
}
@@ -280,7 +281,7 @@ public class AvailableCommandsPacket implements MinecraftPacket {
// Add any redirects
if (redirectTo != -1) {
WireNode redirect = wireNodes.get(redirectTo);
WireNode redirect = wireNodes[redirectTo];
if (redirect.built != null) {
args.redirect(redirect.built);
} else {
@@ -304,7 +305,7 @@ public class AvailableCommandsPacket implements MinecraftPacket {
}
for (int child : children) {
if (wireNodes.get(child).built == null) {
if (wireNodes[child].built == null) {
// The child is not yet deserialized. The node can't be built now.
return false;
}
@@ -312,7 +313,7 @@ public class AvailableCommandsPacket implements MinecraftPacket {
// Associate children with nodes
for (int child : children) {
CommandNode<CommandSource> childNode = wireNodes.get(child).built;
CommandNode<CommandSource> childNode = wireNodes[child].built;
if (!(childNode instanceof RootCommandNode)) {
built.addChild(childNode);
}
@@ -330,10 +331,12 @@ public class AvailableCommandsPacket implements MinecraftPacket {
.add("redirectTo", redirectTo);
if (args != null) {
if (args instanceof LiteralArgumentBuilder literal) {
helper.add("argsLabel", literal.getLiteral());
} else if (args instanceof RequiredArgumentBuilder required) {
helper.add("argsName", required.getName());
if (args instanceof LiteralArgumentBuilder) {
helper.add("argsLabel",
((LiteralArgumentBuilder<CommandSource>) args).getLiteral());
} else if (args instanceof RequiredArgumentBuilder) {
helper.add("argsName",
((RequiredArgumentBuilder<CommandSource, ?>) args).getName());
}
}
@@ -345,20 +348,18 @@ public class AvailableCommandsPacket implements MinecraftPacket {
* A placeholder {@link SuggestionProvider} used internally to preserve the suggestion provider
* name.
*/
public record ProtocolSuggestionProvider(String name) implements SuggestionProvider<CommandSource> {
public static class ProtocolSuggestionProvider implements SuggestionProvider<CommandSource> {
private final String name;
public ProtocolSuggestionProvider(String name) {
this.name = name;
}
@Override
public CompletableFuture<Suggestions> getSuggestions(CommandContext<CommandSource> context,
SuggestionsBuilder builder) {
SuggestionsBuilder builder) throws CommandSyntaxException {
return builder.buildFuture();
}
}
@Override
public int encodeSizeHint(Direction direction, ProtocolVersion version) {
// This is a very complex packet to encode. Paper 1.21.10 + Velocity with Spark has a size of
// 30,334, but this is likely on the lower side. We'll use 128KiB as a more realistically-sized
// amount.
return 128 * 1024;
}
}
@@ -207,22 +207,30 @@ public class BossBarPacket implements MinecraftPacket {
this.uuid = ProtocolUtils.readUuid(buf);
this.action = ProtocolUtils.readVarInt(buf);
switch (action) {
case ADD -> {
case ADD:
this.name = ComponentHolder.read(buf, version);
this.percent = buf.readFloat();
this.color = ProtocolUtils.readVarInt(buf);
this.overlay = ProtocolUtils.readVarInt(buf);
this.flags = buf.readUnsignedByte();
}
case REMOVE -> {}
case UPDATE_PERCENT -> this.percent = buf.readFloat();
case UPDATE_NAME -> this.name = ComponentHolder.read(buf, version);
case UPDATE_STYLE -> {
break;
case REMOVE:
break;
case UPDATE_PERCENT:
this.percent = buf.readFloat();
break;
case UPDATE_NAME:
this.name = ComponentHolder.read(buf, version);
break;
case UPDATE_STYLE:
this.color = ProtocolUtils.readVarInt(buf);
this.overlay = ProtocolUtils.readVarInt(buf);
}
case UPDATE_PROPERTIES -> this.flags = buf.readUnsignedByte();
default -> throw new UnsupportedOperationException("Unknown action " + action);
break;
case UPDATE_PROPERTIES:
this.flags = buf.readUnsignedByte();
break;
default:
throw new UnsupportedOperationException("Unknown action " + action);
}
}
@@ -234,30 +242,36 @@ public class BossBarPacket implements MinecraftPacket {
ProtocolUtils.writeUuid(buf, uuid);
ProtocolUtils.writeVarInt(buf, action);
switch (action) {
case ADD -> {
if (name == null) {
throw new IllegalStateException("No name specified!");
}
name.write(buf);
buf.writeFloat(percent);
ProtocolUtils.writeVarInt(buf, color);
ProtocolUtils.writeVarInt(buf, overlay);
buf.writeByte(flags);
}
case REMOVE -> {}
case UPDATE_PERCENT -> buf.writeFloat(percent);
case UPDATE_NAME -> {
if (name == null) {
throw new IllegalStateException("No name specified!");
}
name.write(buf);
}
case UPDATE_STYLE -> {
case ADD:
if (name == null) {
throw new IllegalStateException("No name specified!");
}
name.write(buf);
buf.writeFloat(percent);
ProtocolUtils.writeVarInt(buf, color);
ProtocolUtils.writeVarInt(buf, overlay);
}
case UPDATE_PROPERTIES -> buf.writeByte(flags);
default -> throw new UnsupportedOperationException("Unknown action " + action);
buf.writeByte(flags);
break;
case REMOVE:
break;
case UPDATE_PERCENT:
buf.writeFloat(percent);
break;
case UPDATE_NAME:
if (name == null) {
throw new IllegalStateException("No name specified!");
}
name.write(buf);
break;
case UPDATE_STYLE:
ProtocolUtils.writeVarInt(buf, color);
ProtocolUtils.writeVarInt(buf, overlay);
break;
case UPDATE_PROPERTIES:
buf.writeByte(flags);
break;
default:
throw new UnsupportedOperationException("Unknown action " + action);
}
}
@@ -22,8 +22,8 @@ import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import java.util.Objects;
import org.checkerframework.checker.nullness.qual.Nullable;
public class ClientSettingsPacket implements MinecraftPacket {
@@ -135,7 +135,7 @@ public class ClientSettingsPacket implements MinecraftPacket {
return "ClientSettings{" + "locale='" + locale + '\'' + ", viewDistance=" + viewDistance +
", chatVisibility=" + chatVisibility + ", chatColors=" + chatColors + ", skinParts=" +
skinParts + ", mainHand=" + mainHand + ", chatFilteringEnabled=" + textFilteringEnabled +
", clientListingAllowed=" + clientListingAllowed + ", particleStatus=" + particleStatus + '}';
", clientListingAllowed=" + clientListingAllowed + ", particleStatus=" + particleStatus + '}';
}
@Override
@@ -206,16 +206,6 @@ public class ClientSettingsPacket implements MinecraftPacket {
return handler.handle(this);
}
@Override
public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
return 1 + ByteBufUtil.utf8MaxBytes(16) + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1;
}
@Override
public int decodeExpectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
return 1 + 0 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1;
}
@Override
public boolean equals(@Nullable final Object o) {
if (this == o) {
@@ -247,7 +237,7 @@ public class ClientSettingsPacket implements MinecraftPacket {
difficulty,
skinParts,
mainHand,
textFilteringEnabled,
textFilteringEnabled,
clientListingAllowed,
particleStatus);
}
@@ -1,101 +0,0 @@
/*
* Copyright (C) 2025 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import net.kyori.adventure.sound.Sound;
import org.jetbrains.annotations.Nullable;
import java.util.Random;
public class ClientboundSoundEntityPacket implements MinecraftPacket {
private static final Random SEEDS_RANDOM = new Random();
private Sound sound;
private @Nullable Float fixedRange;
private int emitterEntityId;
public ClientboundSoundEntityPacket() {}
public ClientboundSoundEntityPacket(Sound sound, @Nullable Float fixedRange, int emitterEntityId) {
this.sound = sound;
this.fixedRange = fixedRange;
this.emitterEntityId = emitterEntityId;
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
throw new UnsupportedOperationException("Decode is not implemented");
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeVarInt(buf, 0); // version-dependent, hardcoded sound ID
ProtocolUtils.writeMinimalKey(buf, sound.name());
buf.writeBoolean(fixedRange != null);
if (fixedRange != null)
buf.writeFloat(fixedRange);
ProtocolUtils.writeSoundSource(buf, protocolVersion, sound.source());
ProtocolUtils.writeVarInt(buf, emitterEntityId);
buf.writeFloat(sound.volume());
buf.writeFloat(sound.pitch());
buf.writeLong(sound.seed().orElse(SEEDS_RANDOM.nextLong()));
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public Sound getSound() {
return sound;
}
public void setSound(Sound sound) {
this.sound = sound;
}
public @Nullable Float getFixedRange() {
return fixedRange;
}
public void setFixedRange(@Nullable Float fixedRange) {
this.fixedRange = fixedRange;
}
public int getEmitterEntityId() {
return emitterEntityId;
}
public void setEmitterEntityId(int emitterEntityId) {
this.emitterEntityId = emitterEntityId;
}
}
@@ -1,109 +0,0 @@
/*
* Copyright (C) 2025 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.sound.Sound;
import net.kyori.adventure.sound.SoundStop;
import javax.annotation.Nullable;
public class ClientboundStopSoundPacket implements MinecraftPacket {
private @Nullable Sound.Source source;
private @Nullable Key soundName;
public ClientboundStopSoundPacket() {}
public ClientboundStopSoundPacket(SoundStop soundStop) {
this(soundStop.source(), soundStop.sound());
}
public ClientboundStopSoundPacket(@Nullable Sound.Source source, @Nullable Key soundName) {
this.source = source;
this.soundName = soundName;
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
int flagsBitmask = buf.readByte();
if ((flagsBitmask & 1) != 0) {
source = ProtocolUtils.readSoundSource(buf, protocolVersion);
} else {
source = null;
}
if ((flagsBitmask & 2) != 0) {
soundName = ProtocolUtils.readKey(buf);
} else {
soundName = null;
}
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
int flagsBitmask = 0;
if (source != null && soundName == null) {
flagsBitmask |= 1;
} else if (soundName != null && source == null) {
flagsBitmask |= 2;
} else if (source != null /*&& sound != null*/) {
flagsBitmask |= 3;
}
buf.writeByte(flagsBitmask);
if (source != null) {
ProtocolUtils.writeSoundSource(buf, protocolVersion, source);
}
if (soundName != null) {
ProtocolUtils.writeMinimalKey(buf, soundName);
}
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
@Nullable
public Sound.Source getSource() {
return source;
}
public void setSource(@Nullable Sound.Source source) {
this.source = source;
}
@Nullable
public Key getSoundName() {
return soundName;
}
public void setSoundName(@Nullable Key soundName) {
this.soundName = soundName;
}
}
@@ -1,45 +0,0 @@
/*
* Copyright (C) 2018-2025 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import io.netty.buffer.ByteBuf;
public class DialogClearPacket implements MinecraftPacket {
public static final DialogClearPacket INSTANCE = new DialogClearPacket();
private DialogClearPacket() {
}
@Override
public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
}
@Override
public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
}
@@ -1,64 +0,0 @@
/*
* Copyright (C) 2018-2025 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import com.velocitypowered.proxy.protocol.StateRegistry;
import io.netty.buffer.ByteBuf;
import net.kyori.adventure.nbt.BinaryTag;
import net.kyori.adventure.nbt.BinaryTagIO;
public class DialogShowPacket implements MinecraftPacket {
private final StateRegistry state;
private int id;
private BinaryTag nbt;
public DialogShowPacket(final StateRegistry state) {
this.state = state;
}
@Override
public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
this.id = this.state == StateRegistry.CONFIG ? 0 : ProtocolUtils.readVarInt(buf);
if (this.id == 0) {
this.nbt = ProtocolUtils.readBinaryTag(buf, protocolVersion, BinaryTagIO.reader());
}
}
@Override
public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
if (this.state == StateRegistry.CONFIG) {
ProtocolUtils.writeBinaryTag(buf, protocolVersion, this.nbt);
} else {
ProtocolUtils.writeVarInt(buf, this.id);
if (this.id == 0) {
ProtocolUtils.writeBinaryTag(buf, protocolVersion, this.nbt);
}
}
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
}
@@ -107,7 +107,7 @@ public class EncryptionResponsePacket implements MinecraftPacket {
}
@Override
public int decodeExpectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) {
public int expectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) {
// It turns out these come out to the same length, whether we're talking >=1.8 or not.
// The length prefix always winds up being 2 bytes.
int base = 256 + 2 + 2;
@@ -123,8 +123,8 @@ public class EncryptionResponsePacket implements MinecraftPacket {
}
@Override
public int decodeExpectedMinLength(ByteBuf buf, Direction direction, ProtocolVersion version) {
int base = decodeExpectedMaxLength(buf, direction, version);
public int expectedMinLength(ByteBuf buf, Direction direction, ProtocolVersion version) {
int base = expectedMaxLength(buf, direction, version);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
// These are "optional"
base -= 128 + 8;
@@ -24,7 +24,6 @@ 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;
public class HandshakePacket implements MinecraftPacket {
@@ -109,21 +108,14 @@ public class HandshakePacket implements MinecraftPacket {
}
@Override
public int decodeExpectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction,
public int expectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
return 7;
}
@Override
public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
public int expectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
return 9 + (MAXIMUM_HOSTNAME_LENGTH * 3);
}
@Override
public int encodeSizeHint(Direction direction, ProtocolVersion version) {
// We could compute an exact size, but 4KiB ought to be enough to encode all reasonable
// sizes of this packet.
return 4 * 1024;
}
}
@@ -52,7 +52,6 @@ public class JoinGamePacket implements MinecraftPacket {
private @Nullable Pair<String, Long> lastDeathPosition; // 1.19+
private int portalCooldown; // 1.20+
private int seaLevel; // 1.21.2+
private boolean onlineMode; // 26.2+
private boolean enforcesSecureChat; // 1.20.5+
public int getEntityId() {
@@ -191,10 +190,6 @@ public class JoinGamePacket implements MinecraftPacket {
this.seaLevel = seaLevel;
}
public void setOnlineMode(boolean onlineMode) {
this.onlineMode = onlineMode;
}
public boolean getEnforcesSecureChat() {
return this.enforcesSecureChat;
}
@@ -218,7 +213,7 @@ public class JoinGamePacket implements MinecraftPacket {
dimensionInfo + '\'' + ", currentDimensionData='" + currentDimensionData + '\'' +
", previousGamemode=" + previousGamemode + ", simulationDistance=" + simulationDistance +
", lastDeathPosition='" + lastDeathPosition + '\'' + ", portalCooldown=" + portalCooldown +
", seaLevel=" + seaLevel + ", onlineMode=" + this.onlineMode +
", seaLevel=" + seaLevel +
'}';
}
@@ -363,10 +358,6 @@ public class JoinGamePacket implements MinecraftPacket {
this.seaLevel = ProtocolUtils.readVarInt(buf);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_26_2)) {
this.onlineMode = buf.readBoolean();
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
this.enforcesSecureChat = buf.readBoolean();
}
@@ -519,10 +510,6 @@ public class JoinGamePacket implements MinecraftPacket {
ProtocolUtils.writeVarInt(buf, seaLevel);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_26_2)) {
buf.writeBoolean(this.onlineMode);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
buf.writeBoolean(this.enforcesSecureChat);
}
@@ -64,28 +64,6 @@ public class KeepAlivePacket implements MinecraftPacket {
}
}
@Override
public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_12_2)) {
return Long.BYTES;
} else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
return 5;
} else {
return Integer.BYTES;
}
}
@Override
public int decodeExpectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_12_2)) {
return Long.BYTES;
} else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
return 1;
} else {
return Integer.BYTES;
}
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
@@ -69,25 +69,33 @@ public class LegacyPlayerListItemPacket implements MinecraftPacket {
Item item = new Item(ProtocolUtils.readUuid(buf));
items.add(item);
switch (action) {
case ADD_PLAYER -> {
case ADD_PLAYER:
item.setName(ProtocolUtils.readString(buf));
item.setProperties(ProtocolUtils.readProperties(buf));
item.setGameMode(ProtocolUtils.readVarInt(buf));
item.setLatency(ProtocolUtils.readVarInt(buf));
item.setDisplayName(readOptionalComponent(buf, version));
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
if (buf.readBoolean()) {
item.setPlayerKey(ProtocolUtils.readPlayerKey(version, buf));
}
if (buf.readBoolean()) {
item.setPlayerKey(ProtocolUtils.readPlayerKey(version, buf));
}
}
}
case UPDATE_GAMEMODE -> item.setGameMode(ProtocolUtils.readVarInt(buf));
case UPDATE_LATENCY -> item.setLatency(ProtocolUtils.readVarInt(buf));
case UPDATE_DISPLAY_NAME -> item.setDisplayName(readOptionalComponent(buf, version));
case REMOVE_PLAYER -> {
//Do nothing, all that is needed is the uuid
}
default -> throw new UnsupportedOperationException("Unknown action " + action);
break;
case UPDATE_GAMEMODE:
item.setGameMode(ProtocolUtils.readVarInt(buf));
break;
case UPDATE_LATENCY:
item.setLatency(ProtocolUtils.readVarInt(buf));
break;
case UPDATE_DISPLAY_NAME:
item.setDisplayName(readOptionalComponent(buf, version));
break;
case REMOVE_PLAYER:
//Do nothing, all that is needed is the uuid
break;
default:
throw new UnsupportedOperationException("Unknown action " + action);
}
}
} else {
@@ -118,32 +126,39 @@ public class LegacyPlayerListItemPacket implements MinecraftPacket {
ProtocolUtils.writeUuid(buf, uuid);
switch (action) {
case ADD_PLAYER -> {
case ADD_PLAYER:
ProtocolUtils.writeString(buf, item.getName());
ProtocolUtils.writeProperties(buf, item.getProperties());
ProtocolUtils.writeVarInt(buf, item.getGameMode());
ProtocolUtils.writeVarInt(buf, item.getLatency());
writeDisplayName(buf, item.getDisplayName(), version);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
if (item.getPlayerKey() != null) {
buf.writeBoolean(true);
ProtocolUtils.writePlayerKey(buf, item.getPlayerKey());
} else {
buf.writeBoolean(false);
}
if (item.getPlayerKey() != null) {
buf.writeBoolean(true);
ProtocolUtils.writePlayerKey(buf, item.getPlayerKey());
} else {
buf.writeBoolean(false);
}
}
}
case UPDATE_GAMEMODE -> ProtocolUtils.writeVarInt(buf, item.getGameMode());
case UPDATE_LATENCY -> ProtocolUtils.writeVarInt(buf, item.getLatency());
case UPDATE_DISPLAY_NAME -> writeDisplayName(buf, item.getDisplayName(), version);
case REMOVE_PLAYER -> {
break;
case UPDATE_GAMEMODE:
ProtocolUtils.writeVarInt(buf, item.getGameMode());
break;
case UPDATE_LATENCY:
ProtocolUtils.writeVarInt(buf, item.getLatency());
break;
case UPDATE_DISPLAY_NAME:
writeDisplayName(buf, item.getDisplayName(), version);
break;
case REMOVE_PLAYER:
// Do nothing, all that is needed is the uuid
}
default -> throw new UnsupportedOperationException("Unknown action " + action);
break;
default:
throw new UnsupportedOperationException("Unknown action " + action);
}
}
} else {
Item item = items.getFirst();
Item item = items.get(0);
Component displayNameComponent = item.getDisplayName();
if (displayNameComponent != null) {
String displayName = LegacyComponentSerializer.legacySection()
@@ -254,7 +269,7 @@ public class LegacyPlayerListItemPacket implements MinecraftPacket {
return this;
}
public @Nullable IdentifiedKey getPlayerKey() {
public IdentifiedKey getPlayerKey() {
return playerKey;
}
}
@@ -36,7 +36,7 @@ public class LoginAcknowledgedPacket implements MinecraftPacket {
}
@Override
public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
public int expectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
return 0;
}
@@ -21,7 +21,6 @@ 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 com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
@@ -87,9 +86,4 @@ public class LoginPluginMessagePacket extends DeferredByteBufHolder implements M
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
@Override
public int encodeSizeHint(Direction direction, ProtocolVersion version) {
return content().readableBytes();
}
}
@@ -21,7 +21,6 @@ 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 com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
@@ -89,9 +88,4 @@ public class LoginPluginResponsePacket extends DeferredByteBufHolder implements
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
@Override
public int encodeSizeHint(Direction direction, ProtocolVersion version) {
return content().readableBytes();
}
}
@@ -42,16 +42,6 @@ public class PingIdentifyPacket implements MinecraftPacket {
buf.writeInt(id);
}
@Override
public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
return Integer.BYTES;
}
@Override
public int decodeExpectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
return Integer.BYTES;
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);

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